canto-1.0.x-dev/src/Plugin/EntityBrowser/Widget/CantoBrowser.php
src/Plugin/EntityBrowser/Widget/CantoBrowser.php
<?php
namespace Drupal\canto\Plugin\EntityBrowser\Widget;
use Drupal\canto\OAuthConnector;
use Drupal\canto\CantoRepository;
use Drupal\canto\Plugin\media\Source\CantodamAsset;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Image\ImageFactory;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\Core\Utility\Token;
use Drupal\entity_browser\WidgetBase;
use Drupal\entity_browser\WidgetValidationManager;
use Drupal\file\FileInterface;
use Drupal\Component\Serialization\Json;
use Drupal\media\Entity\MediaType;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;
use Drupal\media\MediaSourceManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* An Entity Browser widget for creating media entities using Canto.
*
* @EntityBrowserWidget(
* id = "canto_browser",
* label = @Translation("Canto DAM Browser"),
* description = @Translation("Canto Dam Browser."),
* auto_select = FALSE
* )
*/
class CantoBrowser extends WidgetBase {
/**
* The current user account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
/**
* A media source manager.
*
* @var \Drupal\media\MediaSourceManager
*/
protected $sourceManager;
/**
* An entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* Drupal RequestStack service.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* Canto repository.
*
* @var \Drupal\canto\CantoRepository
*/
protected $repository;
/**
* The image factory.
*
* @var \Drupal\Core\Image\ImageFactory
*/
protected $imageFactory;
/**
* The current user account.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The token service.
*
* @var \Drupal\Core\Utility\Token
*/
protected $token;
/**
* Canto browser constructor.
*
* {@inheritdoc}
*/
public function __construct(
$configuration,
$plugin_id,
$plugin_definition,
EventDispatcherInterface $event_dispatcher,
EntityTypeManagerInterface $entity_type_manager,
EntityFieldManagerInterface $entity_field_manager,
WidgetValidationManager $validation_manager,
AccountInterface $account,
MediaSourceManager $sourceManager,
RequestStack $requestStack,
CantoRepository $repository,
ConfigFactoryInterface $config,
ImageFactory $imageFactory,
LanguageManagerInterface $languageManager,
Token $token) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $event_dispatcher, $entity_type_manager, $validation_manager);
$this->user = $account;
$this->sourceManager = $sourceManager;
$this->entityFieldManager = $entity_field_manager;
$this->requestStack = $requestStack;
$this->repository = $repository;
$this->config = $config;
$this->imageFactory = $imageFactory;
$this->languageManager = $languageManager;
$this->token = $token;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration,
$plugin_id,
$plugin_definition,
$container->get('event_dispatcher'),
$container->get('entity_type.manager'),
$container->get('entity_field.manager'),
$container->get('plugin.manager.entity_browser.widget_validation'),
$container->get('current_user'),
$container->get('plugin.manager.media.source'),
$container->get('request_stack'),
$container->get('canto.repository'),
$container->get('config.factory'),
$container->get('image.factory'),
$container->get('language_manager'),
$container->get('token'));
}
/**
* {@inheritdoc}
*
* @todo Add more settings for configuring this widget.
* such as media type ...etc
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$mediaTypeOptions = [];
$mediaTypes = $this->entityTypeManager->getStorage('media_type')
->loadByProperties(['source' => 'cantodam_asset']);
foreach ($mediaTypes as $mediaType) {
$mediaTypeOptions[$mediaType->id()] = $mediaType->label();
}
if (empty($mediaTypeOptions)) {
$url = Url::fromRoute('entity.media_type.add_form')->toString();
$form['media_type'] = [
'#markup' => $this->t("You don't have media type of the Canto DAM asset type. You should <a href=':link'>create one</a>", [':link' => $url]),
];
}
else {
$form['media_type'] = [
'#type' => 'select',
'#title' => $this->t('Media type'),
'#default_value' => $this->configuration['media_type'],
'#options' => $mediaTypeOptions,
];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'media_type' => NULL,
'submit_text' => $this->t('Select assets'),
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function getForm(array &$original_form, FormStateInterface $form_state, array $additional_widget_parameters) {
// Start by inheriting parent form.
$form = parent::getForm($original_form, $form_state, $additional_widget_parameters);
// Add container for assets (and folder buttons)
$form['assets_container'] = [
'#type' => 'container',
'#attributes' => [
'class' => ['assets-browser'],
],
];
$config = $this->config->get('canto.settings');
$form['#attached']['library'][] = 'editor/drupal.editor.dialog';
$form['#attached']['library'][] = 'canto/canto.entity_browser';
$form['#attached']['library'][] = 'canto/canto.uc';
$form['#attached']['drupalSettings']['canto']['env'] = $config->get('env');
$form['#attached']['drupalSettings']['canto']['context'] = 'entity-browser';
// @todo fix entry format.
$entry = $this->checkAccessToken();
if (count($entry) > 0) {
$form['#attached']['drupalSettings']['canto']['accessToken'] = $entry[0]['accessToken'];
$form['#attached']['drupalSettings']['canto']['tenants'] = $entry[0]['subDomain'];
$form['#attached']['drupalSettings']['canto']['tokenType'] = $entry[0]['tokenType'];
$supported_extensions = $this->imageFactory->getSupportedExtensions();
$form['#attached']['drupalSettings']['canto']['allowExtensions'] = implode(';', $supported_extensions);
}
$form['assets_container']['canto_container'] = [
'#type' => 'container',
'#id' => 'cantoPickbox',
'#attributes' => [
'class' => ['canto-pick-box'],
],
];
$form['assets_container']['canto_container']['trigger'] = [
'#prefix' => '<a>',
'#markup' => '<div class="img-box" id="cantoimage"> + Insert Files from Canto</div>',
'#suffix' => '</a>',
];
$form['assets_container']['cantofid'] = [
'#type' => 'hidden',
];
$form['assets_container']['assets_data'] = [
'#type' => 'hidden',
'#attributes' => ['data-assets' => '', 'id' => 'canto-assets-data'],
];
$form['assets_container']['canto_asset_id'] = [
'#type' => 'hidden',
'#attributes' => ['id' => 'canto-asset-id'],
];
$form['assets_container']['actions'] = $form['actions'];
// Hide the submit button and allow JS to trigger upon selection.
$form['assets_container']['actions']['#attributes']['class'][] = 'visually-hidden';
unset($form['actions']);
return $form;
}
/**
* Check the access token for the current user.
*
* @todo this is orginial form the contrib but need refactoring
* possible use userData for storing token.
*/
public function checkAccessToken() {
$user = $this->user;
$userId = $user->id();
$envSettings = $this->config->get('canto.settings')->get('env');
$env = ($envSettings === NULL) ? "canto.com" : $envSettings;
$entry = [
'uid' => $userId,
'env' => $env,
];
$entries = $this->repository->getAccessToken($entry);
if (count($entries) > 0) {
$subDomain = $entries[0]['subDomain'];
$accessToken = $entries[0]['accessToken'];
$isValid = OAuthConnector::checkAccessTokenValid($subDomain, $accessToken);
if (!$isValid) {
$this->repository->delete($entry);
$entries = [];
}
}
return $entries;
}
/**
* {@inheritdoc}
*/
public function validate(array &$form, FormStateInterface $formState) {
if (!empty($formState->getTriggeringElement()['#eb_widget_main_submit'])) {
// The media bundle.
$mediaBundleConfig = $this->entityTypeManager->getStorage('media_type');
$mediaBundle = $mediaBundleConfig->load($this->configuration['media_type']);
// Load the field definitions for this bundle.
$fieldDefinitions = $this->entityFieldManager->getFieldDefinitions('media', $mediaBundle->id());
// Load the file settings to validate against.
$fieldMap = $mediaBundle->getFieldMap();
if (!isset($fieldMap['file']) || !isset($fieldMap['metadata'])) {
$message = $this->t('Missing file/Metadata mapping. Check your media configuration.');
$formState->setError($form['widget'], $message);
return;
}
$fileExtensions = $fieldDefinitions[$fieldMap['file']]->getItemDefinition()
->getSetting('file_extensions');
$supportedExtensions = explode(',', preg_replace('/,?\s/', ',', $fileExtensions));
// The form input uses checkboxes which returns zero for unchecked assets.
// Remove these unchecked assets.
$assetsJson = urldecode($formState->getValue('assets_data'));
$assets = JSON::decode($assetsJson);
// Get the cardinality for the media field that is being populated.
$fieldCardinality = $formState->get([
'entity_browser',
'validators',
'cardinality',
'cardinality',
]);
// Getting all ids, validating field cardinatlity.
if (is_array($assets) && !count($assets)) {
$formState->setError($form['widget']['assets_container'], $this->t('Please select an asset.'));
}
// If the field cardinality is limited and the number of assets selected
// is greater than the field cardinality.
// @todo Support a rich text field where cardinality isn't an issue.
if ($fieldCardinality > 0 && count($assets) > $fieldCardinality) {
$message = $this->formatPlural($fieldCardinality, 'You can not select more than 1 entity.', 'You can not select more than @count entities.');
// Set the error message on the form.
$formState->setError($form['widget']['assets_container']['assets_data'], $message);
}
// Transform the assets data:
$allAssetsData = $this->processAssetsData($assets);
// If the asset's file type does not match allowed file types.
foreach ($allAssetsData as $assetData) {
$info = pathinfo($assetData['displayName']);
$typSupported = in_array($info['extension'], $supportedExtensions);
if (!$typSupported) {
$message = $this->t('Please make another selection. The "@filetype" file type is not one of the supported file types (@supported_types).', [
'@filetype' => $info['extension'],
'@supported_types' => implode(', ', $supportedExtensions),
]);
// Set the error message on the form.
$formState->setError($form, $message);
}
}
// Hold on the transformed data.
$formState->setValue('assets', $allAssetsData);
}
}
/**
* {@inheritdoc}
*/
public function submit(array &$element, array &$form, FormStateInterface $form_state) {
if (!empty($form_state->getTriggeringElement()['#eb_widget_main_submit'])) {
try {
$media = $this->prepareEntities($form, $form_state);
array_walk($media, function (MediaInterface $media_item) {
$media_item->save();
});
$this->selectEntities($media, $form_state);
}
catch (\UnexpectedValueException $e) {
$this->messenger()
->addError($this->t('Canto integration is not configured correctly. Please contact the site administrator.'));
}
}
}
/**
* {@inheritdoc}
*/
protected function prepareEntities(array $form, FormStateInterface $formState) {
$entities = [];
$assets = $formState->getValue('assets');
// Get the remaining asset ids:
$assetIds = array_keys($assets);
if ($assetIds) {
// Load type information.
/** @var \Drupal\media\MediaTypeInterface $media_type */
$media_type = $this->entityTypeManager->getStorage('media_type')
->load($this->configuration['media_type']);
// Get the source field for this type which stores the asset id.
$source_field = $media_type->getSource()
->getSourceFieldDefinition($media_type)
->getName();
// Query for existing entities.
$existing_ids = $this->getExistingEntities($media_type, $source_field, $assetIds);
// Load the entities found.
$entities = $this->entityTypeManager->getStorage('media')
->loadMultiple($existing_ids);
// Loop through the existing entities.
foreach ($entities as $entity) {
// Get the asset id of the current entity.
$assetId = $entity->get($source_field)->value;
// If the asset id of the entity is in the list of asset id's selected.
if (in_array($assetId, $assetIds)) {
// Remove the asset id from the input so it does not get fetched
// and does not get created as a duplicate.
unset($assets[$assetId]);
}
}
}
// Loop through the returned assets.
foreach ($assets as $info) {
// Get the file data from the directUri:
$fileData = file_get_contents($info['directUri']);
if (!$fileData) {
return $formState->setError($form['widget']['assets_container'], $this->t('An error occurred during file retrieval.'));
}
$destination = $this->getFileDestination($media_type);
// @todo if the file exists should we use the original or the new one?
$file = file_save_data($fileData, $destination . '/' . $info['displayName'], FileSystemInterface::EXISTS_REPLACE);
$entity = $this->createMediaEntity($media_type, $file, $info);
// Add the new entity to the array of returned entities.
$entities[] = $entity;
}
return $entities;
}
/**
* Create media entities from canto assets array.
*
* @param \Drupal\media\Entity\MediaType $mediaType
* The media type object.
* @param \Drupal\file\FileInterface $file
* The file to attach.
* @param array $info
* The canto asset info.
*
* @return \Drupal\Core\Entity\EntityInterface
* The media entity created.
*/
public function createMediaEntity(MediaType $mediaType, FileInterface $file, array $info): EntityInterface {
$mediaBundleConfig = $this->entityTypeManager->getStorage('media_type');
$mediaBundle = $mediaBundleConfig->load($this->configuration['media_type']);
$fieldMap = $mediaBundle->getFieldMap();
// Get the source field for this type which stores the asset id.
$source_field = $mediaType->getSource()
->getSourceFieldDefinition($mediaType)
->getName();
// Initialize entity values.
$entity_values = [
'bundle' => $mediaType->id(),
// This should be the current user id.
'uid' => $this->user->id(),
// This should be the current language code.
'langcode' => $this->languageManager->getCurrentLanguage()->getId(),
// This should map the asset status to the drupal entity status.
// @todo ($asset->status === 'active'),
'status' => TRUE,
// Set the entity name to the asset name.
'name' => $file->label(),
$fieldMap['file'] => ['target_id' => $file->id()],
// Set the chosen source field for this entity to the asset id.
$source_field => $info['id'],
];
// Create a new entity to represent the asset.
$entity = $this->entityTypeManager->getStorage('media')
->create($entity_values);
$entity->set(CantodamAsset::METADATA_FIELD_NAME, Json::encode($info));
return $entity;
}
/**
* Get existing entities with asset ID.
*/
public function getExistingEntities($mediaType, $sourceField, $assetIds) {
return $this->entityTypeManager->getStorage('media')
->getQuery()
->accessCheck(TRUE)
// Add a tag to allow other modules to act on the query.
->addTag('canto_existing_entities')
->condition('bundle', $mediaType->id())
->condition($sourceField, $assetIds, 'IN')
->execute();
}
/**
* Get a destination uri from the given a entity type.
*
* @param \Drupal\media\MediaTypeInterface $mediaType
* The media type to check the field configuration on.
*
* @return string
* The uri to use. Defaults to public://cantodam_assets
*/
public function getFileDestination(MediaTypeInterface $mediaType) {
$scheme = $this->config->get('system.file')->get('default_scheme');
$file_directory = 'cantodam_assets';
// Load the field definitions for this bundle.
$field_definitions = $this->entityFieldManager->getFieldDefinitions('media', $mediaType->id());
$map = $mediaType->getFieldMap();
if (!empty($map['file'])) {
$definition = $field_definitions[$map['file']]->getItemDefinition();
$scheme = $definition->getSetting('uri_scheme');
$file_directory = $definition->getSetting('file_directory');
}
// Replace the token for file directory.
if (!empty($file_directory)) {
$file_directory = $this->token->replace($file_directory);
}
return sprintf('%s://%s', $scheme, $file_directory);
}
/**
* Transform the data.
*/
public function processAssetsData(array $assets) {
$results = [];
foreach ($assets as $asset) {
$results[$asset['id']] = $asset;
}
return $results;
}
}
