- 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.
474 lines
16 KiB
PHP
474 lines
16 KiB
PHP
<?php
|
|
namespace controls;
|
|
|
|
class Users
|
|
{
|
|
|
|
public static function permissions( $user_id, $module = '', $action = '' )
|
|
{
|
|
// Pyziak Jacek
|
|
$permissions[ 1 ][ 'projects' ] = true;
|
|
$permissions[ 1 ][ 'finances' ] = true;
|
|
$permissions[ 1 ][ 'wiki' ] = true;
|
|
$permissions[ 1 ][ 'crm' ] = true;
|
|
// Pyziak Grzegorz
|
|
$permissions[ 3 ][ 'projects' ] = true;
|
|
$permissions[ 3 ][ 'finances' ] = true;
|
|
$permissions[ 3 ][ 'wiki' ] = true;
|
|
$permissions[ 3 ][ 'crm' ] = true;
|
|
// Roman Pyrih
|
|
$permissions[ 5 ][ 'projects' ] = true;
|
|
$permissions[ 5 ][ 'finances' ] = false;
|
|
$permissions[ 5 ][ 'wiki' ] = true;
|
|
$permissions[ 5 ][ 'crm' ] = false;
|
|
|
|
if ( $action and isset( $permissions[ $user_id ][ $module ][ $action ] ) )
|
|
{
|
|
return $permissions[ $user_id ][ $module ][ $action ];
|
|
}
|
|
|
|
if ( isset( $permissions[ $user_id ][ $module ] ) )
|
|
{
|
|
return $permissions[ $user_id ][ $module ];
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static function logout()
|
|
{
|
|
$domain = preg_replace( '#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME'] );
|
|
$cookie_name = str_replace( '.', '-', $domain );
|
|
|
|
setcookie( $cookie_name, "", strtotime( "-1 year" ), "/", $domain );
|
|
session_destroy();
|
|
header( 'Location: /' );
|
|
exit;
|
|
}
|
|
|
|
public static function settings_save()
|
|
{
|
|
global $mdb, $user;
|
|
|
|
if ( \factory\Users::settings_save( $user[ 'id' ], \S::get( 'pushover_api' ), \S::get( 'pushover_user' ) ) )
|
|
{
|
|
$user = $mdb -> get( 'users', '*', [ 'id' => $user[ 'id' ] ] );
|
|
\S::set_session( 'user', $user );
|
|
\S::alert( 'Ustawienia zostały zapisane.' );
|
|
}
|
|
header( 'Location: /settings' );
|
|
exit;
|
|
}
|
|
|
|
public static function settings()
|
|
{
|
|
global $user;
|
|
|
|
if ( !$user )
|
|
{
|
|
header( 'Location: /login' );
|
|
exit;
|
|
}
|
|
|
|
return \view\Users::settings(
|
|
$user,
|
|
self::get_cron_dashboard_data()
|
|
);
|
|
}
|
|
|
|
public static function settings_cron_status()
|
|
{
|
|
global $user;
|
|
|
|
header( 'Content-Type: application/json; charset=utf-8' );
|
|
header( 'Cache-Control: no-store, no-cache, must-revalidate, max-age=0' );
|
|
header( 'Pragma: no-cache' );
|
|
|
|
if ( !$user )
|
|
{
|
|
http_response_code( 403 );
|
|
echo json_encode( [ 'status' => 'error', 'message' => 'Brak autoryzacji.' ] );
|
|
exit;
|
|
}
|
|
|
|
echo json_encode( [
|
|
'status' => 'ok',
|
|
'data' => self::get_cron_dashboard_data()
|
|
], JSON_UNESCAPED_UNICODE );
|
|
exit;
|
|
}
|
|
|
|
public static function settings_save_google_ads()
|
|
{
|
|
$fields = [
|
|
'google_ads_developer_token',
|
|
'google_ads_client_id',
|
|
'google_ads_client_secret',
|
|
'google_ads_refresh_token',
|
|
'google_merchant_refresh_token',
|
|
'google_ads_manager_account_id',
|
|
];
|
|
|
|
foreach ( $fields as $field )
|
|
{
|
|
\services\GoogleAdsApi::set_setting( $field, \S::get( $field ) );
|
|
}
|
|
|
|
\services\GoogleAdsApi::set_setting( 'google_ads_debug_enabled', \S::get( 'google_ads_debug_enabled' ) ? '1' : '0' );
|
|
|
|
// wyczyść cached token przy zmianie credentials
|
|
\services\GoogleAdsApi::set_setting( 'google_ads_access_token', null );
|
|
\services\GoogleAdsApi::set_setting( 'google_ads_access_token_expires', null );
|
|
\services\GoogleAdsApi::set_setting( 'google_merchant_access_token', null );
|
|
\services\GoogleAdsApi::set_setting( 'google_merchant_access_token_expires', null );
|
|
|
|
\S::alert( 'Ustawienia Google Ads zostały zapisane.' );
|
|
header( 'Location: /settings' );
|
|
exit;
|
|
}
|
|
|
|
public static function settings_save_openai()
|
|
{
|
|
\services\GoogleAdsApi::set_setting( 'openai_enabled', \S::get( 'openai_enabled' ) ? '1' : '0' );
|
|
\services\GoogleAdsApi::set_setting( 'openai_api_key', \S::get( 'openai_api_key' ) );
|
|
\services\GoogleAdsApi::set_setting( 'openai_model', \S::get( 'openai_model' ) );
|
|
|
|
\S::alert( 'Ustawienia OpenAI zostały zapisane.' );
|
|
header( 'Location: /settings' );
|
|
exit;
|
|
}
|
|
|
|
public static function settings_save_claude()
|
|
{
|
|
\services\GoogleAdsApi::set_setting( 'claude_enabled', \S::get( 'claude_enabled' ) ? '1' : '0' );
|
|
\services\GoogleAdsApi::set_setting( 'claude_api_key', \S::get( 'claude_api_key' ) );
|
|
\services\GoogleAdsApi::set_setting( 'claude_model', \S::get( 'claude_model' ) );
|
|
|
|
\S::alert( 'Ustawienia Claude zostały zapisane.' );
|
|
header( 'Location: /settings' );
|
|
exit;
|
|
}
|
|
|
|
private static function get_cron_dashboard_data()
|
|
{
|
|
global $mdb;
|
|
|
|
$base_url = self::get_base_url();
|
|
$clients_total = (int) $mdb -> query( "SELECT COUNT(*) FROM clients WHERE deleted = 0 AND google_ads_customer_id IS NOT NULL AND google_ads_customer_id <> ''" ) -> fetchColumn();
|
|
|
|
$campaign_window_state = self::get_setting_json( 'cron_campaigns_window_state' );
|
|
$campaign_daily_state = self::get_setting_json( 'cron_campaigns_state' );
|
|
$campaign_dates = self::normalize_dates( $campaign_window_state['sync_dates'] ?? [] );
|
|
$campaign_dates_count = count( $campaign_dates );
|
|
if ( $campaign_dates_count < 1 )
|
|
{
|
|
$campaign_dates = [ date( 'Y-m-d' ) ];
|
|
$campaign_dates_count = 1;
|
|
}
|
|
|
|
$campaign_current_date_index = (int) ( $campaign_window_state['current_date_index'] ?? 0 );
|
|
$campaign_current_date_index = max( 0, min( $campaign_dates_count - 1, $campaign_current_date_index ) );
|
|
$campaign_processed_today = count( self::normalize_ids( $campaign_daily_state['processed_ids'] ?? [] ) );
|
|
$campaign_processed_today = min( $clients_total, $campaign_processed_today );
|
|
$campaign_total = $clients_total * $campaign_dates_count;
|
|
$campaign_processed = min( $campaign_total, ( $campaign_current_date_index * $clients_total ) + $campaign_processed_today );
|
|
$campaign_remaining = max( 0, $campaign_total - $campaign_processed );
|
|
$campaign_active_date = $campaign_window_state['sync_date'] ?? ( $campaign_dates[ $campaign_current_date_index ] ?? '' );
|
|
$campaign_meta = 'Aktywny dzień: ' . ( $campaign_active_date ?: '-' ) . ', okno dni: ' . $campaign_dates_count;
|
|
$campaign_eta_meta = self::build_eta_meta( 'cron_campaigns', $campaign_remaining );
|
|
if ( $campaign_eta_meta !== '' )
|
|
{
|
|
$campaign_meta .= ', ' . $campaign_eta_meta;
|
|
}
|
|
|
|
$products_state = self::get_setting_json( 'cron_products_pipeline_state' );
|
|
$products_dates = self::normalize_dates( $products_state['import_dates'] ?? [] );
|
|
$products_dates_count = count( $products_dates );
|
|
if ( $products_dates_count < 1 )
|
|
{
|
|
$products_dates = [ date( 'Y-m-d' ) ];
|
|
$products_dates_count = 1;
|
|
}
|
|
|
|
$products_current_date_index = (int) ( $products_state['current_date_index'] ?? 0 );
|
|
$products_current_date_index = max( 0, min( $products_dates_count - 1, $products_current_date_index ) );
|
|
$products_phase = (string) ( $products_state['phase'] ?? 'fetch' );
|
|
|
|
$products_fetch_done = count( self::normalize_ids( $products_state['fetch_done_ids'] ?? [] ) );
|
|
$products_aggregate_30_done = count( self::normalize_ids( $products_state['aggregate_30_done_ids'] ?? [] ) );
|
|
$products_aggregate_temp_done = count( self::normalize_ids( $products_state['aggregate_temp_done_ids'] ?? [] ) );
|
|
|
|
$products_fetch_done = min( $clients_total, $products_fetch_done );
|
|
$products_aggregate_30_done = min( $clients_total, $products_aggregate_30_done );
|
|
$products_aggregate_temp_done = min( $clients_total, $products_aggregate_temp_done );
|
|
|
|
$products_per_day_total = $clients_total * 3;
|
|
$products_total = $products_per_day_total * $products_dates_count;
|
|
|
|
$products_done_in_day = 0;
|
|
if ( $products_phase === 'aggregate_30' )
|
|
{
|
|
$products_done_in_day = $clients_total + $products_aggregate_30_done;
|
|
}
|
|
else if ( $products_phase === 'aggregate_temp' )
|
|
{
|
|
$products_done_in_day = ( $clients_total * 2 ) + $products_aggregate_temp_done;
|
|
}
|
|
else if ( $products_phase === 'done' )
|
|
{
|
|
$products_done_in_day = $products_per_day_total;
|
|
}
|
|
else
|
|
{
|
|
$products_done_in_day = $products_fetch_done;
|
|
}
|
|
|
|
$products_done_in_day = min( $products_per_day_total, $products_done_in_day );
|
|
$products_processed = min( $products_total, ( $products_current_date_index * $products_per_day_total ) + $products_done_in_day );
|
|
if ( $products_phase === 'done' )
|
|
{
|
|
$products_processed = $products_total;
|
|
}
|
|
$products_remaining = max( 0, $products_total - $products_processed );
|
|
|
|
$products_phase_labels = [
|
|
'fetch' => 'Pobieranie',
|
|
'aggregate_30' => 'Agregacja 30 dni',
|
|
'aggregate_temp' => 'Agregacja temp',
|
|
'done' => 'Zakończono'
|
|
];
|
|
$products_phase_label = $products_phase_labels[ $products_phase ] ?? $products_phase;
|
|
$products_active_date = $products_state['import_date'] ?? ( $products_dates[ $products_current_date_index ] ?? '' );
|
|
$products_meta = 'Faza: ' . $products_phase_label . ', aktywny dzień: ' . ( $products_active_date ?: '-' ) . ', okno dni: ' . $products_dates_count;
|
|
$products_eta_meta = self::build_eta_meta( 'cron_products', $products_remaining );
|
|
if ( $products_eta_meta !== '' )
|
|
{
|
|
$products_meta .= ', ' . $products_eta_meta;
|
|
}
|
|
|
|
$cron_endpoints = [
|
|
[ 'name' => 'Legacy CRON', 'path' => '/cron.php', 'action' => 'cron_legacy' ],
|
|
[ 'name' => 'Cron kampanii', 'path' => '/cron/cron_campaigns', 'action' => 'cron_campaigns' ],
|
|
[ 'name' => 'Cron produktów', 'path' => '/cron/cron_products', 'action' => 'cron_products' ],
|
|
[ 'name' => 'Cron URL produktów (Merchant)', 'path' => '/cron/cron_products_urls', 'action' => 'cron_products_urls' ],
|
|
[ 'name' => 'Cron fraz', 'path' => '/cron/cron_phrases', 'action' => 'cron_phrases' ],
|
|
[ 'name' => 'Historia 30 dni produktów', 'path' => '/cron/cron_products_history_30', 'action' => 'cron_products_history_30' ],
|
|
[ 'name' => 'Historia 30 dni fraz', 'path' => '/cron/cron_phrases_history_30', 'action' => 'cron_phrases_history_30' ],
|
|
[ 'name' => 'Eksport XML', 'path' => '/cron/cron_xml', 'action' => 'cron_xml' ],
|
|
];
|
|
|
|
$urls = [];
|
|
foreach ( $cron_endpoints as $endpoint )
|
|
{
|
|
$last_key = 'cron_last_invoked_' . $endpoint['action'] . '_at';
|
|
$urls[] = [
|
|
'name' => $endpoint['name'],
|
|
'url' => $base_url . $endpoint['path'],
|
|
'last_invoked_at' => self::format_datetime( \services\GoogleAdsApi::get_setting( $last_key ) ),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'overall_last_invoked_at' => self::format_datetime( \services\GoogleAdsApi::get_setting( 'cron_last_invoked_at' ) ),
|
|
'clients_total' => $clients_total,
|
|
'progress' => [
|
|
[
|
|
'name' => 'Kampanie',
|
|
'processed' => $campaign_processed,
|
|
'total' => $campaign_total,
|
|
'percent' => self::progress_percent( $campaign_processed, $campaign_total ),
|
|
'meta' => $campaign_meta
|
|
],
|
|
[
|
|
'name' => 'Produkty',
|
|
'processed' => $products_processed,
|
|
'total' => $products_total,
|
|
'percent' => self::progress_percent( $products_processed, $products_total ),
|
|
'meta' => $products_meta
|
|
],
|
|
],
|
|
'urls' => $urls
|
|
];
|
|
}
|
|
|
|
private static function get_setting_json( $setting_key )
|
|
{
|
|
$raw = \services\GoogleAdsApi::get_setting( $setting_key );
|
|
if ( !$raw )
|
|
{
|
|
return [];
|
|
}
|
|
|
|
$decoded = json_decode( (string) $raw, true );
|
|
return is_array( $decoded ) ? $decoded : [];
|
|
}
|
|
|
|
private static function normalize_ids( $items )
|
|
{
|
|
$result = [];
|
|
foreach ( (array) $items as $item )
|
|
{
|
|
$id = (int) $item;
|
|
if ( $id > 0 )
|
|
{
|
|
$result[] = $id;
|
|
}
|
|
}
|
|
return array_values( array_unique( $result ) );
|
|
}
|
|
|
|
private static function normalize_dates( $items )
|
|
{
|
|
$result = [];
|
|
foreach ( (array) $items as $item )
|
|
{
|
|
$timestamp = strtotime( (string) $item );
|
|
if ( !$timestamp )
|
|
{
|
|
continue;
|
|
}
|
|
$result[] = date( 'Y-m-d', $timestamp );
|
|
}
|
|
$result = array_values( array_unique( $result ) );
|
|
sort( $result );
|
|
return $result;
|
|
}
|
|
|
|
private static function progress_percent( $processed, $total )
|
|
{
|
|
$processed = (int) $processed;
|
|
$total = (int) $total;
|
|
|
|
if ( $total <= 0 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return (int) round( min( 100, max( 0, ( $processed / $total ) * 100 ) ) );
|
|
}
|
|
|
|
private static function build_eta_meta( $action_name, $remaining_tasks )
|
|
{
|
|
$remaining_tasks = max( 0, (int) $remaining_tasks );
|
|
|
|
if ( $remaining_tasks <= 0 )
|
|
{
|
|
return 'Szacowany koniec: zakończono';
|
|
}
|
|
|
|
$avg_interval_seconds = (float) \services\GoogleAdsApi::get_setting( 'cron_avg_interval_' . $action_name . '_sec' );
|
|
$last_interval_seconds = (int) \services\GoogleAdsApi::get_setting( 'cron_last_interval_' . $action_name . '_sec' );
|
|
|
|
if ( $avg_interval_seconds <= 0 && $last_interval_seconds > 0 )
|
|
{
|
|
$avg_interval_seconds = (float) $last_interval_seconds;
|
|
}
|
|
|
|
if ( $avg_interval_seconds <= 0 )
|
|
{
|
|
return 'Szacowany koniec: brak danych o częstotliwości';
|
|
}
|
|
|
|
$estimated_seconds = (int) max( 1, round( $remaining_tasks * $avg_interval_seconds ) );
|
|
$eta_timestamp = time() + $estimated_seconds;
|
|
|
|
return 'Śr. interwał: '
|
|
. self::format_duration_short( (int) round( $avg_interval_seconds ) )
|
|
. ', szacowany koniec: '
|
|
. date( 'Y-m-d H:i:s', $eta_timestamp )
|
|
. ' (za '
|
|
. self::format_duration_short( $estimated_seconds )
|
|
. ')';
|
|
}
|
|
|
|
private static function format_duration_short( $seconds )
|
|
{
|
|
$seconds = max( 0, (int) $seconds );
|
|
|
|
if ( $seconds < 60 )
|
|
{
|
|
return $seconds . ' sek';
|
|
}
|
|
|
|
$days = (int) floor( $seconds / 86400 );
|
|
$seconds -= $days * 86400;
|
|
$hours = (int) floor( $seconds / 3600 );
|
|
$seconds -= $hours * 3600;
|
|
$minutes = (int) floor( $seconds / 60 );
|
|
|
|
$parts = [];
|
|
if ( $days > 0 )
|
|
{
|
|
$parts[] = $days . ' d';
|
|
}
|
|
if ( $hours > 0 )
|
|
{
|
|
$parts[] = $hours . ' h';
|
|
}
|
|
if ( $minutes > 0 )
|
|
{
|
|
$parts[] = $minutes . ' min';
|
|
}
|
|
|
|
if ( empty( $parts ) )
|
|
{
|
|
return '1 min';
|
|
}
|
|
|
|
return implode( ' ', array_slice( $parts, 0, 2 ) );
|
|
}
|
|
|
|
private static function format_datetime( $value )
|
|
{
|
|
$timestamp = strtotime( (string) $value );
|
|
if ( !$timestamp )
|
|
{
|
|
return 'Brak danych';
|
|
}
|
|
|
|
return date( 'Y-m-d H:i:s', $timestamp );
|
|
}
|
|
|
|
private static function get_base_url()
|
|
{
|
|
$scheme = ( !empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ) ? 'https' : 'http';
|
|
$host = $_SERVER['HTTP_HOST'] ?? ( $_SERVER['SERVER_NAME'] ?? 'localhost' );
|
|
return $scheme . '://' . $host;
|
|
}
|
|
|
|
public static function login()
|
|
{
|
|
if ( $user = \factory\Users::login(
|
|
\S::get( 'email' ),
|
|
md5( \S::get( 'password' ) )
|
|
) )
|
|
{
|
|
// zapamiętaj logowanie
|
|
if ( \S::get( 'remember' ) )
|
|
{
|
|
$domain = preg_replace( '#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME'] );
|
|
$cookie_name = str_replace( '.', '-', $domain );
|
|
|
|
$value = [ 'email' => \S::get( 'email' ), 'hash' => md5( \S::get( 'password' ) ) ];
|
|
$value = json_encode( $value );
|
|
|
|
setcookie( $cookie_name, $value, strtotime( "+1 year" ), "/", $domain );
|
|
}
|
|
|
|
\S::set_session( 'user', $user );
|
|
echo json_encode( [ 'result' => 'true', 'msg' => 'Właśnie zostałeś zalogowany. Za chwilę nastąpi przekierowanie.', 'default_project' => $user[ 'default_project' ] ] );
|
|
}
|
|
else
|
|
{
|
|
echo json_encode( [ 'result' => 'false', 'msg' => 'Podany login i hasło są nieprawidłowe.' ] );
|
|
}
|
|
exit;
|
|
}
|
|
|
|
public static function login_form()
|
|
{
|
|
return \Tpl::view( 'users/login-form' );
|
|
}
|
|
|
|
}
|