357 lines
11 KiB
PHP
357 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Drupal\farm_ui_location\Form;
|
|
|
|
use Drupal\asset\Entity\AssetInterface;
|
|
use Drupal\Component\Serialization\Json;
|
|
use Drupal\Core\Access\AccessResult;
|
|
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
|
use Drupal\Core\Form\FormBase;
|
|
use Drupal\Core\Form\FormStateInterface;
|
|
use Drupal\Core\Session\AccountInterface;
|
|
use Drupal\Core\Url;
|
|
use Drupal\farm_location\AssetLocationInterface;
|
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
|
|
|
/**
|
|
* Form for changing the hierarchy of location assets.
|
|
*
|
|
* @ingroup farm
|
|
*/
|
|
class LocationHierarchyForm extends FormBase {
|
|
|
|
/**
|
|
* The entity type manager service.
|
|
*
|
|
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
|
*/
|
|
protected $entityTypeManager;
|
|
|
|
/**
|
|
* The asset location service.
|
|
*
|
|
* @var \Drupal\farm_location\AssetLocationInterface
|
|
*/
|
|
protected $assetLocation;
|
|
|
|
/**
|
|
* Constructs a new LocationHierarchyForm.
|
|
*
|
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
|
* The entity type manager.
|
|
* @param \Drupal\farm_location\AssetLocationInterface $asset_location
|
|
* The asset location service.
|
|
*/
|
|
public function __construct(EntityTypeManagerInterface $entity_type_manager, AssetLocationInterface $asset_location) {
|
|
$this->entityTypeManager = $entity_type_manager;
|
|
$this->assetLocation = $asset_location;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public static function create(ContainerInterface $container) {
|
|
return new static(
|
|
$container->get('entity_type.manager'),
|
|
$container->get('asset.location')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getFormId() {
|
|
return 'farm_ui_location_form';
|
|
}
|
|
|
|
/**
|
|
* Check access.
|
|
*
|
|
* @param \Drupal\Core\Session\AccountInterface $account
|
|
* The account to check.
|
|
* @param \Drupal\asset\Entity\AssetInterface|null $asset
|
|
* The asset to check (optional).
|
|
*
|
|
* @return \Drupal\Core\Access\AccessResultInterface
|
|
* The access result.
|
|
*/
|
|
public function access(AccountInterface $account, AssetInterface $asset = NULL) {
|
|
|
|
// If the asset is not a location, forbid access.
|
|
if (!$this->assetLocation->isLocation($asset)) {
|
|
return AccessResult::forbidden();
|
|
}
|
|
|
|
// If the asset does not have child locations, forbid access.
|
|
if (empty($this->getLocations($asset))) {
|
|
return AccessResult::forbidden();
|
|
}
|
|
|
|
// Allow access if the asset has child locations.
|
|
return AccessResult::allowedIf($asset->access('view', $account));
|
|
}
|
|
|
|
/**
|
|
* Generate the page title.
|
|
*
|
|
* @param \Drupal\asset\Entity\AssetInterface|null $asset
|
|
* Optionally specify the parent asset that this page is being built for.
|
|
*
|
|
* @return string
|
|
* Returns the translated page title.
|
|
*/
|
|
public function getTitle(AssetInterface $asset = NULL) {
|
|
if (!empty($asset)) {
|
|
return $this->t('Locations in %location', ['%location' => $asset->label()]);
|
|
}
|
|
return $this->t('Locations');
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function buildForm(array $form, FormStateInterface $form_state, AssetInterface $asset = NULL) {
|
|
|
|
// If no asset was specified, show a map of all locations.
|
|
if (is_null($asset)) {
|
|
$form['map'] = [
|
|
'#type' => 'farm_map',
|
|
'#map_type' => 'locations',
|
|
];
|
|
}
|
|
|
|
// Add a DIV for the JavaScript content.
|
|
$form['content'] = [
|
|
'#type' => 'html_tag',
|
|
'#tag' => 'div',
|
|
'#attributes' => [
|
|
'class' => [
|
|
'locations-tree',
|
|
],
|
|
],
|
|
];
|
|
|
|
// Create a hidden field to store hierarchy changes recorded client-side.
|
|
$form['changes'] = [
|
|
'#type' => 'hidden',
|
|
];
|
|
|
|
// Add buttons for toggling drag and drop, saving, and resetting.
|
|
$form['actions'] = ['#type' => 'actions'];
|
|
$form['actions']['toggle'] = [
|
|
'#type' => 'button',
|
|
'#value' => $this->t('Toggle drag and drop'),
|
|
'#attributes' => [
|
|
'class' => [
|
|
'button--secondary',
|
|
],
|
|
],
|
|
];
|
|
$form['actions']['save'] = [
|
|
'#type' => 'submit',
|
|
'#value' => $this->t('Save'),
|
|
'#attributes' => [
|
|
'class' => [
|
|
'button--primary',
|
|
],
|
|
],
|
|
];
|
|
$form['actions']['reset'] = [
|
|
'#type' => 'submit',
|
|
'#value' => $this->t('Reset'),
|
|
'#attributes' => [
|
|
'class' => [
|
|
'button--danger',
|
|
],
|
|
],
|
|
];
|
|
|
|
// Attach the location drag and drop JavaScript.
|
|
$form['#attached']['library'][] = 'farm_ui_location/locations-drag-and-drop';
|
|
$tree = [
|
|
[
|
|
'asset_id' => !empty($asset) ? $asset->id() : '',
|
|
'text' => !empty($asset) ? $asset->label() : $this->t('All locations'),
|
|
'children' => !empty($asset) ? $this->buildTree($asset) : $this->buildTree(),
|
|
'url' => !empty($asset) ? $asset->toUrl('canonical', ['absolute' => TRUE])->toString() : Url::fromRoute('farm.locations', [], ['absolute' => TRUE])->toString(),
|
|
],
|
|
];
|
|
$form['#attached']['drupalSettings']['asset_tree'] = $tree;
|
|
$form['#attached']['drupalSettings']['asset_parent'] = !empty($asset) ? $asset->id() : '';
|
|
|
|
// Return the form.
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* Build the asset tree.
|
|
*
|
|
* @param \Drupal\asset\Entity\AssetInterface|null $asset
|
|
* Optionally specify the parent asset, to only build a sub-tree. If
|
|
* omitted, all assets will be included.
|
|
*
|
|
* @return array
|
|
* Returns the asset tree for use in Drupal JS settings.
|
|
*
|
|
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
|
|
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
|
|
*/
|
|
protected function buildTree(AssetInterface $asset = NULL) {
|
|
$locations = $this->getLocations($asset);
|
|
$tree = [];
|
|
if ($locations) {
|
|
foreach ($locations as $location) {
|
|
$element = [
|
|
'asset_id' => $location->id(),
|
|
'text' => $location->label(),
|
|
'children' => $this->buildTree($location),
|
|
'url' => $location->toUrl('canonical', ['absolute' => TRUE])->toString(),
|
|
];
|
|
$element['original_parent'] = $asset ? $asset->id() : '';
|
|
$tree[] = $element;
|
|
}
|
|
}
|
|
return $tree;
|
|
}
|
|
|
|
/**
|
|
* Gets location assets.
|
|
*
|
|
* @param \Drupal\asset\Entity\AssetInterface|null $asset
|
|
* Optionally provide a parent asset to only retrieve its direct children.
|
|
*
|
|
* @return \Drupal\asset\Entity\AssetInterface[]
|
|
* An array of location assets.
|
|
*
|
|
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
|
|
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
|
|
*/
|
|
protected function getLocations(AssetInterface $asset = NULL) {
|
|
|
|
// Query unarchived location assets.
|
|
$storage = $this->entityTypeManager->getStorage('asset');
|
|
$query = $storage->getQuery()
|
|
->accessCheck(TRUE)
|
|
->condition('is_location', TRUE)
|
|
->condition('status', 'archived', '!=');
|
|
|
|
// Limit to a specific parent or no parent.
|
|
if ($asset) {
|
|
$query->condition('parent', $asset->id());
|
|
}
|
|
else {
|
|
$query->condition('parent', NULL, 'IS NULL');
|
|
}
|
|
|
|
// Query and load the assets.
|
|
$asset_ids = $query->execute();
|
|
if (empty($asset_ids)) {
|
|
return [];
|
|
}
|
|
/** @var \Drupal\asset\Entity\AssetInterface[] $assets */
|
|
$assets = $storage->loadMultiple($asset_ids);
|
|
|
|
// Filter out assets that the user cannot view.
|
|
$assets = array_filter($assets, function ($asset) {
|
|
return $asset->access('view');
|
|
});
|
|
|
|
// Sort assets by name, using natural sort algorithm.
|
|
usort($assets, function ($a, $b) {
|
|
return strnatcmp($a->label(), $b->label());
|
|
});
|
|
|
|
return $assets;
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function submitForm(array &$form, FormStateInterface $form_state) {
|
|
|
|
// Only process the form if the "Save" button was clicked.
|
|
if ($form_state->getTriggeringElement()['#id'] != 'edit-save') {
|
|
return;
|
|
}
|
|
|
|
// Load hierarchy changes. If there are none, do nothing.
|
|
$changes = Json::decode($form_state->getValue('changes'));
|
|
if (empty($changes)) {
|
|
$this->messenger()->addStatus($this->t('No changes were made.'));
|
|
return;
|
|
}
|
|
|
|
// Get asset storage.
|
|
$storage = $this->entityTypeManager->getStorage('asset');
|
|
|
|
// Maintain a list of assets that need to be saved.
|
|
$save_assets = [];
|
|
|
|
// Maintain a list of assets that were not editable by the user.
|
|
$restricted_assets = [];
|
|
|
|
// Iterate through the changes.
|
|
foreach ($changes as $change) {
|
|
|
|
// Load the asset.
|
|
$asset = $storage->load($change['asset_id']);
|
|
|
|
// If the user does not have permission to edit the asset, count it so
|
|
// that we can add a warning message later, and skip it.
|
|
if (!$asset->access('edit')) {
|
|
$restricted_assets[] = $asset;
|
|
continue;
|
|
}
|
|
|
|
// Remove the original parent.
|
|
if (!empty($asset->get('parent'))) {
|
|
/** @var \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $parent */
|
|
foreach ($asset->get('parent') as $delta => $parent) {
|
|
$parent_id = $parent->getValue()['target_id'];
|
|
if ($change['original_parent'] == $parent_id) {
|
|
unset($asset->get('parent')[$delta]);
|
|
if (!array_key_exists($asset->id(), $save_assets)) {
|
|
$save_assets[$asset->id()] = $asset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the new parent, if applicable.
|
|
if (!empty($change['new_parent'])) {
|
|
$asset->get('parent')[] = ['target_id' => $change['new_parent']];
|
|
if (!array_key_exists($asset->id(), $save_assets)) {
|
|
$save_assets[$asset->id()] = $asset;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save assets with a revision message.
|
|
/** @var \Drupal\asset\Entity\AssetInterface[] $save_assets */
|
|
foreach ($save_assets as $asset) {
|
|
$message = $this->t('Parents removed via the Locations drag and drop editor.');
|
|
$parent_names = [];
|
|
foreach ($asset->get('parent') as $parent) {
|
|
$parent_names[] = $storage->load($parent->getValue()['target_id'])->label();
|
|
}
|
|
if (!empty($parent_names)) {
|
|
$message = $this->t('Parents changed to %parents via the Locations drag and drop editor.', ['%parents' => implode(', ', $parent_names)]);
|
|
}
|
|
$asset->setNewRevision(TRUE);
|
|
$asset->setRevisionLogMessage($message);
|
|
$asset->save();
|
|
}
|
|
|
|
// Show a summary of the results.
|
|
$message = $this->formatPlural(count($save_assets), 'Updated the parent hierarchy of %count asset.', 'Updated the parent hierarchy of %count assets.', ['%count' => count($save_assets)]);
|
|
$this->messenger()->addStatus($message);
|
|
|
|
// If any edits were restricted, show a warning.
|
|
if ($restricted_assets) {
|
|
$message = $this->formatPlural(count($restricted_assets), '%count asset could not be changed because you do not have permission.', '%count assets could not be changed because you do not have permission.', ['%count' => count($restricted_assets)]);
|
|
$this->messenger()->addWarning($message);
|
|
}
|
|
}
|
|
|
|
}
|