foldershare-8.x-1.2/src/Plugin/FolderShareCommand/Copy.php
src/Plugin/FolderShareCommand/Copy.php
<?php
namespace Drupal\foldershare\Plugin\FolderShareCommand;
use Symfony\Component\HttpFoundation\Request;
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\RuntimeExceptionWithMarkup;
use Drupal\foldershare\Entity\Exception\ValidationException;
/**
* Defines a command plugin to copy files and folders.
*
* The command copies all selected files and folders to a chosen
* destination folder or the root list.
*
* Configuration parameters:
* - 'parentId': the parent folder, if any.
* - 'selectionIds': selected entities to duplicate.
* - 'destinationId': the destination folder, if any.
*
* @ingroup foldershare
*
* @FolderShareCommand(
* id = "foldersharecommand_copy",
* label = @Translation("Copy"),
* menuNameDefault = @Translation("Copy..."),
* menuName = @Translation("Copy..."),
* description = @Translation("Copy selected files and folders to a new location."),
* category = "copy & move",
* weight = 10,
* userConstraints = {
* "authorpermission",
* },
* parentConstraints = {
* "kinds" = {
* "rootlist",
* "folder",
* },
* "access" = "view",
* },
* destinationConstraints = {
* "kinds" = {
* "rootlist",
* "folder",
* },
* "access" = "create",
* },
* selectionConstraints = {
* "types" = {
* "one",
* "many",
* },
* "kinds" = {
* "any",
* },
* "access" = "view",
* },
* )
*/
class Copy extends CopyMoveBase {
/*--------------------------------------------------------------------
*
* Configuration.
*
*--------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function validateDestinationConstraints() {
if ($this->destinationValidated === TRUE) {
return;
}
if ($this->selectionValidated === FALSE) {
$this->validateSelectionConstraints();
}
// Handle special cases for destination.
$destinationId = $this->getDestinationId();
if ($destinationId === FolderShareInterface::ALL_ROOT_LIST) {
$routeProvider = \Drupal::service('router.route_provider');
$titleResolver = \Drupal::service('title_resolver');
$dummyRequest = new Request();
$route = $routeProvider->getRouteByName(
Constants::ROUTE_ROOT_ITEMS_ALL);
$title = $titleResolver->getTitle($dummyRequest, $route);
throw new ValidationException(FormatUtilities::createFormattedMessage(
t(
'Items cannot be copied to the administrator\'s "@title" list.',
[
'@title' => $title,
]),
t('Please select a subfolder instead.')));
}
if ($destinationId === FolderShareInterface::PUBLIC_ROOT_LIST) {
$routeProvider = \Drupal::service('router.route_provider');
$titleResolver = \Drupal::service('title_resolver');
$dummyRequest = new Request();
$route = $routeProvider->getRouteByName(
Constants::ROUTE_ROOT_ITEMS_PUBLIC);
$title = $titleResolver->getTitle($dummyRequest, $route);
throw new ValidationException(FormatUtilities::createFormattedMessage(
t(
'Items cannot be copied to the "@title" list.',
[
'@title' => $title,
]),
t('Please select a subfolder instead.')));
}
parent::validateDestinationConstraints();
}
/**
* {@inheritdoc}
*/
public function validateParameters() {
if ($this->parametersValidated === TRUE) {
// Already validated.
return;
}
//
// Validate destination.
// ---------------------
// There must be a destination ID. It must be a valid ID. It must
// not be one of the selected items. And it must not be a descendant
// of the selected items.
//
// A positive destination ID is for a folder to receive the selected
// items.
//
// A negative destination ID is for the user's root list.
$destinationId = $this->getDestinationId();
if ($destinationId < 0) {
// Destination is the user's root list. Nothing further to validate.
$this->parametersValidated = TRUE;
return;
}
// Destination is a specific folder.
$destination = FolderShare::load($destinationId);
if ($destination === NULL) {
// Destination ID is not valid. This should have been caught
// well before this validation stage.
throw new ValidationException(FormatUtilities::createFormattedMessage(
t(
'@method was called with an invalid entity ID "@id".',
[
'@method' => __METHOD__,
'@id' => $destinationId,
])));
}
// Verify that the destination is not in the selection. That would
// be a copy to self, which is not valid.
$selectionIds = $this->getSelectionIds();
if (in_array($destinationId, $selectionIds) === TRUE) {
throw new ValidationException(FormatUtilities::createFormattedMessage(
t('Items cannot be copied into themselves.')));
}
// Verify that the destination is not a descendant of the selection.
// That would be a recursive tree copy into itself.
foreach ($selectionIds as $id) {
$item = FolderShare::load($id);
if ($item === NULL) {
// The item does not exist.
continue;
}
if ($destination->isDescendantOfFolderId($item->id()) === TRUE) {
throw new ValidationException(FormatUtilities::createFormattedMessage(
t('Items cannot be copied into their own subfolders.')));
}
unset($item);
}
unset($destination);
$this->parametersValidated = TRUE;
// Garbage collect.
gc_collect_cycles();
}
/*--------------------------------------------------------------------
*
* Configuration form.
*
*--------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function getDescription(bool $forPage) {
// The description varies for page vs. dialog:
//
// - Dialog: The description is longer and has the form "Copy OPERAND
// to a new location, including all of its contents?" For a single item,
// OPERAND is the NAME of the file/folder.
//
// - Page: The description is as for a dialog, except that the single
// item form is not included because it is already in the title.
$selectionIds = $this->getSelectionIds();
if (count($selectionIds) === 1) {
// There is only one item. Load it.
$item = FolderShare::load(reset($selectionIds));
if ($forPage === TRUE) {
// Page description. The page title already gives the name of the
// item to be deleted. Don't include the item's name again here.
if ($item->isFolder() === FALSE) {
return [
t(
'Copy this @operand to a new location.',
[
'@operand' => FolderShare::translateKind($item->getKind()),
]),
];
}
return [
t('Copy this folder to a new location, including all of its contents.'),
];
}
// Dialog description. Include the name of the item to be deleted.
if ($item->isFolder() === FALSE) {
return [
t(
'Copy "@name" to a new location.',
[
'@name' => $item->getName(),
]),
];
}
return [
t(
'Copy "@name" to a new location, including all of its contents.',
[
'@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($selectionKinds) === 1) {
$operand = FolderShare::translateKinds(key($selectionKinds));
}
else {
$operand = FolderShare::translateKinds('items');
}
// Dialog and page description.
//
// Use the count and kind and end in a question mark. For folders,
// include a reminder that all their contents are deleted too.
if (isset($selectionKinds[FolderShare::FOLDER_KIND]) === FALSE) {
return [
t(
'Copy these @operand to a new location.',
[
'@operand' => $operand,
]),
];
}
return [
t(
'Copy these @operand to a new location, including all of their contents?',
[
'@operand' => $operand,
]),
];
}
/**
* {@inheritdoc}
*/
public function getTitle(bool $forPage) {
// The title varies for page vs. dialog:
//
// - Dialog: "Copy".
//
// - Page: The title is longer and has the form "Copy OPERAND", where
// OPERAND can be the name of the item if one item is being deleted,
// or the count and kinds if multiple items are being deleted. This
// follows Drupal convention.
if ($forPage === FALSE) {
return t('Copy');
}
$selectionIds = $this->getSelectionIds();
if (count($selectionIds) === 1) {
// Page title. There is only one item. Load it.
$item = FolderShare::load($selectionIds[0]);
return t(
'Copy "@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');
}
// Include the count and operand kind.
return t(
"Copy @count @operand?",
[
'@count' => count($selectionIds),
'@operand' => $operand,
]);
}
/**
* {@inheritdoc}
*/
public function getSubmitButtonName() {
return t('Copy');
}
/*--------------------------------------------------------------------
*
* Execute.
*
*--------------------------------------------------------------------*/
/**
* {@inheritdoc}
*/
public function execute() {
$ids = $this->getSelectionIds();
$destination = $this->getDestination();
try {
if ($destination === NULL) {
FolderShare::copyToRootMultiple($ids);
}
else {
FolderShare::copyToFolderMultiple($ids, $destination);
}
}
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 copied.",
"@count items have been copied."),
'status');
}
}
}
