brandfolder-8.x-1.x-dev/src/EventSubscriber/WebhookEventSubscriber.php
src/EventSubscriber/WebhookEventSubscriber.php
<?php
namespace Drupal\brandfolder\EventSubscriber;
use Drupal\brandfolder\Event\BrandfolderWebhookEvent;
use Drupal\brandfolder\Plugin\media\Source\BrandfolderImage;
use Drupal\Core\Entity\Query\QueryException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Class WebhookEventSubscriber.
*
* @package brandfolder
*/
class WebhookEventSubscriber implements EventSubscriberInterface {
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
return [
// BrandfolderWebhookEvent::ASSET_CREATE => 'assetCreate',
BrandfolderWebhookEvent::ASSET_UPDATE => 'assetUpdate',
// BrandfolderWebhookEvent::ASSET_DELETE => 'assetDelete',
];
}
/**
* React to a Brandfolder asset being created.
*
* @param \Drupal\brandfolder\Event\BrandfolderWebhookEvent $event
*/
// public function assetCreate(BrandfolderWebhookEvent $event) {
//
// }
/**
* React to a Brandfolder asset being updated.
*
* @param \Drupal\brandfolder\Event\BrandfolderWebhookEvent $event
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityStorageException
*
* @todo: Break some of this code out into other handlers if it becomes unwieldy.
*/
public function assetUpdate(BrandfolderWebhookEvent $event) {
$bf_asset_id = $event->data['key'];
$bf = brandfolder_api();
$params = [
'include' => 'attachments,custom_fields',
];
if ($asset = $bf->fetchAsset($bf_asset_id, $params)) {
$db = \Drupal::database();
// Handle asset merge events. This asset's attachments may formerly have
// been attached to another asset. Update any of our records to ensure the
// latest asset-attachment relationship is reflected.
if (!empty($asset->data->attachments)) {
$attachment_ids = array_map(function($attachment) {
return $attachment->id;
}, $asset->data->attachments);
$db->update('brandfolder_file')
->fields(['bf_asset_id' => $bf_asset_id])
->condition('bf_attachment_id', $attachment_ids, 'IN')
->execute();
}
// Metadata sync.
$updated_entities = [];
$query = $db->select('brandfolder_file', 'bf')
->fields('bf', ['fid', 'bf_attachment_id'])
->condition('bf_asset_id', $bf_asset_id);
if ($query->countQuery()->execute()->fetchField()) {
$entity_type_manager = \Drupal::entityTypeManager();
// Start with alt-text-specific functionality.
// If there is a BF custom field designated for storing
// image alt text, and this updated asset has a value for that
// field, see if corresponding Drupal image fields and/or file entities
// are lacking alt text. If so, pull the text from BF to populate those.
$config = \Drupal::config('brandfolder.settings');
$alt_text_custom_field_id = $config->get('alt_text_custom_field');
if (!empty($alt_text_custom_field_id)) {
// Note: we always Look up the current name associated with the given
// custom field key ID. The name can change at any time in Brandfolder
// without Drupal knowing, but the ID never changes (but we can't
// use the ID directly when getting field values for an asset).
if ($custom_field_keys = $bf->listCustomFields(NULL, FALSE, TRUE)) {
if (isset($custom_field_keys[$alt_text_custom_field_id])) {
$custom_field_name = $custom_field_keys[$alt_text_custom_field_id];
if (!empty($asset->data->custom_field_values[$custom_field_name])) {
$alt_text = $asset->data->custom_field_values[$custom_field_name];
$relevant_fids = $query->execute()->fetchCol(0);
// File Entity module support.
// @todo: Also auto-populate these fields on BF-related file creation.
$fe_alt_text_field_name = 'field_image_alt_text';
try {
$fe_alt_text_field_file_ids = \Drupal::entityQuery('file')
->condition('fid', $relevant_fids, 'IN')
->notExists($fe_alt_text_field_name)
->execute();
if (count($fe_alt_text_field_file_ids) > 0) {
$fe_alt_text_field_files = $entity_type_manager
->getStorage('file')
->loadMultiple($fe_alt_text_field_file_ids);
foreach ($fe_alt_text_field_files as $file) {
$file->set($fe_alt_text_field_name, $alt_text);
$file->save();
}
}
}
catch (QueryException $e) {
// File Entity module presumably not installed or has been
// modified, etc.
}
// Standard image fields referencing relevant files.
$field_manager = \Drupal::service('entity_field.manager');
$image_field_registry = $field_manager->getFieldMapByFieldType('image');
foreach ($image_field_registry as $entity_type => $image_fields) {
foreach ($image_fields as $field_name => $field_data) {
$table_name = "{$entity_type}__{$field_name}";
if ($db->schema()->tableExists($table_name)) {
$entity_id_query = $db->select($table_name, 'img')
->fields('img', ['entity_id'])
->condition("{$field_name}_target_id", $relevant_fids, 'IN')
->isNull("{$field_name}_alt");
if ($entity_id_query->countQuery()
->execute()
->fetchField()) {
$entity_ids = $entity_id_query->execute()->fetchCol();
$entities = $entity_type_manager
->getStorage($entity_type)
->loadMultiple($entity_ids);
foreach ($entities as $entity_id => $entity) {
$field_values = $entity->get($field_name)->getValue();
foreach ($field_values as $index => $field_value) {
if (in_array($field_value['target_id'], $relevant_fids)) {
$field_values[$index]['alt'] = $alt_text;
}
}
$entity->set($field_name, $field_values);
$entity->save();
$updated_entities[$entity_type][] = $entity_id;
}
}
}
}
}
}
}
}
}
// Find media entities with any of the given attachments as their source
// field and trigger a general metadata update (if not already processed
// by alt-text handling above).
// @todo: Will the alt-text-updated media items have had all their other metadata properly updated, though? (Specifically whatever metadata is supposed to be forcefully updated).
$relevant_attachment_ids = $query->execute()->fetchCol(1);
$media_types = $entity_type_manager
->getStorage('media_type')
->loadMultiple();
$media_storage = $entity_type_manager->getStorage('media');
$updated_media_entity_ids = $updated_entities['media'] ?? [-1];
foreach ($media_types as $media_type_id => $media_type) {
$source = $media_type->getSource();
if ($source instanceof BrandfolderImage) {
// If this type has one or more field mappings defined, resave the
// entity. We could try to get fancier and only retrieve entities
// that have one or more empty fields on the mapped field list, but
// that's overkill since we'd have to accommodate various
// field/storage types and corresponding definitions of "empty."
$field_mapping = $media_type->getFieldMap();
if (!empty($field_mapping)) {
$media_entity_ids = \Drupal::entityQuery('media')
->accessCheck(FALSE)
->condition('bundle', $media_type_id)
// Exclude anything we've already resaved above.
->condition('mid', $updated_media_entity_ids, 'NOT IN')
->condition('field_brandfolder_attachment_id', $relevant_attachment_ids, 'IN')
->execute();
if (count($media_entity_ids) > 0) {
$media_entities = $media_storage->loadMultiple($media_entity_ids);
foreach ($media_entities as $media_entity) {
// Forcefully update any metadata-mapped fields that should
// always be updated regardless of whether data exists in the
// field (default Media behavior is only to update if mapped
// fields are empty).
foreach ($media_entity->getTranslationLanguages() as $langcode => $data) {
if ($media_entity->hasTranslation($langcode)) {
$translation = $media_entity->getTranslation($langcode);
$translation_field_mapping = $translation->bundle->entity->getFieldMap();
$forcefully_updated_metadata = array_flip($source->getForcefullyUpdatedMetadataAttributes());
$forcefully_updated_field_mapping = array_intersect_key($translation_field_mapping, $forcefully_updated_metadata);
foreach ($forcefully_updated_field_mapping as $metadata_attribute_name => $entity_field_name) {
if ($translation->hasField($entity_field_name)) {
$translation->set($entity_field_name, $source->getMetadata($translation, $metadata_attribute_name));
}
}
}
}
$media_entity->save();
}
}
}
}
}
}
}
}
/**
* React to a Brandfolder asset being deleted.
*
* @param \Drupal\brandfolder\Event\BrandfolderWebhookEvent $event
*/
// public function assetDelete(BrandfolderWebhookEvent $event) {
//
// }
}
