2025-12-23 07:48:45 +01:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Admin interface for plugin settings and manual sync.
|
|
|
|
|
*
|
|
|
|
|
* @package InformatiqSmartPricing
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Exit if accessed directly.
|
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Admin class.
|
|
|
|
|
*/
|
|
|
|
|
class Informatiq_SP_Admin {
|
|
|
|
|
/**
|
|
|
|
|
* Logger instance.
|
|
|
|
|
*
|
|
|
|
|
* @var Informatiq_SP_Logger
|
|
|
|
|
*/
|
|
|
|
|
private $logger;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Price updater instance.
|
|
|
|
|
*
|
|
|
|
|
* @var Informatiq_SP_Price_Updater
|
|
|
|
|
*/
|
|
|
|
|
private $price_updater;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Constructor.
|
|
|
|
|
*
|
|
|
|
|
* @param Informatiq_SP_Logger $logger Logger instance.
|
|
|
|
|
* @param Informatiq_SP_Price_Updater $price_updater Price updater instance.
|
|
|
|
|
*/
|
|
|
|
|
public function __construct( $logger, $price_updater ) {
|
|
|
|
|
$this->logger = $logger;
|
|
|
|
|
$this->price_updater = $price_updater;
|
|
|
|
|
|
|
|
|
|
// Add admin menu.
|
|
|
|
|
add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
|
|
|
|
|
|
|
|
|
|
// Register settings.
|
|
|
|
|
add_action( 'admin_init', array( $this, 'register_settings' ) );
|
|
|
|
|
|
2026-01-21 08:52:57 +01:00
|
|
|
// Handle OAuth callback.
|
|
|
|
|
add_action( 'admin_init', array( $this, 'handle_oauth_callback' ) );
|
|
|
|
|
|
2025-12-23 07:48:45 +01:00
|
|
|
// Handle AJAX requests.
|
|
|
|
|
add_action( 'wp_ajax_informatiq_sp_manual_sync', array( $this, 'handle_manual_sync' ) );
|
|
|
|
|
add_action( 'wp_ajax_informatiq_sp_test_connection', array( $this, 'handle_test_connection' ) );
|
2026-01-21 08:52:57 +01:00
|
|
|
add_action( 'wp_ajax_informatiq_sp_revoke_auth', array( $this, 'handle_revoke_auth' ) );
|
2026-01-22 19:16:57 +01:00
|
|
|
add_action( 'wp_ajax_informatiq_sp_compare_products', array( $this, 'handle_compare_products' ) );
|
2026-01-23 18:22:17 +01:00
|
|
|
add_action( 'wp_ajax_informatiq_sp_update_price', array( $this, 'handle_update_price' ) );
|
|
|
|
|
add_action( 'wp_ajax_informatiq_sp_bulk_update_prices', array( $this, 'handle_bulk_update_prices' ) );
|
2025-12-23 07:48:45 +01:00
|
|
|
|
|
|
|
|
// Enqueue admin assets.
|
|
|
|
|
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add admin menu under WooCommerce.
|
|
|
|
|
*/
|
|
|
|
|
public function add_admin_menu() {
|
|
|
|
|
add_submenu_page(
|
|
|
|
|
'woocommerce',
|
|
|
|
|
__( 'Smart Pricing', 'informatiq-smart-pricing' ),
|
|
|
|
|
__( 'Smart Pricing', 'informatiq-smart-pricing' ),
|
|
|
|
|
'manage_woocommerce',
|
|
|
|
|
'informatiq-smart-pricing',
|
|
|
|
|
array( $this, 'render_admin_page' )
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register plugin settings.
|
|
|
|
|
*/
|
|
|
|
|
public function register_settings() {
|
|
|
|
|
// Register settings.
|
|
|
|
|
register_setting( 'informatiq_sp_settings', 'informatiq_sp_merchant_id' );
|
2026-01-21 08:52:57 +01:00
|
|
|
register_setting( 'informatiq_sp_settings', 'informatiq_sp_client_id' );
|
|
|
|
|
register_setting( 'informatiq_sp_settings', 'informatiq_sp_client_secret' );
|
2025-12-23 07:48:45 +01:00
|
|
|
register_setting( 'informatiq_sp_settings', 'informatiq_sp_minimum_margin' );
|
|
|
|
|
register_setting( 'informatiq_sp_settings', 'informatiq_sp_auto_update_enabled' );
|
|
|
|
|
register_setting( 'informatiq_sp_settings', 'informatiq_sp_update_frequency' );
|
|
|
|
|
|
|
|
|
|
// Add settings sections.
|
|
|
|
|
add_settings_section(
|
|
|
|
|
'informatiq_sp_google_settings',
|
|
|
|
|
__( 'Google Merchant Center Settings', 'informatiq-smart-pricing' ),
|
|
|
|
|
array( $this, 'render_google_settings_section' ),
|
|
|
|
|
'informatiq_sp_settings'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
add_settings_section(
|
|
|
|
|
'informatiq_sp_pricing_settings',
|
|
|
|
|
__( 'Pricing Settings', 'informatiq-smart-pricing' ),
|
|
|
|
|
array( $this, 'render_pricing_settings_section' ),
|
|
|
|
|
'informatiq_sp_settings'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
add_settings_section(
|
|
|
|
|
'informatiq_sp_automation_settings',
|
|
|
|
|
__( 'Automation Settings', 'informatiq-smart-pricing' ),
|
|
|
|
|
array( $this, 'render_automation_settings_section' ),
|
|
|
|
|
'informatiq_sp_settings'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Add settings fields.
|
|
|
|
|
add_settings_field(
|
|
|
|
|
'informatiq_sp_merchant_id',
|
|
|
|
|
__( 'Google Merchant ID', 'informatiq-smart-pricing' ),
|
|
|
|
|
array( $this, 'render_merchant_id_field' ),
|
|
|
|
|
'informatiq_sp_settings',
|
|
|
|
|
'informatiq_sp_google_settings'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
add_settings_field(
|
2026-01-21 08:52:57 +01:00
|
|
|
'informatiq_sp_oauth_credentials',
|
|
|
|
|
__( 'OAuth 2.0 Credentials', 'informatiq-smart-pricing' ),
|
|
|
|
|
array( $this, 'render_oauth_credentials_field' ),
|
|
|
|
|
'informatiq_sp_settings',
|
|
|
|
|
'informatiq_sp_google_settings'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
add_settings_field(
|
|
|
|
|
'informatiq_sp_authorization',
|
|
|
|
|
__( 'Google Authorization', 'informatiq-smart-pricing' ),
|
|
|
|
|
array( $this, 'render_authorization_field' ),
|
2025-12-23 07:48:45 +01:00
|
|
|
'informatiq_sp_settings',
|
|
|
|
|
'informatiq_sp_google_settings'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
add_settings_field(
|
|
|
|
|
'informatiq_sp_minimum_margin',
|
|
|
|
|
__( 'Minimum Margin (%)', 'informatiq-smart-pricing' ),
|
|
|
|
|
array( $this, 'render_minimum_margin_field' ),
|
|
|
|
|
'informatiq_sp_settings',
|
|
|
|
|
'informatiq_sp_pricing_settings'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
add_settings_field(
|
|
|
|
|
'informatiq_sp_auto_update_enabled',
|
|
|
|
|
__( 'Enable Automatic Updates', 'informatiq-smart-pricing' ),
|
|
|
|
|
array( $this, 'render_auto_update_field' ),
|
|
|
|
|
'informatiq_sp_settings',
|
|
|
|
|
'informatiq_sp_automation_settings'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
add_settings_field(
|
|
|
|
|
'informatiq_sp_update_frequency',
|
|
|
|
|
__( 'Update Frequency', 'informatiq-smart-pricing' ),
|
|
|
|
|
array( $this, 'render_update_frequency_field' ),
|
|
|
|
|
'informatiq_sp_settings',
|
|
|
|
|
'informatiq_sp_automation_settings'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 08:52:57 +01:00
|
|
|
/**
|
|
|
|
|
* Handle OAuth callback from Google.
|
|
|
|
|
*/
|
|
|
|
|
public function handle_oauth_callback() {
|
|
|
|
|
// Check if this is an OAuth callback.
|
|
|
|
|
if ( ! isset( $_GET['informatiq_sp_oauth'] ) || $_GET['informatiq_sp_oauth'] !== 'callback' ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify state parameter.
|
|
|
|
|
$state = isset( $_GET['state'] ) ? sanitize_text_field( $_GET['state'] ) : '';
|
|
|
|
|
$saved_state = get_transient( 'informatiq_sp_oauth_state' );
|
|
|
|
|
|
|
|
|
|
if ( empty( $state ) || $state !== $saved_state ) {
|
|
|
|
|
add_settings_error(
|
|
|
|
|
'informatiq_sp_messages',
|
|
|
|
|
'informatiq_sp_oauth_error',
|
|
|
|
|
__( 'OAuth state mismatch. Please try again.', 'informatiq-smart-pricing' ),
|
|
|
|
|
'error'
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete_transient( 'informatiq_sp_oauth_state' );
|
|
|
|
|
|
|
|
|
|
// Check for errors.
|
|
|
|
|
if ( isset( $_GET['error'] ) ) {
|
|
|
|
|
$error = sanitize_text_field( $_GET['error'] );
|
|
|
|
|
$error_desc = isset( $_GET['error_description'] ) ? sanitize_text_field( $_GET['error_description'] ) : $error;
|
|
|
|
|
add_settings_error(
|
|
|
|
|
'informatiq_sp_messages',
|
|
|
|
|
'informatiq_sp_oauth_error',
|
|
|
|
|
sprintf( __( 'Authorization failed: %s', 'informatiq-smart-pricing' ), $error_desc ),
|
|
|
|
|
'error'
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get authorization code.
|
|
|
|
|
$code = isset( $_GET['code'] ) ? sanitize_text_field( $_GET['code'] ) : '';
|
|
|
|
|
|
|
|
|
|
if ( empty( $code ) ) {
|
|
|
|
|
add_settings_error(
|
|
|
|
|
'informatiq_sp_messages',
|
|
|
|
|
'informatiq_sp_oauth_error',
|
|
|
|
|
__( 'No authorization code received.', 'informatiq-smart-pricing' ),
|
|
|
|
|
'error'
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Exchange code for tokens.
|
|
|
|
|
$client_id = get_option( 'informatiq_sp_client_id' );
|
|
|
|
|
$client_secret = get_option( 'informatiq_sp_client_secret' );
|
|
|
|
|
$redirect_uri = $this->get_oauth_redirect_uri();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$tokens = Informatiq_SP_Google_API::exchange_code_for_tokens(
|
|
|
|
|
$code,
|
|
|
|
|
$client_id,
|
|
|
|
|
$client_secret,
|
|
|
|
|
$redirect_uri
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Save refresh token.
|
|
|
|
|
update_option( 'informatiq_sp_refresh_token', $tokens['refresh_token'] );
|
|
|
|
|
|
|
|
|
|
$this->logger->info( 'Google OAuth authorization successful' );
|
|
|
|
|
|
|
|
|
|
// Redirect to settings page with success message.
|
|
|
|
|
wp_redirect( admin_url( 'admin.php?page=informatiq-smart-pricing&oauth=success' ) );
|
|
|
|
|
exit;
|
|
|
|
|
|
|
|
|
|
} catch ( Exception $e ) {
|
|
|
|
|
$this->logger->error( 'OAuth token exchange failed: ' . $e->getMessage() );
|
|
|
|
|
add_settings_error(
|
|
|
|
|
'informatiq_sp_messages',
|
|
|
|
|
'informatiq_sp_oauth_error',
|
|
|
|
|
sprintf( __( 'Token exchange failed: %s', 'informatiq-smart-pricing' ), $e->getMessage() ),
|
|
|
|
|
'error'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get OAuth redirect URI.
|
|
|
|
|
*
|
|
|
|
|
* @return string Redirect URI.
|
|
|
|
|
*/
|
|
|
|
|
private function get_oauth_redirect_uri() {
|
|
|
|
|
return admin_url( 'admin.php?page=informatiq-smart-pricing&informatiq_sp_oauth=callback' );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if plugin is authorized.
|
|
|
|
|
*
|
|
|
|
|
* @return bool True if authorized.
|
|
|
|
|
*/
|
|
|
|
|
private function is_authorized() {
|
|
|
|
|
return ! empty( get_option( 'informatiq_sp_refresh_token' ) );
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 07:48:45 +01:00
|
|
|
/**
|
|
|
|
|
* Render admin page.
|
|
|
|
|
*/
|
|
|
|
|
public function render_admin_page() {
|
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 08:52:57 +01:00
|
|
|
// Check for OAuth success message.
|
|
|
|
|
if ( isset( $_GET['oauth'] ) && $_GET['oauth'] === 'success' ) {
|
|
|
|
|
add_settings_error(
|
|
|
|
|
'informatiq_sp_messages',
|
|
|
|
|
'informatiq_sp_message',
|
|
|
|
|
__( 'Google authorization successful!', 'informatiq-smart-pricing' ),
|
|
|
|
|
'success'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 07:48:45 +01:00
|
|
|
// Check if form was submitted.
|
|
|
|
|
if ( isset( $_GET['settings-updated'] ) ) {
|
|
|
|
|
// Update cron schedule if frequency changed.
|
|
|
|
|
$frequency = get_option( 'informatiq_sp_update_frequency', 'daily' );
|
|
|
|
|
Informatiq_SP_Scheduler::reschedule( $frequency );
|
|
|
|
|
|
|
|
|
|
add_settings_error(
|
|
|
|
|
'informatiq_sp_messages',
|
|
|
|
|
'informatiq_sp_message',
|
|
|
|
|
__( 'Settings saved successfully.', 'informatiq-smart-pricing' ),
|
|
|
|
|
'success'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
?>
|
|
|
|
|
<div class="wrap informatiq-sp-admin">
|
|
|
|
|
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
|
|
|
|
|
|
|
|
|
|
<?php settings_errors( 'informatiq_sp_messages' ); ?>
|
|
|
|
|
|
|
|
|
|
<div class="informatiq-sp-content">
|
|
|
|
|
<div class="informatiq-sp-main">
|
|
|
|
|
<form action="options.php" method="post">
|
|
|
|
|
<?php
|
|
|
|
|
settings_fields( 'informatiq_sp_settings' );
|
|
|
|
|
do_settings_sections( 'informatiq_sp_settings' );
|
|
|
|
|
submit_button( __( 'Save Settings', 'informatiq-smart-pricing' ) );
|
|
|
|
|
?>
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<hr>
|
|
|
|
|
|
|
|
|
|
<div class="informatiq-sp-actions">
|
|
|
|
|
<h2><?php esc_html_e( 'Manual Actions', 'informatiq-smart-pricing' ); ?></h2>
|
|
|
|
|
|
|
|
|
|
<p>
|
2026-01-21 08:52:57 +01:00
|
|
|
<button type="button" class="button button-secondary" id="informatiq-sp-test-connection" <?php disabled( ! $this->is_authorized() ); ?>>
|
2025-12-23 07:48:45 +01:00
|
|
|
<?php esc_html_e( 'Test Google API Connection', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</button>
|
2026-01-21 08:52:57 +01:00
|
|
|
<button type="button" class="button button-primary" id="informatiq-sp-manual-sync" <?php disabled( ! $this->is_authorized() ); ?>>
|
2025-12-23 07:48:45 +01:00
|
|
|
<?php esc_html_e( 'Run Manual Sync Now', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</button>
|
|
|
|
|
</p>
|
|
|
|
|
|
2026-01-21 08:52:57 +01:00
|
|
|
<?php if ( ! $this->is_authorized() ) : ?>
|
|
|
|
|
<p class="description" style="color: #d63638;">
|
|
|
|
|
<?php esc_html_e( 'Please authorize with Google first to use these features.', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</p>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
2025-12-23 07:48:45 +01:00
|
|
|
<div id="informatiq-sp-sync-status" class="notice" style="display: none;"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<hr>
|
|
|
|
|
|
|
|
|
|
<?php $this->render_schedule_info(); ?>
|
|
|
|
|
|
|
|
|
|
<hr>
|
|
|
|
|
|
2026-01-22 19:16:57 +01:00
|
|
|
<?php $this->render_product_comparison(); ?>
|
|
|
|
|
|
|
|
|
|
<hr>
|
|
|
|
|
|
2025-12-23 07:48:45 +01:00
|
|
|
<?php $this->render_logs_section(); ?>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="informatiq-sp-sidebar">
|
|
|
|
|
<?php $this->render_sidebar(); ?>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render Google settings section description.
|
|
|
|
|
*/
|
|
|
|
|
public function render_google_settings_section() {
|
2026-01-21 08:52:57 +01:00
|
|
|
echo '<p>' . esc_html__( 'Configure your Google Merchant Center API credentials using OAuth 2.0.', 'informatiq-smart-pricing' ) . '</p>';
|
2025-12-23 07:48:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render pricing settings section description.
|
|
|
|
|
*/
|
|
|
|
|
public function render_pricing_settings_section() {
|
|
|
|
|
echo '<p>' . esc_html__( 'Configure pricing rules and safeguards.', 'informatiq-smart-pricing' ) . '</p>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render automation settings section description.
|
|
|
|
|
*/
|
|
|
|
|
public function render_automation_settings_section() {
|
|
|
|
|
echo '<p>' . esc_html__( 'Configure automated price updates.', 'informatiq-smart-pricing' ) . '</p>';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render merchant ID field.
|
|
|
|
|
*/
|
|
|
|
|
public function render_merchant_id_field() {
|
|
|
|
|
$value = get_option( 'informatiq_sp_merchant_id' );
|
|
|
|
|
?>
|
|
|
|
|
<input type="text" name="informatiq_sp_merchant_id" id="informatiq_sp_merchant_id"
|
|
|
|
|
value="<?php echo esc_attr( $value ); ?>" class="regular-text">
|
|
|
|
|
<p class="description">
|
2026-01-21 08:52:57 +01:00
|
|
|
<?php esc_html_e( 'Your Google Merchant Center ID (found in Merchant Center settings).', 'informatiq-smart-pricing' ); ?>
|
2025-12-23 07:48:45 +01:00
|
|
|
</p>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-01-21 08:52:57 +01:00
|
|
|
* Render OAuth credentials field.
|
2025-12-23 07:48:45 +01:00
|
|
|
*/
|
2026-01-21 08:52:57 +01:00
|
|
|
public function render_oauth_credentials_field() {
|
|
|
|
|
$client_id = get_option( 'informatiq_sp_client_id' );
|
|
|
|
|
$client_secret = get_option( 'informatiq_sp_client_secret' );
|
2025-12-23 07:48:45 +01:00
|
|
|
?>
|
2026-01-21 08:52:57 +01:00
|
|
|
<div class="informatiq-sp-oauth-credentials">
|
|
|
|
|
<p>
|
|
|
|
|
<label for="informatiq_sp_client_id"><strong><?php esc_html_e( 'Client ID:', 'informatiq-smart-pricing' ); ?></strong></label><br>
|
|
|
|
|
<input type="text" name="informatiq_sp_client_id" id="informatiq_sp_client_id"
|
|
|
|
|
value="<?php echo esc_attr( $client_id ); ?>" class="large-text">
|
|
|
|
|
</p>
|
|
|
|
|
<p>
|
|
|
|
|
<label for="informatiq_sp_client_secret"><strong><?php esc_html_e( 'Client Secret:', 'informatiq-smart-pricing' ); ?></strong></label><br>
|
|
|
|
|
<input type="password" name="informatiq_sp_client_secret" id="informatiq_sp_client_secret"
|
|
|
|
|
value="<?php echo esc_attr( $client_secret ); ?>" class="regular-text">
|
|
|
|
|
</p>
|
|
|
|
|
<p class="description">
|
|
|
|
|
<?php
|
|
|
|
|
printf(
|
|
|
|
|
/* translators: %s: redirect URI */
|
|
|
|
|
esc_html__( 'Create OAuth 2.0 credentials in Google Cloud Console. Use this as your Authorized redirect URI: %s', 'informatiq-smart-pricing' ),
|
|
|
|
|
'<code>' . esc_html( $this->get_oauth_redirect_uri() ) . '</code>'
|
|
|
|
|
);
|
|
|
|
|
?>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
2025-12-23 07:48:45 +01:00
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 08:52:57 +01:00
|
|
|
/**
|
|
|
|
|
* Render authorization field.
|
|
|
|
|
*/
|
|
|
|
|
public function render_authorization_field() {
|
|
|
|
|
$client_id = get_option( 'informatiq_sp_client_id' );
|
|
|
|
|
$client_secret = get_option( 'informatiq_sp_client_secret' );
|
|
|
|
|
$is_authorized = $this->is_authorized();
|
|
|
|
|
$has_credentials = ! empty( $client_id ) && ! empty( $client_secret );
|
|
|
|
|
|
|
|
|
|
if ( $is_authorized ) {
|
|
|
|
|
?>
|
|
|
|
|
<div class="informatiq-sp-auth-status authorized">
|
|
|
|
|
<span class="dashicons dashicons-yes-alt" style="color: #00a32a;"></span>
|
|
|
|
|
<strong style="color: #00a32a;"><?php esc_html_e( 'Authorized', 'informatiq-smart-pricing' ); ?></strong>
|
|
|
|
|
<p class="description">
|
|
|
|
|
<?php esc_html_e( 'Plugin is authorized to access your Google Merchant Center.', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</p>
|
|
|
|
|
<p>
|
|
|
|
|
<button type="button" class="button button-secondary" id="informatiq-sp-revoke-auth">
|
|
|
|
|
<?php esc_html_e( 'Revoke Authorization', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</button>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<?php
|
|
|
|
|
} elseif ( $has_credentials ) {
|
|
|
|
|
// Generate authorization URL.
|
|
|
|
|
$state = wp_generate_password( 32, false );
|
|
|
|
|
set_transient( 'informatiq_sp_oauth_state', $state, 600 ); // 10 minutes.
|
|
|
|
|
|
|
|
|
|
$auth_url = Informatiq_SP_Google_API::get_authorization_url(
|
|
|
|
|
$client_id,
|
|
|
|
|
$this->get_oauth_redirect_uri(),
|
|
|
|
|
$state
|
|
|
|
|
);
|
|
|
|
|
?>
|
|
|
|
|
<div class="informatiq-sp-auth-status not-authorized">
|
|
|
|
|
<span class="dashicons dashicons-warning" style="color: #dba617;"></span>
|
|
|
|
|
<strong style="color: #dba617;"><?php esc_html_e( 'Not Authorized', 'informatiq-smart-pricing' ); ?></strong>
|
|
|
|
|
<p class="description">
|
|
|
|
|
<?php esc_html_e( 'Click the button below to authorize this plugin with your Google account.', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</p>
|
|
|
|
|
<p>
|
|
|
|
|
<a href="<?php echo esc_url( $auth_url ); ?>" class="button button-primary">
|
|
|
|
|
<?php esc_html_e( 'Authorize with Google', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</a>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<?php
|
|
|
|
|
} else {
|
|
|
|
|
?>
|
|
|
|
|
<div class="informatiq-sp-auth-status no-credentials">
|
|
|
|
|
<span class="dashicons dashicons-info" style="color: #72aee6;"></span>
|
|
|
|
|
<em><?php esc_html_e( 'Enter your OAuth credentials above and save settings first.', 'informatiq-smart-pricing' ); ?></em>
|
|
|
|
|
</div>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 07:48:45 +01:00
|
|
|
/**
|
|
|
|
|
* Render minimum margin field.
|
|
|
|
|
*/
|
|
|
|
|
public function render_minimum_margin_field() {
|
|
|
|
|
$value = get_option( 'informatiq_sp_minimum_margin', '10' );
|
|
|
|
|
?>
|
|
|
|
|
<input type="number" name="informatiq_sp_minimum_margin" id="informatiq_sp_minimum_margin"
|
|
|
|
|
value="<?php echo esc_attr( $value ); ?>" min="0" max="100" step="0.01" class="small-text">
|
|
|
|
|
<span>%</span>
|
|
|
|
|
<p class="description">
|
2026-01-23 18:42:00 +01:00
|
|
|
<?php esc_html_e( 'Maximum price reduction allowed. Suggested prices will never go below (Current Price - Margin%).', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
<br>
|
|
|
|
|
<em><?php esc_html_e( 'Example: With 10%, a €100 product will never be suggested below €90.', 'informatiq-smart-pricing' ); ?></em>
|
2025-12-23 07:48:45 +01:00
|
|
|
</p>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render auto update field.
|
|
|
|
|
*/
|
|
|
|
|
public function render_auto_update_field() {
|
|
|
|
|
$value = get_option( 'informatiq_sp_auto_update_enabled', '1' );
|
|
|
|
|
?>
|
|
|
|
|
<label>
|
|
|
|
|
<input type="checkbox" name="informatiq_sp_auto_update_enabled" value="1"
|
|
|
|
|
<?php checked( $value, '1' ); ?>>
|
|
|
|
|
<?php esc_html_e( 'Enable automatic daily price updates', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</label>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render update frequency field.
|
|
|
|
|
*/
|
|
|
|
|
public function render_update_frequency_field() {
|
|
|
|
|
$value = get_option( 'informatiq_sp_update_frequency', 'daily' );
|
|
|
|
|
?>
|
|
|
|
|
<select name="informatiq_sp_update_frequency" id="informatiq_sp_update_frequency">
|
|
|
|
|
<option value="every_6_hours" <?php selected( $value, 'every_6_hours' ); ?>>
|
|
|
|
|
<?php esc_html_e( 'Every 6 Hours', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</option>
|
|
|
|
|
<option value="twice_daily" <?php selected( $value, 'twice_daily' ); ?>>
|
|
|
|
|
<?php esc_html_e( 'Twice Daily', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</option>
|
|
|
|
|
<option value="daily" <?php selected( $value, 'daily' ); ?>>
|
|
|
|
|
<?php esc_html_e( 'Once Daily', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</option>
|
|
|
|
|
</select>
|
|
|
|
|
<p class="description">
|
|
|
|
|
<?php esc_html_e( 'How often should prices be automatically updated?', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</p>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render schedule information.
|
|
|
|
|
*/
|
|
|
|
|
private function render_schedule_info() {
|
|
|
|
|
$next_run = Informatiq_SP_Scheduler::get_next_run_time();
|
|
|
|
|
$last_run = Informatiq_SP_Scheduler::get_last_run_info();
|
|
|
|
|
?>
|
|
|
|
|
<div class="informatiq-sp-schedule-info">
|
|
|
|
|
<h2><?php esc_html_e( 'Schedule Information', 'informatiq-smart-pricing' ); ?></h2>
|
|
|
|
|
|
|
|
|
|
<table class="widefat">
|
|
|
|
|
<tbody>
|
|
|
|
|
<tr>
|
|
|
|
|
<th><?php esc_html_e( 'Next Scheduled Run:', 'informatiq-smart-pricing' ); ?></th>
|
|
|
|
|
<td>
|
|
|
|
|
<?php
|
|
|
|
|
if ( $next_run ) {
|
|
|
|
|
echo esc_html( date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $next_run ) );
|
|
|
|
|
} else {
|
|
|
|
|
esc_html_e( 'Not scheduled', 'informatiq-smart-pricing' );
|
|
|
|
|
}
|
|
|
|
|
?>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<th><?php esc_html_e( 'Last Run:', 'informatiq-smart-pricing' ); ?></th>
|
|
|
|
|
<td>
|
|
|
|
|
<?php
|
|
|
|
|
if ( ! empty( $last_run['end'] ) ) {
|
|
|
|
|
echo esc_html( date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), strtotime( $last_run['end'] ) ) );
|
|
|
|
|
|
|
|
|
|
if ( ! empty( $last_run['results'] ) ) {
|
|
|
|
|
$results = $last_run['results'];
|
|
|
|
|
printf(
|
|
|
|
|
' - %s',
|
|
|
|
|
sprintf(
|
|
|
|
|
/* translators: %1$d: updated count, %2$d: processed count */
|
|
|
|
|
esc_html__( '%1$d of %2$d products updated', 'informatiq-smart-pricing' ),
|
|
|
|
|
$results['updated'],
|
|
|
|
|
$results['processed']
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
esc_html_e( 'Never', 'informatiq-smart-pricing' );
|
|
|
|
|
}
|
|
|
|
|
?>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-22 19:16:57 +01:00
|
|
|
/**
|
|
|
|
|
* Render product comparison section.
|
|
|
|
|
*/
|
|
|
|
|
private function render_product_comparison() {
|
|
|
|
|
?>
|
|
|
|
|
<div class="informatiq-sp-product-comparison">
|
|
|
|
|
<h2><?php esc_html_e( 'Product Price Comparison', 'informatiq-smart-pricing' ); ?></h2>
|
|
|
|
|
<p class="description">
|
|
|
|
|
<?php esc_html_e( 'Compare WooCommerce product prices with Google Merchant Center prices.', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<?php if ( $this->is_authorized() ) : ?>
|
|
|
|
|
<p>
|
|
|
|
|
<button type="button" class="button button-secondary" id="informatiq-sp-compare-products">
|
|
|
|
|
<?php esc_html_e( 'Load Product Comparison', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</button>
|
|
|
|
|
<span class="spinner" style="float: none; margin-top: 0;"></span>
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<div id="informatiq-sp-comparison-results" style="display: none;">
|
2026-01-23 18:05:17 +01:00
|
|
|
<div id="informatiq-sp-comparison-summary" class="notice notice-info" style="margin: 10px 0; padding: 10px;"></div>
|
2026-01-23 18:22:17 +01:00
|
|
|
|
|
|
|
|
<p style="margin: 10px 0;">
|
|
|
|
|
<button type="button" class="button button-primary" id="informatiq-sp-bulk-update" disabled>
|
|
|
|
|
<?php esc_html_e( 'Bulk Update Selected', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</button>
|
|
|
|
|
<label style="margin-left: 15px;">
|
|
|
|
|
<input type="checkbox" id="informatiq-sp-select-all">
|
|
|
|
|
<?php esc_html_e( 'Select all with suggestions', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</label>
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
<table class="widefat striped" id="informatiq-sp-comparison-table">
|
2026-01-22 19:16:57 +01:00
|
|
|
<thead>
|
|
|
|
|
<tr>
|
2026-01-23 18:22:17 +01:00
|
|
|
<th style="width: 30px;"><input type="checkbox" id="informatiq-sp-select-all-header"></th>
|
2026-02-13 08:02:05 +01:00
|
|
|
<th><?php esc_html_e( 'Brand', 'informatiq-smart-pricing' ); ?></th>
|
2026-01-23 18:49:47 +01:00
|
|
|
<th><?php esc_html_e( 'SKU', 'informatiq-smart-pricing' ); ?></th>
|
2026-01-22 19:16:57 +01:00
|
|
|
<th><?php esc_html_e( 'Product', 'informatiq-smart-pricing' ); ?></th>
|
2026-02-13 08:02:05 +01:00
|
|
|
<th><?php esc_html_e( 'Cost', 'informatiq-smart-pricing' ); ?></th>
|
2026-01-23 18:05:17 +01:00
|
|
|
<th><?php esc_html_e( 'Your Price', 'informatiq-smart-pricing' ); ?></th>
|
2026-01-23 18:22:17 +01:00
|
|
|
<th><?php esc_html_e( 'Suggested', 'informatiq-smart-pricing' ); ?></th>
|
2026-01-23 18:42:00 +01:00
|
|
|
<th title="<?php esc_attr_e( 'Percentage difference from your price', 'informatiq-smart-pricing' ); ?>">
|
|
|
|
|
<?php esc_html_e( '% Diff', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</th>
|
2026-01-23 18:22:17 +01:00
|
|
|
<th title="<?php esc_attr_e( 'Predicted change in impressions', 'informatiq-smart-pricing' ); ?>">
|
|
|
|
|
<?php esc_html_e( 'Impr.', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</th>
|
|
|
|
|
<th title="<?php esc_attr_e( 'Predicted change in clicks', 'informatiq-smart-pricing' ); ?>">
|
|
|
|
|
<?php esc_html_e( 'Clicks', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</th>
|
|
|
|
|
<th title="<?php esc_attr_e( 'Predicted change in conversions', 'informatiq-smart-pricing' ); ?>">
|
|
|
|
|
<?php esc_html_e( 'Conv.', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</th>
|
|
|
|
|
<th><?php esc_html_e( 'Action', 'informatiq-smart-pricing' ); ?></th>
|
2026-01-22 19:16:57 +01:00
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody id="informatiq-sp-comparison-tbody">
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
2026-01-23 18:22:17 +01:00
|
|
|
|
|
|
|
|
<div id="informatiq-sp-pagination" style="margin-top: 15px; text-align: center;"></div>
|
2026-01-22 19:16:57 +01:00
|
|
|
</div>
|
|
|
|
|
<?php else : ?>
|
|
|
|
|
<p class="description" style="color: #d63638;">
|
|
|
|
|
<?php esc_html_e( 'Please authorize with Google first to compare products.', 'informatiq-smart-pricing' ); ?>
|
|
|
|
|
</p>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</div>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 07:48:45 +01:00
|
|
|
/**
|
|
|
|
|
* Render logs section.
|
|
|
|
|
*/
|
|
|
|
|
private function render_logs_section() {
|
|
|
|
|
$logs = $this->logger->get_todays_logs();
|
|
|
|
|
?>
|
|
|
|
|
<div class="informatiq-sp-logs">
|
|
|
|
|
<h2><?php esc_html_e( 'Today\'s Price Updates', 'informatiq-smart-pricing' ); ?></h2>
|
|
|
|
|
|
|
|
|
|
<?php if ( empty( $logs ) ) : ?>
|
|
|
|
|
<p><?php esc_html_e( 'No price updates today.', 'informatiq-smart-pricing' ); ?></p>
|
|
|
|
|
<?php else : ?>
|
|
|
|
|
<table class="widefat striped">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th><?php esc_html_e( 'Time', 'informatiq-smart-pricing' ); ?></th>
|
|
|
|
|
<th><?php esc_html_e( 'Product', 'informatiq-smart-pricing' ); ?></th>
|
|
|
|
|
<th><?php esc_html_e( 'Old Price', 'informatiq-smart-pricing' ); ?></th>
|
|
|
|
|
<th><?php esc_html_e( 'New Price', 'informatiq-smart-pricing' ); ?></th>
|
|
|
|
|
<th><?php esc_html_e( 'Competitor Price', 'informatiq-smart-pricing' ); ?></th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<?php foreach ( $logs as $log ) : ?>
|
|
|
|
|
<tr>
|
|
|
|
|
<td><?php echo esc_html( date_i18n( get_option( 'time_format' ), strtotime( $log->created_at ) ) ); ?></td>
|
|
|
|
|
<td>
|
|
|
|
|
<a href="<?php echo esc_url( get_edit_post_link( $log->product_id ) ); ?>">
|
|
|
|
|
<?php echo esc_html( $log->product_name ); ?>
|
|
|
|
|
</a>
|
|
|
|
|
</td>
|
|
|
|
|
<td><?php echo wp_kses_post( wc_price( $log->old_price ) ); ?></td>
|
|
|
|
|
<td><?php echo wp_kses_post( wc_price( $log->new_price ) ); ?></td>
|
|
|
|
|
<td><?php echo wp_kses_post( wc_price( $log->competitor_price ) ); ?></td>
|
|
|
|
|
</tr>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</div>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Render sidebar.
|
|
|
|
|
*/
|
|
|
|
|
private function render_sidebar() {
|
|
|
|
|
?>
|
|
|
|
|
<div class="informatiq-sp-sidebar-box">
|
2026-01-21 08:52:57 +01:00
|
|
|
<h3><?php esc_html_e( 'Setup Steps', 'informatiq-smart-pricing' ); ?></h3>
|
2025-12-23 07:48:45 +01:00
|
|
|
<ol>
|
2026-01-21 08:52:57 +01:00
|
|
|
<li><?php esc_html_e( 'Create OAuth 2.0 credentials in Google Cloud Console', 'informatiq-smart-pricing' ); ?></li>
|
|
|
|
|
<li><?php esc_html_e( 'Enter Client ID and Client Secret above', 'informatiq-smart-pricing' ); ?></li>
|
|
|
|
|
<li><?php esc_html_e( 'Click "Authorize with Google"', 'informatiq-smart-pricing' ); ?></li>
|
|
|
|
|
<li><?php esc_html_e( 'Enter your Merchant ID', 'informatiq-smart-pricing' ); ?></li>
|
|
|
|
|
<li><?php esc_html_e( 'Set your minimum margin and enable automation', 'informatiq-smart-pricing' ); ?></li>
|
2025-12-23 07:48:45 +01:00
|
|
|
</ol>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="informatiq-sp-sidebar-box">
|
|
|
|
|
<h3><?php esc_html_e( 'Requirements', 'informatiq-smart-pricing' ); ?></h3>
|
|
|
|
|
<ul>
|
|
|
|
|
<li><?php esc_html_e( 'Products must be in stock', 'informatiq-smart-pricing' ); ?></li>
|
|
|
|
|
<li><?php esc_html_e( 'Products must have SKU set', 'informatiq-smart-pricing' ); ?></li>
|
|
|
|
|
<li><?php esc_html_e( 'Products must exist in Google Merchant Center', 'informatiq-smart-pricing' ); ?></li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="informatiq-sp-sidebar-box">
|
|
|
|
|
<h3><?php esc_html_e( 'Support', 'informatiq-smart-pricing' ); ?></h3>
|
|
|
|
|
<p>
|
|
|
|
|
<?php
|
|
|
|
|
printf(
|
|
|
|
|
/* translators: %s: support URL */
|
|
|
|
|
wp_kses_post( __( 'Need help? Visit <a href="%s" target="_blank">informatiq.services</a>', 'informatiq-smart-pricing' ) ),
|
|
|
|
|
'https://informatiq.services'
|
|
|
|
|
);
|
|
|
|
|
?>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<?php
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle manual sync AJAX request.
|
|
|
|
|
*/
|
|
|
|
|
public function handle_manual_sync() {
|
|
|
|
|
check_ajax_referer( 'informatiq_sp_admin', 'nonce' );
|
|
|
|
|
|
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 08:52:57 +01:00
|
|
|
if ( ! $this->is_authorized() ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Please authorize with Google first.', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 07:48:45 +01:00
|
|
|
try {
|
|
|
|
|
$scheduler = new Informatiq_SP_Scheduler( $this->price_updater );
|
|
|
|
|
$results = $scheduler->trigger_manual_update();
|
|
|
|
|
|
|
|
|
|
wp_send_json_success(
|
|
|
|
|
array(
|
|
|
|
|
'message' => sprintf(
|
|
|
|
|
/* translators: %1$d: updated count, %2$d: processed count */
|
|
|
|
|
__( 'Sync completed! Updated %1$d of %2$d products.', 'informatiq-smart-pricing' ),
|
|
|
|
|
$results['updated'],
|
|
|
|
|
$results['processed']
|
|
|
|
|
),
|
|
|
|
|
'results' => $results,
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
} catch ( Exception $e ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => $e->getMessage() ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle test connection AJAX request.
|
|
|
|
|
*/
|
|
|
|
|
public function handle_test_connection() {
|
|
|
|
|
check_ajax_referer( 'informatiq_sp_admin', 'nonce' );
|
|
|
|
|
|
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 08:52:57 +01:00
|
|
|
$merchant_id = get_option( 'informatiq_sp_merchant_id' );
|
|
|
|
|
$client_id = get_option( 'informatiq_sp_client_id' );
|
|
|
|
|
$client_secret = get_option( 'informatiq_sp_client_secret' );
|
|
|
|
|
$refresh_token = get_option( 'informatiq_sp_refresh_token' );
|
2025-12-23 07:48:45 +01:00
|
|
|
|
2026-01-21 08:52:57 +01:00
|
|
|
if ( empty( $merchant_id ) ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Please enter your Merchant ID.', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( empty( $refresh_token ) ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Please authorize with Google first.', 'informatiq-smart-pricing' ) ) );
|
2025-12-23 07:48:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2026-01-21 08:52:57 +01:00
|
|
|
$google_api = new Informatiq_SP_Google_API(
|
|
|
|
|
$merchant_id,
|
|
|
|
|
$client_id,
|
|
|
|
|
$client_secret,
|
|
|
|
|
$refresh_token,
|
|
|
|
|
$this->logger
|
|
|
|
|
);
|
|
|
|
|
$success = $google_api->test_connection();
|
2025-12-23 07:48:45 +01:00
|
|
|
|
|
|
|
|
if ( $success ) {
|
|
|
|
|
wp_send_json_success( array( 'message' => __( 'Connection successful!', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
} else {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Connection failed. Check your credentials.', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
} catch ( Exception $e ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => $e->getMessage() ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 08:52:57 +01:00
|
|
|
/**
|
|
|
|
|
* Handle revoke authorization AJAX request.
|
|
|
|
|
*/
|
|
|
|
|
public function handle_revoke_auth() {
|
|
|
|
|
check_ajax_referer( 'informatiq_sp_admin', 'nonce' );
|
|
|
|
|
|
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete_option( 'informatiq_sp_refresh_token' );
|
|
|
|
|
|
|
|
|
|
$this->logger->info( 'Google OAuth authorization revoked' );
|
|
|
|
|
|
|
|
|
|
wp_send_json_success( array( 'message' => __( 'Authorization revoked.', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-22 19:16:57 +01:00
|
|
|
/**
|
|
|
|
|
* Handle product comparison AJAX request.
|
|
|
|
|
*/
|
|
|
|
|
public function handle_compare_products() {
|
|
|
|
|
check_ajax_referer( 'informatiq_sp_admin', 'nonce' );
|
|
|
|
|
|
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$merchant_id = get_option( 'informatiq_sp_merchant_id' );
|
|
|
|
|
$client_id = get_option( 'informatiq_sp_client_id' );
|
|
|
|
|
$client_secret = get_option( 'informatiq_sp_client_secret' );
|
|
|
|
|
$refresh_token = get_option( 'informatiq_sp_refresh_token' );
|
|
|
|
|
|
|
|
|
|
if ( empty( $merchant_id ) || empty( $refresh_token ) ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Please configure settings and authorize first.', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$google_api = new Informatiq_SP_Google_API(
|
|
|
|
|
$merchant_id,
|
|
|
|
|
$client_id,
|
|
|
|
|
$client_secret,
|
|
|
|
|
$refresh_token,
|
|
|
|
|
$this->logger
|
|
|
|
|
);
|
|
|
|
|
|
2026-01-23 18:22:17 +01:00
|
|
|
// Get price insights from Google (suggested prices + predicted impact).
|
|
|
|
|
$price_insights = $google_api->get_price_insights();
|
|
|
|
|
$this->logger->info( sprintf( 'Loaded %d price insights from Google', count( $price_insights ) ) );
|
2026-01-23 17:45:35 +01:00
|
|
|
|
2026-01-23 18:22:17 +01:00
|
|
|
// Get all WooCommerce in-stock products (no limit - load all).
|
2026-01-22 19:16:57 +01:00
|
|
|
$wc_products = wc_get_products( array(
|
|
|
|
|
'status' => 'publish',
|
|
|
|
|
'stock_status' => 'instock',
|
2026-01-23 18:22:17 +01:00
|
|
|
'limit' => -1,
|
2026-01-22 19:16:57 +01:00
|
|
|
'return' => 'objects',
|
|
|
|
|
'type' => array( 'simple', 'variable' ),
|
|
|
|
|
) );
|
|
|
|
|
|
|
|
|
|
$comparison = array();
|
|
|
|
|
|
|
|
|
|
foreach ( $wc_products as $product ) {
|
|
|
|
|
$sku = $product->get_sku();
|
|
|
|
|
if ( empty( $sku ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-23 18:22:17 +01:00
|
|
|
$product_id = $product->get_id();
|
|
|
|
|
|
2026-02-13 09:28:13 +01:00
|
|
|
// Get brand and cost from post meta.
|
|
|
|
|
$brand = get_post_meta( $product_id, '_brand', true );
|
2026-02-16 11:23:23 +01:00
|
|
|
$cost_raw = get_post_meta( $product_id, '_cost', true );
|
|
|
|
|
|
|
|
|
|
// Convert cost to tax-inclusive to match other prices.
|
|
|
|
|
$cost = null;
|
|
|
|
|
if ( $cost_raw !== '' && $cost_raw !== false ) {
|
|
|
|
|
$cost = wc_get_price_including_tax( $product, array( 'price' => (float) $cost_raw ) );
|
|
|
|
|
$cost = round( $cost, 2 );
|
|
|
|
|
}
|
2026-02-13 08:02:05 +01:00
|
|
|
|
2026-01-23 18:22:17 +01:00
|
|
|
// Get local price (tax-inclusive for comparison with Google).
|
2026-01-22 19:16:57 +01:00
|
|
|
$sale_price = $product->get_sale_price();
|
|
|
|
|
$regular_price = $product->get_regular_price();
|
2026-01-23 17:53:24 +01:00
|
|
|
$base_price = ! empty( $sale_price ) ? $sale_price : $regular_price;
|
2026-01-22 19:16:57 +01:00
|
|
|
$price_type = ! empty( $sale_price ) ? 'sale' : 'regular';
|
|
|
|
|
|
2026-01-23 18:22:17 +01:00
|
|
|
$local_price_incl_tax = null;
|
2026-01-23 17:53:24 +01:00
|
|
|
if ( $base_price ) {
|
2026-01-23 18:22:17 +01:00
|
|
|
$local_price_incl_tax = wc_get_price_including_tax( $product, array( 'price' => $base_price ) );
|
2026-01-23 17:53:24 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-23 18:22:17 +01:00
|
|
|
// Try to find matching price insight by WooCommerce product ID (which is Google's offerId).
|
|
|
|
|
$insight = null;
|
|
|
|
|
if ( isset( $price_insights[ 'offer_' . $product_id ] ) ) {
|
|
|
|
|
$insight = $price_insights[ 'offer_' . $product_id ];
|
2026-01-22 19:16:57 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-23 18:22:17 +01:00
|
|
|
// Calculate potential gain and determine if update is beneficial.
|
|
|
|
|
$suggested_price = $insight['suggested_price'] ?? null;
|
|
|
|
|
$potential_gain = null;
|
2026-01-23 18:42:00 +01:00
|
|
|
$percent_diff = null;
|
|
|
|
|
|
2026-01-23 18:22:17 +01:00
|
|
|
if ( $suggested_price && $local_price_incl_tax ) {
|
|
|
|
|
$potential_gain = round( $suggested_price - $local_price_incl_tax, 2 );
|
2026-01-23 18:42:00 +01:00
|
|
|
|
|
|
|
|
// Calculate percentage difference.
|
|
|
|
|
$percent_diff = round( ( ( $suggested_price - $local_price_incl_tax ) / $local_price_incl_tax ) * 100, 1 );
|
2026-01-22 19:16:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$comparison[] = array(
|
2026-01-23 18:22:17 +01:00
|
|
|
'id' => $product_id,
|
2026-02-13 08:02:05 +01:00
|
|
|
'brand' => $brand ?: '',
|
2026-01-23 18:22:17 +01:00
|
|
|
'name' => $product->get_name(),
|
|
|
|
|
'sku' => $sku,
|
2026-02-13 08:02:05 +01:00
|
|
|
'cost' => $cost,
|
2026-01-23 18:22:17 +01:00
|
|
|
'local_price' => $local_price_incl_tax ? round( $local_price_incl_tax, 2 ) : null,
|
|
|
|
|
'price_type' => $price_type,
|
|
|
|
|
'google_price' => $insight['google_price'] ?? null,
|
|
|
|
|
'suggested_price' => $suggested_price,
|
2026-01-23 18:42:00 +01:00
|
|
|
'percent_diff' => $percent_diff,
|
2026-01-23 18:22:17 +01:00
|
|
|
'potential_gain' => $potential_gain,
|
|
|
|
|
'predicted_impressions_change' => $insight['predicted_impressions_change'] ?? null,
|
|
|
|
|
'predicted_clicks_change' => $insight['predicted_clicks_change'] ?? null,
|
|
|
|
|
'predicted_conversions_change' => $insight['predicted_conversions_change'] ?? null,
|
|
|
|
|
'has_insight' => ! empty( $insight ),
|
2026-01-22 19:16:57 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-23 18:38:49 +01:00
|
|
|
// Sort by predicted conversion change (highest first), products without data last.
|
2026-01-23 18:22:17 +01:00
|
|
|
usort( $comparison, function( $a, $b ) {
|
2026-01-23 18:38:49 +01:00
|
|
|
// Products without insight data go to the end.
|
|
|
|
|
if ( ! $a['has_insight'] && $b['has_insight'] ) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if ( $a['has_insight'] && ! $b['has_insight'] ) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if ( ! $a['has_insight'] && ! $b['has_insight'] ) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort by predicted conversion change (highest first).
|
|
|
|
|
$conv_a = $a['predicted_conversions_change'] ?? -999;
|
|
|
|
|
$conv_b = $b['predicted_conversions_change'] ?? -999;
|
|
|
|
|
return $conv_b <=> $conv_a;
|
2026-01-23 18:22:17 +01:00
|
|
|
} );
|
2026-01-23 17:45:35 +01:00
|
|
|
|
2026-01-22 19:16:57 +01:00
|
|
|
wp_send_json_success( array(
|
2026-01-23 18:22:17 +01:00
|
|
|
'products' => $comparison,
|
|
|
|
|
'insights_count' => count( $price_insights ),
|
|
|
|
|
'wc_count' => count( $wc_products ),
|
|
|
|
|
'currency' => get_woocommerce_currency_symbol(),
|
|
|
|
|
'currency_code' => get_woocommerce_currency(),
|
2026-01-22 19:16:57 +01:00
|
|
|
) );
|
|
|
|
|
|
|
|
|
|
} catch ( Exception $e ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => $e->getMessage() ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-23 18:22:17 +01:00
|
|
|
/**
|
|
|
|
|
* Handle individual price update AJAX request.
|
|
|
|
|
*/
|
|
|
|
|
public function handle_update_price() {
|
|
|
|
|
check_ajax_referer( 'informatiq_sp_admin', 'nonce' );
|
|
|
|
|
|
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0;
|
|
|
|
|
$new_price = isset( $_POST['new_price'] ) ? floatval( $_POST['new_price'] ) : 0;
|
|
|
|
|
|
|
|
|
|
if ( ! $product_id || $new_price <= 0 ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Invalid product ID or price.', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$product = wc_get_product( $product_id );
|
|
|
|
|
if ( ! $product ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Product not found.', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert tax-inclusive price to the format WooCommerce expects.
|
|
|
|
|
$price_to_set = $new_price;
|
|
|
|
|
if ( ! wc_prices_include_tax() ) {
|
|
|
|
|
// Store prices exclude tax, so we need to reverse-calculate.
|
|
|
|
|
$tax_rates = WC_Tax::get_rates( $product->get_tax_class() );
|
|
|
|
|
if ( ! empty( $tax_rates ) ) {
|
|
|
|
|
$tax_rate = 0;
|
|
|
|
|
foreach ( $tax_rates as $rate ) {
|
|
|
|
|
$tax_rate += (float) $rate['rate'];
|
|
|
|
|
}
|
|
|
|
|
$price_to_set = $new_price / ( 1 + ( $tax_rate / 100 ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$price_to_set = round( $price_to_set, wc_get_price_decimals() );
|
|
|
|
|
|
|
|
|
|
// Store old price for logging.
|
|
|
|
|
$old_price = $product->get_sale_price() ?: $product->get_regular_price();
|
|
|
|
|
|
|
|
|
|
// Set as sale price (since we're adjusting to be competitive).
|
|
|
|
|
$product->set_sale_price( $price_to_set );
|
|
|
|
|
$product->save();
|
|
|
|
|
|
|
|
|
|
// Log the update.
|
|
|
|
|
$this->logger->log_price_update(
|
|
|
|
|
$product_id,
|
|
|
|
|
$product->get_name(),
|
|
|
|
|
$old_price,
|
|
|
|
|
$price_to_set,
|
|
|
|
|
$new_price,
|
|
|
|
|
'Manual update to Google suggested price'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
wp_send_json_success( array(
|
|
|
|
|
'message' => sprintf(
|
|
|
|
|
__( 'Price updated for %s: %s', 'informatiq-smart-pricing' ),
|
|
|
|
|
$product->get_name(),
|
|
|
|
|
wc_price( $price_to_set )
|
|
|
|
|
),
|
|
|
|
|
'new_price' => $price_to_set,
|
|
|
|
|
) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handle bulk price update AJAX request.
|
|
|
|
|
*/
|
|
|
|
|
public function handle_bulk_update_prices() {
|
|
|
|
|
check_ajax_referer( 'informatiq_sp_admin', 'nonce' );
|
|
|
|
|
|
|
|
|
|
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'Insufficient permissions', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$updates = isset( $_POST['updates'] ) ? $_POST['updates'] : array();
|
|
|
|
|
|
|
|
|
|
if ( empty( $updates ) || ! is_array( $updates ) ) {
|
|
|
|
|
wp_send_json_error( array( 'message' => __( 'No updates provided.', 'informatiq-smart-pricing' ) ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$updated = 0;
|
|
|
|
|
$errors = 0;
|
|
|
|
|
|
|
|
|
|
foreach ( $updates as $update ) {
|
|
|
|
|
$product_id = absint( $update['product_id'] ?? 0 );
|
|
|
|
|
$new_price = floatval( $update['new_price'] ?? 0 );
|
|
|
|
|
|
|
|
|
|
if ( ! $product_id || $new_price <= 0 ) {
|
|
|
|
|
$errors++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$product = wc_get_product( $product_id );
|
|
|
|
|
if ( ! $product ) {
|
|
|
|
|
$errors++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert tax-inclusive price to the format WooCommerce expects.
|
|
|
|
|
$price_to_set = $new_price;
|
|
|
|
|
if ( ! wc_prices_include_tax() ) {
|
|
|
|
|
$tax_rates = WC_Tax::get_rates( $product->get_tax_class() );
|
|
|
|
|
if ( ! empty( $tax_rates ) ) {
|
|
|
|
|
$tax_rate = 0;
|
|
|
|
|
foreach ( $tax_rates as $rate ) {
|
|
|
|
|
$tax_rate += (float) $rate['rate'];
|
|
|
|
|
}
|
|
|
|
|
$price_to_set = $new_price / ( 1 + ( $tax_rate / 100 ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$price_to_set = round( $price_to_set, wc_get_price_decimals() );
|
|
|
|
|
$old_price = $product->get_sale_price() ?: $product->get_regular_price();
|
|
|
|
|
|
|
|
|
|
$product->set_sale_price( $price_to_set );
|
|
|
|
|
$product->save();
|
|
|
|
|
|
|
|
|
|
$this->logger->log_price_update(
|
|
|
|
|
$product_id,
|
|
|
|
|
$product->get_name(),
|
|
|
|
|
$old_price,
|
|
|
|
|
$price_to_set,
|
|
|
|
|
$new_price,
|
|
|
|
|
'Bulk update to Google suggested price'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$updated++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wp_send_json_success( array(
|
|
|
|
|
'message' => sprintf(
|
|
|
|
|
__( 'Bulk update complete: %d updated, %d errors.', 'informatiq-smart-pricing' ),
|
|
|
|
|
$updated,
|
|
|
|
|
$errors
|
|
|
|
|
),
|
|
|
|
|
'updated' => $updated,
|
|
|
|
|
'errors' => $errors,
|
|
|
|
|
) );
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 07:48:45 +01:00
|
|
|
/**
|
|
|
|
|
* Enqueue admin assets.
|
|
|
|
|
*
|
|
|
|
|
* @param string $hook Current admin page hook.
|
|
|
|
|
*/
|
|
|
|
|
public function enqueue_admin_assets( $hook ) {
|
|
|
|
|
if ( 'woocommerce_page_informatiq-smart-pricing' !== $hook ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wp_enqueue_style(
|
|
|
|
|
'informatiq-sp-admin',
|
|
|
|
|
INFORMATIQ_SP_PLUGIN_URL . 'assets/css/admin.css',
|
|
|
|
|
array(),
|
|
|
|
|
INFORMATIQ_SP_VERSION
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
wp_enqueue_script(
|
|
|
|
|
'informatiq-sp-admin',
|
|
|
|
|
INFORMATIQ_SP_PLUGIN_URL . 'assets/js/admin.js',
|
|
|
|
|
array( 'jquery' ),
|
|
|
|
|
INFORMATIQ_SP_VERSION,
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
wp_localize_script(
|
|
|
|
|
'informatiq-sp-admin',
|
|
|
|
|
'informatiqSP',
|
|
|
|
|
array(
|
|
|
|
|
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
|
|
|
|
'nonce' => wp_create_nonce( 'informatiq_sp_admin' ),
|
|
|
|
|
'strings' => array(
|
2026-01-21 08:52:57 +01:00
|
|
|
'syncInProgress' => __( 'Sync in progress...', 'informatiq-smart-pricing' ),
|
|
|
|
|
'testInProgress' => __( 'Testing connection...', 'informatiq-smart-pricing' ),
|
|
|
|
|
'revokeConfirm' => __( 'Are you sure you want to revoke Google authorization?', 'informatiq-smart-pricing' ),
|
|
|
|
|
'revokeInProgress' => __( 'Revoking...', 'informatiq-smart-pricing' ),
|
2025-12-23 07:48:45 +01:00
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|