link_filebrowser-1.0.1/src/Controller/LinkFileBrowserController.php
src/Controller/LinkFileBrowserController.php
<?php
namespace Drupal\link_filebrowser\Controller;
use LZCompressor\LZString;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\File\FileExists;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* Returns responses for Link filebrowser routes.
*/
class LinkFileBrowserController extends ControllerBase {
/**
* Link file browser Controller constructor.
*
* @param \Drupal\Core\Render\RendererInterface $renderer
* The render service.
* @param \Drupal\Core\File\FileSystemInterface $fileSystem
* The file system service.
* @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager
* The stream wrapper manager.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
*/
public function __construct(protected RendererInterface $renderer, protected FileSystemInterface $fileSystem, protected StreamWrapperManagerInterface $streamWrapperManager, protected AccountInterface $account) {
}
/**
* {@inheritdoc}
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The Drupal service container.
*
* @return static
*/
public static function create(ContainerInterface $container) {
return new self(
$container->get('renderer'),
$container->get('file_system'),
$container->get('stream_wrapper_manager'),
$container->get('current_user'),
);
}
/**
* {@inheritDoc}
*/
public function listFiles(Request $request, $crypt): JsonResponse {
$selection_settings = $this->keyValue('filebrowser')->get($crypt);
if (!empty($selection_settings)) {
$path = [
$this->streamWrapperManager->getViaScheme('public')->getDirectoryPath(),
$selection_settings['folder'],
$request->request->get('directory'),
];
$directory = $this->cleanPath(implode('/', array_filter($path)), TRUE);
$all = !empty($selection_settings['all']);
$maxLevel = $all ? TRUE : 2;
$response = $this->scan($directory, $maxLevel);
}
return new JsonResponse($response ?? []);
}
/**
* {@inheritDoc}
*/
public function scanFiles(Request $request, $crypt): JsonResponse {
$selection_settings = $this->keyValue('filebrowser')->get($crypt);
if (!empty($selection_settings)) {
$entity_type = $selection_settings['entity_type'];
$entity_bundle = $selection_settings['entity_bundle'];
$field_name = $selection_settings['field_name'];
$folder = $selection_settings['folder'];
$all = !empty($selection_settings['all']);
$maxLevel = $all ? TRUE : 2;
$directory = $request->request->get('directory');
$public_path = $this->streamWrapperManager->getViaScheme('public')->getDirectoryPath();
$system_path = $this->cleanPath("$public_path/$folder/$directory", TRUE);
if (!(!empty($system_path) && $this->fileSystem->realpath($system_path))) {
$system_path = $this->getFolder($entity_type, $entity_bundle, $field_name, $system_path);
}
$response = $this->scan($system_path, $maxLevel);
}
return new JsonResponse($response ?? []);
}
/**
* {@inheritDoc}
*/
public function getFolder($entity_type, $entity_bundle, $field_name, $directory = ''): string {
$temp = explode('-', $field_name);
$field_name = $temp[0];
$form_mode = 'default';
$field_widget_settings = $this->entityTypeManager()
->getStorage('entity_form_display')
->load($entity_type . '.' . $entity_bundle . '.' . $form_mode)
->getComponent($field_name)['settings'];
if (stripos($field_widget_settings["folder"], '{') !== FALSE) {
$renderable = [
'#type' => 'inline_template',
'#template' => $field_widget_settings["folder"],
'#context' => [
'entity_type' => $entity_type,
'entity_bundle' => $entity_bundle,
'field_name' => $field_name,
],
];
$field_widget_settings["folder"] = $this->renderer->render($renderable)->__toString();
}
$field_widget_settings["folder"] .= $directory;
$directory = !empty($field_widget_settings["folder"]) ? DIRECTORY_SEPARATOR . $field_widget_settings["folder"] : DIRECTORY_SEPARATOR;
// Get directory in field widget config.
$public_path = $this->streamWrapperManager->getViaScheme('public')->getDirectoryPath();
return $public_path . $directory;
}
/**
* Get files in directory.
*
* {@inheritDoc}
*/
public function scan($dir, $maxLevel = 2, $level = 0): array|object {
$files = [];
$level++;
// Is there actually such a folder/file?
if (file_exists($dir)) {
foreach (scandir($dir) as $f) {
if (!$f || $f[0] == '.') {
// Ignore hidden files.
continue;
}
if (is_dir($dir . '/' . $f)) {
// The path is a folder.
$files[$f] = NULL;
if (!$maxLevel || $level < $maxLevel) {
$files[$f] = $this->scan($dir . '/' . $f, $maxLevel, $level);
}
}
else {
// It is a file.
$file_path = '/' . $dir . '/' . $f;
$extension = pathinfo($file_path, PATHINFO_EXTENSION);
if ($extension !== 'php') {
$files[$f] = $file_path;
}
}
}
}
return $files ?: (object) [];
}
/**
* {@inheritdoc}
*/
public function uploadFile(Request $request, $crypt): JsonResponse {
$selection_settings = $this->keyValue('filebrowser')->get($crypt);
if ($selection_settings && $this->currentUser()->hasPermission('add link file browser')) {
$public_path = $this->streamWrapperManager->getViaScheme('public')->getDirectoryPath();
$folder = $selection_settings['folder'];
$directory = $request->get('path');
$type = $request->get('type');
$directory_upload = $this->cleanPath("$public_path/$folder/$directory", TRUE);
$this->fileSystem->prepareDirectory($directory_upload, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
if (!$type) {
$uploadedFile = $request->files->get('file');
if (!$uploadedFile) {
return new JsonResponse(['error' => $this->t('No files were sent.')], 400);
}
$extends = explode('.', $uploadedFile->getClientOriginalName());
if (strtolower(end($extends)) === 'php') {
return new JsonResponse(['error' => $this->t('File is not supported.')], 400);
}
$path_upload = $directory_upload . DIRECTORY_SEPARATOR . $uploadedFile->getClientOriginalName();
$data = file_get_contents($uploadedFile->getPathname());
$pathSaveFile = $this->fileSystem->saveData($data, $path_upload, FileExists::Replace);
if ($pathSaveFile) {
return new JsonResponse([
'status' => 'success',
'filename' => $uploadedFile->getClientOriginalName(),
'path' => "/$pathSaveFile",
]);
}
else {
return new JsonResponse(['error' => $this->t('Cannot save file.')], 500);
}
}
return new JsonResponse([
'status' => 'success',
'path' => $directory,
]);
}
else {
return new JsonResponse(['error' => $this->t('No permission.')], 403);
}
}
/**
* {@inheritdoc}
*/
public function cleanPath(string $path, $isReplaceSeparatorSystem = FALSE) :string {
$path = preg_replace('/^\/+|\/+$/', '', $path);
$path = preg_replace('/\/+/', '/', $path);
if ($isReplaceSeparatorSystem) {
$path = str_replace('/', DIRECTORY_SEPARATOR, $path);
}
return html_entity_decode($path);
}
/**
* {@inheritdoc}
*
* @throws \Exception
*/
public function shareLink(Request $request, $crypt): BinaryFileResponse|Response {
if (empty($crypt) || $crypt === 'downloaded') {
return new Response($this->t('File downloaded'), 200);
}
$unPack = LZString::decompressFromEncodedURIComponent($crypt);
if ($unPack === FALSE) {
return new Response('Failed to decompress data', 400);
}
$unPack = Json::decode($unPack);
$access = TRUE;
if (array_key_exists('u', $unPack) || array_key_exists('r', $unPack)) {
$access = FALSE;
if (array_key_exists('u', $unPack)) {
if (in_array($this->account->id(), $unPack['u'])) {
$access = TRUE;
}
}
if (array_key_exists('r', $unPack)) {
if (!$access && array_intersect($this->account->getRoles(), $unPack['r'])) {
$access = TRUE;
}
}
}
if ($access) {
// Sanitize path.
$folderFile = str_replace(['../', '..\\'], '', $unPack['p']);
$path = urldecode($folderFile);
$path = $this->cleanPath($path, TRUE);
if (is_file($path)) {
return $this->downloadFile($path);
}
elseif (is_dir($path)) {
$zipPath = sys_get_temp_dir() . '/' . basename($path) . '_' . uniqid() . '.zip';
$this->zipFolder($path, $zipPath);
return $this->downloadFile($zipPath, TRUE);
}
else {
return new Response('File not found: ' . basename($path), 404);
}
}
else {
throw new AccessDeniedHttpException('You do not have permission to access this page.');
}
}
/**
* Create zip file form directory.
*
* @throws \Exception
*/
private function zipFolder($folderPath, $zipFilePath): void {
$zip = new \ZipArchive();
if ($zip->open($zipFilePath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== TRUE) {
throw new \Exception('Can not create zip.');
}
$folderPath = realpath($folderPath);
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($folderPath),
\RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = substr($filePath, strlen($folderPath) + 1);
$zip->addFile($filePath, $relativePath);
}
}
$zip->close();
}
/**
* Download file.
*/
private function downloadFile($filePath, $deleteAfter = FALSE): BinaryFileResponse {
$response = new BinaryFileResponse($filePath);
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
basename($filePath)
);
if ($deleteAfter) {
$response->deleteFileAfterSend(TRUE);
}
return $response;
}
/**
* Share page.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request service.
* @param string $crypt
* The crypt link.
*
* @return array
* A renderable array.
*/
public function sharePage(Request $request, $crypt): array {
$url_share = Url::fromRoute('link_filebrowser.share', ['crypt' => $crypt], ['absolute' => TRUE]);
$redirect = $request->get('r');
return [
'#theme' => 'share_page',
'#redirect' => $redirect,
'#url' => $url_share,
'#attached' => [
'library' => [
'link_filebrowser/browser.share_page',
],
],
];
}
}
