clientId = config('linkedin.client_id', ''); $this->clientSecret = config('linkedin.client_secret', ''); $this->organizationId = config('linkedin.organization_id', '35611757'); $this->redirectUri = config('linkedin.redirect_uri', url('/admin/linkedin/callback')); } /** * Generate OAuth 2.0 Authorization URL for admin redirect */ public function getAuthorizationUrl(): string { $scopes = implode(' ', config('linkedin.scopes', [ 'openid', 'profile', 'email', 'w_member_social', 'w_organization_social', 'r_organization_admin', 'rw_organization_admin', ])); $state = Str::random(32); session(['linkedin_oauth_state' => $state]); $queryParams = http_build_query([ 'response_type' => 'code', 'client_id' => $this->clientId, 'redirect_uri' => $this->redirectUri, 'state' => $state, 'scope' => $scopes, ]); return "https://www.linkedin.com/oauth/v2/authorization?" . $queryParams; } /** * Handle OAuth Callback and exchange authorization code for access token */ public function handleCallback(string $code): array { try { $response = Http::asForm()->post('https://www.linkedin.com/oauth/v2/accessToken', [ 'grant_type' => 'authorization_code', 'code' => $code, 'redirect_uri' => $this->redirectUri, 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, ]); if ($response->failed()) { Log::error('LinkedIn OAuth Token Exchange Failed', [ 'status' => $response->status(), 'body' => $response->body(), ]); return [ 'success' => false, 'message' => 'LinkedIn Access Token alınamadı: ' . ($response->json('error_description') ?? $response->body()), ]; } $data = $response->json(); $accessToken = $data['access_token'] ?? null; $expiresIn = $data['expires_in'] ?? 5184000; // Default 60 days in seconds if (!$accessToken) { return [ 'success' => false, 'message' => 'LinkedIn Access Token yanıt içinde bulunamadı.', ]; } $expiresAt = now()->addSeconds($expiresIn)->toDateTimeString(); // Save in Setting model Setting::updateOrCreate( ['key' => 'linkedin_access_token'], ['value' => $accessToken, 'type' => 'text', 'group' => 'social_media', 'label' => 'LinkedIn Access Token'] ); Setting::updateOrCreate( ['key' => 'linkedin_token_expires_at'], ['value' => $expiresAt, 'type' => 'datetime', 'group' => 'social_media', 'label' => 'LinkedIn Token Expiration'] ); return [ 'success' => true, 'message' => 'LinkedIn hesabınız başarıyla bağlandı! Access Token kaydedildi.', 'expires_at' => $expiresAt, ]; } catch (\Throwable $e) { Log::error('LinkedIn Callback Error: ' . $e->getMessage()); return [ 'success' => false, 'message' => 'Hata oluştu: ' . $e->getMessage(), ]; } } /** * Check if valid LinkedIn Access Token exists */ public function isAuthorized(): bool { $token = Setting::get('linkedin_access_token'); $expiresAt = Setting::get('linkedin_token_expires_at'); if (!$token) { return false; } if ($expiresAt && now()->greaterThanOrEqualTo($expiresAt)) { return false; } return true; } /** * Get remaining days of Access Token */ public function getTokenExpirationStatus(): array { $expiresAt = Setting::get('linkedin_token_expires_at'); if (!$expiresAt) { return [ 'is_valid' => false, 'message' => 'Yetkilendirme yapılmadı.', ]; } $date = \Carbon\Carbon::parse($expiresAt); $diff = now()->diffInDays($date, false); if ($diff <= 0) { return [ 'is_valid' => false, 'message' => 'Erişim anahtarının süresi doldu (' . $date->format('d.m.Y H:i') . '). Yeniden yetkilendirin.', ]; } return [ 'is_valid' => true, 'days_left' => $diff, 'expires_at' => $date->format('d.m.Y H:i'), 'message' => "Erişim anahtarı aktif. Kalan süre: {$diff} gün ({$date->format('d.m.Y H:i')}).", ]; } /** * Share a post to LinkedIn Company Page */ public function sharePost(string $title, string $text, ?string $url = null, ?string $imageUrl = null): array { if (!$this->isAuthorized()) { return [ 'success' => false, 'message' => 'LinkedIn yetkilendirmesi bulunamadı veya süresi dolmuş.', ]; } $token = Setting::get('linkedin_access_token'); $authorUrn = "urn:li:organization:{$this->organizationId}"; // Prepare UGC Post payload $shareCommentary = trim($title . "\n\n" . $text); if ($url) { $shareCommentary .= "\n\n" . $url; } $mediaContent = []; if ($url) { $mediaItem = [ 'status' => 'READY', 'originalUrl' => $url, 'title' => [ 'text' => Str::limit($title, 200), ], 'description' => [ 'text' => Str::limit(strip_tags($text), 250), ], ]; $mediaContent[] = $mediaItem; } $shareContent = [ 'shareCommentary' => [ 'text' => $shareCommentary, ], 'shareMediaCategory' => !empty($mediaContent) ? 'ARTICLE' : 'NONE', ]; if (!empty($mediaContent)) { $shareContent['media'] = $mediaContent; } $payload = [ 'author' => $authorUrn, 'lifecycleState' => 'PUBLISHED', 'specificContent' => [ 'com.linkedin.ugc.ShareContent' => $shareContent, ], 'visibility' => [ 'com.linkedin.ugc.ShareProductVisibility' => 'PUBLIC', ], ]; try { $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . $token, 'X-Restli-Protocol-Version' => '2.0.0', 'Content-Type' => 'application/json', ])->post('https://api.linkedin.com/v2/ugcPosts', $payload); if ($response->successful()) { $postId = $response->header('x-restli-id') ?? $response->json('id'); Log::info('LinkedIn post published successfully', [ 'organization_id' => $this->organizationId, 'post_id' => $postId, ]); return [ 'success' => true, 'message' => 'LinkedIn gönderisi başarıyla paylaşıldı!', 'post_id' => $postId, ]; } Log::error('LinkedIn Share API Failed', [ 'status' => $response->status(), 'body' => $response->body(), ]); return [ 'success' => false, 'message' => 'LinkedIn gönderisi paylaşılamadı: ' . ($response->json('message') ?? $response->body()), ]; } catch (\Throwable $e) { Log::error('LinkedIn Share Post Exception: ' . $e->getMessage()); return [ 'success' => false, 'message' => 'Gönderi paylaşılırken bir hata oluştu: ' . $e->getMessage(), ]; } } }