Last active
April 23, 2025 16:15
-
-
Save croxton/3ddac43c7770e899e70dc72b79a086ad to your computer and use it in GitHub Desktop.
Passle API -> Craft CMS -> Feed Me
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Passle Import Controller | |
* | |
* Transform Passle API queries to a format that can be imported with Feed Me. | |
* | |
* @link https://hallmark-design.co.uk | |
* @copyright Copyright (c) 2025 Mark Croxton | |
*/ | |
namespace modules\custommodule\controllers; | |
use Craft; | |
use craft\web\Controller; | |
use GuzzleHttp\Exception\GuzzleException; | |
use yii\base\InvalidConfigException; | |
use yii\web\Response; | |
use GuzzleHttp\Client; | |
use Craft\helpers\StringHelper; | |
/** | |
* Import Controller | |
* | |
* @author Mark Croxton | |
* @package customModule | |
* @since 1.0.0 | |
*/ | |
class ImportController extends Controller | |
{ | |
// Protected Properties | |
// ========================================================================= | |
/** | |
* @var bool|array Allows anonymous access to this controller's actions. | |
* The actions must be in 'kebab-case' | |
* @access protected | |
*/ | |
protected int|bool|array $allowAnonymous = [ | |
'posts', | |
'tags', | |
'people', | |
'sync' | |
]; | |
// Public Methods | |
// ========================================================================= | |
/** | |
* Import Passle posts | |
* | |
* @param int $page | |
* @return Response | |
* @throws GuzzleException | |
*/ | |
public function actionPosts(int $page=1) : Response | |
{ | |
$url = getenv('PASSLE_ENDPOINT_POSTS'); | |
$shortCode = getenv('PASSLE_SHORT_CODE'); | |
$limit = 20; | |
$jsonArray = $this->passleApi($url, [ | |
'PassleShortcode' => $shortCode, | |
'PageNumber' => $page, | |
'ItemsPerPage' => $limit, | |
'SortOrder' => 1, | |
'FeaturedImage' => [ | |
'Width' => 1600, | |
'Height' => 900 | |
] | |
] | |
); | |
// Add status of posts | |
foreach ($jsonArray['Posts'] as &$post) { | |
$post['Status'] = 'enabled'; | |
} | |
unset($post); | |
if ( array_key_exists('TotalCount', $jsonArray) && ($page * $limit) < $jsonArray['TotalCount'] ) { | |
$jsonArray['next'] = Craft::getAlias('@web/api/custom/import/posts?page=') . ($page + 1); | |
} | |
// output feed | |
$output = $this->asJson($jsonArray); | |
$output->setStatusCode(200); | |
return $output; | |
} | |
/** | |
* Import Passle tag groups | |
* | |
* @return Response | |
* @throws GuzzleException | |
*/ | |
public function actionTags() : Response | |
{ | |
$url = getenv('PASSLE_ENDPOINT_TAGGROUPS'); | |
$shortCode = getenv('PASSLE_SHORT_CODE'); | |
$jsonArray = $this->passleApi($url, [ | |
'PassleShortcode' => $shortCode | |
] | |
); | |
$formatted = []; | |
if (array_key_exists('TagGroups', $jsonArray)) { | |
foreach ($jsonArray['TagGroups'] as $group) { | |
$formatted[] = array( | |
'title' => $group['Name'], | |
'slug' => StringHelper::slugify($group['Name']), | |
'parent' => "" | |
); | |
foreach ($group['Tags'] as $tag) { | |
$formatted[] = array( | |
'title' => $tag, | |
'slug' => StringHelper::slugify($tag), | |
'parent' => $group['Name'] | |
); | |
} | |
} | |
} | |
// Get a one-dimensional array of all the tags that were grouped | |
$groupedTags = array_column($formatted, 'title'); | |
// Now get all other tags | |
$url = getenv('PASSLE_ENDPOINT_TAGS'); | |
// Create a new Guzzle Client instance | |
$jsonArray = $this->passleApi($url, [ | |
'PassleShortcode' => $shortCode | |
] | |
); | |
if (array_key_exists('Tags', $jsonArray)) { | |
foreach ($jsonArray['Tags'] as $tag) { | |
if (!in_array($tag, $groupedTags, true)) { | |
$formatted[] = [ | |
'title' => $tag, | |
'slug' => StringHelper::slugify($tag), | |
'parent' => 'Uncategorized' | |
]; | |
} | |
} | |
} | |
// Output feed | |
$output = $this->asJson($formatted); | |
$output->setStatusCode(200); | |
return $output; | |
} | |
/** | |
* Import Passle people | |
* | |
* @param int $page | |
* @return Response | |
* @throws GuzzleException | |
*/ | |
public function actionPeople(int $page=1) : Response | |
{ | |
$url = getenv('PASSLE_ENDPOINT_PEOPLE'); | |
$shortCode = getenv('PASSLE_SHORT_CODE'); | |
$limit = 200; | |
// Passle API query | |
$jsonArray = $this->passleApi($url, [ | |
'PassleShortcode' => $shortCode, | |
'PageNumber' => $page, | |
'ItemsPerPage' => $limit, | |
'SortOrder' => 1, | |
'Image' => [ | |
'Width' => 800, | |
'Height' => 800 | |
] | |
] | |
); | |
if ( array_key_exists('TotalCount', $jsonArray) && ($page * $limit) < $jsonArray['TotalCount'] ) { | |
$jsonArray['next'] = Craft::getAlias('@web/api/custom/import/people?page=') . ($page + 1); | |
} | |
// output feed | |
$output = $this->asJson($jsonArray); | |
$output->setStatusCode(200); | |
return $output; | |
} | |
/** | |
* Sync Passle posts and tags | |
* | |
* @return Response | |
* @throws GuzzleException | |
* @throws \Exception | |
* @throws \Throwable | |
*/ | |
public function actionSync() : Response | |
{ | |
$url = getenv('PASSLE_ENDPOINT_SYNC'); | |
$shortCode = getenv('PASSLE_SHORT_CODE'); | |
// Get last sync date | |
$passleSet = Craft::$app->globals->getSetByHandle('passle'); | |
$dateFrom = \DateTimeImmutable::createFromMutable($passleSet->eventDate) | |
->setTimezone(new \DateTimeZone("UTC")) | |
->format("Y-m-d\\TH:i:sp"); | |
// Passle API query | |
$jsonArray = $this->passleApi($url, [ | |
'PassleShortcode' => $shortCode, | |
'DateFrom' => $dateFrom | |
] | |
); | |
/* | |
SyncPost = 1 | |
DeletePost = 2 | |
SyncAuthor = 3 | |
DeleteAuthor = 4 | |
UpdateFeaturedPost = 5 | |
Ping = 6 | |
*/ | |
// Build an array of shortcodes to update or delete (disable) | |
$postsToUpdate = []; | |
$postsToDelete = []; | |
if (array_key_exists('Updates', $jsonArray)) { | |
foreach($jsonArray['Updates'] as $update) { | |
// We only interested in updated and deleted posts | |
if ($update['Action'] === 2) { | |
$postsToDelete[] = $update['Data']['Shortcode']; | |
} elseif ($update['Action'] === 1) { | |
$postsToUpdate[] = $update['Data']['Shortcode']; | |
} | |
} | |
} | |
// Get updated posts to sync | |
if (count($postsToUpdate) > 0) { | |
$url = getenv('PASSLE_ENDPOINT_SYNC_POSTS'); | |
$jsonArray = $this->passleApi($url, [ | |
'PostShortcode' => implode(',', $postsToUpdate), | |
'FeaturedImage' => [ | |
'Width' => 1600, | |
'Height' => 900 | |
] | |
] | |
); | |
} else { | |
$jsonArray = [ | |
"TotalCount" => 0, | |
"PageNumber" => 1, | |
"PageSize" => 20, | |
"Posts" => [] | |
]; | |
} | |
// Update status of posts | |
foreach ($jsonArray['Posts'] as &$post) { | |
$post['Status'] = 'enabled'; | |
} | |
unset($post); | |
// Append posts to delete (disable) to the end | |
if ( count($postsToDelete) > 0) { | |
// get matching entries (note: only enabled entries will be matched) | |
$entriesToDelete = \craft\elements\Entry::find() | |
->section('posts') | |
->shortcode($postsToDelete) | |
->all(); | |
foreach ($entriesToDelete as $entry) { | |
$jsonArray['Posts'][] = [ | |
"PostTitle" => $entry->title, | |
"PostSlug" => $entry->slug, | |
"Status" => 0, | |
"PostShortcode" => $entry->shortcode | |
]; | |
} | |
$jsonArray['TotalCount'] += count($entriesToDelete); | |
} | |
// Output feed | |
$output = $this->asJson($jsonArray); | |
$output->setStatusCode(200); | |
return $output; | |
} | |
/** | |
* Make a Passle API request | |
* | |
* @param string $url | |
* @param array $query | |
* @return array | |
* @throws GuzzleException | |
* @throws \RuntimeException | |
*/ | |
private function passleApi(string $url, array $query) : array | |
{ | |
$client = new Client(); | |
$response = $client->request('GET', $url, [ | |
'headers' => [ | |
'APIKey' => getenv('PASSLE_API_KEY') | |
], | |
'query' => $query | |
]); | |
// Get the status code of the response | |
$statusCode = $response->getStatusCode(); | |
$jsonArray = []; | |
$error = null; | |
if ($statusCode === 200) { | |
try { | |
// Parse the JSON response | |
$data = $response->getBody(); | |
$jsonArray = json_decode($data, true, 512, JSON_THROW_ON_ERROR); | |
} catch (\GuzzleHttp\Exception\GuzzleException $e) { | |
// Handle the Guzzle exception | |
$error = 'Guzzle error: ' . $e->getMessage(); | |
} catch (\JsonException $e) { | |
// Handle the Json exception | |
$error = 'Json error: ' . $e->getMessage(); | |
} | |
} else { | |
// Handle the status error codes from Passle | |
$error = match($statusCode) { | |
400 => "Passle error: The request parameters are incorrect", | |
403 => "Passle error: The API Key is incorrect", | |
500 => "Passle error: The server couldn't process the request", | |
default => "Passle error: Sorry, something went wrong", | |
}; | |
} | |
if ($error !== null) { | |
throw new \RuntimeException($error); | |
} | |
return $jsonArray; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment