arlo-1.x-dev/src/Controller/ArloAPIController.php
src/Controller/ArloAPIController.php
<?php
namespace Drupal\arlo\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Database\Connection;
use Drupal\node\Entity\Node;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Class ArloAPIController.
*/
class ArloAPIController extends ControllerBase {
/**
* Service endpoint function. Handles the submission from the Arlo
* webhook.
*/
public function endpoint(Request $request) {
$config = $this->config('arlo.settings');
$webhook_key = $config->get('webhook_key');
// Get the input from our posted data. If no data was posted, then we can
// bail on the operation.
$content = $request->getContent();
$headers = $_SERVER;
/**
* We need to figure out how to verify the signature of each incoming message
* as it relates to the webhook key for the webhook. We have an example of
* how it works here:
*
* https://github.com/ArloSoftware/webhookclients/blob/master/python-flask/app.py
*/
$auth = FALSE;
foreach ($headers as $header => $value) {
if (strtolower($header) == 'http_x_arlo_signature') {
$key_bytes = base64_decode($webhook_key);
$calculated = base64_encode(hash_hmac('sha512', $content, $key_bytes, TRUE));
if ($value == $calculated) {
$auth = TRUE;
}
}
}
if ($auth === TRUE) {
$data = json_decode($content, FALSE);
// What we get from the API is metadata only. For "Event" resource types, the
// resourceID is the event id. This needs to then be used to fetch the event
// information from Arlo and either create a new event or update an existing
// one as it exists in Drupal.
if (!empty($data)) {
// Handle event events.
if (!empty($data->events)) {
foreach ($data->events as $event) {
switch ($event->type) {
case 'Event.Created':
$fetchedEvent = $this->fetchEvent($event->resourceId);
if (!empty($fetchedEvent)) {
$this->createEvent($fetchEvent);
}
break;
case 'Event.Updated':
$fetchedEvent = $this->fetchEvent($event->resourceId);
if (!empty($fetchedEvent)) {
$this->updateEvents($fetchedEvent);
}
else {
// TODO: Not sure yet if we should delete the reference if there is no data
// or handle the error differently. Right now we will mark this issue
// as to be done.
}
break;
default:
break;
}
}
}
$response = [
'data' => 'Success',
'method' => 'GET'
];
}
else {
$response = [
'data' => 'Failure',
'method' => 'GET'
];
}
}
else {
$response = [
'data' => 'Failure',
'method' => 'GET'
];
}
return new JsonResponse($response);
}
/**
* Get a specific event from the Arlo API and return the result.
*
* @param int eventID
* The EventID (or sometimes known as resourceID) of the event that we wish to
* get from the Arlo API. If left blank wil return all events.
*
* @return array $items
* An array of returned items. A single item if the eventID is specified or all
* of the events if it is not.
*/
public function fetchEvent($eventID = NULL) {
/**
* Sample URL structure to fetch the data we need from an event. A full
* list of fields can be found at:
*
* https://developer.arlo.co/doc/api/2012-02-01/pub/resources/eventsearch
*
* Note that if no eventID is passed, it will return all events which will
* then need to be iterated through - so always be prepared for > 1 result.
*/
$config = $this->config('arlo.settings');
$platform_id = $config->get('platform_id');
$filter = (!empty($eventID)) ? '&filter=eventID=' . $eventID : NULL;
$url = 'https://' . $platform_id . '/api/2012-02-01/pub/resources/eventsearch?fields=eventid,name,description,summary,sessionsdescription,presenters,viewuri' . $filter;
try {
$request = \Drupal::httpClient()->get($url, [
'headers' => [
'Accept' => 'application/json',
],
]);
$status = $request->getStatusCode();
$response = $request->getBody();
$data = json_decode($response);
if (!empty($data->Items)) {
return $data->Items;
}
else {
return [];
}
}
catch (RequestException $e) {
// TODO: Handle the error.
}
}
/**
*
* Update an existing event when new information is presented via the update
* webhook.
*
* @param array $payload
* The payload array of objects that comes back from the Arlo API.
*
* @return array $status
* Returns an array with the count of SAVED_NEW and SAVED_UPDATED counts.
*/
public function createEvent($payload) {
$status = [
SAVED_NEW => 0,
SAVED_UPDATED => 0,
];
foreach ($payload as $key => $event) {
$node = Node::create(['type' => 'arlo_event']);
$node->set('title', htmlspecialchars($event->Name));
$node->set('field_arlo_link', $event->ViewUri);
$node->set('field_arlo_summary', $event->Summary);
$node->set('field_arlo_event_id', $event->EventID);
$node->set('body', $event->Description->Text);
$node->enforceIsNew();
$return = $node->save();
$status[$return]++;
}
return $status;
}
/**
* Synchronize events from Arlo into our Arlo Events content type.
*
* @return void
*/
public function syncEvents() {
$events = $this->fetchEvent();
$this->updateEvents($events);
\Drupal::messenger()->addStatus('Arlo Events/Template have been synchronized.');
(new RedirectResponse('/admin'))->send();
}
/**
* Update an existing event when new information is presented via the update
* webhook.
*
* @param array $payload
* The payload array of objects that comes back from the Arlo API.
*
* @return array $status
* Returns an array with the count of SAVED_NEW and SAVED_UPDATED counts.
*/
public function updateEvents($payload) {
$status = [
SAVED_NEW => 0,
SAVED_UPDATED => 0,
];
foreach ($payload as $key => $event) {
// We need to determine from the EventID if this is a new entry or
// an update to an existing one.
$nids = \Drupal::entityTypeManager()->getStorage('node')->getQuery()
->condition('field_arlo_event_id', $event->EventID)
->condition('type', 'arlo_event')
->accessCheck(TRUE)
->execute();
if (!empty($nids)) {
if (count($nids) > 1) {
// Now this would be a problem if we have more than one node of a
// particular event. If this is the case, we need to remove all of
// the existing nodes and save a new one.
//
// This is a failsafe for duplicate arlo_event nodes.
\Drupal::logger('arlo')->notice('Arlo EventID ' . $event->EventID . ' has more than one node. Destroying existing nodes and creating a single new one.');
$handler = \Drupal::entityTypeManager()->getStorage("node");
$entities = $handler->loadMultiple($nids);
$handler->delete($entities);
$status = $this->createEvent(array($event));
}
else {
// Yes, we only have one nid, but it comes in an array, so iterate
// to make it easy.
foreach ($nids as $nid) {
$node = \Drupal::entityTypeManager()->getStorage('node')->load($nid);
$node->set('title', htmlspecialchars($event->Name));
$node->set('field_arlo_link', $event->ViewUri);
$node->set('field_arlo_summary', $event->Summary);
$node->set('field_arlo_event_id', $event->EventID);
$node->set('body', $event->Description->Text);
$node->save();
}
}
}
else {
$node = Node::create(['type' => 'arlo_event']);
$node->set('title', htmlspecialchars($event->Name));
$node->set('field_arlo_link', $event->ViewUri);
$node->set('field_arlo_summary', $event->Summary);
$node->set('field_arlo_event_id', $event->EventID);
$node->set('body', $event->Description->Text);
$node->enforceIsNew();
$return = $node->save();
$status[$return]++;
}
}
return $status;
}
}
