foldershare-8.x-1.2/src/Plugin/FolderShareCommand/ChangeOwner.php
src/Plugin/FolderShareCommand/ChangeOwner.php
<?php
namespace Drupal\foldershare\Plugin\FolderShareCommand;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\Entity\User;
use Drupal\foldershare\Constants;
use Drupal\foldershare\Settings;
use Drupal\foldershare\Utilities\FormatUtilities;
use Drupal\foldershare\Utilities\UserUtilities;
use Drupal\foldershare\Entity\FolderShare;
use Drupal\foldershare\Entity\Exception\RuntimeExceptionWithMarkup;
use Drupal\foldershare\Entity\Exception\ValidationException;
/**
* Defines a command plugin to change ownership of files or folders.
*
* The command sets the UID for the owner of all selected entities.
* Owenrship changes recurse through all folder content as well.
*
* Configuration parameters:
* - 'parentId': the parent folder, if any.
* - 'selectionIds': selected entities to change ownership on.
* - 'uid': the UID of the new owner.
*
* @ingroup foldershare
*
* @FolderShareCommand(
* id = "foldersharecommand_change_owner",
* label = @Translation("Change Owner"),
* menuNameDefault = @Translation("Change Owner..."),
* menuName = @Translation("Change Owner..."),
* description = @Translation("Change the owner of selected files and folders, and optionally for all of a folder's descendants. This command is only available for content administrators."),
* category = "administer",
* weight = 10,
* userConstraints = {
* "adminpermission",
* },
* parentConstraints = {
* "kinds" = {
* "rootlist",
* "any",
* },
* "access" = "view",
* },
* selectionConstraints = {
* "types" = {
* "parent",
* "one",
* "many",
* },
* "kinds" = {
* "any",
* },
* "access" = "chown",
* },
* )
*/
class ChangeOwner extends FolderShareCommandBase {
/*--------------------------------------------------------------------
*
* Configuration.
*
*--------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
// Add room for the UID and a recursion flag.
$config = parent::defaultConfiguration();
$config['uid'] = '';
$config['changedescendants'] = 'FALSE';
return $config;
}
/**
* {@inheritdoc}
*/
public function validateParameters() {
if ($this->parametersValidated === TRUE) {
return;
}
// Get the new UID from the configuration and check if it is valid.
$uid = $this->configuration['uid'];
if ($uid === NULL) {
// When there is no UID in the configuration, it is probably because
// the form auto-complete did not recognize the user name the user
// typed in, and therefore could not map it to a UID. So make the
// error message about an unknown user name, not a missing UID.
throw new ValidationException(FormatUtilities::createFormattedMessage(
t('The user name does not match any user account at this site.'),
t('Please check that the name is correct and for an existing account.')));
}
$user = User::load($uid);
if ($user === NULL) {
throw new ValidationException(FormatUtilities::createFormattedMessage(
t(
'The user ID "@uid" does not match any user account at this site.',
[
'@uid' => $uid,
]),
t('Please check that the ID is correct and for an existing account.')));
}
$this->parametersValidated = TRUE;
}
/*--------------------------------------------------------------------
*
* Configuration form setup.
*
*--------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function hasConfigurationForm() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getDescription(bool $forPage) {
$selectionIds = $this->getSelectionIds();
if (empty($selectionIds) === TRUE) {
$item = $this->getParent();
}
else {
$item = FolderShare::load(reset($selectionIds));
}
$isShared = $item->getRootItem()->isAccessShared();
$isRoot = $item->isRootItem();
if ($isShared === TRUE && $isRoot === TRUE) {
return [
'',
t('This item is shared. Changing its owner will end shared access and may affect other users.'),
];
}
return [];
}
/**
* {@inheritdoc}
*/
public function getTitle(bool $forPage) {
// The title varies for page vs. dialog:
//
// - Dialog: "Change owner".
//
// - Page: The title is longer and has the form "Change the owner of
// OPERAND?". where OPERAND can be the name of the item if one item
// is being changed, or the count and kinds if multiple items are
// being changed. This follows Drupal convention.
if ($forPage === FALSE) {
return t('Change owner');
}
$selectionIds = $this->getSelectionIds();
if (empty($selectionIds) === TRUE) {
$selectionIds[] = $this->getParentId();
}
if (count($selectionIds) === 1) {
// Page title. There is only one item. Load it.
$item = FolderShare::load($selectionIds[0]);
return t(
'Change owner of "@name"?',
[
'@name' => $item->getName(),
]);
}
// Find the kinds for each of the selection IDs. Then choose an
// operand based on the selection's single kind, or "items".
$selectionKinds = FolderShare::findKindsForIds($selectionIds);
if (count($selectionIds) === 1) {
$kind = key($selectionKinds);
$operand = FolderShare::translateKind($kind);
}
elseif (count($selectionKinds) === 1) {
$kind = key($selectionKinds);
$operand = FolderShare::translateKinds($kind);
}
else {
$operand = FolderShare::translateKinds('items');
}
// Page title. Include the count and operand kind. Question mark.
return t(
"Change owner of @count @operand?",
[
'@count' => count($selectionIds),
'@operand' => $operand,
]);
}
/**
* {@inheritdoc}
*/
public function getSubmitButtonName() {
return t('Change');
}
/*--------------------------------------------------------------------
*
* Configuration form.
*
*--------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(
array $form,
FormStateInterface $formState) {
// Find the kinds for each of the selection IDs.
$selectionIds = $this->getSelectionIds();
$selectionKinds = FolderShare::findKindsForIds($selectionIds);
$hasFolders = (isset($selectionKinds[FolderShare::FOLDER_KIND]) === TRUE);
// Use the UID of the current user as the default value.
$account = \Drupal::currentUser();
$this->configuration['uid'] = $account->id();
$formInput = $formState->getUserInput();
// The command wrapper provides form basics:
// - Attached libraries.
// - Page title (if not an AJAX dialog).
// - Description (from ::getDescription()).
// - Submit buttion (labeled with ::getSubmitButtonName()).
// - Cancel button (if AJAX dialog).
//
// Add a text field for the new owner, and optionally a checkbox for
// recursing through folder contents.
$description = '';
switch (Settings::getUserAutocompleteStyle()) {
default:
case 'none':
case 'name-only':
$description = t('Enter the account name of a user.');
break;
case 'name-email':
case 'name-masked-email':
$description = t('Enter the account name or email address of a user.');
break;
}
$defaultUser = '';
if (isset($formInput['owner']) === TRUE) {
$defaultUser = $formInput['owner'];
}
if (empty($defaultUser) === TRUE) {
$defaultUser = $account->getAccountName();
}
$form['owner'] = [
'#type' => 'textfield',
'#maxlength' => 256,
'#default_value' => $defaultUser,
'#name' => 'owner',
'#title' => t('New owner:'),
'#weight' => 10,
'#required' => TRUE,
'#size' => 30,
'#attributes' => [
'autofocus' => 'autofocus',
'spellcheck' => 'false',
'class' => [
Constants::MODULE . '-changeowneritem-owner',
],
],
];
$form['ownerdescription'] = [
'#type' => 'html_tag',
'#tag' => 'p',
'#value' => $description,
'#weight' => 11,
'#attributes' => [
'class' => [
'description',
],
],
];
// If the site allows user autocomplete, set up the text field.
if (Settings::getUserAutocompleteStyle() !== 'none') {
$form['owner']['#autocomplete_route_name'] =
'entity.foldershare.userautocomplete';
$form['owner']['#autocomplete_route_parameters'] = [
'excludeBlocked' => 0,
];
}
if ($hasFolders === TRUE) {
$form['changedescendants'] = [
'#type' => 'checkbox',
'#name' => 'changedescendants',
'#weight' => 20,
'#title' => t('Apply to enclosed items'),
'#default_value' => ($this->configuration['changedescendants'] === 'TRUE'),
'#attributes' => [
'class' => [
Constants::MODULE . '-changeowneritem-changedescendants',
],
],
];
}
$form['status'] = [
'#type' => 'status_messages',
'#weight' => 30,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(
array &$form,
FormStateInterface $formState) {
// Get the entered text for the new owner.
$name = $formState->getValue('owner');
// Remove leading white space.
$cleanedName = mb_ereg_replace('^\s+', '', $name);
if ($cleanedName !== FALSE) {
$name = $cleanedName;
}
// Remove trailing white space.
$cleanedName = mb_ereg_replace('\s+$', '', $name);
if ($cleanedName !== FALSE) {
$name = $cleanedName;
}
if (empty($name) === TRUE) {
// Empty user.
// Since the field is required, Drupal adds a default error message
// when the field is left empty. Get rid of that so we can provide
// a more helpful message.
$formState->clearErrors();
// And now intentionally use an empty error message. This will
// highlight the form field but not show a message. Such a message
// would be redundant and just say "Enter an account name", which
// is what the description under the field already says.
$formState->setErrorByName('owner', '');
// Clear the name field.
// Since a validation error does not rebuild the form, updating the
// form's state does not work to insure the form name field is empty.
$form['owner']['#value'] = '';
$formState->setRebuild();
return;
}
// Use the given name to find a user then add them to the grants list.
$uid = UserUtilities::findUser($name);
if ($uid === (-1)) {
// User not found.
$formState->setErrorByName(
'owner',
t(
'"%name" is not a recognized account at this site.',
[
'%name' => $name,
]));
// Insure the name field has the cleaned name.
// Since a validation error does not rebuild the form, updating the
// form's state does not work to insure the form is showing the cleaned
// name. Set it into the form directly.
$form['owner']['#value'] = $name;
$formState->setRebuild();
return;
}
$this->configuration['uid'] = $uid;
// Get the descendants flag, if any.
$this->configuration['changedescendants'] = 'FALSE';
if ($formState->hasValue('changedescendants') === TRUE &&
$formState->getValue('changedescendants') === 1) {
$this->configuration['changedescendants'] = 'TRUE';
}
// Validate.
try {
$this->validateParameters();
}
catch (RuntimeExceptionWithMarkup $e) {
// Unfortunately, setErrorByName() will not accept complex markup,
// such as for multi-line error messages. If we pass the markup,
// the function strips it down to the last line of text. To avoid
// this, we have to pass the message text instead.
$formState->setErrorByName('owner', $e->getMessage());
$formState->setRebuild();
}
catch (\Exception $e) {
$formState->setErrorByName('owner', $e->getMessage());
$formState->setRebuild();
}
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(
array &$form,
FormStateInterface $formState) {
if ($this->isValidated() === TRUE) {
$this->execute();
}
}
/*---------------------------------------------------------------------
*
* Execution behavior.
*
*---------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function getExecuteBehavior() {
if (empty($this->getSelectionIds()) === TRUE) {
// When there is no selection, execution falls back to operating
// on the current parent. After a change, the breadcrumbs or
// other page decoration may differ. We need to refresh the page.
return FolderShareCommandInterface::POST_EXECUTE_PAGE_REFRESH;
}
// When there is a selection, execution changes that selection on the
// current page. While columns may change, the page doesn't, so we
// only need to refresh the view.
return FolderShareCommandInterface::POST_EXECUTE_VIEW_REFRESH;
}
/*--------------------------------------------------------------------
*
* Execute.
*
*--------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function execute() {
$ids = $this->getSelectionIds();
if (empty($ids) === TRUE) {
$ids[] = $this->getParentId();
}
try {
FolderShare::changeOwnerIdMultiple(
$ids,
$this->configuration['uid'],
($this->configuration['changedescendants'] === 'TRUE'));
}
catch (RuntimeExceptionWithMarkup $e) {
\Drupal::messenger()->addMessage($e->getMarkup(), 'error', TRUE);
}
catch (\Exception $e) {
\Drupal::messenger()->addMessage($e->getMessage(), 'error');
}
if (Settings::getCommandNormalCompletionReportEnable() === TRUE) {
\Drupal::messenger()->addMessage(
\Drupal::translation()->formatPlural(
count($ids),
"The item has been changed.",
"@count items have been changed."),
'status');
}
}
}
