265 lines
8.4 KiB
PHP
265 lines
8.4 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Models\Setting;
|
||
use Illuminate\Support\Facades\Http;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Illuminate\Support\Str;
|
||
|
||
class LinkedInService
|
||
{
|
||
protected string $clientId;
|
||
protected string $clientSecret;
|
||
protected string $organizationId;
|
||
protected string $redirectUri;
|
||
|
||
public function __construct()
|
||
{
|
||
$this->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(),
|
||
];
|
||
}
|
||
}
|
||
}
|