jobRepository = $jobRepository ?? new ContaiJobRepository(); } public function handleRequest(): void { if (!$this->isPostRequest()) { return; } if (!$this->verifyNonce()) { $this->redirectWithError(__('Security verification failed. Please try again.', '1platform-content-ai')); return; } if (!$this->verifyCapability()) { $this->redirectWithError(__('You do not have permission to perform this action.', '1platform-content-ai')); return; } $action = $this->getRequestedAction(); if (!$action) { return; } $this->executeCommand($action); } private function isPostRequest(): bool { return isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST'; } private function verifyNonce(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash return isset($_POST[self::NONCE_FIELD]) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST[self::NONCE_FIELD])), self::NONCE_ACTION); } private function verifyCapability(): bool { return current_user_can('manage_options'); } private function getRequestedAction(): ?string { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified in handleRequest() via verifyNonce(). if (isset($_POST['contai_extract_keywords'])) { return self::ACTION_EXTRACT; } return null; } private function executeCommand(string $action): void { try { switch ($action) { case self::ACTION_EXTRACT: $result = $this->extractKeywords(); break; default: $this->redirectWithError(__('Invalid action.', '1platform-content-ai')); return; } if ($result['success']) { $this->redirectWithSuccess($result['message']); } else { $this->redirectWithError($result['message']); } } catch (Exception $e) { $this->redirectWithError($e->getMessage()); } } private function extractKeywords(): array { // Validate credits before enqueueing keyword extraction require_once __DIR__ . '/../../../services/billing/CreditGuard.php'; $creditGuard = new ContaiCreditGuard(); $creditCheck = $creditGuard->validateCredits(); if (!$creditCheck['has_credits']) { return [ 'success' => false, 'message' => $creditCheck['message'] ]; } $validation = $this->validateExtractionRequest(); if (!$validation['valid']) { return [ 'success' => false, 'message' => $validation['message'] ]; } $payload = [ 'topic' => $validation['topic'], 'country' => $validation['country'], 'lang' => $validation['lang'], ]; $job = ContaiJob::create( ContaiKeywordExtractionJob::TYPE, $payload, 0 ); $created = $this->jobRepository->create($job); if (!$created) { return [ 'success' => false, 'message' => __('Failed to enqueue keyword extraction job.', '1platform-content-ai') ]; } return [ 'success' => true, 'message' => sprintf( /* translators: %s: topic name for keyword extraction */ __('Keyword extraction job has been queued for: %s. You can check the Keywords List page for results.', '1platform-content-ai'), $validation['topic'] ) ]; } private function validateExtractionRequest(): array { // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified in handleRequest() via verifyNonce(). $topic = sanitize_text_field(wp_unslash($_POST['contai_topic'] ?? '')); $country = sanitize_text_field(wp_unslash($_POST['contai_country'] ?? '')); $lang = sanitize_text_field(wp_unslash($_POST['contai_target_language'] ?? '')); // phpcs:enable WordPress.Security.NonceVerification.Missing if (empty($topic)) { return ['valid' => false, 'message' => __('Please enter a topic for keyword discovery.', '1platform-content-ai')]; } if (mb_strlen($topic) < 3 || mb_strlen($topic) > 200) { return ['valid' => false, 'message' => __('Topic must be between 3 and 200 characters.', '1platform-content-ai')]; } $validLanguages = ['en', 'es']; if (!in_array($lang, $validLanguages, true)) { return ['valid' => false, 'message' => __('Invalid language selected.', '1platform-content-ai')]; } $validCountries = ['us', 'es']; if (!in_array($country, $validCountries, true)) { return ['valid' => false, 'message' => __('Invalid country selected.', '1platform-content-ai')]; } return [ 'valid' => true, 'topic' => $topic, 'country' => $country, 'lang' => $lang, ]; } private function redirectWithSuccess(string $message): void { $this->redirect([ 'success' => 1, 'message' => $message ]); } private function redirectWithError(string $message): void { $this->redirect([ 'error' => 1, 'message' => $message ]); } private function redirect(array $params): void { $url = add_query_arg( array_merge( [ 'page' => 'contai-content-generator', 'section' => 'keyword-extractor' ], $params ), admin_url('admin.php') ); wp_safe_redirect($url); exit; } }