<?php
namespace App\Utils;
use Symfony\Component\HttpFoundation\RequestStack;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\HootsuiteToken;
use App\Entity\HootsuiteSocialProfile;
use DateTimeImmutable;
/**
* Class HootsuiteHelper
*
* This class provides helper functions for interacting with Hootsuite.
*/
class HootsuiteHelper
{
protected $requestStack;
protected $entityManager;
public function __construct(RequestStack $requestStack, EntityManagerInterface $entityManager)
{
$this->requestStack = $requestStack;
$this->entityManager = $entityManager;
}
protected $baseUrl = "https://platform.hootsuite.com/";
protected $clientID = "a760a0ab-04d5-4ff3-b86b-1412f0def467";
protected $clientSecret = "hws7jUvYPweg";
protected $authHeader = "YTc2MGEwYWItMDRkNS00ZmYzLWI4NmItMTQxMmYwZGVmNDY3Omh3czdqVXZZUHdlZw==";
protected $redirectUri = "https://staging.rooferscoffeeshop.com/admin/apps/hootsuite";
public function getAccessToken($code = null)
{
if ($code == null) {
$accessToken = $this->getAccessTokenFromDB();
if ($accessToken == false) {
$refreshToken = $this->getRefreshTokenFromDB();
$refreshedTokens = $this->refreshAccessToken($refreshToken);
$refreshedTokens = json_decode($refreshedTokens, true);
$accessToken = $refreshedTokens['access_token'];
return $accessToken;
} else {
return $accessToken;
}
} else {
return $this->getAccessTokenFromCode($code);
}
}
/**
* Retrieves the access token from the database.
*
* @return string|false The access token if it is still valid, false otherwise.
*/
public function getAccessTokenFromDB()
{
$tokens = $this->entityManager->getRepository(HootsuiteToken::class)->find(1);
$result = $tokens->getAccessToken();
$expires_in = $tokens->getExpiresIn();
$created_at = $tokens->getCreatedAt();
$now = new \DateTime();
$now = $now->getTimestamp();
$created_at = $created_at->getTimestamp();
if ($now - $created_at > $expires_in) {
return false;
}
return $result;
}
/**
* Retrieves the refresh token from the database.
*
* @return string The refresh token.
*/
public function getRefreshTokenFromDB()
{
$tokens = $this->entityManager->getRepository(HootsuiteToken::class)->find(1);
return $tokens->getRefreshToken();
}
/**
* Retrieves an access token from the provided authorization code.
*
* @param string $code The authorization code.
* @return string The response from the API containing the access token.
* @throws \Exception If there is an error getting the access token.
*/
public function getAccessTokenFromCode($code)
{
$uri = $this->baseUrl . "oauth2/token";
$headers = array(
'Accept: application/json;charset=utf-8',
'Content-Type: application/x-www-form-urlencoded',
"Authorization: Basic $this->authHeader"
);
$body = "grant_type=authorization_code&code=$code&redirect_uri=$this->redirectUri";
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $uri,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => $headers
)
);
$response = curl_exec($curl);
curl_close($curl);
if (isset($response['error'])) {
throw new \Exception("Error getting access token: " . $response['error']);
}
$this->saveTokensToDB(json_decode($response, true));
return $response;
}
/**
* Refreshes the access token using the provided refresh token.
*
* @param string $refreshToken The refresh token.
* @return string The response from the API.
* @throws \Exception If there is an error refreshing the access token.
*/
public function refreshAccessToken($refreshToken)
{
$uri = $this->baseUrl . "oauth2/token";
$headers = array(
'Accept: application/json;charset=utf-8',
'Content-Type: application/x-www-form-urlencoded',
"Authorization: Basic $this->authHeader"
);
$body = "grant_type=refresh_token&refresh_token=$refreshToken";
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $uri,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => $headers
)
);
$response = curl_exec($curl);
curl_close($curl);
if (isset($response['error'])) {
throw new \Exception("Error refreshing access token: " . $response['error']);
}
$this->saveTokensToDB(json_decode($response, true));
return $response;
}
/**
* Saves the tokens to the database.
*
* @param array $tokens The tokens to be saved.
* @throws \Exception If any of the required tokens are not found in the response.
*/
private function saveTokensToDB($tokens)
{
if (!isset($tokens['access_token'])) {
throw new \Exception("Access token not found in response");
}
if (!isset($tokens['expires_in'])) {
throw new \Exception("Expires in not found in response");
}
if (!isset($tokens['refresh_token'])) {
throw new \Exception("Refresh token not found in response");
}
// update THE ACCESS TOKEN
$accessToken = $tokens['access_token'];
$expiresIn = $tokens['expires_in'];
$refreshToken = $tokens['refresh_token'];
$eTokens = $this->entityManager->getRepository(HootsuiteToken::class)->find(1);
$eTokens->setAccessToken($accessToken);
$eTokens->setExpiresIn($expiresIn);
$eTokens->setRefreshToken($refreshToken);
$createdAt = new DateTimeImmutable();
$eTokens->setCreatedAt($createdAt);
$this->entityManager->persist($eTokens);
$this->entityManager->flush();
}
/**
* Retrieves the social profiles from Hootsuite.
*
* @return string The response containing the social profiles.
* @throws \Exception If there is an error getting the social profiles.
*/
public function getSocialProfiles()
{
$uri = $this->baseUrl . "v1/socialProfiles";
$accessToken = $this->getAccessToken();
$headers = array(
"Accept: application/json;charset=utf-8",
"Content-Type: application/json;charset=utf-8",
"Authorization: Bearer $accessToken"
);
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $uri,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => $headers
)
);
$response = curl_exec($curl);
curl_close($curl);
// throw error if response is not 200
if (isset($response['error'])) {
throw new \Exception("Error getting social profiles: " . $response['error']);
}
// these should be refreshed reguarly but stored in the database
return $response;
}
public function updateSocialProfiles()
{
$socialProfiles = $this->getSocialProfiles();
$socialProfiles = json_decode($socialProfiles, true);
$socialProfiles = $socialProfiles['data'];
$allIds = [];
foreach ($socialProfiles as $profile) {
$id = $profile['id'];
$type = $profile['type'] ?? "";
$socialNetworkId = $profile['socialNetworkId'] ?? "";
$socialNetworkUsername = $profile['socialNetworkUsername'] ?? "";
$avatarUrl = $profile['avatarUrl'] ?? "";
$owner = $profile['owner'] ?? "";
$ownerId = $profile['ownerId'] ?? "";
$eProfile = $this->entityManager->getRepository(HootsuiteSocialProfile::class)->find($id);
if ($eProfile == null) {
$eProfile = new HootsuiteSocialProfile();
}
$eProfile->setId($id);
$eProfile->setType($type);
$eProfile->setSocialNetworkId($socialNetworkId);
$eProfile->setSocialNetworkUsername($socialNetworkUsername);
$eProfile->setAvatarUrl($avatarUrl);
$eProfile->setOwner($owner);
$eProfile->setOwnerId($ownerId);
$eProfile->setStatus(1);
$this->entityManager->persist($eProfile);
$allIds[] = $id;
}
$results = $this->entityManager
->createQuery(
implode(
" ",
array(
"UPDATE",
"App\Entity\HootsuiteSocialProfile p",
"SET",
"p.status = 0",
"WHERE",
"p.id NOT IN (",
implode(", ", $allIds),
")",
)
)
)
->getResult();
$this->entityManager->flush();
}
// actual meat and potatoes
/**
* Creates a media upload URI for Hootsuite.
*
* @param array $media.
* @return string The response from the API call.
*/
public function createMediaUploadUri($media)
{
$uri = $this->baseUrl . "v1/media";
$accessToken = $this->getAccessToken();
$imageSize = $media['size'];
$imageMime = $media['mime'];
$postBody = json_encode(
array(
"sizeBytes" => $imageSize,
"mimeType" => $imageMime
)
);
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $uri,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $postBody,
CURLOPT_HTTPHEADER => array(
"Accept: application/json;charset=utf-8",
"Content-Type: application/json;charset=utf-8",
"Authorization: Bearer $accessToken"
),
)
);
$response = curl_exec($curl);
curl_close($curl);
if (isset($response['error'])) {
throw new \Exception("Error creating media upload URI: " . $response['error']);
}
// should return a respone like:
/*{
"data": {
"id": "aHR0cHM6Ly9ob290c3VpdGUtdmlkZW8uczMuYW1hem9uYXdzLmNvbS9wcm9kdWN0aW9uLzEyMjU1MjQ0XzgyOTVmZjllLWFkOWYtNGNlNy1iOGE3LTgwNzI0NDAwYTBhZS5tcDQ=",
"uploadUrl": "https://hootsuite-video.s3.amazonaws.com/production/12255244_01942650-3d42-42b8-a191-aa84eb45d105.mp4?AWSAccessKeyId=AKIAIM7ASX2JTE3ZFAAA&Expires=1471978770&Signature=b%2B196oEHxySdmE%2FC34ZRL6pXSAI%3D",
"uploadUrlDurationSeconds": 1799
}
}*/
return $response;
}
/**
* Uploads media to a specified URL using cURL.
*
* @param string $uploadUrl The URL to upload the media to.
* @param array $media The path of the media file to be uploaded.
* @return string The response from the cURL request.
*/
public function uploadMedia($uploadUrl, $media)
{
$accessToken = $this->getAccessToken();
$imageMime = $media['mime'];
$imageSize = $media['size'];
$headers = array(
"Content-Type: $imageMime",
"Content-Length: $imageSize",
);
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $uploadUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => file_get_contents($media['image']),
CURLOPT_HTTPHEADER => $headers
)
);
$response = curl_exec($curl);
curl_close($curl);
if (isset($response['error'])) {
throw new \Exception("Error uploading media: " . $response['error']);
}
return $response;
}
/**
* Confirm the media upload by checking the status of the uploaded media.
*
* @param string|null $mediaID The ID of the media to be confirmed.
* @return string|bool The response from the API or false if the mediaID is null.
*/
public function confirmMediaUpload($mediaID = null)
{
if ($mediaID == null) {
return false;
}
$accessToken = $this->getAccessToken();
$uri = $this->baseUrl . "v1/media/$mediaID";
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $uri,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => array(
"Accept: application/json;charset=utf-8",
"Content-Type: application/json;charset=utf-8",
"Authorization: Bearer $accessToken"
),
)
);
$response = curl_exec($curl);
curl_close($curl);
return $response;
}
/**
* Schedule a post on Hootsuite.
*
* @param string $text The text content of the post.
* @param array $mediaIDs An array of media IDs to be attached to the post (optional).
* @param array $socialProfileIDs An array of social profile IDs to which the post will be sent (optional).
* @param string $scheduledTime The scheduled time for the post in ISO 8601 format.
* @return string The response from the Hootsuite API.
*/
public function schedulePost($text, $mediaIDs = [], $socialProfileIDs = [], $scheduledTime)
{
if(!$scheduledTime || !$text || count($socialProfileIDs) == 0) {
return false;
}
$scheduledDateTimeObj = \DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $scheduledTime, new \DateTimeZone('UTC'));
$dateTimeNow = new \DateTime();
$dateTimeNow->setTimezone(new \DateTimeZone('UTC'));
if($scheduledDateTimeObj < $dateTimeNow) {
return false;
}
$uri = $this->baseUrl . "v1/messages";
$accessToken = $this->getAccessToken();
$rawPostBody = array(
"text" => $text,
"scheduledSendTime" => $scheduledTime,
"socialProfileIds" => $socialProfileIDs
);
if (count($mediaIDs) > 0) {
// each mediaid should be an object {id: "mediaID"}
$mediaIDs = array_map(function ($mediaID) {
return array ("id" => $mediaID);
}, $mediaIDs);
$rawPostBody["media"] = $mediaIDs;
}
$postBody = json_encode($rawPostBody);
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $uri,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $postBody,
CURLOPT_HTTPHEADER => array(
"Accept: application/json;charset=utf-8",
"Content-Type: application/json;charset=utf-8",
"Authorization: Bearer $accessToken"
),
)
);
$response = curl_exec($curl);
curl_close($curl);
return $response;
}
/**
* Retrieves a message from Hootsuite based on the provided message ID.
*
* @param int $messageID The ID of the message to retrieve.
* @return string The response from the Hootsuite API.
*/
public function getMessage($messageID)
{
$uri = $this->baseUrl . "v1/messages/$messageID";
$accessToken = $this->getAccessToken();
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $uri,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => array(
"Accept: application/json;charset=utf-8",
"Content-Type: application/json;charset=utf-8",
"Authorization: Bearer $accessToken"
),
)
);
$response = curl_exec($curl);
curl_close($curl);
return $response;
}
/**
* Deletes a message from Hootsuite based on the provided message ID.
*
* @param int $messageID The ID of the message to delete.
* @return string The response from the Hootsuite API.
*/
public function deleteMessage($messageID)
{
$uri = $this->baseUrl . "v1/messages/$messageID";
$accessToken = $this->getAccessToken();
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $uri,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => array(
"Accept: application/json;charset=utf-8",
"Content-Type: application/json;charset=utf-8",
"Authorization: Bearer $accessToken"
),
)
);
$response = curl_exec($curl);
curl_close($curl);
return $response;
}
}