2021-04-09 12:59:18 +02:00
< ? php
2021-12-01 13:28:01 +01:00
namespace Drupal\farm_ui_location\Form ;
2021-04-09 12:59:18 +02:00
use Drupal\asset\Entity\AssetInterface ;
2021-12-01 13:31:03 +01:00
use Drupal\Component\Serialization\Json ;
2021-05-28 19:31:07 +02:00
use Drupal\Core\Access\AccessResult ;
2021-12-01 13:28:01 +01:00
use Drupal\Core\Entity\EntityTypeManagerInterface ;
use Drupal\Core\Form\FormBase ;
use Drupal\Core\Form\FormStateInterface ;
2021-05-28 19:31:07 +02:00
use Drupal\Core\Session\AccountInterface ;
2021-12-22 14:33:18 +01:00
use Drupal\Core\Url ;
2021-06-05 15:13:02 +02:00
use Drupal\farm_location\AssetLocationInterface ;
2021-04-09 12:59:18 +02:00
use Symfony\Component\DependencyInjection\ContainerInterface ;
/**
2021-12-01 13:28:01 +01:00
* Form for changing the hierarchy of location assets .
*
* @ ingroup farm
2021-04-09 12:59:18 +02:00
*/
2021-12-01 13:28:01 +01:00
class LocationHierarchyForm extends FormBase {
/**
* The entity type manager service .
*
* @ var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager ;
2021-04-09 12:59:18 +02:00
2021-06-05 15:13:02 +02:00
/**
* The asset location service .
*
* @ var \Drupal\farm_location\AssetLocationInterface
*/
protected $assetLocation ;
2021-04-09 12:59:18 +02:00
/**
2021-12-01 13:28:01 +01:00
* Constructs a new LocationHierarchyForm .
2021-04-09 12:59:18 +02:00
*
2021-12-01 13:28:01 +01:00
* @ param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager .
2021-06-05 15:13:02 +02:00
* @ param \Drupal\farm_location\AssetLocationInterface $asset_location
* The asset location service .
2021-04-09 12:59:18 +02:00
*/
2021-12-01 13:28:01 +01:00
public function __construct ( EntityTypeManagerInterface $entity_type_manager , AssetLocationInterface $asset_location ) {
$this -> entityTypeManager = $entity_type_manager ;
2021-06-05 15:13:02 +02:00
$this -> assetLocation = $asset_location ;
2021-04-09 12:59:18 +02:00
}
/**
* { @ inheritdoc }
*/
public static function create ( ContainerInterface $container ) {
return new static (
2021-12-01 13:28:01 +01:00
$container -> get ( 'entity_type.manager' ),
2021-06-05 15:13:02 +02:00
$container -> get ( 'asset.location' )
2021-04-09 12:59:18 +02:00
);
}
2021-12-01 13:28:01 +01:00
/**
* { @ inheritdoc }
*/
public function getFormId () {
return 'farm_ui_location_form' ;
}
2021-05-28 19:31:07 +02:00
/**
2021-06-05 15:13:02 +02:00
* Check access .
2021-12-01 13:28:01 +01:00
*
* @ 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 .
2021-05-28 19:31:07 +02:00
*/
public function access ( AccountInterface $account , AssetInterface $asset = NULL ) {
2021-06-05 15:13:02 +02:00
// If the asset is not a location, forbid access.
if ( ! $this -> assetLocation -> isLocation ( $asset )) {
return AccessResult :: forbidden ();
2021-05-28 19:31:07 +02:00
}
2024-01-18 20:29:40 +01:00
// If the asset does not have child locations, forbid access.
if ( empty ( $this -> getLocations ( $asset ))) {
return AccessResult :: forbidden ();
}
2021-06-05 15:13:02 +02:00
// Allow access if the asset has child locations.
2024-01-18 20:29:40 +01:00
return AccessResult :: allowedIf ( $asset -> access ( 'view' , $account ));
2021-05-28 19:31:07 +02:00
}
2021-06-05 15:03:12 +02:00
/**
* 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' );
}
2021-04-09 12:59:18 +02:00
/**
2021-12-01 13:28:01 +01:00
* { @ inheritdoc }
2021-04-09 12:59:18 +02:00
*/
2021-12-01 13:28:01 +01:00
public function buildForm ( array $form , FormStateInterface $form_state , AssetInterface $asset = NULL ) {
2021-06-02 12:23:33 +02:00
2024-01-17 22:38:15 +01:00
// If no asset was specified, show a map of all locations.
if ( is_null ( $asset )) {
$form [ 'map' ] = [
'#type' => 'farm_map' ,
'#map_type' => 'locations' ,
];
}
2021-12-01 13:28:01 +01:00
// Add a DIV for the JavaScript content.
$form [ 'content' ] = [
2021-04-09 12:59:18 +02:00
'#type' => 'html_tag' ,
'#tag' => 'div' ,
'#attributes' => [
'class' => [
2021-06-01 16:51:23 +02:00
'locations-tree' ,
2021-04-09 12:59:18 +02:00
],
],
];
2021-12-01 13:31:03 +01:00
// Create a hidden field to store hierarchy changes recorded client-side.
$form [ 'changes' ] = [
'#type' => 'hidden' ,
];
2021-12-01 13:28:01 +01:00
// 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' ),
2021-06-05 22:16:13 +02:00
'#attributes' => [
'class' => [
'button--secondary' ,
],
],
];
2021-12-01 13:28:01 +01:00
$form [ 'actions' ][ 'save' ] = [
'#type' => 'submit' ,
'#value' => $this -> t ( 'Save' ),
2021-04-09 12:59:18 +02:00
'#attributes' => [
'class' => [
'button--primary' ,
],
],
];
2021-12-01 13:28:01 +01:00
$form [ 'actions' ][ 'reset' ] = [
'#type' => 'submit' ,
'#value' => $this -> t ( 'Reset' ),
2021-04-09 12:59:18 +02:00
'#attributes' => [
'class' => [
'button--danger' ,
],
],
];
2021-12-01 13:28:01 +01:00
// Attach the location drag and drop JavaScript.
$form [ '#attached' ][ 'library' ][] = 'farm_ui_location/locations-drag-and-drop' ;
2021-06-02 12:23:33 +02:00
$tree = [
[
2021-12-01 13:31:03 +01:00
'asset_id' => ! empty ( $asset ) ? $asset -> id () : '' ,
2021-06-05 20:56:52 +02:00
'text' => ! empty ( $asset ) ? $asset -> label () : $this -> t ( 'All locations' ),
'children' => ! empty ( $asset ) ? $this -> buildTree ( $asset ) : $this -> buildTree (),
2021-12-22 14:33:18 +01:00
'url' => ! empty ( $asset ) ? $asset -> toUrl ( 'canonical' , [ 'absolute' => TRUE ]) -> toString () : Url :: fromRoute ( 'farm.locations' , [], [ 'absolute' => TRUE ]) -> toString (),
2021-06-05 20:56:52 +02:00
],
2021-06-02 12:23:33 +02:00
];
2021-12-01 13:28:01 +01:00
$form [ '#attached' ][ 'drupalSettings' ][ 'asset_tree' ] = $tree ;
2021-12-01 13:31:03 +01:00
$form [ '#attached' ][ 'drupalSettings' ][ 'asset_parent' ] = ! empty ( $asset ) ? $asset -> id () : '' ;
2021-12-01 13:28:01 +01:00
// Return the form.
return $form ;
2021-04-09 12:59:18 +02:00
}
/**
2021-04-16 18:37:19 +02:00
* Build the asset tree .
*
2021-04-09 12:59:18 +02:00
* @ param \Drupal\asset\Entity\AssetInterface | null $asset
2021-04-16 18:37:19 +02:00
* Optionally specify the parent asset , to only build a sub - tree . If
* omitted , all assets will be included .
2021-04-09 12:59:18 +02:00
*
* @ return array
2021-04-16 18:37:19 +02:00
* Returns the asset tree for use in Drupal JS settings .
*
2021-04-09 12:59:18 +02:00
* @ throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @ throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function buildTree ( AssetInterface $asset = NULL ) {
2021-06-05 15:20:19 +02:00
$locations = $this -> getLocations ( $asset );
2021-05-28 19:31:07 +02:00
$tree = [];
2021-06-05 15:20:19 +02:00
if ( $locations ) {
foreach ( $locations as $location ) {
2021-05-28 19:31:07 +02:00
$element = [
2021-12-01 13:31:03 +01:00
'asset_id' => $location -> id (),
2021-06-05 15:20:19 +02:00
'text' => $location -> label (),
'children' => $this -> buildTree ( $location ),
'url' => $location -> toUrl ( 'canonical' , [ 'absolute' => TRUE ]) -> toString (),
2021-05-28 19:31:07 +02:00
];
2021-12-01 13:31:03 +01:00
$element [ 'original_parent' ] = $asset ? $asset -> id () : '' ;
2021-05-28 19:31:07 +02:00
$tree [] = $element ;
}
}
return $tree ;
}
/**
2021-06-05 15:20:19 +02:00
* Gets location assets .
2021-05-28 19:31:07 +02:00
*
2021-06-05 15:20:19 +02:00
* @ param \Drupal\asset\Entity\AssetInterface | null $asset
* Optionally provide a parent asset to only retrieve its direct children .
2021-05-28 19:31:07 +02:00
*
2021-06-05 15:20:19 +02:00
* @ return \Drupal\asset\Entity\AssetInterface []
* An array of location assets .
2021-05-28 19:31:07 +02:00
*
* @ throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @ throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
2021-06-05 15:20:19 +02:00
protected function getLocations ( AssetInterface $asset = NULL ) {
2021-09-29 22:41:46 +02:00
// Query unarchived location assets.
2021-12-01 13:28:01 +01:00
$storage = $this -> entityTypeManager -> getStorage ( 'asset' );
2021-09-29 22:41:46 +02:00
$query = $storage -> getQuery ()
-> accessCheck ( TRUE )
-> condition ( 'is_location' , TRUE )
2022-01-14 20:50:11 +01:00
-> condition ( 'status' , 'archived' , '!=' );
2021-09-29 22:41:46 +02:00
// Limit to a specific parent or no parent.
2021-04-09 12:59:18 +02:00
if ( $asset ) {
$query -> condition ( 'parent' , $asset -> id ());
}
else {
$query -> condition ( 'parent' , NULL , 'IS NULL' );
}
2021-09-29 22:41:46 +02:00
2022-01-14 20:50:11 +01:00
// Query and load the assets.
2021-04-09 12:59:18 +02:00
$asset_ids = $query -> execute ();
2021-05-28 19:31:07 +02:00
if ( empty ( $asset_ids )) {
2021-06-05 15:20:19 +02:00
return [];
2021-04-09 12:59:18 +02:00
}
2021-06-05 15:20:19 +02:00
/** @var \Drupal\asset\Entity\AssetInterface[] $assets */
$assets = $storage -> loadMultiple ( $asset_ids );
2022-01-14 20:50:11 +01:00
2024-01-17 23:06:22 +01:00
// Filter out assets that the user cannot view.
$assets = array_filter ( $assets , function ( $asset ) {
return $asset -> access ( 'view' );
});
2022-01-14 20:50:11 +01:00
// Sort assets by name, using natural sort algorithm.
usort ( $assets , function ( $a , $b ) {
return strnatcmp ( $a -> label (), $b -> label ());
});
2021-06-05 15:20:19 +02:00
return $assets ;
2021-04-09 12:59:18 +02:00
}
2021-12-01 13:31:03 +01:00
/**
* { @ 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 = [];
2024-01-17 23:06:39 +01:00
// Maintain a list of assets that were not editable by the user.
$restricted_assets = [];
2021-12-01 13:31:03 +01:00
// Iterate through the changes.
foreach ( $changes as $change ) {
// Load the asset.
$asset = $storage -> load ( $change [ 'asset_id' ]);
2024-01-17 23:06:39 +01:00
// 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 ;
}
2021-12-01 13:31:03 +01:00
// 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 );
2024-01-17 23:06:39 +01:00
// 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 );
}
2021-12-01 13:31:03 +01:00
}
2021-04-09 12:59:18 +02:00
}