5, 'page_limit' => 50, 'max_orders' => 200, ]; private const OAUTH_SCOPES = [ AllegroOAuthClient::ORDERS_READ_SCOPE, AllegroOAuthClient::SALE_OFFERS_READ_SCOPE, AllegroOAuthClient::SHIPMENTS_READ_SCOPE, AllegroOAuthClient::SHIPMENTS_WRITE_SCOPE, ]; public function __construct( private readonly Template $template, private readonly Translator $translator, private readonly AuthService $auth, private readonly AllegroIntegrationRepository $repository, private readonly AllegroStatusMappingRepository $statusMappings, private readonly OrderStatusRepository $orderStatuses, private readonly CronRepository $cronRepository, private readonly AllegroOAuthClient $oauthClient, private readonly AllegroOrderImportService $orderImportService, private readonly AllegroStatusDiscoveryService $statusDiscoveryService, private readonly string $appUrl, private readonly ?CarrierDeliveryMethodMappingRepository $deliveryMappings = null, private readonly ?AllegroApiClient $apiClient = null, private readonly ?ApaczkaIntegrationRepository $apaczkaRepository = null, private readonly ?ApaczkaApiClient $apaczkaApiClient = null ) { } public function index(Request $request): Response { $envParam = trim((string) $request->input('env', '')); $activeEnv = in_array($envParam, ['sandbox', 'production'], true) ? $envParam : $this->repository->getActiveEnvironment(); $settings = $this->repository->getSettings($activeEnv); $tab = trim((string) $request->input('tab', 'integration')); if (!in_array($tab, ['integration', 'statuses', 'settings', 'delivery'], true)) { $tab = 'integration'; } $defaultRedirectUri = $this->defaultRedirectUri(); if (trim((string) ($settings['redirect_uri'] ?? '')) === '') { $settings['redirect_uri'] = $defaultRedirectUri; } $this->ensureDefaultSchedulesExist(); $importIntervalSeconds = $this->currentImportIntervalSeconds(); $statusSyncDirection = $this->currentStatusSyncDirection(); $statusSyncIntervalMinutes = $this->currentStatusSyncIntervalMinutes(); $deliveryServicesData = $tab === 'delivery' ? $this->loadDeliveryServices($settings) : [[], [], '']; $html = $this->template->render('settings/allegro', [ 'title' => $this->translator->get('settings.allegro.title'), 'activeMenu' => 'settings', 'activeSettings' => 'allegro', 'user' => $this->auth->user(), 'csrfToken' => Csrf::token(), 'settings' => $settings, 'activeTab' => $tab, 'importIntervalSeconds' => $importIntervalSeconds, 'statusSyncDirection' => $statusSyncDirection, 'statusSyncIntervalMinutes' => $statusSyncIntervalMinutes, 'statusMappings' => $this->statusMappings->listMappings(), 'orderproStatuses' => $this->orderStatuses->listStatuses(), 'defaultRedirectUri' => $defaultRedirectUri, 'errorMessage' => (string) Flash::get('settings_error', ''), 'successMessage' => (string) Flash::get('settings_success', ''), 'warningMessage' => (string) Flash::get('settings_warning', ''), 'deliveryMappings' => $this->deliveryMappings !== null ? $this->deliveryMappings->listMappings('allegro', 0) : [], 'orderDeliveryMethods' => $this->deliveryMappings !== null ? $this->deliveryMappings->getDistinctOrderDeliveryMethods('allegro', 0) : [], 'allegroDeliveryServices' => $deliveryServicesData[0], 'apaczkaDeliveryServices' => $deliveryServicesData[1], 'allegroDeliveryServicesError' => $deliveryServicesData[2], 'inpostDeliveryServices' => array_values(array_filter( $deliveryServicesData[0], static fn(array $svc) => stripos((string) ($svc['carrierId'] ?? ''), 'inpost') !== false )), ], 'layouts/app'); return Response::html($html); } public function save(Request $request): Response { $redirectTo = $this->resolveRedirectPath((string) $request->input('return_to', '/settings/integrations/allegro')); if (!Csrf::validate((string) $request->input('_token', ''))) { Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); return Response::redirect($redirectTo); } $environment = trim((string) $request->input('environment', 'sandbox')); if (!in_array($environment, ['sandbox', 'production'], true)) { Flash::set('settings_error', $this->translator->get('settings.allegro.validation.environment_invalid')); return Response::redirect($redirectTo); } $clientId = trim((string) $request->input('client_id', '')); if ($clientId !== '' && mb_strlen($clientId) > 128) { Flash::set('settings_error', $this->translator->get('settings.allegro.validation.client_id_too_long')); return Response::redirect($redirectTo); } $redirectUriInput = trim((string) $request->input('redirect_uri', '')); $redirectUri = $redirectUriInput !== '' ? $redirectUriInput : $this->defaultRedirectUri(); if (!$this->isValidHttpUrl($redirectUri)) { Flash::set('settings_error', $this->translator->get('settings.allegro.validation.redirect_uri_invalid')); return Response::redirect($redirectTo); } $ordersFetchStartDate = trim((string) $request->input('orders_fetch_start_date', '')); if ($ordersFetchStartDate !== '' && !$this->isValidDate($ordersFetchStartDate)) { Flash::set('settings_error', $this->translator->get('settings.allegro.validation.orders_fetch_start_date_invalid')); return Response::redirect($redirectTo); } try { $this->repository->saveSettings([ 'environment' => $environment, 'client_id' => $clientId, 'client_secret' => trim((string) $request->input('client_secret', '')), 'redirect_uri' => $redirectUri, 'orders_fetch_enabled' => (string) $request->input('orders_fetch_enabled', '0') === '1', 'orders_fetch_start_date' => $ordersFetchStartDate, ]); $this->ensureDefaultSchedulesExist(); Flash::set('settings_success', $this->translator->get('settings.allegro.flash.saved')); } catch (Throwable $exception) { Flash::set( 'settings_error', $this->translator->get('settings.allegro.flash.save_failed') . ' ' . $exception->getMessage() ); } return Response::redirect($redirectTo); } public function saveImportSettings(Request $request): Response { $csrfError = $this->validateCsrf((string) $request->input('_token', '')); if ($csrfError !== null) { return $csrfError; } $intervalMinutesRaw = (int) $request->input('orders_import_interval_minutes', 5); $intervalMinutes = max(1, min(1440, $intervalMinutesRaw)); if ($intervalMinutesRaw !== $intervalMinutes) { Flash::set('settings_error', $this->translator->get('settings.allegro.validation.orders_import_interval_invalid')); return Response::redirect('/settings/integrations/allegro?tab=settings'); } $statusSyncDirection = trim((string) $request->input( 'status_sync_direction', self::STATUS_SYNC_DIRECTION_ALLEGRO_TO_ORDERPRO )); if (!in_array($statusSyncDirection, $this->allowedStatusSyncDirections(), true)) { Flash::set('settings_error', $this->translator->get('settings.allegro.validation.status_sync_direction_invalid')); return Response::redirect('/settings/integrations/allegro?tab=settings'); } $statusSyncIntervalRaw = (int) $request->input( 'status_sync_interval_minutes', self::STATUS_SYNC_DEFAULT_INTERVAL_MINUTES ); $statusSyncInterval = max(1, min(1440, $statusSyncIntervalRaw)); if ($statusSyncIntervalRaw !== $statusSyncInterval) { Flash::set('settings_error', $this->translator->get('settings.allegro.validation.status_sync_interval_invalid')); return Response::redirect('/settings/integrations/allegro?tab=settings'); } $existing = $this->findImportSchedule(); $priority = (int) ($existing['priority'] ?? self::ORDERS_IMPORT_DEFAULT_PRIORITY); $maxAttempts = (int) ($existing['max_attempts'] ?? self::ORDERS_IMPORT_DEFAULT_MAX_ATTEMPTS); $payload = is_array($existing['payload'] ?? null) ? (array) $existing['payload'] : self::ORDERS_IMPORT_DEFAULT_PAYLOAD; $enabled = array_key_exists('enabled', $existing) ? (bool) $existing['enabled'] : true; $statusSchedule = $this->findStatusSyncSchedule(); $statusPriority = (int) ($statusSchedule['priority'] ?? self::ORDERS_IMPORT_DEFAULT_PRIORITY); $statusMaxAttempts = (int) ($statusSchedule['max_attempts'] ?? self::ORDERS_IMPORT_DEFAULT_MAX_ATTEMPTS); $statusEnabled = array_key_exists('enabled', $statusSchedule) ? (bool) $statusSchedule['enabled'] : true; try { $this->cronRepository->upsertSchedule( self::ORDERS_IMPORT_JOB_TYPE, $intervalMinutes * 60, $priority, $maxAttempts, $payload, $enabled ); $this->cronRepository->upsertSchedule( self::STATUS_SYNC_JOB_TYPE, $statusSyncInterval * 60, $statusPriority, $statusMaxAttempts, null, $statusEnabled ); $this->cronRepository->upsertSetting('allegro_status_sync_direction', $statusSyncDirection); $this->cronRepository->upsertSetting('allegro_status_sync_interval_minutes', (string) $statusSyncInterval); Flash::set('settings_success', $this->translator->get('settings.allegro.flash.import_settings_saved')); } catch (Throwable $exception) { Flash::set( 'settings_error', $this->translator->get('settings.allegro.flash.import_settings_save_failed') . ' ' . $exception->getMessage() ); } return Response::redirect('/settings/integrations/allegro?tab=settings'); } public function saveStatusMapping(Request $request): Response { $csrfError = $this->validateCsrf((string) $request->input('_token', '')); if ($csrfError !== null) { return $csrfError; } $allegroStatusCode = strtolower(trim((string) $request->input('allegro_status_code', ''))); $orderproStatusCode = strtolower(trim((string) $request->input('orderpro_status_code', ''))); $allegroStatusName = trim((string) $request->input('allegro_status_name', '')); if ($allegroStatusCode === '') { Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.allegro_status_required')); return Response::redirect('/settings/integrations/allegro?tab=statuses'); } if ($orderproStatusCode === '') { Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.orderpro_status_required')); return Response::redirect('/settings/integrations/allegro?tab=statuses'); } if (!$this->orderStatusCodeExists($orderproStatusCode)) { Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.orderpro_status_not_found')); return Response::redirect('/settings/integrations/allegro?tab=statuses'); } try { $this->statusMappings->upsertMapping($allegroStatusCode, $allegroStatusName !== '' ? $allegroStatusName : null, $orderproStatusCode); Flash::set('settings_success', $this->translator->get('settings.allegro.statuses.flash.saved')); } catch (Throwable $exception) { Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed') . ' ' . $exception->getMessage()); } return Response::redirect('/settings/integrations/allegro?tab=statuses'); } public function saveStatusMappingsBulk(Request $request): Response { $csrfError = $this->validateCsrf((string) $request->input('_token', '')); if ($csrfError !== null) { return $csrfError; } $codes = $request->input('allegro_status_code', []); $names = $request->input('allegro_status_name', []); $selectedOrderproCodes = $request->input('orderpro_status_code', []); if (!is_array($codes) || !is_array($names) || !is_array($selectedOrderproCodes)) { Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed')); return Response::redirect('/settings/integrations/allegro?tab=statuses'); } try { foreach ($codes as $index => $rawCode) { $allegroStatusCode = strtolower(trim((string) $rawCode)); if ($allegroStatusCode === '') { continue; } $allegroStatusName = trim((string) ($names[$index] ?? '')); $orderproStatusCodeRaw = strtolower(trim((string) ($selectedOrderproCodes[$index] ?? ''))); $orderproStatusCode = $orderproStatusCodeRaw !== '' ? $orderproStatusCodeRaw : null; if ($orderproStatusCode !== null && !$this->orderStatusCodeExists($orderproStatusCode)) { Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.orderpro_status_not_found')); return Response::redirect('/settings/integrations/allegro?tab=statuses'); } $this->statusMappings->upsertMapping( $allegroStatusCode, $allegroStatusName !== '' ? $allegroStatusName : null, $orderproStatusCode ); } Flash::set('settings_success', $this->translator->get('settings.allegro.statuses.flash.saved_bulk')); } catch (Throwable $exception) { Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.save_failed') . ' ' . $exception->getMessage()); } return Response::redirect('/settings/integrations/allegro?tab=statuses'); } public function deleteStatusMapping(Request $request): Response { $csrfError = $this->validateCsrf((string) $request->input('_token', '')); if ($csrfError !== null) { return $csrfError; } $mappingId = max(0, (int) $request->input('mapping_id', 0)); if ($mappingId <= 0) { Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.mapping_not_found')); return Response::redirect('/settings/integrations/allegro?tab=statuses'); } try { $this->statusMappings->deleteMappingById($mappingId); Flash::set('settings_success', $this->translator->get('settings.allegro.statuses.flash.deleted')); } catch (Throwable $exception) { Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.delete_failed') . ' ' . $exception->getMessage()); } return Response::redirect('/settings/integrations/allegro?tab=statuses'); } public function syncStatusesFromAllegro(Request $request): Response { $csrfError = $this->validateCsrf((string) $request->input('_token', '')); if ($csrfError !== null) { return $csrfError; } try { $result = $this->statusDiscoveryService->discoverAndStoreStatuses(5, 100); Flash::set( 'settings_success', $this->translator->get('settings.allegro.statuses.flash.sync_ok', [ 'discovered' => (string) ((int) ($result['discovered'] ?? 0)), 'samples' => (string) ((int) ($result['samples'] ?? 0)), ]) ); } catch (Throwable $exception) { Flash::set('settings_error', $this->translator->get('settings.allegro.statuses.flash.sync_failed') . ' ' . $exception->getMessage()); } return Response::redirect('/settings/integrations/allegro?tab=statuses'); } public function startOAuth(Request $request): Response { $csrfError = $this->validateCsrf((string) $request->input('_token', '')); if ($csrfError !== null) { return $csrfError; } try { $credentials = $this->requireOAuthCredentials(); $state = bin2hex(random_bytes(24)); $_SESSION[self::OAUTH_STATE_SESSION_KEY] = $state; $url = $this->oauthClient->buildAuthorizeUrl( (string) $credentials['environment'], (string) $credentials['client_id'], (string) $credentials['redirect_uri'], $state, self::OAUTH_SCOPES ); return Response::redirect($url); } catch (Throwable $exception) { Flash::set('settings_error', $exception->getMessage()); return Response::redirect('/settings/integrations/allegro'); } } public function oauthCallback(Request $request): Response { $error = trim((string) $request->input('error', '')); if ($error !== '') { $description = trim((string) $request->input('error_description', '')); $message = $this->translator->get('settings.allegro.flash.oauth_failed'); if ($description !== '') { $message .= ' ' . $description; } Flash::set('settings_error', $message); return Response::redirect('/settings/integrations/allegro'); } $state = trim((string) $request->input('state', '')); $expectedState = trim((string) ($_SESSION[self::OAUTH_STATE_SESSION_KEY] ?? '')); unset($_SESSION[self::OAUTH_STATE_SESSION_KEY]); if ($state === '' || $expectedState === '' || !hash_equals($expectedState, $state)) { Flash::set('settings_error', $this->translator->get('settings.allegro.flash.oauth_state_invalid')); return Response::redirect('/settings/integrations/allegro'); } $authorizationCode = trim((string) $request->input('code', '')); if ($authorizationCode === '') { Flash::set('settings_error', $this->translator->get('settings.allegro.flash.oauth_code_missing')); return Response::redirect('/settings/integrations/allegro'); } try { $credentials = $this->requireOAuthCredentials(); $token = $this->oauthClient->exchangeAuthorizationCode( (string) $credentials['environment'], (string) $credentials['client_id'], (string) $credentials['client_secret'], (string) $credentials['redirect_uri'], $authorizationCode ); $expiresAt = null; if ((int) ($token['expires_in'] ?? 0) > 0) { $expiresAt = (new DateTimeImmutable('now')) ->add(new DateInterval('PT' . (int) $token['expires_in'] . 'S')) ->format('Y-m-d H:i:s'); } $this->repository->saveTokens( (string) ($token['access_token'] ?? ''), (string) ($token['refresh_token'] ?? ''), (string) ($token['token_type'] ?? ''), (string) ($token['scope'] ?? ''), $expiresAt ); Flash::set('settings_success', $this->translator->get('settings.allegro.flash.oauth_connected')); } catch (Throwable $exception) { Flash::set('settings_error', $this->translator->get('settings.allegro.flash.oauth_failed') . ' ' . $exception->getMessage()); } return Response::redirect('/settings/integrations/allegro'); } public function importSingleOrder(Request $request): Response { $csrfError = $this->validateCsrf((string) $request->input('_token', '')); if ($csrfError !== null) { return $csrfError; } $checkoutFormId = trim((string) $request->input('checkout_form_id', '')); if ($checkoutFormId === '') { Flash::set('settings_error', $this->translator->get('settings.allegro.flash.checkout_form_id_required')); return Response::redirect('/settings/integrations/allegro'); } try { $result = $this->orderImportService->importSingleOrder($checkoutFormId); $imageDiagnostics = is_array($result['image_diagnostics'] ?? null) ? $result['image_diagnostics'] : []; Flash::set( 'settings_success', $this->translator->get('settings.allegro.flash.import_single_ok', [ 'source_order_id' => (string) ($result['source_order_id'] ?? $checkoutFormId), 'local_id' => (string) ((int) ($result['order_id'] ?? 0)), 'action' => !empty($result['created']) ? $this->translator->get('settings.allegro.import_action.created') : $this->translator->get('settings.allegro.import_action.updated'), ]) . ' ' . $this->translator->get('settings.allegro.flash.import_single_media_summary', [ 'with_image' => (string) ((int) ($imageDiagnostics['with_image'] ?? 0)), 'total_items' => (string) ((int) ($imageDiagnostics['total_items'] ?? 0)), 'without_image' => (string) ((int) ($imageDiagnostics['without_image'] ?? 0)), ]) ); $warningDetails = $this->buildImportImageWarningMessage($imageDiagnostics); if ($warningDetails !== '') { Flash::set('settings_warning', $warningDetails); } } catch (Throwable $exception) { Flash::set( 'settings_error', $this->translator->get('settings.allegro.flash.import_single_failed') . ' ' . $exception->getMessage() ); } return Response::redirect('/settings/integrations/allegro'); } public function saveDeliveryMappings(Request $request): Response { $csrfError = $this->validateCsrf((string) $request->input('_token', '')); if ($csrfError !== null) { return $csrfError; } if ($this->deliveryMappings === null) { Flash::set('settings_error', 'Delivery mappings not configured.'); return Response::redirect('/settings/integrations/allegro?tab=delivery'); } $orderMethods = (array) $request->input('order_delivery_method', []); $carriers = (array) $request->input('carrier', []); $allegroMethodIds = (array) $request->input('allegro_delivery_method_id', []); $apaczkaMethodIds = (array) $request->input('apaczka_delivery_method_id', []); $credentialsIds = (array) $request->input('allegro_credentials_id', []); $carrierIds = (array) $request->input('allegro_carrier_id', []); $serviceNames = (array) $request->input('allegro_service_name', []); $mappings = []; foreach ($orderMethods as $idx => $orderMethod) { $orderMethod = trim((string) $orderMethod); $carrier = trim((string) ($carriers[$idx] ?? 'allegro')); $provider = $carrier === 'apaczka' ? 'apaczka' : 'allegro_wza'; $providerServiceId = $provider === 'apaczka' ? trim((string) ($apaczkaMethodIds[$idx] ?? '')) : trim((string) ($allegroMethodIds[$idx] ?? '')); if ($orderMethod === '' || $providerServiceId === '') { continue; } $mappings[] = [ 'order_delivery_method' => $orderMethod, 'provider' => $provider, 'provider_service_id' => $providerServiceId, 'provider_account_id' => $provider === 'allegro_wza' ? trim((string) ($credentialsIds[$idx] ?? '')) : '', 'provider_carrier_id' => $provider === 'allegro_wza' ? trim((string) ($carrierIds[$idx] ?? '')) : '', 'provider_service_name' => trim((string) ($serviceNames[$idx] ?? '')), ]; } try { $this->deliveryMappings->saveMappings('allegro', 0, $mappings); Flash::set('settings_success', $this->translator->get('settings.allegro.delivery.flash.saved')); } catch (Throwable $exception) { Flash::set('settings_error', $this->translator->get('settings.allegro.delivery.flash.save_failed') . ' ' . $exception->getMessage()); } return Response::redirect('/settings/integrations/allegro?tab=delivery'); } /** * @param array $settings * @return array{0: array>, 1: array>, 2: string} */ private function loadDeliveryServices(array $settings): array { $allegroServices = []; $apaczkaServices = []; $errorMessage = ''; if ($this->apiClient !== null) { $isConnected = (bool) ($settings['is_connected'] ?? false); if (!$isConnected) { $errorMessage = $this->translator->get('settings.allegro.delivery.not_connected'); } else { try { $oauth = $this->repository->getTokenCredentials(); if ($oauth === null) { $errorMessage = $this->translator->get('settings.allegro.delivery.not_connected'); } else { $env = (string) ($oauth['environment'] ?? 'sandbox'); $accessToken = trim((string) ($oauth['access_token'] ?? '')); if ($accessToken === '') { $errorMessage = $this->translator->get('settings.allegro.delivery.not_connected'); } else { try { $response = $this->apiClient->getDeliveryServices($env, $accessToken); } catch (RuntimeException $ex) { if (trim($ex->getMessage()) === 'ALLEGRO_HTTP_401') { $refreshed = $this->refreshOAuthToken($oauth); if ($refreshed === null) { $errorMessage = $this->translator->get('settings.allegro.delivery.not_connected'); $response = []; } else { $response = $this->apiClient->getDeliveryServices($env, $refreshed); } } else { throw $ex; } } if (is_array($response ?? null)) { $allegroServices = is_array($response['services'] ?? null) ? $response['services'] : []; } } } } catch (Throwable $e) { $errorMessage = $e->getMessage(); } } } if ($this->apaczkaRepository !== null && $this->apaczkaApiClient !== null) { try { $credentials = $this->apaczkaRepository->getApiCredentials(); if (is_array($credentials)) { $apaczkaServices = $this->apaczkaApiClient->getServiceStructure( (string) ($credentials['app_id'] ?? ''), (string) ($credentials['app_secret'] ?? '') ); } } catch (Throwable $exception) { if ($errorMessage === '') { $errorMessage = $exception->getMessage(); } } } return [$allegroServices, $apaczkaServices, $errorMessage]; } /** * @param array $oauth */ private function refreshOAuthToken(array $oauth): ?string { try { $token = $this->oauthClient->refreshAccessToken( (string) ($oauth['environment'] ?? 'sandbox'), (string) ($oauth['client_id'] ?? ''), (string) ($oauth['client_secret'] ?? ''), (string) ($oauth['refresh_token'] ?? '') ); $expiresIn = max(0, (int) ($token['expires_in'] ?? 0)); $expiresAt = $expiresIn > 0 ? (new DateTimeImmutable('now'))->add(new DateInterval('PT' . $expiresIn . 'S'))->format('Y-m-d H:i:s') : null; $refreshToken = trim((string) ($token['refresh_token'] ?? '')); if ($refreshToken === '') { $refreshToken = (string) ($oauth['refresh_token'] ?? ''); } $this->repository->saveTokens( (string) ($token['access_token'] ?? ''), $refreshToken, (string) ($token['token_type'] ?? ''), (string) ($token['scope'] ?? ''), $expiresAt ); return trim((string) ($token['access_token'] ?? '')) ?: null; } catch (Throwable) { return null; } } private function defaultRedirectUri(): string { $base = trim($this->appUrl); if ($base === '') { $base = 'http://localhost:8000'; } return rtrim($base, '/') . '/settings/integrations/allegro/oauth/callback'; } private function validateCsrf(string $token): ?Response { if (Csrf::validate($token)) { return null; } Flash::set('settings_error', $this->translator->get('auth.errors.csrf_expired')); return Response::redirect('/settings/integrations/allegro'); } private function resolveRedirectPath(string $candidate): string { $value = trim($candidate); if ($value === '') { return '/settings/integrations/allegro'; } if (!str_starts_with($value, '/settings/integrations')) { return '/settings/integrations/allegro'; } return $value; } /** * @return array */ private function requireOAuthCredentials(): array { $credentials = $this->repository->getOAuthCredentials(); if ($credentials === null) { throw new IntegrationConfigException($this->translator->get('settings.allegro.flash.credentials_missing')); } return $credentials; } private function isValidHttpUrl(string $url): bool { $trimmed = trim($url); if ($trimmed === '') { return false; } if (filter_var($trimmed, FILTER_VALIDATE_URL) === false) { return false; } $scheme = strtolower((string) parse_url($trimmed, PHP_URL_SCHEME)); return $scheme === 'http' || $scheme === 'https'; } private function isValidDate(string $value): bool { if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) !== 1) { return false; } $date = DateTimeImmutable::createFromFormat('Y-m-d', $value); return $date instanceof DateTimeImmutable && $date->format('Y-m-d') === $value; } private function orderStatusCodeExists(string $code): bool { $needle = strtolower(trim($code)); if ($needle === '') { return false; } foreach ($this->orderStatuses->listStatuses() as $row) { $statusCode = strtolower(trim((string) ($row['code'] ?? ''))); if ($statusCode === $needle) { return true; } } return false; } /** * @param array $imageDiagnostics */ private function buildImportImageWarningMessage(array $imageDiagnostics): string { $withoutImage = (int) ($imageDiagnostics['without_image'] ?? 0); if ($withoutImage <= 0) { return ''; } $reasonCountsRaw = $imageDiagnostics['reason_counts'] ?? []; if (!is_array($reasonCountsRaw) || $reasonCountsRaw === []) { return $this->translator->get('settings.allegro.flash.import_single_media_warning_generic', [ 'without_image' => (string) $withoutImage, ]); } $parts = []; foreach ($reasonCountsRaw as $reason => $countRaw) { $count = (int) $countRaw; if ($count <= 0) { continue; } $parts[] = $this->reasonLabel((string) $reason) . ': ' . $count; } if ($parts === []) { return $this->translator->get('settings.allegro.flash.import_single_media_warning_generic', [ 'without_image' => (string) $withoutImage, ]); } return $this->translator->get('settings.allegro.flash.import_single_media_warning', [ 'without_image' => (string) $withoutImage, 'reasons' => implode(', ', $parts), ]); } private function reasonLabel(string $reasonCode): string { return match ($reasonCode) { 'missing_offer_id' => 'brak ID oferty', 'missing_in_checkout_form' => 'brak obrazka w checkout form', 'missing_in_offer_api' => 'brak obrazka w API oferty', 'offer_api_access_denied_403' => 'brak uprawnien API ofert (403)', 'offer_api_unauthorized_401' => 'token nieautoryzowany dla API ofert (401)', 'offer_api_not_found_404' => 'oferta nie znaleziona (404)', 'offer_api_request_failed' => 'blad zapytania do API oferty', default => str_starts_with($reasonCode, 'offer_api_http_') ? 'blad API oferty (' . str_replace('offer_api_http_', '', $reasonCode) . ')' : $reasonCode, }; } private function currentImportIntervalSeconds(): int { $schedule = $this->findImportSchedule(); $value = (int) ($schedule['interval_seconds'] ?? self::ORDERS_IMPORT_DEFAULT_INTERVAL_SECONDS); return max(60, min(86400, $value)); } /** * @return array */ private function findImportSchedule(): array { try { $schedules = $this->cronRepository->listSchedules(); } catch (Throwable) { return []; } foreach ($schedules as $schedule) { if (!is_array($schedule)) { continue; } if ((string) ($schedule['job_type'] ?? '') !== self::ORDERS_IMPORT_JOB_TYPE) { continue; } return $schedule; } return []; } /** * @return array */ private function findStatusSyncSchedule(): array { try { $schedules = $this->cronRepository->listSchedules(); } catch (Throwable) { return []; } foreach ($schedules as $schedule) { if (!is_array($schedule)) { continue; } if ((string) ($schedule['job_type'] ?? '') !== self::STATUS_SYNC_JOB_TYPE) { continue; } return $schedule; } return []; } private function currentStatusSyncDirection(): string { $value = trim($this->cronRepository->getStringSetting( 'allegro_status_sync_direction', self::STATUS_SYNC_DIRECTION_ALLEGRO_TO_ORDERPRO )); if (!in_array($value, $this->allowedStatusSyncDirections(), true)) { return self::STATUS_SYNC_DIRECTION_ALLEGRO_TO_ORDERPRO; } return $value; } private function currentStatusSyncIntervalMinutes(): int { return $this->cronRepository->getIntSetting( 'allegro_status_sync_interval_minutes', self::STATUS_SYNC_DEFAULT_INTERVAL_MINUTES, 1, 1440 ); } /** * @return array */ private function allowedStatusSyncDirections(): array { return [ self::STATUS_SYNC_DIRECTION_ALLEGRO_TO_ORDERPRO, self::STATUS_SYNC_DIRECTION_ORDERPRO_TO_ALLEGRO, ]; } private function ensureDefaultSchedulesExist(): void { try { if ($this->findImportSchedule() === []) { $this->cronRepository->upsertSchedule( self::ORDERS_IMPORT_JOB_TYPE, self::ORDERS_IMPORT_DEFAULT_INTERVAL_SECONDS, self::ORDERS_IMPORT_DEFAULT_PRIORITY, self::ORDERS_IMPORT_DEFAULT_MAX_ATTEMPTS, self::ORDERS_IMPORT_DEFAULT_PAYLOAD, true ); } if ($this->findStatusSyncSchedule() === []) { $this->cronRepository->upsertSchedule( self::STATUS_SYNC_JOB_TYPE, self::STATUS_SYNC_DEFAULT_INTERVAL_MINUTES * 60, self::ORDERS_IMPORT_DEFAULT_PRIORITY, self::ORDERS_IMPORT_DEFAULT_MAX_ATTEMPTS, null, true ); } } catch (Throwable) { // non-critical: schedules will be created when user explicitly saves import settings } } }