foldershare-8.x-1.2/src/Plugin/FolderShareCommand/FolderShareCommandBase.php
src/Plugin/FolderShareCommand/FolderShareCommandBase.php
<?php namespace Drupal\foldershare\Plugin\FolderShareCommand; use Drupal\Component\Plugin\PluginBase; use Drupal\Component\Plugin\ConfigurableInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\user\Entity\User; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Drupal\foldershare\Constants; use Drupal\foldershare\Settings; use Drupal\foldershare\Utilities\FormatUtilities; use Drupal\foldershare\FolderShareInterface; use Drupal\foldershare\Entity\FolderShare; use Drupal\foldershare\Entity\Exception\ValidationException; use Drupal\foldershare\Entity\FolderShareAccessControlHandler; /** * Defines the base class for plugins for shared folder commands. * * A command performs an operation within the context of a parent folder * or list of folders, and operates upon a selection of FolderShare objects. * Typical operations are to create, delete, edit, rename, copy, move, * share, or change the ownership of items. * * A command implementation always includes three parts: * * - Validation that checks if a given configuration of parameters is * consistent and valid for the command. * * - Access checks that confirm the user has permission to do the command. * * - Execution that performs the command. * * All commands use a configuration containing named parameters used as * operands for the command. These parameters may include the ID of the * parent folder, the IDs of operand files and folders, and so forth. * * Some commands may provide an optional form where a user may enter * additional parameters. A "folder rename" command, for instance, may * offer a form for entering a new name. When used, the form is expected * to create a configuration that is then passed to validation and * execution methods to do the operation. * * Callers may skip the command form and use their own mechanism to prompt * for configuration parameters. They may then call the command's validation * and execution methods directly to do the operation. * * For example, here are several situations that might all use the same * command: * * - Direct use. Code may instantiate a command plugin, set the configuration, * then call the plugin's validation, and execution methods directly * to perform an operation. No command form or UI would be used. * * - Custom UI. Code that creates its own user interface, with or without * Javascript, may prompt the user for parameters, the invoke the command * as above for direct use. The command's own forms would not be used. * * - Form page UI. Drupal's convention of full-page forms may be used to * host a plugin command's forms plus a mechanism to choose a parent * folder context and select files and folders to operate upon (these * are not normally part of a command's own forms). On a submit, the * command maps the form's values to its configuration, then validates, * and executes. * * - Embedded form UI. When an overlay/dialog can be used, the plugin * command's forms may be embedded within the overlay. Any mechanism to * select the parent folder context and select files and folders must be * done before this overlay. On a submit of the overlay form, the * command maps the form's values to its configuration, then validates * and executes. * * @ingroup foldershare */ abstract class FolderShareCommandBase extends PluginBase implements ConfigurableInterface, FolderShareCommandInterface { /*-------------------------------------------------------------------- * * Fields. * * These fields provide state set during different stages of command * handling. The parent, destination, and selection are set as the * command is validatedfor a specific configuration, which is held * in the parent class. The validation flags are set during stages * of validation so that repeated calls to validate don't repeat * the associated work. * *--------------------------------------------------------------------*/ /** * The loaded parent folder, if any. * * @var \Drupal\foldershare\FolderShareInterface */ private $parent ; /** * The loaded destination folder, if any. * * @var \Drupal\foldershare\FolderShareInterface */ private $destination ; /** * The loaded selection, if any. * * The selection is an associative array where keys are kinds, and * values are arrays of FolderShare entities. * * @var \Drupal\foldershare\FolderShareInterface[] */ private $selection ; /** * A flag indicating if command is allowed on the site. * * @var bool */ protected $commandAllowed ; /** * A flag indicating if user constraints have been validated. * * @var bool */ protected $userValidated ; /** * A flag indicating if parent constraints have been validated. * * @var bool */ protected $parentValidated ; /** * A flag indicating if selection constraints have been validated. * * @var bool */ protected $selectionValidated ; /** * A flag indicating if destination constraints have been validated. * * @var bool */ protected $destinationValidated ; /** * A flag indicating if additional parameters have been validated. * * @var bool */ protected $parametersValidated ; /*-------------------------------------------------------------------- * * Construct. * * These functions create an instance of a command. Every instance * has a definition that describes the command, and a configuration * that provides operands for the command. The definition never * changes, but the configuration may change as the command is * prepared for execution. Validation focuses on the configuration, * while the plugin manager has already insured that the command * definition is proper. * *--------------------------------------------------------------------*/ /** * Constructs a new plugin with the given configuration. * * @param array $configuration * The array of named parameters for the new command instance. * @param string $pluginId * The ID for the new plugin instance. * @param mixed $pluginDefinition * The plugin's implementation definition. */ public function __construct( array $configuration , $pluginId , $pluginDefinition ) { parent::__construct( $configuration , $pluginId , $pluginDefinition ); // Set the configuration, which adds in defaults and clears // cached values. $this ->setConfiguration( $configuration ); } /*-------------------------------------------------------------------- * * Configuration. * (Implements ConfigurableInterface) * * These functions handle the command's configuration, which is an * associative array of named operands for the command. All commands * support several well-known operands: * * - parentId: The entity ID of the parent item, such as a folder. * * - destinationId: The entity ID of the destination item, such as a folder. * * - selectionIds: An array of entity IDs of selected items, such as * files and folders. * * For parentId and destinationId, two special negative values are supported: * - FolderShareInterface::USER_ROOT_LIST indicates the user's root list. * - self::EMPTY_ITEM_ID indicates no ID has been provided (yet?). * * For selectionIds, the array may be empty, but the individual values must * all be valid positive entity IDs. * * When a command is created, an initial configuration is set by the * caller, combined with a default configuration. Thereafter the caller * may change the configuration. On any change, internal state is reset * to insure that the new configuration gets validated and used in * further calls to the command. * *--------------------------------------------------------------------*/ /** * Returns a required default configuration. * * The related function, defaultConfiguration(), is intended to provide * a default initial configuration for the command. Subclasses may * override this to change the default. Unfortunately, they also may * override this and fail to provide a default. * * To insure that there is always a valid starting default configuration, * this function returns the required minimal default configuration. * * @return array * Returns an associative array with keys and values set for a * required minimal configuration. * * @see defaultConfiguration() */ private function requiredDefaultConfiguration() { // // The required minimal default configuration has the well-known // common configuration keys and initial values for: // - no parent. // - no destination. // - no selection. return [ 'parentId' => self::EMPTY_ITEM_ID, 'destinationId' => self::EMPTY_ITEM_ID, 'selectionIds' => [], 'uploadClass' => '' , ]; } /** * {@inheritdoc} */ public function defaultConfiguration() { // // Return a default fully-initialized configuration used during // construction of the command, or to reset it for repeated use. // // Subclasses may override this function to add further configuration // defaults. return $this ->requiredDefaultConfiguration(); } /** * {@inheritdoc} */ public function getConfiguration() { // // Return the current configuration, which is an associative array // with well-known and subclass-specific keys for command operands. return $this ->configuration; } /** * {@inheritdoc} */ public function setConfiguration( array $configuration ) { // // Set the configuration. // // To insure that the configuration always has minimal default settings, // the given configuration is merged with the command's defaults and // a minimal required set of defaults. $this ->configuration = ( $configuration + $this ->defaultConfiguration()); $this ->configuration += $this ->requiredDefaultConfiguration(); // Clear cached values for the parent (if any), destination (if any), // and selection (if any). $this ->parent = NULL; $this ->destination = NULL; $this ->selection = []; // Clear validation flags. The new configuration is not validated until // the validation functions are called explicitly, or until the // configuration is used. $this ->validated = FALSE; $this ->commandAllowed = FALSE; $this ->parentValidated = FALSE; $this ->selectionValidated = FALSE; $this ->destinationValidated = FALSE; $this ->parametersValidated = FALSE; // If the configuration has a list of selected entity IDs, clean the // list by converting all values to integers and tossing anything // invalid. This makes later use of the list easier. $ids = $this ->configuration[ 'selectionIds' ]; if ( empty ( $ids ) === FALSE) { foreach ( $ids as $index => $id ) { $iid = (int) $id ; if ( $iid >= 0) { $ids [ $index ] = $iid ; } } } else { $ids = []; } $this ->configuration[ 'selectionIds' ] = $ids ; // If the configuration has parent or destination IDs, make sure // they are integers. if ( empty ( $this ->configuration[ 'parentId' ]) === FALSE) { $this ->configuration[ 'parentId' ] = (int) $this ->configuration[ 'parentId' ]; } if ( empty ( $this ->configuration[ 'destinationId' ]) === FALSE) { $this ->configuration[ 'destinationId' ] = (int) $this ->configuration[ 'destinationId' ]; } } /*-------------------------------------------------------------------- * * Configuration shortcuts - Misc. * *--------------------------------------------------------------------*/ /** * Returns true if the command uses file uploads. * * @return bool * Returns TRUE if the command's special handling includes 'upload'. */ public function doesFileUploads() { $def = $this ->getPluginDefinition(); return (in_array( 'upload' , $def [ 'specialHandling' ])); } /*-------------------------------------------------------------------- * * Configuration shortcuts - Parent. * *--------------------------------------------------------------------*/ /** * Gets and loads the configuration's parent folder, if any. * * If the parent folder has not been loaded yet, it is loaded and * cached. * * If there is no parent folder specified, or if a root list is specified, * then a NULL is returned. * * @return \Drupal\foldershare\FolderShareInterface * Returns the parent folder, or NULL if there is no parent or the parent * is a root list. * * @see ::getParentId() */ protected function getParent() { // If there is a loaded parent, return it. if ( $this ->parent !== NULL) { return $this ->parent; } // If there is no parent or the parent is the root list, return NULL. $parentId = $this ->getParentId(); if ( $parentId < 0) { return NULL; } // Load the parent and cache the result. $this ->parent = FolderShare::load( $parentId ); return $this ->parent; } /** * Gets the configuration's parent folder ID, if any. * * @return int * Returns the positive integer folder ID of the parent folder. * The negative self::EMPTY_ITEM_ID indicates that no parent has been set. * The negative FolderShareInterface::USER_ROOT_LIST indicates the * user's root list. * * @see ::getParent() */ protected function getParentId() { if ( empty ( $this ->configuration[ 'parentId' ]) === TRUE) { return self::EMPTY_ITEM_ID; } return (int) $this ->configuration[ 'parentId' ]; } /** * Returns TRUE if the configuration's parent folder ID has been set. * * @return bool * Returns TRUE if the configuration's parent ID field has been set * and is not EMPTY_ITEM_ID. */ protected function isParentIdSet() { return ( $this ->getParentId() !== self::EMPTY_ITEM_ID); } /** * Sets the configuration's parent folder ID. * * @param int $id * The positive integer folder ID of the destination folder. Setting * the value to self::EMPTY_ITEM_ID marks the field as empty. Setting * the value to FolderShareInterface::USER_ROOT_LIST indicates the * user's root list. */ protected function setParentId(int $id ) { $this ->configuration[ 'parentId' ] = $id ; $this ->parent = NULL; } /*-------------------------------------------------------------------- * * Configuration shortcuts - Destination. * *--------------------------------------------------------------------*/ /** * Gets and loads the configuration's destination folder, if any. * * If the destination folder has not been loaded yet, it is loaded * and cached. * * If there is no destination folder specified, or if a root list is * specified, then a NULL is returned. * * @return \Drupal\foldershare\FolderShareInterface * Returns the destination folder, or NULL if there is no destination * or the destination is a root list. * * @see ::getDestinationId() */ protected function getDestination() { // If there is a loaded destination, return it. if ( $this ->destination !== NULL) { return $this ->destination; } // If there is no destination in the configuration, return NULL. $destinationId = $this ->getDestinationId(); if ( $destinationId < 0) { return NULL; } // Load the destination and cache the result. $this ->destination = FolderShare::load( $destinationId ); return $this ->destination; } /** * Gets the configuration's destination folder ID, if any. * * @return int * Returns the positive integer folder ID of the destination folder. * The negative self::EMPTY_ITEM_ID indicates that no destination has * been set. The negative FolderShareInterface::USER_ROOT_LIST indicates * the user's root list. * * @see ::getDestination() */ protected function getDestinationId() { if ( empty ( $this ->configuration[ 'destinationId' ]) === TRUE) { return self::EMPTY_ITEM_ID; } return (int) $this ->configuration[ 'destinationId' ]; } /** * Returns TRUE if the configuration's destination folder ID has been set. * * @return bool * Returns TRUE if the configuration's destination ID field has been set * and is not EMPTY_ITEM_ID. */ protected function isDestinationIdSet() { return ( $this ->getDestinationId() !== self::EMPTY_ITEM_ID); } /** * Sets the configuration's destination folder ID. * * @param int $id * The positive integer folder ID of the destination folder. Setting * the value to self::EMPTY_ITEM_ID marks the field as empty. Setting * the value to FolderShareInterface::USER_ROOT_LIST indicates the * user's root list. */ protected function setDestinationId(int $id ) { $this ->configuration[ 'destinationId' ] = $id ; $this ->destination = NULL; } /*-------------------------------------------------------------------- * * Configuration shortcuts - Selection. * *--------------------------------------------------------------------*/ /** * Gets and loads the configuration's array of selected items, if any. * * If the selected items have not been loaded yet, they are loaded * and cached. If there are no selected items, an empty array is returned. * If any file cannot be loaded, a NULL is included in the array. * * The returned associative array has FolderShare kind names as keys, * and values that are arrays of loaded FolderShare objects. * * @return \Drupal\foldershare\FolderShareInterface[] * Returns an array of selected items, or an empty array if there * are no selected items. */ protected function getSelection() { // If there are loaded selected items, return them. if ( empty ( $this ->selection) === FALSE) { return $this ->selection; } // If there is no selection in the configuration, return an empty array. $ids = $this ->getSelectionIds(); if ( empty ( $ids ) === TRUE) { return []; } // Load the items, sort them by kind, and cache the result. $items = FolderShare::loadMultiple( $ids ); $sel = []; foreach ( $items as $item ) { if ( $item === NULL) { continue ; } $kind = $item ->getKind(); if (isset( $sel [ $kind ]) === FALSE) { $sel [ $kind ] = [ $item ]; } else { $sel [ $kind ][] = $item ; } } $this ->selection = $sel ; return $this ->selection; } /** * Gets the configuration's array of selected entity IDs. * * @return int[] * Returns an array of positive integer entity IDs for selected items, * or an empty array if there are no selected items. The returned IDs * cannot be negative. */ protected function getSelectionIds() { $ids = $this ->configuration[ 'selectionIds' ]; if ( empty ( $ids ) === TRUE) { return []; } return $ids ; } /*-------------------------------------------------------------------- * * Validate. * * Validation may be partial or complete. Partial validation means there * are errors or something is missing. Complete validation means the * configuration is ready for use in executing a command. * * To keep track of partial validation, the configuration can be validated * in stages, in this order: * - Validate command use. * - Validate parent. * - Validate selection. * - Validate destination. * - Validate additional parameters. * * Command use validation checks if the site allows the command at all. * * Parent validation insures the parent folder ID in the configuration * meets the command's criteria (see command annotation). * * Selection validation insures the selection IDs in the configuration * meets the command's criteria (see command annotation). * * Destination validation insures the destination folder ID in the * configuration meets the command's criteria (see command annotation). * * Additional parameter validation lets the command validate any special * arguments it may need. These are usually collected from forms. * * Each validation stage sets flags upon completion so that the * validation doesn't have to be repeated if nothing changes in the * configuration. * *--------------------------------------------------------------------*/ /** * {@inheritdoc} */ public function isValidated() { return $this ->commandAllowed === TRUE && $this ->userValidated === TRUE && $this ->parentValidated === TRUE && $this ->selectionValidated === TRUE && $this ->destinationValidated === TRUE && $this ->parametersValidated === TRUE; } /** * {@inheritdoc} */ public function validateConfiguration() { // Validate command is enabled by site. $this ->validateCommandAllowed(); // Validate the user constraints. $this ->validateUserConstraints(); // Validate the parent constraints. $this ->validateParentConstraints(); // Validate the selection constraints. $this ->validateSelectionConstraints(); // Validate the destination constraints. $this ->validateDestinationConstraints(); // Validate any other parameters for the command. $this ->validateParameters(); } /** * {@inheritdoc} */ public function validateCommandAllowed() { if ( $this ->commandAllowed === TRUE) { return ; } // Check site settings to insure the command is allowed. if (Settings::getCommandMenuRestrict() === TRUE) { $commandId = $this ->getPluginDefinition()[ 'id' ]; $allowedIds = Settings::getCommandMenuAllowed(); if (in_array( $commandId , $allowedIds ) === FALSE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'The @command operation is not available.' , [ '@command' => $this ->getPluginDefinition()[ 'label' ], ]), t( 'The site administrator has disabled this activity.' ))); } } // Validated! $this ->commandAllowed = TRUE; } /** * {@inheritdoc} */ public function validateUserConstraints() { if ( $this ->userValidated === TRUE) { return ; } if ( $this ->commandAllowed === FALSE) { $this ->validateCommandAllowed(); } // // Setup. // ------ // Get the command's definition and its constraints. $def = $this ->getPluginDefinition(); $userConstraints = $def [ 'userConstraints' ]; $user = \Drupal::currentUser(); // // Check user constraints. // ----------------------- // If the "any" constraint is present, no checking is needed. Otherwise, // check each of the well-known user constraints. if (in_array( 'any' , $userConstraints ) === FALSE) { $allowed = FALSE; if (in_array( 'authenticated' , $userConstraints ) === TRUE && $user ->isAuthenticated() === TRUE) { $allowed = TRUE; } elseif (in_array( 'anonymous' , $userConstraints ) === TRUE && $user ->isAnonymous() === TRUE) { $allowed = TRUE; } elseif (in_array( 'adminpermission' , $userConstraints ) === TRUE && $user ->hasPermission(Constants::ADMINISTER_PERMISSION) === TRUE) { $allowed = TRUE; } elseif (in_array( 'noadminpermission' , $userConstraints ) === TRUE && $user ->hasPermission(Constants::ADMINISTER_PERMISSION) === FALSE) { $allowed = TRUE; } elseif (in_array( 'viewpermission' , $userConstraints ) === TRUE && $user ->hasPermission(Constants::VIEW_PERMISSION) === TRUE) { $allowed = TRUE; } elseif (in_array( 'authorpermission' , $userConstraints ) === TRUE && $user ->hasPermission(Constants::AUTHOR_PERMISSION) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharepermission' , $userConstraints ) === TRUE && $user ->hasPermission(Constants::SHARE_PERMISSION) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharepublicpermission' , $userConstraints ) === TRUE && $user ->hasPermission(Constants::SHARE_PUBLIC_PERMISSION) === TRUE) { $allowed = TRUE; } if ( $allowed === FALSE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'You do not have permission to complete this operation.' ), t( 'This command is restricted to specific types of users.' ))); } } // Validated! $this ->userValidated = TRUE; } /** * {@inheritdoc} */ public function validateParentConstraints() { if ( $this ->parentValidated === TRUE) { return ; } if ( $this ->userValidated === FALSE) { $this ->validateUserConstraints(); } // // Setup. // ------ // Get the command's definition and its constraints. $def = $this ->getPluginDefinition(); $label = $def [ 'label' ]; // Get the parent's kind. // // At this stage, the parent ID can be one of: // - FolderShareInterface::USER_ROOT_LIST. // - FolderShareInterface::PUBLIC_ROOT_LIST. // - FolderShareInterface::ALL_ROOT_LIST. // - A valid entity ID. $parentId = $this ->getParentId(); $parent = NULL; if ( $parentId < 0) { // Simplify special entity IDs. switch ( $parentId ) { default : case FolderShareInterface::ALL_ROOT_LIST: // While an admin can operate on anybody's root items in the "all" // list, if they create anything (new folder, duplicate, upload, // compress, uncompress, etc.), it goes into their own root list. // So, treat the "all" list as the user's root list. $parentId = FolderShareInterface::USER_ROOT_LIST; $this ->setParentId( $parentId ); break ; case FolderShareInterface::USER_ROOT_LIST: case FolderShareInterface::PUBLIC_ROOT_LIST: // Continue to distinguish between the user and public lists. break ; case self::EMPTY_ITEM_ID: // This should not happen. It means the UI did not fill in the // parent ID. throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Missing parent folder ID for the "@command" command.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } $parentKind = 'rootlist' ; } else { $parent = $this ->getParent(); if ( $parent === NULL) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Missing parent folder ID for the "@command" command.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } if ( $parent ->isSystemHidden() === TRUE) { // Hidden items do not exist. throw new NotFoundHttpException( FolderShare::getStandardHiddenMessage( $parent ->getName())); } if ( $parent ->isSystemDisabled() === TRUE) { // Disabled items cannot be used. throw new ConflictHttpException( FolderShare::getStandardDisabledMessage( 'accessed' , $parent ->getName())); } $parentKind = $parent ->getKind(); } // // Check parent kind. // ------------------ // Compare the parent kind against the list of supported kinds // for the command. $allowedKinds = $def [ 'parentConstraints' ][ 'kinds' ]; if (in_array( 'any' , $allowedKinds ) === FALSE) { if ( $parentKind === 'rootlist' && in_array( 'rootlist' , $allowedKinds ) === FALSE) { // Parent is a root list but the command doesn't support that. throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Root list provided instead of a required parent folder ID for the "@command" command.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } elseif (in_array( $parentKind , $allowedKinds ) === FALSE) { // Parent provided but its kind is not supported by the command. throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Parent item provided does not meet the "@command" command\'s requirements.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } } // // Check parent ownership. // ----------------------- // Confirm that the parent meets ownership constraints. // Skip the check if the command supports 'any' as an ownership choice // (which is the default). $allowedOwnership = $def [ 'parentConstraints' ][ 'ownership' ]; $user = \Drupal::currentUser(); if (in_array( 'any' , $allowedOwnership ) === FALSE) { $userId = (int) $user ->id(); $anonymous = User::getAnonymousUser(); $anonId = (int) $anonymous ->id(); $allowed = FALSE; if ( $parent !== NULL) { // There is a parent entity. Check it against the allowed // ownership constraints. // // Since access grants are set on the root only, use it to determine // the sharing status below. $root = $parent ->getRootItem(); if (in_array( 'ownedbyuser' , $allowedOwnership ) === TRUE && $parent ->isOwnedBy( $userId ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'ownedbyanonymous' , $allowedOwnership ) === TRUE && $parent ->isOwnedBy( $anonId ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'ownedbyanother' , $allowedOwnership ) === TRUE && $parent ->isOwnedBy( $userId ) === FALSE) { $allowed = TRUE; } elseif (in_array( 'sharedbyuser' , $allowedOwnership ) === TRUE && $root ->isSharedBy( $userId ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharedwithusertoview' , $allowedOwnership ) === TRUE && $root ->isSharedWith( $userId , 'view' ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharedwithusertoauthor' , $allowedOwnership ) === TRUE && $root ->isSharedWith( $userId , 'author' ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharedwithanonymoustoview' , $allowedOwnership ) === TRUE && $root ->isSharedWith( $anonId , 'view' ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharedwithanonymoustoauthor' , $allowedOwnership ) === TRUE && $root ->isSharedWith( $anonId , 'author' ) === TRUE) { $allowed = TRUE; } } else { // There is no parent entity. The parent is a rootlist. For purposes // of parent ownership checking: // - USER_ROOT_LIST is owned by the current user. // - PUBLIC_ROOT_LIST is owned by anonymous. if ( $parentId === FolderShareInterface::USER_ROOT_LIST) { if (in_array( 'ownedbyuser' , $allowedOwnership ) === TRUE) { $allowed = TRUE; } } elseif ( $parentId === FolderShareInterface::PUBLIC_ROOT_LIST) { if (in_array( 'ownedbyanonymous' , $allowedOwnership ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharedwithanonymoustoview' , $allowedOwnership ) === TRUE && $anonymous ->hasPermission(Constants::VIEW_PERMISSION) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharedwithanonymoustoauthor' , $allowedOwnership ) === TRUE && $anonymous ->hasPermission(Constants::AUTHOR_PERMISSION) === TRUE) { $allowed = TRUE; } } } if ( $allowed === FALSE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Parent item provided does not meet the "@command" command\'s ownership requirements.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } } // // Check parent access. // -------------------- // Check if the required access is allowed for the parent or root list. // Handle 'create' specially since it is not available via access(). $allowedAccess = $def [ 'parentConstraints' ][ 'access' ]; if ( $parent !== NULL) { $allowed = $parent ->access( $allowedAccess , $user , FALSE); } else { $summary = FolderShareAccessControlHandler::getRootAccessSummary( $parentId , $user ); if (isset( $summary [ $allowedAccess ]) === TRUE && $summary [ $allowedAccess ] === TRUE) { $allowed = TRUE; } else { $allowed = FALSE; } } if ( $allowed === FALSE) { if ( $parentId >= 0) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'You do not have permission to complete this operation.' ), t( 'The current folder is restricted and cannot be accessed.' ))); } throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'You do not have permission to complete this operation.' ), t( 'The current top-level list is restricted and cannot be accessed.' ))); } // Validated! $this ->parentValidated = TRUE; } /** * {@inheritdoc} */ public function validateSelectionConstraints() { if ( $this ->selectionValidated === TRUE) { return ; } if ( $this ->parentValidated === FALSE) { $this ->validateParentConstraints(); } $def = $this ->getPluginDefinition(); $label = $def [ 'label' ]; $allowedTypes = $def [ 'selectionConstraints' ][ 'types' ]; if (in_array( 'none' , $allowedTypes ) === TRUE) { // No selection required! // // Validated! $this ->selectionValidated = TRUE; return ; } // // Setup. // ------ // Get the parent ID. Parent validation has already simplified the // range of values to: // - FolderShareInterface::USER_ROOT_LIST. // - FolderShareInterface::PUBLIC_ROOT_LIST. // - A valid entity ID. $parentId = $this ->getParentId(); // Count the total number of selected items. $selectionIds = $this ->getSelectionIds(); $nSelected = count ( $selectionIds ); $selectionIsParent = FALSE; // // Check selection size. // --------------------- // Insure the selection size is compatible with the command. // // Config selection Command constraints Result // ---------------- ------------------- ------ // none, one, many none needed allowed. // none can have parent allowed, use parent. // none cannot have parent fail, need selection. // one can have one allowed, use selection. // one cannot have one fail, need more. // many can have many allowed, use selection. // many cannot have many fail, need fewer. $canHaveOne = in_array( 'one' , $allowedTypes ); $canHaveMany = in_array( 'many' , $allowedTypes ); $canHaveParent = in_array( 'parent' , $allowedTypes ); $allowed = TRUE; $failureIsMultiple = TRUE; if ( $nSelected === 0) { if ( $canHaveParent === TRUE && $parentId >= 0) { // There is no selection, the command can default to the parent, // and there is a parent! $nSelected = 1; $selectionIsParent = TRUE; } else { // There is no selection, but either the command cannot default to // the parent, or there is no parent. Fail. $allowed = FALSE; $failureIsMultiple = ( $canHaveMany === TRUE); } } elseif ( $nSelected > 1 && $canHaveMany === FALSE) { // There are multiple items selected, but the command does not allow // that. Fail. $allowed = FALSE; $failureIsMultiple = FALSE; } elseif ( $nSelected === 1 && $canHaveOne === FALSE && $canHaveMany === FALSE) { // There is just one item selected, but the command does not allow // having just one or even lots of items selected. Fail. $allowed = FALSE; $failureIsMultiple = TRUE; } if ( $allowed === FALSE) { if ( $failureIsMultiple === TRUE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Multiple selection IDs required for the "@command" command.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Single selection ID required for the "@command" command.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } if ( $selectionIsParent === FALSE) { $selection = $this ->getSelection(); } else { $parent = $this ->getParent(); $selection [ $parent ->getKind()] = [ $parent ]; } // // Check selection kinds. // ---------------------- // Insure the kinds of items in the selection are compatible with // the command. // // Config selection Command constraints Result // ---------------- ------------------- ------ // any any allowed, use selection. // specific kind kind allowed, if they match. $allowedKinds = $def [ 'selectionConstraints' ][ 'kinds' ]; if (in_array( 'any' , $allowedKinds ) === FALSE) { $allowed = TRUE; foreach ( array_keys ( $selection ) as $kind ) { if (in_array( $kind , $allowedKinds ) === FALSE) { $allowed = FALSE; break ; } } if ( $allowed === FALSE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Selection provided does not meet the "@command" command\'s kind requirements.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } } // // Check selection validity and parentage. // --------------------------------------- // Confirm that all of the selected items are children of the parent, // if any. Skip this check if the selection has been defaulted to // the parent. // // Config parent Item's parent Result // ------------- ------------- ------ // USER_ROOT_LIST USER_ROOT_LIST Allowed. // USER_ROOT_LIST integer Fail, item should have no parent. // PUBLIC_ROOT_LIST USER_ROOT_LIST Allowed. // PUBLIC_ROOT_LIST integer Fail, item should have no parent. // integer USER_ROOT_LIST Fail, item should have a parent. // integer integer Allowed if they match. if ( $selectionIsParent === FALSE) { foreach ( $selection as $kind => $items ) { foreach ( $items as $item ) { if ( $item === NULL || $item ->isSystemHidden() === TRUE) { // Bad items or hidden items do not exist. throw new NotFoundHttpException( FolderShare::getStandardHiddenMessage( $item ->getName())); } if ( $item ->isSystemDisabled() === TRUE) { // Disabled items cannot be used. throw new ConflictHttpException( FolderShare::getStandardDisabledMessage( 'accessed' , $item ->getName())); } if ( $parentId === FolderShareInterface::USER_ROOT_LIST || $parentId === FolderShareInterface::PUBLIC_ROOT_LIST) { // The configuration's parent is the user or public root list. // If the item is a root item, then allowed. $allowed = ( $item ->isRootItem() === TRUE); } else { // The configuration's parent is a non-root item. If the item // is not a root, then allowed. $allowed = ( $item ->isRootItem() === FALSE); } if ( $allowed === FALSE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Selection provided does not meet the "@command" command\'s parentage requirements.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } } } } // // Check selection ownership. // -------------------------- // Confirm that all of the selected items meet ownership constraints. // Skip the check if the command supports 'any' as an ownership choice // (which is the default). $allowedOwnership = $def [ 'selectionConstraints' ][ 'ownership' ]; $user = \Drupal::currentUser(); if (in_array( 'any' , $allowedOwnership ) === FALSE) { $userId = (int) $user ->id(); $anonymous = User::getAnonymousUser(); $anonId = (int) $anonymous ->id(); $checkOwnedByAnonymous = in_array( 'ownedbyanonymous' , $allowedOwnership ); $checkOwnedByAnother = in_array( 'ownedbyanother' , $allowedOwnership ); $checkOwnedByUser = in_array( 'ownedbyuser' , $allowedOwnership ); $checkSharedByUser = in_array( 'sharedbyuser' , $allowedOwnership ); $checkSharedWithAnonymousToView = in_array( 'sharedwithanonymoustoview' , $allowedOwnership ); $checkSharedWithAnonymousToAuthor = in_array( 'sharedwithanonymoustoauthor' , $allowedOwnership ); $checkSharedWithUserToView = in_array( 'sharedwithusertoview' , $allowedOwnership ); $checkSharedWithUserToAuthor = in_array( 'sharedwithusertoauthor' , $allowedOwnership ); // Since access grants are set on the root only, use it to determine // the sharing status below. Get the root below from the first selected // item. All items in the selection list have already been validated to // have the same parent, so they'll have the same root too. $root = NULL; $allowed = TRUE; foreach ( $selection as $kind => $items ) { foreach ( $items as $item ) { $itemIsAllowed = FALSE; // For the first item in the selection, get the root. It will be // the same root for everything in the selection. if ( $root === NULL) { $root = $item ->getRootItem(); } if ( $checkOwnedByUser === TRUE && $item ->isOwnedBy( $userId ) === TRUE) { $itemIsAllowed = TRUE; } elseif ( $checkOwnedByAnonymous === TRUE && $item ->isOwnedBy( $anonId ) === TRUE) { $itemIsAllowed = TRUE; } elseif ( $checkOwnedByAnother === TRUE && $item ->isOwnedBy( $userId ) === FALSE) { $itemIsAllowed = TRUE; } elseif ( $checkSharedByUser === TRUE && $root ->isSharedBy( $userId ) === TRUE) { $itemIsAllowed = TRUE; } elseif ( $checkSharedWithUserToView === TRUE && $root ->isSharedWith( $userId , 'view' ) === TRUE) { $itemIsAllowed = TRUE; } elseif ( $checkSharedWithUserToAuthor === TRUE && $root ->isSharedWith( $userId , 'author' ) === TRUE) { $itemIsAllowed = TRUE; } elseif ( $checkSharedWithAnonymousToView === TRUE && $root ->isSharedWith( $anonId , 'view' ) === TRUE) { $itemIsAllowed = TRUE; } elseif ( $checkSharedWithAnonymousToAuthor === TRUE && $root ->isSharedWith( $anonId , 'author' ) === TRUE) { $itemIsAllowed = TRUE; } if ( $itemIsAllowed === FALSE) { $allowed = FALSE; break 2; } } } if ( $allowed === FALSE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Selection provided does not meet the "@command" command\'s ownership requirements.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } } // // Check selection file extensions. // -------------------------------- // Confirm that all of the selected items meet file name extension // constraints. Skip the check if the command has no extensions // (which is the default). $allowedExtensions = $def [ 'selectionConstraints' ][ 'fileExtensions' ]; if ( empty ( $allowedExtensions ) === FALSE) { $allowed = TRUE; foreach ( $selection as $kind => $items ) { foreach ( $items as $item ) { $ext = $item ->getExtension(); if ( empty ( $ext ) === TRUE) { $allowed = FALSE; break 2; } if (in_array( $ext , $allowedExtensions ) === FALSE) { $allowed = FALSE; break 2; } } } if ( $allowed === FALSE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Selection provided does not meet the "@command" command\'s file name extension requirements.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } } // // Check selection access. // ----------------------- // If there is a selection and access is required, check for access. // Commands that do not require a selection, or selection access, have // an access operation of 'none'. $allowedAccess = $def [ 'selectionConstraints' ][ 'access' ]; $allowed == TRUE; foreach ( $selection as $items ) { foreach ( $items as $item ) { if ( $item ->access( $allowedAccess , $user , FALSE) === FALSE) { $allowed = FALSE; break 2; } } } if ( $allowed === FALSE) { if ( $selectionIsParent === FALSE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'You do not have permission to complete this operation.' ), t( 'One or more selected items are restricted and cannot be accessed.' ))); } if ( $parentId >= 0) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'You do not have permission to complete this operation.' ), t( 'The current folder is restricted and cannot be accessed.' ))); } throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'You do not have permission to complete this operation.' ), t( 'The current top-level list is restricted and cannot be accessed.' ))); } // Validated! $this ->selectionValidated = TRUE; } /** * {@inheritdoc} */ public function validateDestinationConstraints() { if ( $this ->destinationValidated === TRUE) { return ; } if ( $this ->selectionValidated === FALSE) { $this ->validateSelectionConstraints(); } $def = $this ->getPluginDefinition(); $label = $def [ 'label' ]; $allowedKinds = $def [ 'destinationConstraints' ][ 'kinds' ]; if (in_array( 'none' , $allowedKinds ) === TRUE) { // No destination required. // // Validated! $this ->destinationValidated = TRUE; return ; } // // Setup. // ------ // Get the destinations's kind. $destinationId = $this ->getDestinationId(); $destination = NULL; if ( $destinationId === self::EMPTY_ITEM_ID) { $destinationKind = 'none' ; } elseif ( $destinationId === FolderShareInterface::USER_ROOT_LIST) { $destinationKind = 'rootlist' ; } else { $destination = $this ->getDestination(); if ( $destination === NULL) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Missing destination folder ID for the "@command" command.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } if ( $destination ->isSystemHidden() === TRUE) { // Hidden items do not exist. throw new NotFoundHttpException( FolderShare::getStandardHiddenMessage( $destination ->getName())); } if ( $destination ->isSystemDisabled() === TRUE) { // Disabled items cannot be used. throw new ConflictHttpException( FolderShare::getStandardDisabledMessage( 'accessed' , $destination ->getName())); } $destinationKind = $destination ->getKind(); } // // Check destination kind. // ----------------------- // Compare the destination kind against the list of supported kinds // for the command. if ( $destinationKind === 'none' && in_array( 'none' , $allowedKinds ) === FALSE) { // There is no destination but the command doesn't support that. throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Missing required destination folder ID for the "@command" command.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } elseif ( $destinationKind === 'rootlist' && in_array( 'rootlist' , $allowedKinds ) === FALSE) { // Destination is a root list but the command doesn't support that. throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Root list provided instead of a required destination folder ID for the "@command" command.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } elseif (in_array( $destinationKind , $allowedKinds ) === FALSE && in_array( 'any' , $allowedKinds ) === FALSE) { // Destination provided but its kind is not supported by the command. throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Destination item provided does not meet the "@command" command\'s requirements.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } // // Check destination ownership. // ---------------------------- // Confirm that the destination meets ownership constraints. // Skip the check if the command supports 'any' as an ownership choice // (which is the default). // // Rootlists are not owned, so there is no ownership constraint for them. $allowedOwnership = $def [ 'destinationConstraints' ][ 'ownership' ]; $user = \Drupal::currentUser(); if (in_array( 'any' , $allowedOwnership ) === FALSE) { $userId = (int) $user ->id(); $anonymous = User::getAnonymousUser(); $anonId = (int) $anonymous ->id(); if ( $destination !== NULL) { // There is a destination entity. Check it against the allowed // ownership constraints. // // Since access grants are set on the root only, use it to determine // the sharing status below. $root = $destination ->getRootItem(); $allowed = FALSE; if (in_array( 'ownedbyuser' , $allowedOwnership ) === TRUE && $destination ->isOwnedBy( $userId ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'ownedbyanonymous' , $allowedOwnership ) === TRUE && $destination ->isOwnedBy( $anonId ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'ownedbyanother' , $allowedOwnership ) === TRUE && $destination ->isOwnedBy( $userId ) === FALSE) { $allowed = TRUE; } elseif (in_array( 'sharedbyuser' , $allowedOwnership ) === TRUE && $root ->isSharedBy( $userId ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharedwithusertoview' , $allowedOwnership ) === TRUE && $root ->isSharedWith( $userId , 'view' ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharedwithusertoauthor' , $allowedOwnership ) === TRUE && $root ->isSharedWith( $userId , 'author' ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharedwithanonymoustoview' , $allowedOwnership ) === TRUE && $root ->isSharedWith( $anonId , 'view' ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharedwithanonymoustoauthor' , $allowedOwnership ) === TRUE && $root ->isSharedWith( $anonId , 'author' ) === TRUE) { $allowed = TRUE; } } else { // There is no destination entity. The destination is a rootlist. // For purposes of destination ownership checking: // - USER_ROOT_LIST is owned by the current user. // - PUBLIC_ROOT_LIST is owned by anonymous. if ( $destinationId === FolderShareInterface::USER_ROOT_LIST) { if (in_array( 'ownedbyuser' , $allowedOwnership ) === TRUE) { $allowed = TRUE; } } elseif ( $destinationId === FolderShareInterface::PUBLIC_ROOT_LIST) { if (in_array( 'ownedbyanonymous' , $allowedOwnership ) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharedwithanonymoustoview' , $allowedOwnership ) === TRUE && $anonymous ->hasPermission(Constants::VIEW_PERMISSION) === TRUE) { $allowed = TRUE; } elseif (in_array( 'sharedwithanonymoustoauthor' , $allowedOwnership ) === TRUE && $anonymous ->hasPermission(Constants::AUTHOR_PERMISSION) === TRUE) { $allowed = TRUE; } } } if ( $allowed === FALSE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'Destination item provided does not meet the "@command" command\'s ownership requirements.' , [ '@command' => $label , ]), t( 'This is probably due to a programming error in the user interface. Please report this to the developers.' ))); } } // // Check destination access // ------------------------ // If there is a destination and access is required, check for access. // Commands that do not require a destination, or destination access, have // an access operation of 'none'. $allowedAccess = $def [ 'destinationConstraints' ][ 'access' ]; if ( $destination !== NULL) { $allowed = $destination ->access( $allowedAccess , $user , FALSE); } else { $summary = FolderShareAccessControlHandler::getRootAccessSummary( $destinationId , $user ); if (isset( $summary [ $allowedAccess ]) === TRUE && $summary [ $allowedAccess ] === TRUE) { $allowed = TRUE; } else { $allowed = FALSE; } } if ( $allowed === FALSE) { throw new ValidationException(FormatUtilities::createFormattedMessage( t( 'You do not have permission to complete this operation.' ), t( 'The destination folder is restricted and cannot be accessed.' ))); } // Validated! $this ->destinationValidated = TRUE; } /** * {@inheritdoc} */ public function validateParameters() { if ( $this ->parametersValidated === TRUE) { return ; } if ( $this ->destinationValidated === FALSE) { $this ->validateDestinationConstraints(); } // Validated! $this ->parametersValidated = TRUE; } /*-------------------------------------------------------------------- * * Execute. * *--------------------------------------------------------------------*/ /** * {@inheritdoc} */ abstract public function execute(); /*--------------------------------------------------------------------- * * Execution behavior. * *---------------------------------------------------------------------*/ /** * {@inheritdoc} */ public function getExecuteBehavior() { return FolderShareCommandInterface::POST_EXECUTE_VIEW_REFRESH; } /** * {@inheritdoc} */ public function getExecuteRedirectUrl() { return NULL; } /*--------------------------------------------------------------------- * * Configuration forms. * *---------------------------------------------------------------------*/ /** * {@inheritdoc} */ public function getDescription(bool $forPage ) { return []; } /** * {@inheritdoc} */ public function getTitle(bool $forPage ) { return $this ->getPluginDefinition()[ 'label' ]; } /** * {@inheritdoc} */ public function getSubmitButtonName() { return t( 'OK' ); } /** * {@inheritdoc} */ public function hasConfigurationForm() { return FALSE; } /** * {@inheritdoc} */ public function buildConfigurationForm( array $form , FormStateInterface $formState ) { return $form ; } /** * {@inheritdoc} */ public function validateConfigurationForm( array & $form , FormStateInterface $formState ) { } /** * {@inheritdoc} */ public function submitConfigurationForm( array & $form , FormStateInterface $formState ) { } /*-------------------------------------------------------------------- * * Debug. * *--------------------------------------------------------------------*/ /** * Returns a string representation of the plugin. * * @return string * A string representation of the plugin. */ public function __toString() { $def = $this ->getPluginDefinition(); $s = 'FolderShareCommand: ' . $def [ 'id' ] . "\n" ; $s .= ' parent ID: "' . $this ->getParentId() . "\ "\n" ; $s .= ' destination ID: "' . $this ->getDestinationId() . "\ "\n" ; $s .= ' selection IDs: ' ; $ids = $this ->getSelectionIds(); if ( empty ( $ids ) === TRUE) { $s .= '(empty)' ; } else { foreach ( $ids as $id ) { $s .= ' "' . $id . '"' ; } } $s .= "\n" ; $s .= ' configuration keys: ' ; foreach ( array_keys ( $this ->configuration) as $key ) { $s .= ' "' . $key . '"' ; } $s .= "\n" ; return $s ; } } |