filebrowser-8.x-2.x-dev/src/Controller/DefaultController.php
src/Controller/DefaultController.php
<?php
namespace Drupal\filebrowser\Controller;
use Drupal;
use Drupal\Core\Ajax\AfterCommand;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\RemoveCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Render\HtmlResponse;
use Drupal\Core\StreamWrapper\StreamWrapperManager;
use Drupal\Core\Url;
use Drupal\filebrowser\Filebrowser;
use Drupal\filebrowser\FilebrowserManager;
use Drupal\filebrowser\Services\FilebrowserValidator;
use Drupal\filebrowser\Services\Common;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use ZipArchive;
/**
* Default controller for the filebrowser module.
*/
class DefaultController extends ControllerBase {
/**
* @var \Drupal\filebrowser\FilebrowserManager $filebrowserManager
*/
protected $filebrowserManager;
/**
* @var \Drupal\filebrowser\Services\FilebrowserValidator
*/
protected $validator;
/**
* @var \Drupal\filebrowser\Services\Common
*/
protected $common;
/**
* @var \Drupal\Core\File\FileUrlGeneratorInterface
*/
protected $fileUrlGenerator;
/**
* DefaultController constructor.
*
* @param FilebrowserManager $filebrowserManager
* @param FilebrowserValidator $validator
* @param Common $common
*
*/
public function __construct(FilebrowserManager $filebrowserManager, FilebrowserValidator $validator, Common $common, FileUrlGeneratorInterface $fileUrlGenerator) {
$this->filebrowserManager = $filebrowserManager;
$this->validator = $validator;
$this->common = $common;
$this->fileUrlGenerator = $fileUrlGenerator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('filebrowser.manager'),
$container->get('filebrowser.validator'),
$container->get('filebrowser.common'),
$container->get('file_url_generator')
);
}
/**
* Callback for
* route: filebrowser.page_download
* path: filebrowser/download/{fid}
* @param int $fid Id of the file selected in the download link
* @return RedirectResponse | StreamedResponse
*/
public function pageDownload($fid) {
/* @var NodeInterface $node **/
$node_content = $this->common->nodeContentLoad($fid);
// If $fid doesn't point to a valid file, $node_content is FALSE.
if (!$node_content) {
throw new NotFoundHttpException();
}
$file_data = unserialize($node_content['file_data']);
$filebrowser = new Filebrowser($node_content['nid']);
// Download method is 'public' and the uri is public://
// we will send the browser to the file location.
// todo:
// RedirectResponse needs a relative path so we will convert the full url into a relative path
// This is done here, but should be moved to a better place in Common
$file_path = $this->fileUrlGenerator->transformRelative($file_data->url);
if ($filebrowser->downloadManager == 'public' && StreamWrapperManager::getScheme($file_data->uri) == 'public') {
return new RedirectResponse($file_path);
}
// we will stream the file
else {
// load the node containing the file so we can check
// for the access rights
// User needs "view" permission on the node to download the file
$node = Node::load($node_content['nid']);
if (isset($node) && $node->access('view')) {
// Stream the file
$file = $file_data->uri;
// in case you need the container
//$container = $this->container;
$response = new StreamedResponse(function () use ($file) {
$handle = fopen($file, 'r') or exit("Cannot open file $file");
while (!feof($handle)) {
$buffer = fread($handle, 1024);
echo $buffer;
flush();
}
fclose($handle);
});
$response->headers->set('Content-Type', $file_data->mimetype);
$content_disposition = $filebrowser->forceDownload ? 'attachment' : 'inline';
$response->headers->set('Content-Disposition', $content_disposition . '; filename="' . $file_data->filename . '";');
return $response;
}
elseif (isset($node)) {
throw new AccessDeniedHttpException();
}
else {
throw new NotFoundHttpException();
}
}
}
/**
* @param int $nid
* @param int $query_fid In case of a sub folder, the fid of the sub folder
* @param string $op - The operation called by the submit button ('upload', 'delete')
* @param string $method - Defines if Ajax should be used
* @param string|null $fids A string containing the field id's of the files
* to be processed.
*
* @return array | AjaxResponse
*/
public function actionFormSubmitAction($nid, $query_fid, $op, $method, $fids = NULL) {
// $op == archive does not use a form
if ($op == 'archive') {
return $this->actionArchive($nid, $fids);
}
// continue for buttons needing a form
// Determine the requested form name
$op = ucfirst($op);
$form_name = 'Drupal\filebrowser\Form\\' . $op . 'Form';
//debug($form_name);
$form = Drupal::formBuilder()->getForm($form_name, $nid, $query_fid, $fids, $method == 'ajax');
// If JS enabled
if ($method == 'ajax' && $op <> 'Archive') {
// Create an AjaxResponse.
$response = new AjaxResponse();
// Remove old error in case they exist.
$response->addCommand(new RemoveCommand('#filebrowser-form-action-error'));
// Remove slide-downs if they exist.
$response->addCommand(new RemoveCommand('.form-in-slide-down'));
// Insert event details after event.
$response->addCommand(new AfterCommand('#form-action-actions-wrapper', $form));
return $response;
}
else {
return $form;
}
}
public function inlineDescriptionForm($nid, $query_fid, $fids) {
return \Drupal::formBuilder()->getForm('Drupal\filebrowser\Form\InlineDescriptionForm', $nid, $query_fid, $fids);
}
/**
* @function
* Creates a ZIP archive from local or S3 files and directories.
*
* @param string $fids
* Comma-separated list of file entity IDs.
* @param int $nid
* Node id of the node calling the archive
*
* @return Response
* A file download response on success or a redirect with an error message on failure.
*/
public function actionArchive($nid, $fids): Response {
$fid_array = explode(',', $fids);
$itemsToArchive = $this->common->nodeContentLoadMultiple($fid_array);
$file_system = \Drupal::service('file_system');
$archive_path = 'public://archive_' . uniqid() . '.zip';
$zip_path = $file_system->realpath($archive_path);
$archive = new \ZipArchive();
$created = $archive->open($zip_path, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
if ($created !== TRUE) {
\Drupal::logger('filebrowser')->error('Cannot create archive at @path (error code: @code)', [
'@path' => $zip_path,
'@code' => $created,
]);
\Drupal::messenger()->addError('Cannot create archive at @path (error code: @code)');
$route = $nid
? Url::fromRoute('entity.node.canonical', ['node' => $nid])
: Url::fromRoute('<front>');
return new RedirectResponse($route->toString());
}
foreach ($itemsToArchive as $item) {
$file_data = unserialize($item['file_data']);
$uri = $file_data->uri;
$scheme = \Drupal::service('stream_wrapper_manager')->getScheme($uri);
$is_local = $file_system->realpath($uri) !== false;
$filename = $file_data->filename;
if ($is_local) {
if ($file_data->type === 'file') {
$file_path = $file_system->realpath($uri);
$archive->addFile($file_path, $filename);
}
elseif ($file_data->type === 'dir') {
$dirPath = $file_system->realpath($uri);
if (!$dirPath || !is_dir($dirPath)) {
\Drupal::logger('filebrowser')->error('Directory not found or not accessible: @path', ['@path' => $uri]);
continue;
}
$rootDirName = basename($dirPath);
$archive->addEmptyDir($rootDirName);
$iterator = new \RecursiveDirectoryIterator($dirPath, \RecursiveDirectoryIterator::SKIP_DOTS);
$dirFiles = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
$baseLen = strlen($dirPath) + 1;
foreach ($dirFiles as $fileInfo) {
$realPath = $fileInfo->getRealPath();
$relativePath = $rootDirName . '/' . substr($realPath, $baseLen);
if ($fileInfo->isDir()) {
$archive->addEmptyDir($relativePath);
} elseif ($fileInfo->isFile()) {
$archive->addFromString($relativePath, file_get_contents($realPath));
}
}
}
}
else {
// Remote storage (e.g., S3 or other cloud)
if ($file_data->type === 'file') {
$temp_uri = 'temporary://' . $filename;
if ($file_system->copy($uri, $temp_uri, 1)) {
$file_path = $file_system->realpath($temp_uri);
$archive->addFile($file_path, $filename);
}
else {
\Drupal::logger('filebrowser')->error(t('Failed to copy remote file @file', ['@file' => $uri]));
}
}
else {
\Drupal::logger('filebrowser')->info('Skipping remote directory @uri', ['@uri' => $uri]);
\Drupal::messenger()->addWarning(t('Archiving cloud directory @filename is not supported. Open the directory and select the files manually.', ['@filename' => $filename]));
}
}
}
$archive->close();
if (!file_exists($zip_path)) {
\Drupal::logger('filebrowser')->error(t('ZIP file was not created: @path', ['@path' => $zip_path]));
\Drupal::messenger()->addError(t('ZIP file was not created.'));
$route = $nid
? Url::fromRoute('entity.node.canonical', ['node' => $nid])
: Url::fromRoute('<front>');
return new RedirectResponse($route->toString());
}
$response = new BinaryFileResponse($zip_path);
$response->deleteFileAfterSend(true);
$response->trustXSendfileTypeHeader();
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT);
$response->prepare(Request::createFromGlobals());
return $response;
}
public function noItemsError() {
$error = $this->t('You didn\'t select any item');
// Create an AjaxResponse.
$response = new AjaxResponse();
// Remove old events
$response->addCommand(new RemoveCommand('#filebrowser-form-action-error'));
$response->addCommand(new RemoveCommand('.form-in-slide-down'));
// Insert event details after event.
// $response->addCommand(new AfterCommand('#form-action-actions-wrapper', $html));
$response->addCommand(new AlertCommand($error));
return $response;
}
}
