foldershare-8.x-1.2/src/Form/UIFolderTableMenu.php
src/Form/UIFolderTableMenu.php
<?php namespace Drupal\foldershare\Form; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\File; use Drupal\Core\Routing\RedirectDestinationTrait; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Ajax\RedirectCommand; use Drupal\Core\Ajax\OpenModalDialogCommand; use Drupal\Core\Ajax\InvokeCommand; use Drupal\Core\Url; use Drupal\Core\Form\FormBuilderInterface; use Drupal\user\Entity\User; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\foldershare\Constants; use Drupal\foldershare\ManageFilenameExtensions; use Drupal\foldershare\Settings; use Drupal\foldershare\Utilities\FormatUtilities; use Drupal\foldershare\FolderShareInterface; use Drupal\foldershare\Entity\FolderShare; use Drupal\foldershare\Entity\FolderShareAccessControlHandler; use Drupal\foldershare\Plugin\FolderShareCommandManager; use Drupal\foldershare\Plugin\FolderShareCommand\FolderShareCommandInterface; use Drupal\foldershare\Ajax\OpenErrorDialogCommand; use Drupal\foldershare\Entity\Exception\RuntimeExceptionWithMarkup; /** * Creates a form for the file and folder table menu. * * This form manages a menu of commands (e.g. new, delete, copy) and their * operands. The available commands are defined by command plugins whose * attributes sort commands into categories and define the circumstances * under which a command may be envoked (e.g. for one item or many, on files, * on folders, etc.). * * This form includes: * - A field to specify the command. * - A field containing the current selection, if any. * - A set of fields with command operands, such as the parent and destination. * - A file field used to specify uploaded files. * - A submit button. * * This form is hidden and none of its fields are intended to be directly * set by a user. Instead, Javascript fills in the form based upon the * current row selection, the results of a drag-and-drop, or the file choices * from a browser-provided file dialog. * * Javascript creates a menu button and pull-down menu of commands. * Javascript also creates a context menu of commands for table rows. * Scripting handles row selection, multi-row selection, double-click to * open, drag-and-drop of rows, and drag-and-drop of files from the desktop * into the table for uploading. * * This form *requires* that a view nearby on the page contain a base UI * view field plugin that adds selection checkboxes and entity attributes * to all rows in the view. * * <B>Warning:</B> This class is strictly internal to the FolderShare * module. The class's existance, name, and content may change from * release to release without any promise of backwards compatability. * * @ingroup foldershare */ class UIFolderTableMenu extends FormBase { use RedirectDestinationTrait; /*-------------------------------------------------------------------- * * Fields - cached from dependency injection. * *--------------------------------------------------------------------*/ /** * The module handler, set at construction time. * * @var \Drupal\Core\Extension\ModuleHandlerInterface */ private $moduleHandler ; /** * The plugin command manager. * * @var \Drupal\foldershare\FolderShareCommand\FolderShareCommandManager */ private $commandPluginManager ; /** * The form builder. * * @var \Drupal\Core\Form\FormBuilderInterface */ private $formBuilder ; /*-------------------------------------------------------------------- * * Fields - set by menu choice. * *--------------------------------------------------------------------*/ /** * The current validated command prior to its execution. * * The command already has its configuration set. It has been * validated, but has not yet had access controls checked and it * has not been executed. * * @var \Drupal\foldershare\FolderShareCommand\FolderShareCommandInterface */ protected $command ; /*-------------------------------------------------------------------- * * Construction. * *--------------------------------------------------------------------*/ /** * Constructs a new form. * * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler * The module handler. * @param \Drupal\foldershare\FolderShareCommand\FolderShareCommandManager $pm * The command plugin manager. * @param \Drupal\Core\Form\FormBuilderInterface $formBuilder * The form builder. */ public function __construct( ModuleHandlerInterface $moduleHandler , FolderShareCommandManager $pm , FormBuilderInterface $formBuilder ) { $this ->moduleHandler = $moduleHandler ; $this ->commandPluginManager = $pm ; $this ->formBuilder = $formBuilder ; $this ->command = NULL; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container ) { return new static ( $container ->get( 'module_handler' ), $container ->get( 'foldershare.plugin.manager.foldersharecommand' ), $container ->get( 'form_builder' ) ); } /*-------------------------------------------------------------------- * * Command utilities. * *--------------------------------------------------------------------*/ /** * Returns an array of well known menu command categories. * * For each entry, the key is the category machine name, and the value * is the translated name for the category. * * @return array * Returns an associative array with category names as keys and * translated strings as values. */ private function getWellKnownMenuCategories() { return [ 'open' => (string) $this ->t( 'Open' ), 'import & export' => (string) $this ->t( 'Import/Export' ), 'close' => (string) $this ->t( 'Close' ), 'edit' => (string) $this ->t( 'Edit' ), 'delete' => (string) $this ->t( 'Delete' ), 'copy & move' => (string) $this ->t( 'Copy/Move' ), 'save' => (string) $this ->t( 'Save' ), 'archive' => (string) $this ->t( 'Archive' ), 'message' => (string) $this ->t( 'Message' ), 'settings' => (string) $this ->t( 'Settings' ), 'administer' => (string) $this ->t( 'Administer' ), ]; } /** * Creates an instance of a command and prevalidates it. * * Prevalidation checks configuration fields that must be specified at * the time the command is invoked in these forms, prior to any additional * configuration changes made by a command's own forms. This includes: * * - Validate the command is allowed. * - Validate parent constraints. * - Validate selection constraints. * * The command's validation methods all throw exceptions. These are caught * here and converted to form error messages. The command is returned, * or NULL if the command could not be created. * * @param \Drupal\Core\Form\FormStateInterface $formState * The current form state. * @param string $commandClass * The form state entry to which to report command errors. * @param string $commandId * The command plugin ID. * @param array $configuration * The initial command configuration. * * @return \Drupal\foldershare\Plugin\FolderShareCommand\FolderShareCommandInterface * Returns an initialized command, or NULL if the command could not be * created. If there were validation errors, form state has been updated. */ private function prevalidateCommand( FormStateInterface $formState , string $commandClass , string $commandId , array $configuration ) { // // Get the command // --------------- // Get the command plugin manager, find the definition, and create an // instance of the command. if ( $this ->commandPluginManager === NULL) { $formState ->setErrorByName( $commandClass , FormatUtilities::createFormattedMessage( $this ->t( 'Missing FolderShare command plugin manager' ), $this ->t( 'This is a critical site configuration problem. Please report this to the site administrator.' ))); return NULL; } $commandDef = $this ->commandPluginManager->getDefinition( $commandId , FALSE); if ( $commandDef === NULL) { $formState ->setErrorByName( $commandClass , FormatUtilities::createFormattedMessage( $this ->t( 'Unrecognized FolderShare command ID "@id".' , [ '@id' => $commandId , ]), $this ->t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); return NULL; } // Create a command instance. $command = $this ->commandPluginManager->createInstance( $commandDef [ 'id' ], $configuration ); // // Prevalidate // ----------- // Validate command allowed, parent constraints, and selection constraints. try { $command ->validateCommandAllowed(); $command ->validateParentConstraints(); $command ->validateSelectionConstraints(); } catch (RuntimeExceptionWithMarkup $e ) { $formState ->setErrorByName( $commandClass , $e ->getMarkup()); } catch (\Exception $e ) { $formState ->setErrorByName( $commandClass , $e ->getMessage()); } return $command ; } /*-------------------------------------------------------------------- * * Form setup. * *--------------------------------------------------------------------*/ /** * {@inheritdoc} */ public function getFormId() { return mb_strtolower( str_replace ( '\\' , '_' , get_class( $this ))); } /*-------------------------------------------------------------------- * * Form build. * *--------------------------------------------------------------------*/ /** * {@inheritdoc} */ public function buildForm( array $form , FormStateInterface $formState = NULL) { // // Get context attributes // ---------------------- // Get attributes of the current context, including: // - the ID of the page entity, if any // - the page entity's kind. // - the user's access permissions. // // The page entity ID comes from the form build arguments. $args = $formState ->getBuildInfo()[ 'args' ]; if ( empty ( $args ) === TRUE) { $pageEntityId = FolderShareInterface::USER_ROOT_LIST; } else { $pageEntityId = intval ( $args [0]); } if ( $pageEntityId < 0) { // Menu is for a root list page. $pageEntity = NULL; $kind = 'rootlist' ; $disabled = FALSE; $perm = FolderShareAccessControlHandler::getRootAccessSummary( $pageEntityId ); } else { // Menu is for an entity page. $pageEntity = FolderShare::load( $pageEntityId ); if ( $pageEntity === NULL) { // Invalid entity. Revert to root list. $kind = 'rootlist' ; $disabled = FALSE; $pageEntityId = FolderShareInterface::USER_ROOT_LIST; $perm = FolderShareAccessControlHandler::getRootAccessSummary( $pageEntityId ); } else { $kind = $pageEntity ->getKind(); $disabled = $pageEntity ->isSystemDisabled(); $perm = FolderShareAccessControlHandler::getAccessSummary( $pageEntity ); } } // Get the command list. if ( $disabled === TRUE) { // No commands are allowed on a disabled entity. $commands = []; } else { $commands = Settings::getAllowedCommandDefinitions(); } // Get the user. $user = $this ->currentUser(); $userId = (int) $user ->id(); $anonymous = User::getAnonymousUser(); // Create an array that lists the names of access control operators // (e.g. "view", "update", "delete") if the user has permission for // those operators on this parent entity (if there is one). $access = []; foreach ( $perm as $op => $allowed ) { if ( $allowed === TRUE) { $access [] = $op ; } } // // Form setup // ---------- // Add the form's ID as a class for styling. // // The 'drupalSettings' attribute defines arbitrary data that can be // attached to the page. Here we use it to: // - Flag whether AJAX is enabled. // - Give the ID and human-readable name of this module. // - Give translations for various terms. // - Give singular and plural translations of entity kinds. // - List all installed commands and their attributes. $kinds = [ FolderShare::FOLDER_KIND, FolderShare::FILE_KIND, FolderShare::IMAGE_KIND, FolderShare::MEDIA_KIND, 'rootlist' , 'item' , ]; $kindTerms = []; foreach ( $kinds as $k ) { $kindTerms [ $k ] = [ 'singular' => FolderShare::translateKind( $k , MB_CASE_UPPER), 'plural' => FolderShare::translateKinds( $k , MB_CASE_UPPER), ]; } $categories = $this ->getWellKnownMenuCategories(); $isAnonymous = ( $user ->isAnonymous() === TRUE); $isAuthenticated = ( $user ->isAuthenticated() === TRUE); $hasAdmin = ( $user ->hasPermission(Constants::ADMINISTER_PERMISSION) === TRUE); $hasAuthor = ( $user ->hasPermission(Constants::AUTHOR_PERMISSION) === TRUE); $hasView = ( $user ->hasPermission(Constants::VIEW_PERMISSION) === TRUE); $hasShare = ( $user ->hasPermission(Constants::SHARE_PERMISSION) === TRUE); $hasSharePublic = ( $user ->hasPermission(Constants::SHARE_PUBLIC_PERMISSION) === TRUE); $extension = '' ; $ownerId = (-1); $ownedByUser = FALSE; $ownedByAnother = FALSE; $ownedByAnonymous = FALSE; $sharedByUser = FALSE; $sharedWithUserToView = FALSE; $sharedWithUserToAuthor = FALSE; $sharedWithAnonymousToView = FALSE; $sharedWithAnonymousToAuthor = FALSE; if ( $pageEntity !== NULL) { $ownerId = $pageEntity ->getOwnerId(); $anonId = (int) $anonymous ->id(); if ( $ownerId === $userId ) { $ownedByUser = TRUE; } elseif ( $ownerId === $anonId ) { $ownedByAnonymous = TRUE; } else { $ownedByAnother = TRUE; } $extension = $pageEntity ->getExtension(); // Since access grants are set on the root only, get the root and // use it to determine the sharing state. $root = $pageEntity ->getRootItem(); $sharedByUser = $root ->isSharedBy( $userId ); $sharedWithUserToView = $root ->isSharedWith( $userId , 'view' ); $sharedWithUserToAuthor = $root ->isSharedWith( $userId , 'author' ); $sharedWithAnonymousToView = $root ->isSharedWith( $anonId , 'view' ); $sharedWithAnonymousToAuthor = $root ->isSharedWith( $anonId , 'author' ); } elseif ( $pageEntityId === FolderShareInterface::USER_ROOT_LIST) { $ownerId = $user ->id(); $ownedByUser = TRUE; } elseif ( $pageEntityId === FolderShareInterface::PUBLIC_ROOT_LIST) { $ownerId = $anonymous ->id(); $ownedByAnonymous = TRUE; $sharedWithAnonymousToView = $anonymous ->hasPermission( Constants::VIEW_PERMISSION); $sharedWithAnonymousToAuthor = $anonymous ->hasPermission( Constants::AUTHOR_PERMISSION); } $form [ '#attached' ][ 'drupalSettings' ][ 'foldershare' ] = [ 'ajaxEnabled' => Constants::ENABLE_UI_COMMAND_DIALOGS, 'module' => [ 'id' => Constants::MODULE, 'title' => $this ->moduleHandler->getName(Constants::MODULE), 'submenuthreshold' => Settings::getCommandMenuSubmenuThreshold(), 'pollinterval' => Settings::getStatusPollingInterval(), ], 'page' => [ 'id' => $pageEntityId , 'kind' => $kind , 'disabled' => $disabled , 'extension' => $extension , 'ownerid' => $ownerId , 'ownedbyuser' => $ownedByUser , 'ownedbyanonymous' => $ownedByAnonymous , 'ownedbyanother' => $ownedByAnother , 'sharedbyuser' => $sharedByUser , 'sharedwithusertoview' => $sharedWithUserToView , 'sharedwithusertoauthor' => $sharedWithUserToAuthor , 'sharedwithanonymoustoview' => $sharedWithAnonymousToView , 'sharedwithanonymoustoauthor' => $sharedWithAnonymousToAuthor , ], 'user' => [ 'id' => $user ->id(), 'accountName' => $user ->getAccountName(), 'displayName' => $user ->getDisplayName(), 'pageAccess' => $access , 'anonymous' => $isAnonymous , 'authenticated' => $isAuthenticated , 'adminpermission' => $hasAdmin , 'noadminpermission' => $hasAdmin !== TRUE, 'authorpermission' => $hasAuthor , 'sharepermission' => $hasShare , 'sharepublicpermission' => $hasSharePublic , 'viewpermission' => $hasView , ], 'terminology' => [ 'kinds' => $kindTerms , 'text' => [ 'this' => $this ->t( 'this' ), 'menu' => $this ->t( 'menu' ), 'upload_dnd_not_supported' => (string) $this ->t( "<p><strong>Drag-and-drop file upload is not supported.</strong></p><p>This feature is not supported by this web browser.</p>" ), 'upload_dnd_invalid_singular' => (string) $this ->t( "<p><strong>Drag-and-drop item cannot be uploaded.</strong></p><p>You may not have access to the item, or it may be a folder. Folder upload is not supported.</p>" ), 'upload_dnd_invalid_plural' => (string) $this ->t( "<p><strong>Drag-and-drop items cannot be uploaded.</strong></p><p>You may not have access to these items, or one of them may be a folder. Folder upload is not supported.</p>" ), ], 'categories' => $categories , ], 'categories' => array_keys ( $categories ), 'commands' => $commands , ]; // // Create UI // --------- // The UI is primarily built by Javascript based upon the command list // and other information in the settings attached to the form above. // The remainder of the form contains input fields and a submit button // for sending a command's ID and operands back to the server. // // Set up classes. $uiClass = 'foldershare-folder-table-menu' ; $submitClass = $uiClass . '-submit' ; $uploadClass = $uiClass . '-upload' ; $commandClass = $uiClass . '-commandname' ; $selectionClass = $uiClass . '-selection' ; $parentIdClass = $uiClass . '-parentId' ; $destinationIdClass = $uiClass . '-destinationId' ; // When AJAX is enabled, add an AJAX callback to the submit button. $submitAjax = '' ; if (Constants::ENABLE_UI_COMMAND_DIALOGS === TRUE) { $submitAjax = [ 'callback' => '::submitFormAjax' , 'event' => 'submit' , ]; } $form [ '#attributes' ][ 'class' ][] = 'foldershare-folder-table-menu-form' ; $form [ $uiClass ] = [ '#type' => 'container' , '#weight' => -100, '#attributes' => [ 'class' => [ $uiClass ], ], // The form acts as a container for Javascript-generated menus and // menu buttons. Those items need to be visible, but the form's // inputs for sending a command back to the server never need to // be visible. These are grouped into a container that is marked hidden. 'hiddenGroup' => [ '#type' => 'container' , '#attributes' => [ 'class' => [ 'hidden' ], ], // Add the command plugin ID field to indicate the selected command. // Later, when a user selects a command, Javascript sets this field // to the command plugin's unique ID. // // The command is a required input. There's no point in submitting // a form without one. // // Implementation note: There is no documented maximum plugin ID // length, but most Drupal IDs are limited to 256 characters. So // we use that limit. $commandClass => [ '#type' => 'textfield' , '#maxlength' => 256, '#size' => 1, '#default_value' => '' , '#required' => TRUE, ], // Add the selection field that lists the IDs of selected entities. // Later, when a user selects a command, Javascript sets this field // to a list of entity IDs for zero or more items currently selected // from the view. // // The selection is optional. Some commands have no selection (such // as "New folder" and "Upload files"). // // Implementation note: The textfield will be set with a JSON-encoded // string containing the list of numeric entity IDs for selected // entities in the view. There is no maximum view length if the site // disables paging. Drupal's default field maximum is 128 characters, // which is probably sufficient, but it is conceivable it could be // exceeded for a large selection. The HTML default maximum is // 524288 characters, so we use that. $selectionClass => [ '#type' => 'textfield' , '#maxlength' => 524288, '#size' => 1, '#default_value' => $formState ->getValue( $selectionClass ), ], // Add the parent field that gives the parent ID of a file or folder. // // The parent ID is not required, though Javascript always sets it. // If not set, the entity ID of the current page is used. This is // typical for most command use. Drag-and-drop operations, however, // may move/copy/upload items into a selected subfolder, and that // subfolder's parent ID is set in this field. // // Implementation note: The textfield may be left empty or set to // a single numeric entity ID. Since entity IDs are 64 bits, the // maximum number of characters here is 20 digits. $parentIdClass => [ '#type' => 'textfield' , '#maxlength' => 24, '#size' => 1, '#default_value' => $formState ->getValue( $parentIdClass ), ], // Add the destination field that gives the entity ID of a // destination folder for move/copy operations. Later, when a user // selects a command, Javascript may set this field if the destination // is known. If the field is left empty, the command may prompt for // the move/copy destination. // // The destination ID field is optional. Most commands don't use it. // // Implementation note: The textfield may be left empty or set to // a single numeric entity ID. Since entity IDs are 64 bits, the // maximum number of characters here is 20 digits. $destinationIdClass => [ '#type' => 'textfield' , '#maxlength' => 24, '#size' => 1, '#default_value' => $formState ->getValue( $destinationIdClass ), ], // Add the file field for uploading files. Later, when a user // selects a command that needs to upload a file, Javascript invokes // the browser's file dialog to set this field. // // The field needs to have a processing callback to set up file // extension filtering, if file extension limitations are enabled // for the module. // // The upload field is optional and it is only used by file upload // commands. $uploadClass => [ '#type' => 'file' , '#multiple' => TRUE, '#process' => [ [ get_class( $this ), 'processFileField' , ], ], ], // Add the submit button for the form. Javascript triggers the // submit when a command is selected from the menu. $submitClass => [ '#type' => 'submit' , '#value' => '' , '#name' => $submitClass , '#ajax' => $submitAjax , ], ], ]; return $form ; } /** * Process the file field in the view UI form to add extension handling. * * The 'file' field directs the browser to prompt the user for one or * more files to upload. This prompt is done using the browser's own * file dialog. When this module's list of allowed file extensions has * been set, and this function is added as a processing function for * the 'file' field, it adds the extensions to the list of allowed * values used by the browser's file dialog. * * @param mixed $element * The form element to process. * @param Drupal\Core\Form\FormStateInterface $formState * The current form state. * @param mixed $completeForm * The full form. */ public static function processFileField( & $element , FormStateInterface $formState , & $completeForm ) { // Let the file field handle the '#multiple' flag, etc. File::processFile( $element , $formState , $completeForm ); // Get the list of allowed file extensions for FolderShare files. $extensions = ManageFilenameExtensions::getAllowedNameExtensions(); // If there are extensions, add them to the form element. if ( empty ( $extensions ) === FALSE) { // The extensions list is space separated without leading dots. But // we need comma separated with dots. Map one to the other. $list = []; foreach (mb_split( ' ' , $extensions ) as $ext ) { $list [] = '.' . $ext ; } $element [ '#attributes' ][ 'accept' ] = implode( ',' , $list ); } return $element ; } /*-------------------------------------------------------------------- * * Form validate. * *--------------------------------------------------------------------*/ /** * {@inheritdoc} */ public function validateForm( array & $form , FormStateInterface $formState ) { // // Setup // ----- // Set up classes. $uiClass = 'foldershare-folder-table-menu' ; $commandClass = $uiClass . '-commandname' ; $selectionClass = $uiClass . '-selection' ; $parentIdClass = $uiClass . '-parentId' ; $destinationIdClass = $uiClass . '-destinationId' ; $uploadClass = $uiClass . '-upload' ; // // Get parent ID (if any) // ---------------------- // The parent entity ID, if present, is the sole URL argument. $args = $formState ->getBuildInfo()[ 'args' ]; if ( empty ( $args ) === TRUE) { $parentId = FolderShareInterface::USER_ROOT_LIST; } else { $parentId = intval ( $args [0]); // The parent ID may still be negative, indicating a root list. } // If a parent ID is in the form, it overrides the URL, which describes // the page the operation began on, and not necessary the context in // which it should take place. $formParentId = $formState ->getValue( $parentIdClass ); if ( empty ( $formParentId ) === FALSE) { $parentId = intval ( $formParentId ); // The parent ID may still be negative, indicating a root list. } // // Get command // ----------- // The command's plugin ID is set in the command field. Get it and // the command definition. $commandId = $formState ->getValue( $commandClass ); if ( empty ( $commandId ) === TRUE) { // Fail. This should never happen. The field is required, so the form // should not be submittable without a command. $formState ->setErrorByName( $commandClass , $this ->t( 'Please select a command from the menu.' )); return ; } // // Get selection (if any) // ---------------------- // The selection field contains a JSON encoded array of entity IDs // in the selection. The list could be empty. $selectionIds = json_decode( $formState ->getValue( $selectionClass ), TRUE); // // Get destination (if any) // ------------------------ // The destination field contains a single numeric entity ID for the // destination of a move/copy. The value could be empty. $destinationId = $formState ->getValue( $destinationIdClass ); if ( empty ( $destinationId ) === TRUE) { $destinationId = FolderShareCommandInterface::EMPTY_ITEM_ID; } else { $destinationId = intval ( $destinationId ); } // // Create configuration // -------------------- // Create an initial command configuration. $configuration = [ 'parentId' => $parentId , 'selectionIds' => $selectionIds , 'destinationId' => $destinationId , 'uploadClass' => $uploadClass , ]; // // Prevalidate // ----------- // Create a command instance. Prevalidate and add errors to form state. // If the command could not be created, a NULL is returned and a message // is already in form state. $command = $this ->prevalidateCommand( $formState , $commandClass , $commandId , $configuration ); if ( $command !== NULL) { $this ->command = $command ; } } /*-------------------------------------------------------------------- * * Form submit (no-AJAX). * *--------------------------------------------------------------------*/ /** * {@inheritdoc} */ public function submitForm( array & $form , FormStateInterface $formState ) { if (Constants::ENABLE_UI_COMMAND_DIALOGS === TRUE) { // AJAX is in use. Let the AJAX submit method handle it. return ; } if ( $this ->command === NULL) { return ; } // // Pre-execute redirect to another page, if required. // -------------------------------------------------- // If the command needs to redirect to a special page (such as an edit // form), redirect instead of executing. $executeBehavior = $this ->command->getExecuteBehavior(); if ( $executeBehavior === FoldrShareCommandInterface::PRE_EXECUTE_PAGE_REDIRECT) { // Get the URL to redirect to. If the URL is empty (it shouldn't be), // then fall back to teh current page. $url = $this ->command->getExecuteRedirectUrl(); if ( empty ( $url ) === TRUE) { $url = Url::fromRoute( '<current>' ); } // Set the redirect. $formState ->setRedirectUrl( $url ); return ; } // // Redirect to command's form, if required. // ---------------------------------------- // If the command has its own configuration form, redirect to a page that // hosts the form. This is common and can be a form as simple as a // delete command's "Are you sure?" prompt. // // If the command has any post-execute behaviors, they will be handled // by the form. try { if ( $this ->command->hasConfigurationForm() === TRUE) { $parameters = [ 'pluginId' => $this ->command->getPluginId(), 'configuration' => $this ->command->getConfiguration(), 'url' => $this ->getRequest()->getUri(), 'enableAjax' => Constants::ENABLE_UI_COMMAND_DIALOGS, ]; // Set the redirect. $formState ->setRedirect( 'entity.foldersharecommand.plugin' , [ 'encoded' => base64_encode (json_encode( $parameters )), ]); return ; } } catch (RuntimeExceptionWithMarkup $e ) { \Drupal::messenger()->addMessage( $e ->getMarkup(), 'error' ); } catch (\Exception $e ) { $this ->messenger()->addMessage( $e ->getMessage(), 'error' ); return ; } // // Execute directly. // ----------------- // The command doesn't need a redirect to a special page or to a // configuration form hosting page. Execute it directly. try { $this ->command->validateConfiguration(); $this ->command->execute(); } catch (AccessDeniedHttpException $e ) { $this ->messenger()->addMessage(FormatUtilities::createFormattedMessage( $this ->t( 'You do not have sufficient permissions.' ), $this ->t( 'The operation could not be completed.' )), 'error' ); } catch (NotFoundHttpException $e ) { $this ->messenger()->addMessage(FormatUtilities::createFormattedMessage( $this ->t( 'One or more items could not be found.' ), $this ->t( 'The operation could not be completed.' )), 'error' ); } catch (RuntimeExceptionWithMarkup $e ) { $this ->messenger()->addMessage( $e ->getMarkup(), 'error' ); } catch (\Exception $e ) { $this ->messenger()->addMessage( $e ->getMessage(), 'error' ); } // // Post execute. // ------------- // After the command has executed, handle its post-execute behavior. $executeBehavior = $this ->command->getExecuteBehavior(); switch ( $executeBehavior ) { case FolderShareCommandInterface::POST_EXECUTE_PAGE_REDIRECT: // Redirect to a new page. $url = $this ->command->getExecuteRedirectUrl(); if ( empty ( $url ) === TRUE) { // No URL? Fall through and let the current page refresh. break ; } // Set the redirect. $formState ->setRedirectUrl( $url ); break ; case FolderShareCommandInterface::POST_EXECUTE_PAGE_REFRESH: case FolderShareCommandInterface::POST_EXECUTE_VIEW_REFRESH: // Page or view refresh. Since this submit form is only used // when there is no AJAX enabled, there is no way to do a // view refresh separate from a page refresh. And a page // refresh happens automatically by simply returning. break ; } $this ->command = NULL; } /*-------------------------------------------------------------------- * * Form submit (AJAX) * *--------------------------------------------------------------------*/ /** * Handles form submission via AJAX. * * If the selected command has no configuration form, the command is * executed immediately. Any errors are reported in a modal error dialog. * * If the selected command requires a redirect to a full-page form, the * redirect is executed immediately. * * Otherwise, when the selected command has a configuration form, the * form is built and added to a modal dialog. * * @param array $form * An array of form elements. * @param \Drupal\Core\Form\FormStateInterface $formState * The input values for the form's elements. * * @return \Drupal\Core\Ajax\AjaxResponse * Returns an AJAX response. */ public function submitFormAjax( array & $form , FormStateInterface $formState ) { if ( $this ->command === NULL) { // Errors that would cause this are catastrophic and will float to // the page's messages area. $response = new AjaxResponse(); $url = Url::fromRoute( '<current>' ); $response ->addCommand( new RedirectCommand( $url ->toString())); return $response ; } // // Report form errors. // ------------------- // If prevalidation failed, there will be form errors to report. if ( $formState ->hasAnyErrors() === TRUE) { $response = new AjaxResponse(); $dialog = new OpenErrorDialogCommand( $this ->t( 'Problem' )); $dialog ->setFromFormErrors( $formState ); $response ->addCommand( $dialog ); return $response ; } // // Pre-execute redirect to another page, if required. // -------------------------------------------------- // If the command needs to redirect to a special page (such as an edit // form), redirect instead of executing. $executeBehavior = $this ->command->getExecuteBehavior(); if ( $executeBehavior === FolderShareCommandInterface::PRE_EXECUTE_PAGE_REDIRECT) { // Get the URL to redirect to. If the URL is empty (it shouldn't be), // then fall back to teh current page. $url = $this ->command->getExecuteRedirectUrl(); if ( empty ( $url ) === TRUE) { $url = Url::fromRoute( '<current>' ); } // Tell the client to redirect. $response = new AjaxResponse(); $response ->addCommand( new RedirectCommand( $url ->toString())); return $response ; } // // Embed command form in dialog, if any. // ------------------------------------- // If the command has its own configuration form, add that form to a // modal dialog. // // If the command has any post-execute behaviors, they will be handled // by the form. if ( $this ->command->hasConfigurationForm() === TRUE) { $parameters = [ 'pluginId' => $this ->command->getPluginId(), 'configuration' => $this ->command->getConfiguration(), 'enableAjax' => Constants::ENABLE_UI_COMMAND_DIALOGS, ]; // The request's URL needs to be sent to the command as a page to // return to. Unfortunately, because we are in an AJAX submit form, // there are AJAX query parameters on the URL. We won't want them // when returning to the page, so strip them off. $request = $this ->getRequest(); $urlParts = parse_url ( $request ->getUri()); if ( empty ( $urlParts [ 'query' ]) === FALSE) { // Parse the query part of the URL. $queryArguments = []; parse_str ( $urlParts [ 'query' ], $queryArguments ); $q = []; // Ignore AJAX query arguments. foreach ( $queryArguments as $key => $value ) { if ( $key !== '_wrapper_format' && $key !== 'ajax_form' ) { $q [ $key ] = $value ; } } $urlParts [ 'query' ] = http_build_query( $q ); } // And reassemble the URL. $url = '' ; if ( empty ( $urlParts [ 'scheme' ]) === FALSE) { $url .= $urlParts [ 'scheme' ] . ':' ; } if ( empty ( $urlParts [ 'host' ]) === FALSE) { $url .= '//' . $urlParts [ 'host' ]; if ( empty ( $urlParts [ 'port' ]) === FALSE) { $url .= ':' . $urlParts [ 'port' ]; } } $url .= $urlParts [ 'path' ]; if ( empty ( $urlParts [ 'fragment' ]) === FALSE) { $url .= '?' . $urlParts [ 'fragment' ]; } if ( empty ( $urlParts [ 'query' ]) === FALSE) { $url .= '?' . $urlParts [ 'query' ]; } $parameters [ 'url' ] = $url ; $parameters [ 'parentFormId' ] = str_replace ( '_' , '-' , $this ->getFormId()); // Encode for inclusion as a route parameter. $encoded = base64_encode (json_encode( $parameters )); // Build a form for the command. The returned form may have // prompts, buttons, attachments, etc. $form = $this ->formBuilder->getForm( CommandFormWrapper:: class , $encoded ); if ( $form === NULL) { // Form build failed, probably due to validation errors. $response = new AjaxResponse(); $cmd = new OpenErrorDialogCommand( $this ->t( 'Problem' )); $cmd ->setFromPageMessages(); $response ->addCommand( $cmd ); return $response ; } if (isset( $form [ '#title' ]) === TRUE) { $title = (string) $form [ '#title' ]; } else { $title = $this ->command->getPluginDefinition()[ 'label' ]; } // Return the form within a modal dialog. $response = new AjaxResponse(); $response ->setAttachments( $form [ '#attached' ]); $response ->addCommand( new OpenModalDialogCommand( $title , $form , [ 'modal' => TRUE, 'draggable' => FALSE, 'resizable' => FALSE, 'refreshAfterClose' => FALSE, 'closeOnEscape' => TRUE, 'closeText' => $this ->t( 'Cancel and close' ), 'width' => '75%' , 'classes' => [ 'ui-dialog' => [ 'foldershare-ui-dialog' , ], ], ])); return $response ; } // // Execute directly. // ----------------- // The command doesn't need a redirect to a special page or present // a configuration form. Execute it directly. $response = new AjaxResponse(); try { $this ->command->validateConfiguration(); $this ->command->execute(); } catch (AccessDeniedHttpException $e ) { $this ->messenger()->addMessage(FormatUtilities::createFormattedMessage( $this ->t( 'You do not have sufficient permissions.' ), $this ->t( 'The operation could not be completed.' )), 'error' ); } catch (NotFoundHttpException $e ) { $this ->messenger()->addMessage(FormatUtilities::createFormattedMessage( $this ->t( 'One or more items could not be found.' ), $this ->t( 'The operation could not be completed.' )), 'error' ); } catch (RuntimeExceptionWithMarkup $e ) { $this ->messenger()->addMessage( $e ->getMarkup(), 'error' ); } catch (\Exception $e ) { $this ->messenger()->addMessage( $e ->getMessage(), 'error' ); } // If there are any errors, present them in an error dialog. // Errors can be page errors or form errors. $msgs = $this ->messenger()->all(); if (isset( $msgs [ 'error' ]) === TRUE || isset( $msgs [ 'warning' ]) === TRUE) { // There are page errors. $cmd = new OpenErrorDialogCommand( $this ->t( 'Problem' )); $cmd ->setFromPageMessages(); $response ->addCommand( $cmd ); } elseif ( $formState ->hasAnyErrors() === TRUE) { // There are command form errors. $cmd = new OpenErrorDialogCommand( $this ->t( 'Problem' )); $cmd ->setFromFormErrors( $formState ); $response ->addCommand( $cmd ); } else { // Otherwise the command executed correctly. Handle the post-execute // behaviors. $executeBehavior = $this ->command->getExecuteBehavior(); switch ( $executeBehavior ) { case FolderShareCommandInterface::POST_EXECUTE_PAGE_REDIRECT: // Redirect to a new page. $url = $this ->command->getExecuteRedirectUrl(); if ( empty ( $url ) === TRUE) { $url = Url::fromRoute( '<current>' )->toString(); } // Set the redirect. $response ->addCommand( new RedirectCommand( $url )); break ; default : case FolderShareCommandInterface::POST_EXECUTE_PAGE_REFRESH: // Redirect to the current page. $url = Url::fromRoute( '<current>' )->toString(); $response ->addCommand( new RedirectCommand( $url )); break ; case FolderShareCommandInterface::POST_EXECUTE_VIEW_REFRESH: // Refresh the view associated with the menu the command came from. $selector = '#' . str_replace ( '_' , '-' , $this ->getFormId()); $method = 'trigger' ; $args = [ 'FolderShareRefreshView' , ]; $response ->addCommand( new InvokeCommand( $selector , $method , $args )); break ; } } $this ->command = NULL; return $response ; } } |