Files
adsPRO/templates/users/settings.php
Jacek Pyziak efbdcce08a feat: Add XML file management functionality
- Created XmlFiles control class for handling XML file views and regeneration.
- Implemented method to retrieve clients with XML feeds in the factory class.
- Added database migration to include google_merchant_account_id in clients table.
- Created migrations for products_keyword_planner_terms and products_merchant_sync_log tables.
- Added campaign_keywords table migration for managing campaign keyword data.
- Developed main view template for displaying XML files and their statuses.
- Introduced a debug script for analyzing product URLs and their statuses.
2026-02-18 21:23:53 +01:00

480 lines
21 KiB
PHP

<?php
$settings_tab = \S::get( 'settings_tab' ) === 'cron' ? 'cron' : 'general';
$cron_data = is_array( $this -> cron_data ?? null ) ? $this -> cron_data : [];
$cron_progress = is_array( $cron_data['progress'] ?? null ) ? $cron_data['progress'] : [];
$cron_urls = is_array( $cron_data['urls'] ?? null ) ? $cron_data['urls'] : [];
?>
<div class="settings-tabs">
<a href="/settings?settings_tab=general" class="settings-tab <?= $settings_tab === 'general' ? 'active' : ''; ?>">
<i class="fa-solid fa-sliders"></i> Ustawienia
</a>
<a href="/settings?settings_tab=cron" class="settings-tab <?= $settings_tab === 'cron' ? 'active' : ''; ?>">
<i class="fa-solid fa-clock-rotate-left"></i> CRON
</a>
</div>
<?php if ( $settings_tab === 'cron' ): ?>
<div class="row">
<div class="col-12">
<div class="settings-card">
<div class="settings-card-header">
<div class="settings-card-icon"><i class="fa-solid fa-clock-rotate-left"></i></div>
<div>
<h3>Status CRON</h3>
<small>Adresy URL do wywołań, postęp pipeline i ostatnie uruchomienia</small>
</div>
</div>
<div class="cron-status-overview">
<div><strong>Ostatnie wywołanie:</strong> <span data-cron-overall-last-invoked><?= htmlspecialchars( (string) ( $cron_data['overall_last_invoked_at'] ?? 'Brak danych' ) ); ?></span></div>
<div><strong>Klienci z Google Ads ID:</strong> <span data-cron-clients-total><?= (int) ( $cron_data['clients_total'] ?? 0 ); ?></span></div>
</div>
<div class="cron-progress-list">
<?php foreach ( $cron_progress as $progress_index => $item ): ?>
<?php
$processed = (int) ( $item['processed'] ?? 0 );
$total = (int) ( $item['total'] ?? 0 );
$percent = (int) ( $item['percent'] ?? 0 );
?>
<div class="cron-progress-item" data-cron-progress-item="<?= (int) $progress_index; ?>">
<div class="cron-progress-head">
<strong><?= htmlspecialchars( (string) ( $item['name'] ?? 'CRON' ) ); ?></strong>
<span data-cron-progress-summary><?= $processed; ?> / <?= $total; ?> (<?= $percent; ?>%)</span>
</div>
<div class="cron-progress-bar">
<span data-cron-progress-fill style="width: <?= $percent; ?>%;"></span>
</div>
<small data-cron-progress-meta><?= htmlspecialchars( (string) ( $item['meta'] ?? '' ) ); ?></small>
</div>
<?php endforeach; ?>
</div>
<div class="cron-url-list">
<?php foreach ( $cron_urls as $url_index => $row ): ?>
<div class="cron-url-item" data-cron-url-item="<?= (int) $url_index; ?>">
<div class="cron-url-top">
<strong><?= htmlspecialchars( (string) ( $row['name'] ?? 'Cron' ) ); ?></strong>
<small>Ostatnio: <span data-cron-url-last-invoked><?= htmlspecialchars( (string) ( $row['last_invoked_at'] ?? 'Brak danych' ) ); ?></span></small>
</div>
<code><?= htmlspecialchars( (string) ( $row['url'] ?? '' ) ); ?></code>
</div>
<?php endforeach; ?>
</div>
<a href="/settings?settings_tab=cron" class="btn btn-primary btn-sm">
<i class="fa-solid fa-rotate-right mr5"></i> Odśwież status
</a>
</div>
</div>
</div>
<?php else: ?>
<div class="row">
<div class="col-12 col-md-6">
<div class="settings-card">
<div class="settings-card-header">
<div class="settings-card-icon"><i class="fa-solid fa-lock"></i></div>
<div>
<h3>Zmiana hasła</h3>
<small>Zmień swoje stare hasło na nowe</small>
</div>
</div>
<form method="POST" id="password-settings" action="/users/password_change/">
<div class="settings-field">
<label for="password_old">Stare hasło</label>
<div class="settings-input-wrap">
<i class="fa-solid fa-key settings-input-icon"></i>
<input type="password" id="password_old" name="password_old" class="form-control" required placeholder="Wprowadź stare hasło" />
<button type="button" class="settings-toggle-pw" onclick="password_toggle( this, 'password_old' )">
<i class="fa-solid fa-eye"></i>
</button>
</div>
</div>
<div class="settings-field">
<label for="password_new">Nowe hasło</label>
<div class="settings-input-wrap">
<i class="fa-solid fa-key settings-input-icon"></i>
<input type="password" id="password_new" name="password_new" class="form-control" required placeholder="Wprowadź nowe hasło" />
<button type="button" class="settings-toggle-pw" onclick="password_toggle( this, 'password_new' )">
<i class="fa-solid fa-eye"></i>
</button>
</div>
</div>
<button type="submit" class="btn btn-success"><i class="fa-solid fa-check mr5"></i>Zmień hasło</button>
</form>
</div>
</div>
</div>
<div class="row" style="margin-top: 25px;">
<div class="col-12">
<div class="settings-card">
<div class="settings-card-header">
<div class="settings-card-icon"><i class="fa-brands fa-google"></i></div>
<div>
<h3>Google Ads API</h3>
<small>Dane do połączenia z Google Ads REST API (wymagane do synchronizacji kampanii)</small>
</div>
</div>
<?php
$last_error = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
if ( $last_error ): ?>
<div class="settings-alert-error">
<i class="fa-solid fa-triangle-exclamation"></i>
<span><strong>Ostatni błąd API:</strong> <?= htmlspecialchars( $last_error ); ?></span>
</div>
<?php endif; ?>
<form method="POST" id="google-ads-settings" action="/settings/save_google_ads">
<div class="settings-field" style="margin-bottom: 16px;">
<label class="settings-toggle-label">
<?php $google_ads_debug_enabled = \services\GoogleAdsApi::get_setting( 'google_ads_debug_enabled' ) !== '0'; ?>
<input type="hidden" name="google_ads_debug_enabled" value="0" />
<input type="checkbox" name="google_ads_debug_enabled" value="1" class="settings-toggle-checkbox" <?= $google_ads_debug_enabled ? 'checked' : ''; ?> />
<span class="settings-toggle-switch"></span>
<span class="settings-toggle-text">Włącz debug Google Ads API</span>
</label>
</div>
<div class="settings-fields-grid">
<div class="settings-field">
<label for="google_ads_developer_token">Developer Token</label>
<input type="text" id="google_ads_developer_token" name="google_ads_developer_token" class="form-control" value="<?= htmlspecialchars( \services\GoogleAdsApi::get_setting( 'google_ads_developer_token' ) ); ?>" placeholder="np. ABcdEf1234..." />
</div>
<div class="settings-field">
<label for="google_ads_client_id">OAuth2 Client ID</label>
<input type="text" id="google_ads_client_id" name="google_ads_client_id" class="form-control" value="<?= htmlspecialchars( \services\GoogleAdsApi::get_setting( 'google_ads_client_id' ) ); ?>" placeholder="np. 123456789.apps.googleusercontent.com" />
</div>
<div class="settings-field">
<label for="google_ads_client_secret">OAuth2 Client Secret</label>
<div class="settings-input-wrap">
<input type="password" id="google_ads_client_secret" name="google_ads_client_secret" class="form-control" value="<?= htmlspecialchars( \services\GoogleAdsApi::get_setting( 'google_ads_client_secret' ) ); ?>" />
<button type="button" class="settings-toggle-pw" onclick="password_toggle( this, 'google_ads_client_secret' )">
<i class="fa-solid fa-eye"></i>
</button>
</div>
</div>
<div class="settings-field">
<label for="google_ads_refresh_token">OAuth2 Refresh Token</label>
<div class="settings-input-wrap">
<input type="password" id="google_ads_refresh_token" name="google_ads_refresh_token" class="form-control" value="<?= htmlspecialchars( \services\GoogleAdsApi::get_setting( 'google_ads_refresh_token' ) ); ?>" />
<button type="button" class="settings-toggle-pw" onclick="password_toggle( this, 'google_ads_refresh_token' )">
<i class="fa-solid fa-eye"></i>
</button>
</div>
</div>
<div class="settings-field">
<label for="google_merchant_refresh_token">Merchant API Refresh Token</label>
<div class="settings-input-wrap">
<input type="password" id="google_merchant_refresh_token" name="google_merchant_refresh_token" class="form-control" value="<?= htmlspecialchars( \services\GoogleAdsApi::get_setting( 'google_merchant_refresh_token' ) ); ?>" placeholder="Refresh token dla scope content" />
<button type="button" class="settings-toggle-pw" onclick="password_toggle( this, 'google_merchant_refresh_token' )">
<i class="fa-solid fa-eye"></i>
</button>
</div>
</div>
<div class="settings-field">
<label for="google_ads_manager_account_id">Manager Account ID <span class="text-muted">(opcjonalnie, dla MCC)</span></label>
<input type="text" id="google_ads_manager_account_id" name="google_ads_manager_account_id" class="form-control" value="<?= htmlspecialchars( \services\GoogleAdsApi::get_setting( 'google_ads_manager_account_id' ) ); ?>" placeholder="np. 123-456-7890" />
</div>
</div>
<button type="button" class="btn btn-success" onclick="$( '#google-ads-settings' ).submit();"><i class="fa-solid fa-check mr5"></i>Zapisz ustawienia Google Ads</button>
</form>
</div>
</div>
</div>
<div class="row" style="margin-top: 25px;">
<div class="col-12">
<div class="settings-card">
<div class="settings-card-header">
<div class="settings-card-icon"><i class="fa-solid fa-robot"></i></div>
<div>
<h3>OpenAI (ChatGPT)</h3>
<small>Klucz API i model do optymalizacji tytułów i opisów produktów przez AI</small>
</div>
</div>
<form method="POST" id="openai-settings" action="/settings/save_openai">
<div class="settings-field" style="margin-bottom: 16px;">
<label class="settings-toggle-label">
<?php $openai_enabled = \services\GoogleAdsApi::get_setting( 'openai_enabled' ) !== '0'; ?>
<input type="hidden" name="openai_enabled" value="0" />
<input type="checkbox" name="openai_enabled" value="1" class="settings-toggle-checkbox" <?= $openai_enabled ? 'checked' : ''; ?> />
<span class="settings-toggle-switch"></span>
<span class="settings-toggle-text">Włącz OpenAI (ChatGPT)</span>
</label>
</div>
<div class="settings-fields-grid">
<div class="settings-field">
<label for="openai_api_key">API Key</label>
<div class="settings-input-wrap">
<input type="password" id="openai_api_key" name="openai_api_key" class="form-control" value="<?= htmlspecialchars( \services\GoogleAdsApi::get_setting( 'openai_api_key' ) ); ?>" placeholder="sk-..." />
<button type="button" class="settings-toggle-pw" onclick="password_toggle( this, 'openai_api_key' )">
<i class="fa-solid fa-eye"></i>
</button>
</div>
</div>
<div class="settings-field">
<label for="openai_model">Model</label>
<?php $current_model = \services\GoogleAdsApi::get_setting( 'openai_model' ) ?: 'gpt-5-mini'; ?>
<select id="openai_model" name="openai_model" class="form-control">
<option value="gpt-5.2" <?= $current_model === 'gpt-5.2' ? 'selected' : ''; ?>>GPT-5.2 (najnowszy, $1.75/$14 per 1M)</option>
<option value="gpt-5-mini" <?= $current_model === 'gpt-5-mini' ? 'selected' : ''; ?>>GPT-5 Mini (szybki, $0.25/$2 per 1M)</option>
<option value="gpt-4.1" <?= $current_model === 'gpt-4.1' ? 'selected' : ''; ?>>GPT-4.1</option>
<option value="gpt-4.1-mini" <?= $current_model === 'gpt-4.1-mini' ? 'selected' : ''; ?>>GPT-4.1 Mini</option>
<option value="gpt-4o" <?= $current_model === 'gpt-4o' ? 'selected' : ''; ?>>GPT-4o (legacy)</option>
<option value="gpt-4o-mini" <?= $current_model === 'gpt-4o-mini' ? 'selected' : ''; ?>>GPT-4o Mini (legacy)</option>
</select>
</div>
</div>
<button type="button" class="btn btn-success" onclick="$( '#openai-settings' ).submit();"><i class="fa-solid fa-check mr5"></i>Zapisz ustawienia OpenAI</button>
</form>
</div>
</div>
</div>
<div class="row" style="margin-top: 25px;">
<div class="col-12">
<div class="settings-card">
<div class="settings-card-header">
<div class="settings-card-icon"><i class="fa-solid fa-brain"></i></div>
<div>
<h3>Claude (Anthropic)</h3>
<small>Klucz API i model Claude do optymalizacji tytułów i opisów produktów przez AI</small>
</div>
</div>
<form method="POST" id="claude-settings" action="/settings/save_claude">
<div class="settings-field" style="margin-bottom: 16px;">
<label class="settings-toggle-label">
<?php $claude_enabled = \services\GoogleAdsApi::get_setting( 'claude_enabled' ) !== '0'; ?>
<input type="hidden" name="claude_enabled" value="0" />
<input type="checkbox" name="claude_enabled" value="1" class="settings-toggle-checkbox" <?= $claude_enabled ? 'checked' : ''; ?> />
<span class="settings-toggle-switch"></span>
<span class="settings-toggle-text">Włącz Claude (Anthropic)</span>
</label>
</div>
<div class="settings-fields-grid">
<div class="settings-field">
<label for="claude_api_key">API Key</label>
<div class="settings-input-wrap">
<input type="password" id="claude_api_key" name="claude_api_key" class="form-control" value="<?= htmlspecialchars( \services\GoogleAdsApi::get_setting( 'claude_api_key' ) ); ?>" placeholder="sk-ant-..." />
<button type="button" class="settings-toggle-pw" onclick="password_toggle( this, 'claude_api_key' )">
<i class="fa-solid fa-eye"></i>
</button>
</div>
</div>
<div class="settings-field">
<label for="claude_model">Model</label>
<?php $current_claude_model = \services\GoogleAdsApi::get_setting( 'claude_model' ) ?: 'claude-sonnet-4-5-20250929'; ?>
<select id="claude_model" name="claude_model" class="form-control">
<option value="claude-opus-4-6" <?= $current_claude_model === 'claude-opus-4-6' ? 'selected' : ''; ?>>Claude Opus 4.6 (najpotężniejszy)</option>
<option value="claude-sonnet-4-5-20250929" <?= $current_claude_model === 'claude-sonnet-4-5-20250929' ? 'selected' : ''; ?>>Claude Sonnet 4.5 (zbalansowany)</option>
<option value="claude-haiku-4-5-20251001" <?= $current_claude_model === 'claude-haiku-4-5-20251001' ? 'selected' : ''; ?>>Claude Haiku 4.5 (szybki, tani)</option>
</select>
</div>
</div>
<button type="button" class="btn btn-success" onclick="$( '#claude-settings' ).submit();"><i class="fa-solid fa-check mr5"></i>Zapisz ustawienia Claude</button>
</form>
</div>
</div>
</div>
<?php endif; ?>
<script type="text/javascript">
function password_toggle( btn, id )
{
var icon = btn.querySelector( 'i' );
var input = document.getElementById( id );
if ( !input || !icon )
{
return;
}
if ( input.type === 'password' )
{
input.type = 'text';
icon.classList.remove( 'fa-eye' );
icon.classList.add( 'fa-eye-slash' );
}
else
{
input.type = 'password';
icon.classList.remove( 'fa-eye-slash' );
icon.classList.add( 'fa-eye' );
}
}
<?php if ( $settings_tab === 'cron' ): ?>
(function()
{
var refresh_interval_ms = 60000;
var refresh_timer = null;
var cron_status_url = '/settings/cron_status';
function set_text( selector, value )
{
var node = document.querySelector( selector );
if ( !node )
{
return;
}
node.textContent = value;
}
function render_progress( progress )
{
if ( !Array.isArray( progress ) )
{
return;
}
progress.forEach( function( item, index )
{
var container = document.querySelector( '[data-cron-progress-item="' + index + '"]' );
if ( !container )
{
return;
}
var processed = parseInt( item && item.processed ? item.processed : 0, 10 );
var total = parseInt( item && item.total ? item.total : 0, 10 );
var percent = parseInt( item && item.percent ? item.percent : 0, 10 );
if ( isNaN( processed ) ) processed = 0;
if ( isNaN( total ) ) total = 0;
if ( isNaN( percent ) ) percent = 0;
percent = Math.max( 0, Math.min( 100, percent ) );
var summary = container.querySelector( '[data-cron-progress-summary]' );
if ( summary )
{
summary.textContent = processed + ' / ' + total + ' (' + percent + '%)';
}
var fill = container.querySelector( '[data-cron-progress-fill]' );
if ( fill )
{
fill.style.width = percent + '%';
}
var meta = container.querySelector( '[data-cron-progress-meta]' );
if ( meta )
{
meta.textContent = item && item.meta ? item.meta : '';
}
} );
}
function render_urls( urls )
{
if ( !Array.isArray( urls ) )
{
return;
}
urls.forEach( function( row, index )
{
var container = document.querySelector( '[data-cron-url-item="' + index + '"]' );
if ( !container )
{
return;
}
var last_invoked = container.querySelector( '[data-cron-url-last-invoked]' );
if ( last_invoked )
{
last_invoked.textContent = row && row.last_invoked_at ? row.last_invoked_at : 'Brak danych';
}
} );
}
function render_cron_data( data )
{
if ( !data || typeof data !== 'object' )
{
return;
}
set_text( '[data-cron-overall-last-invoked]', data.overall_last_invoked_at || 'Brak danych' );
set_text( '[data-cron-clients-total]', data.clients_total || 0 );
render_progress( data.progress );
render_urls( data.urls );
}
function schedule_refresh()
{
if ( refresh_timer )
{
clearTimeout( refresh_timer );
}
refresh_timer = setTimeout( refresh_status, refresh_interval_ms );
}
function refresh_status()
{
if ( document.hidden )
{
schedule_refresh();
return;
}
if ( typeof window.fetch !== 'function' )
{
window.location.reload();
return;
}
fetch( cron_status_url + '?_ts=' + Date.now(), {
method: 'GET',
credentials: 'same-origin',
cache: 'no-store',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
} )
.then( function( response )
{
if ( !response.ok )
{
throw new Error( 'HTTP ' + response.status );
}
return response.json();
} )
.then( function( payload )
{
if ( payload && payload.status === 'ok' )
{
render_cron_data( payload.data || {} );
}
} )
.catch( function()
{
} )
.finally( function()
{
schedule_refresh();
} );
}
document.addEventListener( 'visibilitychange', function()
{
if ( document.hidden )
{
if ( refresh_timer )
{
clearTimeout( refresh_timer );
}
return;
}
refresh_status();
} );
window.addEventListener( 'beforeunload', function()
{
if ( refresh_timer )
{
clearTimeout( refresh_timer );
}
} );
schedule_refresh();
})();
<?php endif; ?>
</script>