vendor/pimcore/pimcore/bundles/AdminBundle/Controller/Admin/Asset/AssetController.php line 245

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Bundle\AdminBundle\Controller\Admin\Asset;
  15. use Pimcore\Bundle\AdminBundle\Controller\Admin\ElementControllerBase;
  16. use Pimcore\Bundle\AdminBundle\Controller\Traits\AdminStyleTrait;
  17. use Pimcore\Bundle\AdminBundle\Controller\Traits\ApplySchedulerDataTrait;
  18. use Pimcore\Bundle\AdminBundle\Helper\GridHelperService;
  19. use Pimcore\Bundle\AdminBundle\Security\CsrfProtectionHandler;
  20. use Pimcore\Config;
  21. use Pimcore\Controller\KernelControllerEventInterface;
  22. use Pimcore\Controller\Traits\ElementEditLockHelperTrait;
  23. use Pimcore\Db\Helper;
  24. use Pimcore\Event\Admin\ElementAdminStyleEvent;
  25. use Pimcore\Event\AdminEvents;
  26. use Pimcore\Event\AssetEvents;
  27. use Pimcore\Event\Model\Asset\ResolveUploadTargetEvent;
  28. use Pimcore\File;
  29. use Pimcore\Loader\ImplementationLoader\Exception\UnsupportedException;
  30. use Pimcore\Logger;
  31. use Pimcore\Messenger\AssetPreviewImageMessage;
  32. use Pimcore\Model;
  33. use Pimcore\Model\Asset;
  34. use Pimcore\Model\DataObject\ClassDefinition\Data\ManyToManyRelation;
  35. use Pimcore\Model\DataObject\Concrete;
  36. use Pimcore\Model\Element;
  37. use Pimcore\Model\Element\ValidationException;
  38. use Pimcore\Model\Metadata;
  39. use Pimcore\Model\Schedule\Task;
  40. use Pimcore\Tool;
  41. use Symfony\Component\EventDispatcher\GenericEvent;
  42. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  43. use Symfony\Component\HttpFoundation\JsonResponse;
  44. use Symfony\Component\HttpFoundation\Request;
  45. use Symfony\Component\HttpFoundation\Response;
  46. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  47. use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
  48. use Symfony\Component\HttpFoundation\StreamedResponse;
  49. use Symfony\Component\HttpKernel\Event\ControllerEvent;
  50. use Symfony\Component\Mime\MimeTypes;
  51. use Symfony\Component\Process\Process;
  52. use Symfony\Component\Routing\Annotation\Route;
  53. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  54. use Symfony\Contracts\Translation\TranslatorInterface;
  55. /**
  56.  * @Route("/asset")
  57.  *
  58.  * @internal
  59.  */
  60. class AssetController extends ElementControllerBase implements KernelControllerEventInterface
  61. {
  62.     use AdminStyleTrait;
  63.     use ElementEditLockHelperTrait;
  64.     use ApplySchedulerDataTrait;
  65.     /**
  66.      * @var Asset\Service
  67.      */
  68.     protected $_assetService;
  69.     /**
  70.      * @Route("/tree-get-root", name="pimcore_admin_asset_treegetroot", methods={"GET"})
  71.      *
  72.      * @param Request $request
  73.      *
  74.      * @return JsonResponse
  75.      */
  76.     public function treeGetRootAction(Request $request)
  77.     {
  78.         return parent::treeGetRootAction($request);
  79.     }
  80.     /**
  81.      * @Route("/delete-info", name="pimcore_admin_asset_deleteinfo", methods={"GET"})
  82.      *
  83.      * @param Request $request
  84.      * @param EventDispatcherInterface $eventDispatcher
  85.      *
  86.      * @return JsonResponse
  87.      */
  88.     public function deleteInfoAction(Request $requestEventDispatcherInterface $eventDispatcher)
  89.     {
  90.         return parent::deleteInfoAction($request$eventDispatcher);
  91.     }
  92.     /**
  93.      * @Route("/get-data-by-id", name="pimcore_admin_asset_getdatabyid", methods={"GET"})
  94.      *
  95.      * @param Request $request
  96.      *
  97.      * @return JsonResponse
  98.      */
  99.     public function getDataByIdAction(Request $requestEventDispatcherInterface $eventDispatcher)
  100.     {
  101.         $assetId = (int)$request->get('id');
  102.         $type = (string)$request->get('type');
  103.         $asset Asset::getById($assetId);
  104.         if (!$asset instanceof Asset) {
  105.             return $this->adminJson(['success' => false'message' => "asset doesn't exist"]);
  106.         }
  107.         // check for lock on non-folder items only.
  108.         if ($type !== 'folder' && ($asset->isAllowed('publish') || $asset->isAllowed('delete'))) {
  109.             if (Element\Editlock::isLocked($assetId'asset')) {
  110.                 return $this->getEditLockResponse($assetId'asset');
  111.             }
  112.             Element\Editlock::lock($request->get('id'), 'asset');
  113.         }
  114.         $asset = clone $asset;
  115.         $asset->setParent(null);
  116.         $asset->setStream(null);
  117.         $data $asset->getObjectVars();
  118.         $data['locked'] = $asset->isLocked();
  119.         if ($asset instanceof Asset\Text) {
  120.             if ($asset->getFileSize() < 2000000) {
  121.                 // it doesn't make sense to show a preview for files bigger than 2MB
  122.                 $data['data'] = \ForceUTF8\Encoding::toUTF8($asset->getData());
  123.             } else {
  124.                 $data['data'] = false;
  125.             }
  126.         } elseif ($asset instanceof Asset\Document) {
  127.             $data['pdfPreviewAvailable'] = (bool)$this->getDocumentPreviewPdf($asset);
  128.         } elseif ($asset instanceof Asset\Video) {
  129.             $videoInfo = [];
  130.             if (\Pimcore\Video::isAvailable()) {
  131.                 $config Asset\Video\Thumbnail\Config::getPreviewConfig();
  132.                 $thumbnail $asset->getThumbnail($config, ['mp4']);
  133.                 if ($thumbnail) {
  134.                     if ($thumbnail['status'] == 'finished') {
  135.                         $videoInfo['previewUrl'] = $thumbnail['formats']['mp4'];
  136.                         $videoInfo['width'] = $asset->getWidth();
  137.                         $videoInfo['height'] = $asset->getHeight();
  138.                         $metaData $asset->getSphericalMetaData();
  139.                         if (isset($metaData['ProjectionType']) && strtolower($metaData['ProjectionType']) == 'equirectangular') {
  140.                             $videoInfo['isVrVideo'] = true;
  141.                         }
  142.                     }
  143.                 }
  144.             }
  145.             $data['videoInfo'] = $videoInfo;
  146.         } elseif ($asset instanceof Asset\Image) {
  147.             $imageInfo = [];
  148.             $previewUrl $this->generateUrl('pimcore_admin_asset_getimagethumbnail', [
  149.                 'id' => $asset->getId(),
  150.                 'treepreview' => true,
  151.                 '_dc' => time(),
  152.             ]);
  153.             if ($asset->isAnimated()) {
  154.                 $previewUrl $this->generateUrl('pimcore_admin_asset_getasset', [
  155.                     'id' => $asset->getId(),
  156.                     '_dc' => time(),
  157.                 ]);
  158.             }
  159.             $imageInfo['previewUrl'] = $previewUrl;
  160.             if ($asset->getWidth() && $asset->getHeight()) {
  161.                 $imageInfo['dimensions'] = [];
  162.                 $imageInfo['dimensions']['width'] = $asset->getWidth();
  163.                 $imageInfo['dimensions']['height'] = $asset->getHeight();
  164.             }
  165.             $imageInfo['exiftoolAvailable'] = (bool)\Pimcore\Tool\Console::getExecutable('exiftool');
  166.             if (!$asset->getEmbeddedMetaData(false)) {
  167.                 $asset->getEmbeddedMetaData(truefalse); // read Exif, IPTC and XPM like in the old days ...
  168.             }
  169.             $data['imageInfo'] = $imageInfo;
  170.         }
  171.         $predefinedMetaData Metadata\Predefined\Listing::getByTargetType('asset', [$asset->getType()]);
  172.         $predefinedMetaDataGroups = [];
  173.         /** @var Metadata\Predefined $item */
  174.         foreach ($predefinedMetaData as $item) {
  175.             if ($item->getGroup()) {
  176.                 $predefinedMetaDataGroups[$item->getGroup()] = true;
  177.             }
  178.         }
  179.         $data['predefinedMetaDataGroups'] = array_keys($predefinedMetaDataGroups);
  180.         $data['properties'] = Element\Service::minimizePropertiesForEditmode($asset->getProperties());
  181.         $data['metadata'] = Asset\Service::expandMetadataForEditmode($asset->getMetadata());
  182.         $data['versionDate'] = $asset->getModificationDate();
  183.         $data['filesizeFormatted'] = $asset->getFileSize(true);
  184.         $data['filesize'] = $asset->getFileSize();
  185.         $data['fileExtension'] = File::getFileExtension($asset->getFilename());
  186.         $data['idPath'] = Element\Service::getIdPath($asset);
  187.         $data['userPermissions'] = $asset->getUserPermissions($this->getAdminUser());
  188.         $frontendPath $asset->getFrontendFullPath();
  189.         $data['url'] = preg_match('/^http(s)?:\\/\\/.+/'$frontendPath) ?
  190.             $frontendPath :
  191.             $request->getSchemeAndHttpHost() . $frontendPath;
  192.         $data['scheduledTasks'] = array_map(
  193.             static function (Task $task) {
  194.                 return $task->getObjectVars();
  195.             },
  196.             $asset->getScheduledTasks()
  197.         );
  198.         $this->addAdminStyle($assetElementAdminStyleEvent::CONTEXT_EDITOR$data);
  199.         $data['php'] = [
  200.             'classes' => array_merge([get_class($asset)], array_values(class_parents($asset))),
  201.             'interfaces' => array_values(class_implements($asset)),
  202.         ];
  203.         $event = new GenericEvent($this, [
  204.             'data' => $data,
  205.             'asset' => $asset,
  206.         ]);
  207.         $eventDispatcher->dispatch($eventAdminEvents::ASSET_GET_PRE_SEND_DATA);
  208.         $data $event->getArgument('data');
  209.         if ($asset->isAllowed('view')) {
  210.             return $this->adminJson($data);
  211.         }
  212.         throw $this->createAccessDeniedHttpException();
  213.     }
  214.     /**
  215.      * @Route("/tree-get-childs-by-id", name="pimcore_admin_asset_treegetchildsbyid", methods={"GET"})
  216.      *
  217.      * @param Request $request
  218.      *
  219.      * @return JsonResponse
  220.      */
  221.     public function treeGetChildsByIdAction(Request $requestEventDispatcherInterface $eventDispatcher)
  222.     {
  223.         $allParams array_merge($request->request->all(), $request->query->all());
  224.         $assets = [];
  225.         $cv false;
  226.         $asset Asset::getById($allParams['node']);
  227.         $filter $request->get('filter');
  228.         $limit = (int)$allParams['limit'];
  229.         if (!is_null($filter)) {
  230.             if (substr($filter, -1) != '*') {
  231.                 $filter .= '*';
  232.             }
  233.             $filter str_replace('*''%'$filter);
  234.             $limit 100;
  235.             $offset 0;
  236.         } elseif (!$allParams['limit']) {
  237.             $limit 100000000;
  238.         }
  239.         $offset = isset($allParams['start']) ? (int)$allParams['start'] : 0;
  240.         $filteredTotalCount 0;
  241.         if ($asset->hasChildren()) {
  242.             if ($allParams['view']) {
  243.                 $cv \Pimcore\Model\Element\Service::getCustomViewById($allParams['view']);
  244.             }
  245.             // get assets
  246.             $childrenList = new Asset\Listing();
  247.             $childrenList->addConditionParam('parentId = ?', [$asset->getId()]);
  248.             $childrenList->filterAccessibleByUser($this->getAdminUser(), $asset);
  249.             if (!is_null($filter)) {
  250.                 $childrenList->addConditionParam('CAST(assets.filename AS CHAR CHARACTER SET utf8) COLLATE utf8_general_ci LIKE ?', [$filter]);
  251.             }
  252.             $childrenList->setLimit($limit);
  253.             $childrenList->setOffset($offset);
  254.             $childrenList->setOrderKey("FIELD(assets.type, 'folder') DESC, CAST(assets.filename AS CHAR CHARACTER SET utf8) COLLATE utf8_general_ci ASC"false);
  255.             \Pimcore\Model\Element\Service::addTreeFilterJoins($cv$childrenList);
  256.             $beforeListLoadEvent = new GenericEvent($this, [
  257.                 'list' => $childrenList,
  258.                 'context' => $allParams,
  259.             ]);
  260.             $eventDispatcher->dispatch($beforeListLoadEventAdminEvents::ASSET_LIST_BEFORE_LIST_LOAD);
  261.             /** @var Asset\Listing $childrenList */
  262.             $childrenList $beforeListLoadEvent->getArgument('list');
  263.             $children $childrenList->load();
  264.             $filteredTotalCount $childrenList->getTotalCount();
  265.             foreach ($children as $childAsset) {
  266.                 $assetTreeNode $this->getTreeNodeConfig($childAsset);
  267.                 if ($assetTreeNode['permissions']['list'] == 1) {
  268.                     $assets[] = $assetTreeNode;
  269.                 }
  270.             }
  271.         }
  272.         //Hook for modifying return value - e.g. for changing permissions based on asset data
  273.         $event = new GenericEvent($this, [
  274.             'assets' => $assets,
  275.         ]);
  276.         $eventDispatcher->dispatch($eventAdminEvents::ASSET_TREE_GET_CHILDREN_BY_ID_PRE_SEND_DATA);
  277.         $assets $event->getArgument('assets');
  278.         if ($allParams['limit']) {
  279.             return $this->adminJson([
  280.                 'offset' => $offset,
  281.                 'limit' => $limit,
  282.                 'total' => $asset->getChildAmount($this->getAdminUser()),
  283.                 'overflow' => !is_null($filter) && ($filteredTotalCount $limit),
  284.                 'nodes' => $assets,
  285.                 'filter' => $request->get('filter') ? $request->get('filter') : '',
  286.                 'inSearch' => (int)$request->get('inSearch'),
  287.             ]);
  288.         } else {
  289.             return $this->adminJson($assets);
  290.         }
  291.     }
  292.     /**
  293.      * @Route("/add-asset", name="pimcore_admin_asset_addasset", methods={"POST"})
  294.      *
  295.      * @param Request $request
  296.      * @param Config $config
  297.      *
  298.      * @return JsonResponse
  299.      */
  300.     public function addAssetAction(Request $requestConfig $config)
  301.     {
  302.         try {
  303.             $res $this->addAsset($request$config);
  304.             $response = [
  305.                 'success' => $res['success'],
  306.             ];
  307.             if ($res['success']) {
  308.                 $response['asset'] = [
  309.                     'id' => $res['asset']->getId(),
  310.                     'path' => $res['asset']->getFullPath(),
  311.                     'type' => $res['asset']->getType(),
  312.                 ];
  313.             }
  314.             return $this->adminJson($response);
  315.         } catch (\Exception $e) {
  316.             return $this->adminJson([
  317.                 'success' => false,
  318.                 'message' => $e->getMessage(),
  319.             ]);
  320.         }
  321.     }
  322.     /**
  323.      * @Route("/add-asset-compatibility", name="pimcore_admin_asset_addassetcompatibility", methods={"POST"})
  324.      *
  325.      * @param Request $request
  326.      * @param Config $config
  327.      *
  328.      * @return JsonResponse
  329.      */
  330.     public function addAssetCompatibilityAction(Request $requestConfig $config)
  331.     {
  332.         try {
  333.             // this is a special action for the compatibility mode upload (without flash)
  334.             $res $this->addAsset($request$config);
  335.             $response $this->adminJson([
  336.                 'success' => $res['success'],
  337.                 'msg' => $res['success'] ? 'Success' 'Error',
  338.                 'id' => $res['asset'] ? $res['asset']->getId() : null,
  339.                 'fullpath' => $res['asset'] ? $res['asset']->getRealFullPath() : null,
  340.                 'type' => $res['asset'] ? $res['asset']->getType() : null,
  341.             ]);
  342.             $response->headers->set('Content-Type''text/html');
  343.             return $response;
  344.         } catch (\Exception $e) {
  345.             return $this->adminJson([
  346.                 'success' => false,
  347.                 'message' => $e->getMessage(),
  348.             ]);
  349.         }
  350.     }
  351.     /**
  352.      * @Route("/exists", name="pimcore_admin_asset_exists", methods={"GET"})
  353.      *
  354.      * @param Request $request
  355.      *
  356.      * @return JsonResponse
  357.      *
  358.      * @throws \Exception
  359.      */
  360.     public function existsAction(Request $request)
  361.     {
  362.         $parentAsset \Pimcore\Model\Asset::getById((int)$request->get('parentId'));
  363.         return new JsonResponse([
  364.             'exists' => Asset\Service::pathExists($parentAsset->getRealFullPath().'/'.$request->get('filename')),
  365.         ]);
  366.     }
  367.     /**
  368.      * @param Request $request
  369.      * @param Config $config
  370.      *
  371.      * @return array
  372.      *
  373.      * @throws \Exception
  374.      */
  375.     protected function addAsset(Request $requestConfig $config)
  376.     {
  377.         $defaultUploadPath $config['assets']['default_upload_path'] ?? '/';
  378.         if (array_key_exists('Filedata'$_FILES)) {
  379.             $filename $_FILES['Filedata']['name'];
  380.             $sourcePath $_FILES['Filedata']['tmp_name'];
  381.         } elseif ($request->get('type') == 'base64') {
  382.             $filename $request->get('filename');
  383.             $sourcePath PIMCORE_SYSTEM_TEMP_DIRECTORY '/upload-base64' uniqid() . '.tmp';
  384.             $data preg_replace('@^data:[^,]+;base64,@'''$request->get('data'));
  385.             File::put($sourcePathbase64_decode($data));
  386.         } else {
  387.             throw new \Exception('The filename of the asset is empty');
  388.         }
  389.         $parentId $request->get('parentId');
  390.         $parentPath $request->get('parentPath');
  391.         if ($request->get('dir') && $request->get('parentId')) {
  392.             // this is for uploading folders with Drag&Drop
  393.             // param "dir" contains the relative path of the file
  394.             $parent Asset::getById((int) $request->get('parentId'));
  395.             $dir $request->get('dir');
  396.             if (strpos($dir'..') !== false) {
  397.                 throw new \Exception('not allowed');
  398.             }
  399.             $newPath $parent->getRealFullPath() . '/' trim($dir'/ ');
  400.             $maxRetries 5;
  401.             $newParent null;
  402.             for ($retries 0$retries $maxRetries$retries++) {
  403.                 try {
  404.                     $newParent Asset\Service::createFolderByPath($newPath);
  405.                     break;
  406.                 } catch (\Exception $e) {
  407.                     if ($retries < ($maxRetries 1)) {
  408.                         $waitTime rand(100000900000); // microseconds
  409.                         usleep($waitTime); // wait specified time until we restart the transaction
  410.                     } else {
  411.                         // if the transaction still fail after $maxRetries retries, we throw out the exception
  412.                         throw $e;
  413.                     }
  414.                 }
  415.             }
  416.             if ($newParent) {
  417.                 $parentId $newParent->getId();
  418.             }
  419.         } elseif (!$request->get('parentId') && $parentPath) {
  420.             $parent Asset::getByPath($parentPath);
  421.             if ($parent instanceof Asset\Folder) {
  422.                 $parentId $parent->getId();
  423.             }
  424.         }
  425.         $filename Element\Service::getValidKey($filename'asset');
  426.         if (empty($filename)) {
  427.             throw new \Exception('The filename of the asset is empty');
  428.         }
  429.         $context $request->get('context');
  430.         if ($context) {
  431.             $context json_decode($contexttrue);
  432.             $context $context ?: [];
  433.             $this->validateManyToManyRelationAssetType($context$filename$sourcePath);
  434.             $event = new ResolveUploadTargetEvent($parentId$filename$context);
  435.             \Pimcore::getEventDispatcher()->dispatch($eventAssetEvents::RESOLVE_UPLOAD_TARGET);
  436.             $filename Element\Service::getValidKey($event->getFilename(), 'asset');
  437.             $parentId $event->getParentId();
  438.         }
  439.         if (!$parentId) {
  440.             $parentId Asset\Service::createFolderByPath($defaultUploadPath)->getId();
  441.         }
  442.         $parentAsset Asset::getById((int)$parentId);
  443.         if (!$request->get('allowOverwrite')) {
  444.             // check for duplicate filename
  445.             $filename $this->getSafeFilename($parentAsset->getRealFullPath(), $filename);
  446.         }
  447.         if (!$parentAsset->isAllowed('create')) {
  448.             throw $this->createAccessDeniedHttpException(
  449.                 'Missing the permission to create new assets in the folder: ' $parentAsset->getRealFullPath()
  450.             );
  451.         }
  452.         if (is_file($sourcePath) && filesize($sourcePath) < 1) {
  453.             throw new \Exception('File is empty!');
  454.         } elseif (!is_file($sourcePath)) {
  455.             throw new \Exception('Something went wrong, please check upload_max_filesize and post_max_size in your php.ini as well as the write permissions of your temporary directories.');
  456.         }
  457.         // check if there is a requested type and if matches the asset type of the uploaded file
  458.         $uploadAssetType $request->get('uploadAssetType');
  459.         if ($uploadAssetType) {
  460.             $mimetype MimeTypes::getDefault()->guessMimeType($sourcePath);
  461.             $assetType Asset::getTypeFromMimeMapping($mimetype$filename);
  462.             if ($uploadAssetType !== $assetType) {
  463.                 throw new \Exception("Mime type $mimetype does not match with asset type: $uploadAssetType");
  464.             }
  465.         }
  466.         if ($request->get('allowOverwrite') && Asset\Service::pathExists($parentAsset->getRealFullPath().'/'.$filename)) {
  467.             $asset Asset::getByPath($parentAsset->getRealFullPath().'/'.$filename);
  468.             $asset->setStream(fopen($sourcePath'rb'falseFile::getContext()));
  469.             $asset->save();
  470.         } else {
  471.             $asset Asset::create($parentId, [
  472.                 'filename' => $filename,
  473.                 'sourcePath' => $sourcePath,
  474.                 'userOwner' => $this->getAdminUser()->getId(),
  475.                 'userModification' => $this->getAdminUser()->getId(),
  476.             ]);
  477.         }
  478.         @unlink($sourcePath);
  479.         return [
  480.             'success' => true,
  481.             'asset' => $asset,
  482.         ];
  483.     }
  484.     /**
  485.      * @param string $targetPath
  486.      * @param string $filename
  487.      *
  488.      * @return string
  489.      */
  490.     protected function getSafeFilename($targetPath$filename)
  491.     {
  492.         $pathinfo pathinfo($filename);
  493.         $originalFilename $pathinfo['filename'];
  494.         $originalFileextension = empty($pathinfo['extension']) ? '' '.' $pathinfo['extension'];
  495.         $count 1;
  496.         if ($targetPath == '/') {
  497.             $targetPath '';
  498.         }
  499.         while (true) {
  500.             if (Asset\Service::pathExists($targetPath '/' $filename)) {
  501.                 $filename $originalFilename '_' $count $originalFileextension;
  502.                 $count++;
  503.             } else {
  504.                 return $filename;
  505.             }
  506.         }
  507.     }
  508.     /**
  509.      * @Route("/replace-asset", name="pimcore_admin_asset_replaceasset", methods={"POST", "PUT"})
  510.      *
  511.      * @param Request $request
  512.      * @param TranslatorInterface $translator
  513.      *
  514.      * @return JsonResponse
  515.      *
  516.      * @throws \Exception
  517.      */
  518.     public function replaceAssetAction(Request $requestTranslatorInterface $translator)
  519.     {
  520.         $asset Asset::getById((int) $request->get('id'));
  521.         $newFilename Element\Service::getValidKey($_FILES['Filedata']['name'], 'asset');
  522.         $mimetype MimeTypes::getDefault()->guessMimeType($_FILES['Filedata']['tmp_name']);
  523.         $newType Asset::getTypeFromMimeMapping($mimetype$newFilename);
  524.         if ($newType != $asset->getType()) {
  525.             return $this->adminJson([
  526.                 'success' => false,
  527.                 'message' => sprintf($translator->trans('asset_type_change_not_allowed', [], 'admin'), $asset->getType(), $newType),
  528.             ]);
  529.         }
  530.         $stream fopen($_FILES['Filedata']['tmp_name'], 'r+');
  531.         $asset->setStream($stream);
  532.         $asset->setCustomSetting('thumbnails'null);
  533.         $asset->setUserModification($this->getAdminUser()->getId());
  534.         $newFileExt File::getFileExtension($newFilename);
  535.         $currentFileExt File::getFileExtension($asset->getFilename());
  536.         if ($newFileExt != $currentFileExt) {
  537.             $newFilename preg_replace('/\.' $currentFileExt '$/i''.' $newFileExt$asset->getFilename());
  538.             $newFilename Element\Service::getSafeCopyName($newFilename$asset->getParent());
  539.             $asset->setFilename($newFilename);
  540.         }
  541.         if ($asset->isAllowed('publish')) {
  542.             $asset->save();
  543.             $response $this->adminJson([
  544.                 'id' => $asset->getId(),
  545.                 'path' => $asset->getRealFullPath(),
  546.                 'success' => true,
  547.             ]);
  548.             // set content-type to text/html, otherwise (when application/json is sent) chrome will complain in
  549.             // Ext.form.Action.Submit and mark the submission as failed
  550.             $response->headers->set('Content-Type''text/html');
  551.             return $response;
  552.         } else {
  553.             throw new \Exception('missing permission');
  554.         }
  555.     }
  556.     /**
  557.      * @Route("/add-folder", name="pimcore_admin_asset_addfolder", methods={"POST"})
  558.      *
  559.      * @param Request $request
  560.      *
  561.      * @return JsonResponse
  562.      */
  563.     public function addFolderAction(Request $request)
  564.     {
  565.         $success false;
  566.         $parentAsset Asset::getById((int)$request->get('parentId'));
  567.         $equalAsset Asset::getByPath($parentAsset->getRealFullPath() . '/' $request->get('name'));
  568.         if ($parentAsset->isAllowed('create')) {
  569.             if (!$equalAsset) {
  570.                 $asset Asset::create($request->get('parentId'), [
  571.                     'filename' => $request->get('name'),
  572.                     'type' => 'folder',
  573.                     'userOwner' => $this->getAdminUser()->getId(),
  574.                     'userModification' => $this->getAdminUser()->getId(),
  575.                 ]);
  576.                 $success true;
  577.             }
  578.         } else {
  579.             Logger::debug('prevented creating asset because of missing permissions');
  580.         }
  581.         return $this->adminJson(['success' => $success]);
  582.     }
  583.     /**
  584.      * @Route("/delete", name="pimcore_admin_asset_delete", methods={"DELETE"})
  585.      *
  586.      * @param Request $request
  587.      *
  588.      * @return JsonResponse
  589.      */
  590.     public function deleteAction(Request $request)
  591.     {
  592.         $type $request->get('type');
  593.         if ($type === 'childs') {
  594.             trigger_deprecation(
  595.                 'pimcore/pimcore',
  596.                 '10.4',
  597.                 'Type childs is deprecated. Use children instead'
  598.             );
  599.             $type 'children';
  600.         }
  601.         if ($type === 'children') {
  602.             $parentAsset Asset::getById((int) $request->get('id'));
  603.             $list = new Asset\Listing();
  604.             $list->setCondition('path LIKE ?', [Helper::escapeLike($parentAsset->getRealFullPath()) . '/%']);
  605.             $list->setLimit((int)$request->get('amount'));
  606.             $list->setOrderKey('LENGTH(path)'false);
  607.             $list->setOrder('DESC');
  608.             $deletedItems = [];
  609.             foreach ($list as $asset) {
  610.                 $deletedItems[$asset->getId()] = $asset->getRealFullPath();
  611.                 if ($asset->isAllowed('delete') && !$asset->isLocked()) {
  612.                     $asset->delete();
  613.                 }
  614.             }
  615.             return $this->adminJson(['success' => true'deleted' => $deletedItems]);
  616.         }
  617.         if ($request->get('id')) {
  618.             $asset Asset::getById((int) $request->get('id'));
  619.             if ($asset && $asset->isAllowed('delete')) {
  620.                 if ($asset->isLocked()) {
  621.                     return $this->adminJson([
  622.                         'success' => false,
  623.                         'message' => 'prevented deleting asset, because it is locked: ID: ' $asset->getId(),
  624.                     ]);
  625.                 }
  626.                 $asset->delete();
  627.                 return $this->adminJson(['success' => true]);
  628.             }
  629.         }
  630.         throw $this->createAccessDeniedHttpException();
  631.     }
  632.     /**
  633.      * @param Asset $element
  634.      *
  635.      * @return array
  636.      */
  637.     protected function getTreeNodeConfig($element)
  638.     {
  639.         $asset $element;
  640.         $permissions =  $asset->getUserPermissions($this->getAdminUser());
  641.         $tmpAsset = [
  642.             'id' => $asset->getId(),
  643.             'key' => $element->getKey(),
  644.             'text' => htmlspecialchars($asset->getFilename()),
  645.             'type' => $asset->getType(),
  646.             'path' => $asset->getRealFullPath(),
  647.             'basePath' => $asset->getRealPath(),
  648.             'locked' => $asset->isLocked(),
  649.             'lockOwner' => $asset->getLocked() ? true false,
  650.             'elementType' => 'asset',
  651.             'permissions' => [
  652.                 'remove' => $permissions['delete'],
  653.                 'settings' => $permissions['settings'],
  654.                 'rename' => $permissions['rename'],
  655.                 'publish' => $permissions['publish'],
  656.                 'view' => $permissions['view'],
  657.                 'list' => $permissions['list'],
  658.             ],
  659.         ];
  660.         $hasChildren $asset->getDao()->hasChildren($this->getAdminUser());
  661.         // set type specific settings
  662.         if ($asset instanceof Asset\Folder) {
  663.             $tmpAsset['leaf'] = false;
  664.             $tmpAsset['expanded'] = !$hasChildren;
  665.             $tmpAsset['loaded'] = !$hasChildren;
  666.             $tmpAsset['permissions']['create'] = $permissions['create'];
  667.             $tmpAsset['thumbnail'] = $this->getThumbnailUrl($asset, ['origin' => 'treeNode']);
  668.         } else {
  669.             $tmpAsset['leaf'] = true;
  670.             $tmpAsset['expandable'] = false;
  671.             $tmpAsset['expanded'] = false;
  672.         }
  673.         $this->addAdminStyle($assetElementAdminStyleEvent::CONTEXT_TREE$tmpAsset);
  674.         if ($asset instanceof Asset\Image) {
  675.             try {
  676.                 $tmpAsset['thumbnail'] = $this->getThumbnailUrl($asset, ['origin' => 'treeNode']);
  677.                 // we need the dimensions for the wysiwyg editors, so that they can resize the image immediately
  678.                 if ($asset->getCustomSetting('imageDimensionsCalculated')) {
  679.                     $tmpAsset['imageWidth'] = $asset->getCustomSetting('imageWidth');
  680.                     $tmpAsset['imageHeight'] = $asset->getCustomSetting('imageHeight');
  681.                 }
  682.             } catch (\Exception $e) {
  683.                 Logger::debug('Cannot get dimensions of image, seems to be broken.');
  684.             }
  685.         } elseif ($asset->getType() == 'video') {
  686.             try {
  687.                 if (\Pimcore\Video::isAvailable()) {
  688.                     $tmpAsset['thumbnail'] = $this->getThumbnailUrl($asset, ['origin' => 'treeNode']);
  689.                 }
  690.             } catch (\Exception $e) {
  691.                 Logger::debug('Cannot get dimensions of video, seems to be broken.');
  692.             }
  693.         } elseif ($asset->getType() == 'document') {
  694.             try {
  695.                 // add the PDF check here, otherwise the preview layer in admin is shown without content
  696.                 if (\Pimcore\Document::isAvailable() && \Pimcore\Document::isFileTypeSupported($asset->getFilename())) {
  697.                     $tmpAsset['thumbnail'] = $this->getThumbnailUrl($asset, ['origin' => 'treeNode']);
  698.                 }
  699.             } catch (\Exception $e) {
  700.                 Logger::debug('Cannot get dimensions of video, seems to be broken.');
  701.             }
  702.         }
  703.         $tmpAsset['cls'] = '';
  704.         if ($asset->isLocked()) {
  705.             $tmpAsset['cls'] .= 'pimcore_treenode_locked ';
  706.         }
  707.         if ($asset->getLocked()) {
  708.             $tmpAsset['cls'] .= 'pimcore_treenode_lockOwner ';
  709.         }
  710.         return $tmpAsset;
  711.     }
  712.     /**
  713.      * @param Asset $asset
  714.      * @param array $params
  715.      *
  716.      * @return null|string
  717.      */
  718.     protected function getThumbnailUrl(Asset $asset, array $params = [])
  719.     {
  720.         $defaults = [
  721.             'id' => $asset->getId(),
  722.             'treepreview' => true,
  723.             '_dc' => $asset->getModificationDate(),
  724.         ];
  725.         $params array_merge($defaults$params);
  726.         if ($asset instanceof Asset\Image) {
  727.             return $this->generateUrl('pimcore_admin_asset_getimagethumbnail'$params);
  728.         }
  729.         if ($asset instanceof Asset\Folder) {
  730.             return $this->generateUrl('pimcore_admin_asset_getfolderthumbnail'$params);
  731.         }
  732.         if ($asset instanceof Asset\Video && \Pimcore\Video::isAvailable()) {
  733.             return $this->generateUrl('pimcore_admin_asset_getvideothumbnail'$params);
  734.         }
  735.         if ($asset instanceof Asset\Document && \Pimcore\Document::isAvailable() && $asset->getPageCount()) {
  736.             return $this->generateUrl('pimcore_admin_asset_getdocumentthumbnail'$params);
  737.         }
  738.         if ($asset instanceof Asset\Audio) {
  739.             return '/bundles/pimcoreadmin/img/flat-color-icons/speaker.svg';
  740.         }
  741.         if ($asset instanceof Asset) {
  742.             return '/bundles/pimcoreadmin/img/filetype-not-supported.svg';
  743.         }
  744.     }
  745.     /**
  746.      * @Route("/update", name="pimcore_admin_asset_update", methods={"PUT"})
  747.      *
  748.      * @param Request $request
  749.      *
  750.      * @return JsonResponse
  751.      *
  752.      * @throws \Exception
  753.      */
  754.     public function updateAction(Request $request)
  755.     {
  756.         $success false;
  757.         $allowUpdate true;
  758.         $updateData array_merge($request->request->all(), $request->query->all());
  759.         $asset Asset::getById((int) $request->get('id'));
  760.         if ($asset->isAllowed('settings')) {
  761.             $asset->setUserModification($this->getAdminUser()->getId());
  762.             // if the position is changed the path must be changed || also from the children
  763.             if ($parentId $request->get('parentId')) {
  764.                 $parentAsset Asset::getById((int) $parentId);
  765.                 //check if parent is changed i.e. asset is moved
  766.                 if ($asset->getParentId() != $parentAsset->getId()) {
  767.                     if (!$parentAsset->isAllowed('create')) {
  768.                         throw new \Exception('Prevented moving asset - no create permission on new parent ');
  769.                     }
  770.                     $intendedPath $parentAsset->getRealPath();
  771.                     $pKey $parentAsset->getKey();
  772.                     if (!empty($pKey)) {
  773.                         $intendedPath .= $parentAsset->getKey() . '/';
  774.                     }
  775.                     $assetWithSamePath Asset::getByPath($intendedPath $asset->getKey());
  776.                     if ($assetWithSamePath != null) {
  777.                         $allowUpdate false;
  778.                     }
  779.                     if ($asset->isLocked()) {
  780.                         $allowUpdate false;
  781.                     }
  782.                 }
  783.             }
  784.             if ($allowUpdate) {
  785.                 if ($request->get('filename') != $asset->getFilename() && !$asset->isAllowed('rename')) {
  786.                     unset($updateData['filename']);
  787.                     Logger::debug('prevented renaming asset because of missing permissions ');
  788.                 }
  789.                 $asset->setValues($updateData);
  790.                 try {
  791.                     $asset->save();
  792.                     $success true;
  793.                 } catch (\Exception $e) {
  794.                     return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  795.                 }
  796.             } else {
  797.                 $msg 'prevented moving asset, asset with same path+key already exists at target location or the asset is locked. ID: ' $asset->getId();
  798.                 Logger::debug($msg);
  799.                 return $this->adminJson(['success' => $success'message' => $msg]);
  800.             }
  801.         } elseif ($asset->isAllowed('rename') && $request->get('filename')) {
  802.             //just rename
  803.             try {
  804.                 $asset->setFilename($request->get('filename'));
  805.                 $asset->save();
  806.                 $success true;
  807.             } catch (\Exception $e) {
  808.                 return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  809.             }
  810.         } else {
  811.             Logger::debug('prevented update asset because of missing permissions ');
  812.         }
  813.         return $this->adminJson(['success' => $success]);
  814.     }
  815.     /**
  816.      * @Route("/webdav{path}", name="pimcore_admin_webdav", requirements={"path"=".*"})
  817.      */
  818.     public function webdavAction()
  819.     {
  820.         $homeDir Asset::getById(1);
  821.         try {
  822.             $publicDir = new Asset\WebDAV\Folder($homeDir);
  823.             $objectTree = new Asset\WebDAV\Tree($publicDir);
  824.             $server = new \Sabre\DAV\Server($objectTree);
  825.             $server->setBaseUri($this->generateUrl('pimcore_admin_webdav', ['path' => '/']));
  826.             // lock plugin
  827.             /** @var \Doctrine\DBAL\Driver\PDOConnection $pdo */
  828.             $pdo \Pimcore\Db::get()->getWrappedConnection();
  829.             $lockBackend = new \Sabre\DAV\Locks\Backend\PDO($pdo);
  830.             $lockBackend->tableName 'webdav_locks';
  831.             $lockPlugin = new \Sabre\DAV\Locks\Plugin($lockBackend);
  832.             $server->addPlugin($lockPlugin);
  833.             // browser plugin
  834.             $server->addPlugin(new \Sabre\DAV\Browser\Plugin());
  835.             $server->start();
  836.         } catch (\Exception $e) {
  837.             Logger::error((string) $e);
  838.         }
  839.         exit;
  840.     }
  841.     /**
  842.      * @Route("/save", name="pimcore_admin_asset_save", methods={"PUT","POST"})
  843.      *
  844.      * @param Request $request
  845.      * @param EventDispatcherInterface $eventDispatcher
  846.      *
  847.      * @return JsonResponse
  848.      *
  849.      * @throws \Exception
  850.      */
  851.     public function saveAction(Request $requestEventDispatcherInterface $eventDispatcher)
  852.     {
  853.         $asset Asset::getById((int) $request->get('id'));
  854.         if (!$asset) {
  855.             throw $this->createNotFoundException('Asset not found');
  856.         }
  857.         if ($asset->isAllowed('publish')) {
  858.             // metadata
  859.             if ($request->get('metadata')) {
  860.                 $metadata $this->decodeJson($request->get('metadata'));
  861.                 $metadataEvent = new GenericEvent($this, [
  862.                     'id' => $asset->getId(),
  863.                     'metadata' => $metadata,
  864.                 ]);
  865.                 $eventDispatcher->dispatch($metadataEventAdminEvents::ASSET_METADATA_PRE_SET);
  866.                 $metadata $metadataEvent->getArgument('metadata');
  867.                 $metadataValues $metadata['values'];
  868.                 $metadataValues Asset\Service::minimizeMetadata($metadataValues'editor');
  869.                 $asset->setMetadataRaw($metadataValues);
  870.             }
  871.             // properties
  872.             if ($request->get('properties')) {
  873.                 $properties = [];
  874.                 $propertiesData $this->decodeJson($request->get('properties'));
  875.                 if (is_array($propertiesData)) {
  876.                     foreach ($propertiesData as $propertyName => $propertyData) {
  877.                         $value $propertyData['data'];
  878.                         try {
  879.                             $property = new Model\Property();
  880.                             $property->setType($propertyData['type']);
  881.                             $property->setName($propertyName);
  882.                             $property->setCtype('asset');
  883.                             $property->setDataFromEditmode($value);
  884.                             $property->setInheritable($propertyData['inheritable']);
  885.                             $properties[$propertyName] = $property;
  886.                         } catch (\Exception $e) {
  887.                             Logger::err("Can't add " $propertyName ' to asset ' $asset->getRealFullPath());
  888.                         }
  889.                     }
  890.                     $asset->setProperties($properties);
  891.                 }
  892.             }
  893.             $this->applySchedulerDataToElement($request$asset);
  894.             if ($request->get('data')) {
  895.                 $asset->setData($request->get('data'));
  896.             }
  897.             // image specific data
  898.             if ($asset instanceof Asset\Image) {
  899.                 if ($request->get('image')) {
  900.                     $imageData $this->decodeJson($request->get('image'));
  901.                     if (isset($imageData['focalPoint'])) {
  902.                         $asset->setCustomSetting('focalPointX'$imageData['focalPoint']['x']);
  903.                         $asset->setCustomSetting('focalPointY'$imageData['focalPoint']['y']);
  904.                         $asset->removeCustomSetting('disableFocalPointDetection');
  905.                     }
  906.                 } else {
  907.                     // wipe all data
  908.                     $asset->removeCustomSetting('focalPointX');
  909.                     $asset->removeCustomSetting('focalPointY');
  910.                     $asset->setCustomSetting('disableFocalPointDetection'true);
  911.                 }
  912.             }
  913.             $asset->setUserModification($this->getAdminUser()->getId());
  914.             if ($request->get('task') === 'session') {
  915.                 // save to session only
  916.                 Asset\Service::saveElementToSession($asset);
  917.             } else {
  918.                 $asset->save();
  919.             }
  920.             $treeData $this->getTreeNodeConfig($asset);
  921.             return $this->adminJson([
  922.                 'success' => true,
  923.                 'data' => [
  924.                     'versionDate' => $asset->getModificationDate(),
  925.                     'versionCount' => $asset->getVersionCount(),
  926.                 ],
  927.                 'treeData' => $treeData,
  928.             ]);
  929.         } else {
  930.             throw $this->createAccessDeniedHttpException();
  931.         }
  932.     }
  933.     /**
  934.      * @Route("/publish-version", name="pimcore_admin_asset_publishversion", methods={"POST"})
  935.      *
  936.      * @param Request $request
  937.      *
  938.      * @return JsonResponse
  939.      */
  940.     public function publishVersionAction(Request $request)
  941.     {
  942.         $id = (int)$request->get('id');
  943.         $version Model\Version::getById($id);
  944.         $asset $version?->loadData();
  945.         if (!$asset) {
  946.             throw $this->createNotFoundException('Version with id [' $id "] doesn't exist");
  947.         }
  948.         $currentAsset Asset::getById($asset->getId());
  949.         if ($currentAsset->isAllowed('publish')) {
  950.             try {
  951.                 $asset->setUserModification($this->getAdminUser()->getId());
  952.                 $asset->save();
  953.                 $treeData $this->getTreeNodeConfig($asset);
  954.                 return $this->adminJson(['success' => true'treeData' => $treeData]);
  955.             } catch (\Exception $e) {
  956.                 return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  957.             }
  958.         }
  959.         throw $this->createAccessDeniedHttpException();
  960.     }
  961.     /**
  962.      * @Route("/show-version", name="pimcore_admin_asset_showversion", methods={"GET"})
  963.      *
  964.      * @param Request $request
  965.      *
  966.      * @return Response
  967.      */
  968.     public function showVersionAction(Request $request)
  969.     {
  970.         $id = (int)$request->get('id');
  971.         $version Model\Version::getById($id);
  972.         $asset $version?->loadData();
  973.         if (!$asset) {
  974.             throw $this->createNotFoundException('Version with id [' $id "] doesn't exist");
  975.         }
  976.         if (!$asset->isAllowed('versions')) {
  977.             throw $this->createAccessDeniedHttpException('Permission denied, version id [' $id ']');
  978.         }
  979.         $loader \Pimcore::getContainer()->get('pimcore.implementation_loader.asset.metadata.data');
  980.         return $this->render(
  981.             '@PimcoreAdmin/Admin/Asset/showVersion' ucfirst($asset->getType()) . '.html.twig',
  982.             [
  983.                 'asset' => $asset,
  984.                 'loader' => $loader,
  985.             ]
  986.         );
  987.     }
  988.     /**
  989.      * @Route("/download", name="pimcore_admin_asset_download", methods={"GET"})
  990.      *
  991.      * @param Request $request
  992.      *
  993.      * @return StreamedResponse
  994.      */
  995.     public function downloadAction(Request $request)
  996.     {
  997.         $asset Asset::getById((int) $request->get('id'));
  998.         if (!$asset) {
  999.             throw $this->createNotFoundException('Asset not found');
  1000.         }
  1001.         if (!$asset->isAllowed('view')) {
  1002.             throw $this->createAccessDeniedException('not allowed to view asset');
  1003.         }
  1004.         $stream $asset->getStream();
  1005.         if (!is_resource($stream)) {
  1006.             throw $this->createNotFoundException('Unable to get resource for asset ' $asset->getId());
  1007.         }
  1008.         return new StreamedResponse(function () use ($stream) {
  1009.             fpassthru($stream);
  1010.         }, 200, [
  1011.             'Content-Type' => $asset->getMimeType(),
  1012.             'Content-Disposition' => sprintf('attachment; filename="%s"'$asset->getFilename()),
  1013.             'Content-Length' => $asset->getFileSize(),
  1014.         ]);
  1015.     }
  1016.     /**
  1017.      * @Route("/download-image-thumbnail", name="pimcore_admin_asset_downloadimagethumbnail", methods={"GET"})
  1018.      *
  1019.      * @param Request $request
  1020.      *
  1021.      * @return BinaryFileResponse
  1022.      */
  1023.     public function downloadImageThumbnailAction(Request $request)
  1024.     {
  1025.         $image Asset\Image::getById((int) $request->get('id'));
  1026.         if (!$image) {
  1027.             throw $this->createNotFoundException('Asset not found');
  1028.         }
  1029.         if (!$image->isAllowed('view')) {
  1030.             throw $this->createAccessDeniedException('not allowed to view thumbnail');
  1031.         }
  1032.         $config null;
  1033.         $thumbnail null;
  1034.         $thumbnailName $request->get('thumbnail');
  1035.         $thumbnailFile null;
  1036.         $deleteThumbnail true;
  1037.         if ($request->get('config')) {
  1038.             $config $this->decodeJson($request->get('config'));
  1039.         } elseif ($request->get('type')) {
  1040.             $predefined = [
  1041.                 'web' => [
  1042.                     'resize_mode' => 'scaleByWidth',
  1043.                     'width' => 3500,
  1044.                     'dpi' => 72,
  1045.                     'format' => 'JPEG',
  1046.                     'quality' => 85,
  1047.                 ],
  1048.                 'print' => [
  1049.                     'resize_mode' => 'scaleByWidth',
  1050.                     'width' => 6000,
  1051.                     'dpi' => 300,
  1052.                     'format' => 'JPEG',
  1053.                     'quality' => 95,
  1054.                 ],
  1055.                 'office' => [
  1056.                     'resize_mode' => 'scaleByWidth',
  1057.                     'width' => 1190,
  1058.                     'dpi' => 144,
  1059.                     'format' => 'JPEG',
  1060.                     'quality' => 90,
  1061.                 ],
  1062.             ];
  1063.             $config $predefined[$request->get('type')];
  1064.         } elseif ($thumbnailName) {
  1065.             $thumbnail $image->getThumbnail($thumbnailName);
  1066.             $deleteThumbnail false;
  1067.         }
  1068.         if ($config) {
  1069.             $thumbnailConfig = new Asset\Image\Thumbnail\Config();
  1070.             $thumbnailConfig->setName('pimcore-download-' $image->getId() . '-' md5($request->get('config')));
  1071.             if ($config['resize_mode'] == 'scaleByWidth') {
  1072.                 $thumbnailConfig->addItem('scaleByWidth', [
  1073.                     'width' => $config['width'],
  1074.                 ]);
  1075.             } elseif ($config['resize_mode'] == 'scaleByHeight') {
  1076.                 $thumbnailConfig->addItem('scaleByHeight', [
  1077.                     'height' => $config['height'],
  1078.                 ]);
  1079.             } else {
  1080.                 $thumbnailConfig->addItem('resize', [
  1081.                     'width' => $config['width'],
  1082.                     'height' => $config['height'],
  1083.                 ]);
  1084.             }
  1085.             $thumbnailConfig->setQuality($config['quality']);
  1086.             $thumbnailConfig->setFormat($config['format']);
  1087.             $thumbnailConfig->setRasterizeSVG(true);
  1088.             if ($thumbnailConfig->getFormat() == 'JPEG') {
  1089.                 $thumbnailConfig->setPreserveMetaData(true);
  1090.                 if (empty($config['quality'])) {
  1091.                     $thumbnailConfig->setPreserveColor(true);
  1092.                 }
  1093.             }
  1094.             $thumbnail $image->getThumbnail($thumbnailConfig);
  1095.             $thumbnailFile $thumbnail->getLocalFile();
  1096.             $exiftool \Pimcore\Tool\Console::getExecutable('exiftool');
  1097.             if ($thumbnailConfig->getFormat() == 'JPEG' && $exiftool && isset($config['dpi']) && $config['dpi']) {
  1098.                 $process = new Process([$exiftool'-overwrite_original''-xresolution=' . (int)$config['dpi'], '-yresolution=' . (int)$config['dpi'], '-resolutionunit=inches'$thumbnailFile]);
  1099.                 $process->run();
  1100.             }
  1101.         }
  1102.         if ($thumbnail) {
  1103.             $thumbnailFile $thumbnailFile ?: $thumbnail->getLocalFile();
  1104.             $downloadFilename preg_replace(
  1105.                 '/\.' preg_quote(File::getFileExtension($image->getFilename())) . '$/i',
  1106.                 '.' $thumbnail->getFileExtension(),
  1107.                 $image->getFilename()
  1108.             );
  1109.             $downloadFilename strtolower($downloadFilename);
  1110.             clearstatcache();
  1111.             $response = new BinaryFileResponse($thumbnailFile);
  1112.             $response->headers->set('Content-Type'$thumbnail->getMimeType());
  1113.             $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT$downloadFilename);
  1114.             $this->addThumbnailCacheHeaders($response);
  1115.             $response->deleteFileAfterSend($deleteThumbnail);
  1116.             return $response;
  1117.         }
  1118.         throw $this->createNotFoundException('Thumbnail not found');
  1119.     }
  1120.     /**
  1121.      * @Route("/get-asset", name="pimcore_admin_asset_getasset", methods={"GET"})
  1122.      *
  1123.      * @param Request $request
  1124.      *
  1125.      * @return StreamedResponse
  1126.      */
  1127.     public function getAssetAction(Request $request)
  1128.     {
  1129.         $image Asset::getById((int)$request->get('id'));
  1130.         if (!$image) {
  1131.             throw $this->createNotFoundException('Asset not found');
  1132.         }
  1133.         if (!$image->isAllowed('view')) {
  1134.             throw $this->createAccessDeniedException('not allowed to view asset');
  1135.         }
  1136.         $stream $image->getStream();
  1137.         if (!is_resource($stream)) {
  1138.             throw $this->createNotFoundException('Unable to get resource for asset ' $image->getId());
  1139.         }
  1140.         $response = new StreamedResponse(function () use ($stream) {
  1141.             fpassthru($stream);
  1142.         }, 200, [
  1143.             'Content-Type' => $image->getMimeType(),
  1144.             'Access-Control-Allow-Origin' => '*',
  1145.         ]);
  1146.         $this->addThumbnailCacheHeaders($response);
  1147.         return $response;
  1148.     }
  1149.     /**
  1150.      * @Route("/get-image-thumbnail", name="pimcore_admin_asset_getimagethumbnail", methods={"GET"})
  1151.      *
  1152.      * @param Request $request
  1153.      *
  1154.      * @return StreamedResponse|JsonResponse|BinaryFileResponse
  1155.      */
  1156.     public function getImageThumbnailAction(Request $request)
  1157.     {
  1158.         $fileinfo $request->get('fileinfo');
  1159.         $image Asset\Image::getById((int)$request->get('id'));
  1160.         if (!$image) {
  1161.             throw $this->createNotFoundException('Asset not found');
  1162.         }
  1163.         if (!$image->isAllowed('view')) {
  1164.             throw $this->createAccessDeniedException('not allowed to view thumbnail');
  1165.         }
  1166.         $thumbnailConfig null;
  1167.         if ($request->get('thumbnail')) {
  1168.             $thumbnailConfig $image->getThumbnailConfig($request->get('thumbnail'));
  1169.         }
  1170.         if (!$thumbnailConfig) {
  1171.             if ($request->get('config')) {
  1172.                 $thumbnailConfig $image->getThumbnailConfig($this->decodeJson($request->get('config')));
  1173.             } else {
  1174.                 $thumbnailConfig $image->getThumbnailConfig(array_merge($request->request->all(), $request->query->all()));
  1175.             }
  1176.         } else {
  1177.             // no high-res images in admin mode (editmode)
  1178.             // this is mostly because of the document's image editable, which doesn't know anything about the thumbnail
  1179.             // configuration, so the dimensions would be incorrect (double the size)
  1180.             $thumbnailConfig->setHighResolution(1);
  1181.         }
  1182.         $format strtolower($thumbnailConfig->getFormat());
  1183.         if ($format == 'source' || $format == 'print') {
  1184.             $thumbnailConfig->setFormat('PNG');
  1185.             $thumbnailConfig->setRasterizeSVG(true);
  1186.         }
  1187.         if ($request->get('treepreview')) {
  1188.             $thumbnailConfig Asset\Image\Thumbnail\Config::getPreviewConfig();
  1189.             if ($request->get('origin') === 'treeNode' && !$image->getThumbnail($thumbnailConfig)->exists()) {
  1190.                 \Pimcore::getContainer()->get('messenger.bus.pimcore-core')->dispatch(
  1191.                     new AssetPreviewImageMessage($image->getId())
  1192.                 );
  1193.                 throw $this->createNotFoundException(sprintf('Tree preview thumbnail not available for asset %s'$image->getId()));
  1194.             }
  1195.         }
  1196.         $cropPercent $request->get('cropPercent');
  1197.         if ($cropPercent && filter_var($cropPercentFILTER_VALIDATE_BOOLEAN)) {
  1198.             $thumbnailConfig->addItemAt(0'cropPercent', [
  1199.                 'width' => $request->get('cropWidth'),
  1200.                 'height' => $request->get('cropHeight'),
  1201.                 'y' => $request->get('cropTop'),
  1202.                 'x' => $request->get('cropLeft'),
  1203.             ]);
  1204.             $thumbnailConfig->generateAutoName();
  1205.         }
  1206.         $thumbnail $image->getThumbnail($thumbnailConfig);
  1207.         if ($fileinfo) {
  1208.             return $this->adminJson([
  1209.                 'width' => $thumbnail->getWidth(),
  1210.                 'height' => $thumbnail->getHeight(), ]);
  1211.         }
  1212.         $stream $thumbnail->getStream();
  1213.         if (!$stream) {
  1214.             return new BinaryFileResponse(PIMCORE_PATH '/bundles/AdminBundle/Resources/public/img/filetype-not-supported.svg');
  1215.         }
  1216.         $response = new StreamedResponse(function () use ($stream) {
  1217.             fpassthru($stream);
  1218.         }, 200, [
  1219.             'Content-Type' => $thumbnail->getMimeType(),
  1220.             'Access-Control-Allow-Origin''*',
  1221.         ]);
  1222.         $this->addThumbnailCacheHeaders($response);
  1223.         return $response;
  1224.     }
  1225.     /**
  1226.      * @Route("/get-folder-thumbnail", name="pimcore_admin_asset_getfolderthumbnail", methods={"GET"})
  1227.      *
  1228.      * @param Request $request
  1229.      *
  1230.      * @return StreamedResponse
  1231.      */
  1232.     public function getFolderThumbnailAction(Request $request)
  1233.     {
  1234.         $folder null;
  1235.         if ($request->get('id')) {
  1236.             $folder Asset\Folder::getById((int)$request->get('id'));
  1237.             if ($folder instanceof  Asset\Folder) {
  1238.                 if (!$folder->isAllowed('view')) {
  1239.                     throw $this->createAccessDeniedException('not allowed to view thumbnail');
  1240.                 }
  1241.                 $stream $folder->getPreviewImage();
  1242.                 if (!$stream) {
  1243.                     throw $this->createNotFoundException(sprintf('Tree preview thumbnail not available for asset %s'$folder->getId()));
  1244.                 } else {
  1245.                     $response = new StreamedResponse(function () use ($stream) {
  1246.                         fpassthru($stream);
  1247.                     }, 200, [
  1248.                         'Content-Type' => 'image/jpg',
  1249.                     ]);
  1250.                 }
  1251.                 $this->addThumbnailCacheHeaders($response);
  1252.                 return $response;
  1253.             }
  1254.         }
  1255.         throw $this->createNotFoundException('could not load asset folder');
  1256.     }
  1257.     /**
  1258.      * @Route("/get-video-thumbnail", name="pimcore_admin_asset_getvideothumbnail", methods={"GET"})
  1259.      *
  1260.      * @param Request $request
  1261.      *
  1262.      * @return StreamedResponse
  1263.      */
  1264.     public function getVideoThumbnailAction(Request $request)
  1265.     {
  1266.         $video null;
  1267.         if ($request->get('id')) {
  1268.             $video Asset\Video::getById((int)$request->get('id'));
  1269.         } elseif ($request->get('path')) {
  1270.             $video Asset\Video::getByPath($request->get('path'));
  1271.         }
  1272.         if (!$video) {
  1273.             throw $this->createNotFoundException('could not load video asset');
  1274.         }
  1275.         if (!$video->isAllowed('view')) {
  1276.             throw $this->createAccessDeniedException('not allowed to view thumbnail');
  1277.         }
  1278.         $thumbnail array_merge($request->request->all(), $request->query->all());
  1279.         if ($request->get('treepreview')) {
  1280.             $thumbnail Asset\Image\Thumbnail\Config::getPreviewConfig();
  1281.         }
  1282.         $time null;
  1283.         if (is_numeric($request->get('time'))) {
  1284.             $time = (int)$request->get('time');
  1285.         }
  1286.         if ($request->get('settime')) {
  1287.             $video->removeCustomSetting('image_thumbnail_asset');
  1288.             $video->setCustomSetting('image_thumbnail_time'$time);
  1289.             $video->save();
  1290.         }
  1291.         $image null;
  1292.         if ($request->get('image')) {
  1293.             $image Asset\Image::getById((int)$request->get('image'));
  1294.         }
  1295.         if ($request->get('setimage') && $image) {
  1296.             $video->removeCustomSetting('image_thumbnail_time');
  1297.             $video->setCustomSetting('image_thumbnail_asset'$image->getId());
  1298.             $video->save();
  1299.         }
  1300.         $thumb $video->getImageThumbnail($thumbnail$time$image);
  1301.         if ($request->get('origin') === 'treeNode' && !$thumb->exists()) {
  1302.             \Pimcore::getContainer()->get('messenger.bus.pimcore-core')->dispatch(
  1303.                 new AssetPreviewImageMessage($video->getId())
  1304.             );
  1305.             throw $this->createNotFoundException(sprintf('Tree preview thumbnail not available for asset %s'$video->getId()));
  1306.         }
  1307.         $stream $thumb->getStream();
  1308.         if (!$stream) {
  1309.             throw $this->createNotFoundException('Unable to get video thumbnail for video ' $video->getId());
  1310.         }
  1311.         $response = new StreamedResponse(function () use ($stream) {
  1312.             fpassthru($stream);
  1313.         }, 200, [
  1314.             'Content-Type' => 'image/' $thumb->getFileExtension(),
  1315.         ]);
  1316.         $this->addThumbnailCacheHeaders($response);
  1317.         return $response;
  1318.     }
  1319.     /**
  1320.      * @Route("/get-document-thumbnail", name="pimcore_admin_asset_getdocumentthumbnail", methods={"GET"})
  1321.      *
  1322.      * @param Request $request
  1323.      *
  1324.      * @return StreamedResponse|BinaryFileResponse
  1325.      */
  1326.     public function getDocumentThumbnailAction(Request $request)
  1327.     {
  1328.         $document Asset\Document::getById((int)$request->get('id'));
  1329.         if (!$document) {
  1330.             throw $this->createNotFoundException('could not load document asset');
  1331.         }
  1332.         if (!$document->isAllowed('view')) {
  1333.             throw $this->createAccessDeniedException('not allowed to view thumbnail');
  1334.         }
  1335.         $thumbnail Asset\Image\Thumbnail\Config::getByAutoDetect(array_merge($request->request->all(), $request->query->all()));
  1336.         $format strtolower($thumbnail->getFormat());
  1337.         if ($format == 'source') {
  1338.             $thumbnail->setFormat('jpeg'); // default format for documents is JPEG not PNG (=too big)
  1339.         }
  1340.         if ($request->get('treepreview')) {
  1341.             $thumbnail Asset\Image\Thumbnail\Config::getPreviewConfig();
  1342.         }
  1343.         $page 1;
  1344.         if (is_numeric($request->get('page'))) {
  1345.             $page = (int)$request->get('page');
  1346.         }
  1347.         $thumb $document->getImageThumbnail($thumbnail$page);
  1348.         if ($request->get('origin') === 'treeNode' && !$thumb->exists()) {
  1349.             \Pimcore::getContainer()->get('messenger.bus.pimcore-core')->dispatch(
  1350.                 new AssetPreviewImageMessage($document->getId())
  1351.             );
  1352.             throw $this->createNotFoundException(sprintf('Tree preview thumbnail not available for asset %s'$document->getId()));
  1353.         }
  1354.         $stream $thumb->getStream();
  1355.         if ($stream) {
  1356.             $response = new StreamedResponse(function () use ($stream) {
  1357.                 fpassthru($stream);
  1358.             }, 200, [
  1359.                 'Content-Type' => 'image/' $thumb->getFileExtension(),
  1360.             ]);
  1361.         } else {
  1362.             $response = new BinaryFileResponse(PIMCORE_PATH '/bundles/AdminBundle/Resources/public/img/filetype-not-supported.svg');
  1363.         }
  1364.         $this->addThumbnailCacheHeaders($response);
  1365.         return $response;
  1366.     }
  1367.     /**
  1368.      * @param Response $response
  1369.      */
  1370.     protected function addThumbnailCacheHeaders(Response $response)
  1371.     {
  1372.         $lifetime 300;
  1373.         $date = new \DateTime('now');
  1374.         $date->add(new \DateInterval('PT' $lifetime 'S'));
  1375.         $response->setMaxAge($lifetime);
  1376.         $response->setPublic();
  1377.         $response->setExpires($date);
  1378.         $response->headers->set('Pragma''');
  1379.     }
  1380.     /**
  1381.      * @Route("/get-preview-document", name="pimcore_admin_asset_getpreviewdocument", methods={"GET"})
  1382.      *
  1383.      * @param Request $request
  1384.      *
  1385.      * @return StreamedResponse
  1386.      */
  1387.     public function getPreviewDocumentAction(Request $request)
  1388.     {
  1389.         $asset Asset\Document::getById((int) $request->get('id'));
  1390.         if (!$asset) {
  1391.             throw $this->createNotFoundException('could not load document asset');
  1392.         }
  1393.         if ($asset->isAllowed('view')) {
  1394.             $stream $this->getDocumentPreviewPdf($asset);
  1395.             if ($stream) {
  1396.                 return new StreamedResponse(function () use ($stream) {
  1397.                     fpassthru($stream);
  1398.                 }, 200, [
  1399.                     'Content-Type' => 'application/pdf',
  1400.                 ]);
  1401.             } else {
  1402.                 throw $this->createNotFoundException('Unable to get preview for asset ' $asset->getId());
  1403.             }
  1404.         } else {
  1405.             throw $this->createAccessDeniedException('Access to asset ' $asset->getId() . ' denied');
  1406.         }
  1407.     }
  1408.     /**
  1409.      * @param Asset\Document $asset
  1410.      *
  1411.      * @return resource|null
  1412.      */
  1413.     protected function getDocumentPreviewPdf(Asset\Document $asset)
  1414.     {
  1415.         $stream null;
  1416.         if ($asset->getMimeType() == 'application/pdf') {
  1417.             $stream $asset->getStream();
  1418.         }
  1419.         if (!$stream && $asset->getPageCount() && \Pimcore\Document::isAvailable() && \Pimcore\Document::isFileTypeSupported($asset->getFilename())) {
  1420.             try {
  1421.                 $document \Pimcore\Document::getInstance();
  1422.                 $stream $document->getPdf($asset);
  1423.             } catch (\Exception $e) {
  1424.                 // nothing to do
  1425.             }
  1426.         }
  1427.         return $stream;
  1428.     }
  1429.     /**
  1430.      * @Route("/get-preview-video", name="pimcore_admin_asset_getpreviewvideo", methods={"GET"})
  1431.      *
  1432.      * @param Request $request
  1433.      *
  1434.      * @return Response
  1435.      */
  1436.     public function getPreviewVideoAction(Request $request)
  1437.     {
  1438.         $asset Asset\Video::getById((int) $request->get('id'));
  1439.         if (!$asset) {
  1440.             throw $this->createNotFoundException('could not load video asset');
  1441.         }
  1442.         if (!$asset->isAllowed('view')) {
  1443.             throw $this->createAccessDeniedException('not allowed to preview');
  1444.         }
  1445.         $previewData = ['asset' => $asset];
  1446.         $config Asset\Video\Thumbnail\Config::getPreviewConfig();
  1447.         $thumbnail $asset->getThumbnail($config, ['mp4']);
  1448.         if ($thumbnail) {
  1449.             $previewData['asset'] = $asset;
  1450.             $previewData['thumbnail'] = $thumbnail;
  1451.             if ($thumbnail['status'] == 'finished') {
  1452.                 return $this->render(
  1453.                     '@PimcoreAdmin/Admin/Asset/getPreviewVideoDisplay.html.twig',
  1454.                     $previewData
  1455.                 );
  1456.             } else {
  1457.                 return $this->render(
  1458.                     '@PimcoreAdmin/Admin/Asset/getPreviewVideoError.html.twig',
  1459.                     $previewData
  1460.                 );
  1461.             }
  1462.         } else {
  1463.             return $this->render(
  1464.                 '@PimcoreAdmin/Admin/Asset/getPreviewVideoError.html.twig',
  1465.                 $previewData
  1466.             );
  1467.         }
  1468.     }
  1469.     /**
  1470.      * @Route("/serve-video-preview", name="pimcore_admin_asset_servevideopreview", methods={"GET"})
  1471.      *
  1472.      * @param Request $request
  1473.      *
  1474.      * @return StreamedResponse
  1475.      */
  1476.     public function serveVideoPreviewAction(Request $request)
  1477.     {
  1478.         $asset Asset\Video::getById((int) $request->get('id'));
  1479.         if (!$asset) {
  1480.             throw $this->createNotFoundException('could not load video asset');
  1481.         }
  1482.         if (!$asset->isAllowed('view')) {
  1483.             throw $this->createAccessDeniedException('not allowed to preview');
  1484.         }
  1485.         $config Asset\Video\Thumbnail\Config::getPreviewConfig();
  1486.         $thumbnail $asset->getThumbnail($config, ['mp4']);
  1487.         $storagePath $asset->getRealPath() . '/' preg_replace('@^' preg_quote($asset->getPath(), '@') . '@'''urldecode($thumbnail['formats']['mp4']));
  1488.         $storage Tool\Storage::get('thumbnail');
  1489.         if ($storage->fileExists($storagePath)) {
  1490.             $fs $storage->fileSize($storagePath);
  1491.             $stream $storage->readStream($storagePath);
  1492.             return new StreamedResponse(function () use ($stream) {
  1493.                 fpassthru($stream);
  1494.             }, 200, [
  1495.                 'Content-Type' => 'video/mp4',
  1496.                 'Content-Length' => $fs,
  1497.                 'Accept-Ranges' => 'bytes',
  1498.             ]);
  1499.         } else {
  1500.             throw $this->createNotFoundException('Video thumbnail not found');
  1501.         }
  1502.     }
  1503.     /**
  1504.      * @Route("/image-editor", name="pimcore_admin_asset_imageeditor", methods={"GET"})
  1505.      *
  1506.      * @param Request $request
  1507.      *
  1508.      * @return Response
  1509.      */
  1510.     public function imageEditorAction(Request $request)
  1511.     {
  1512.         $asset Asset::getById((int) $request->get('id'));
  1513.         if (!$asset->isAllowed('view')) {
  1514.             throw $this->createAccessDeniedException('Not allowed to preview');
  1515.         }
  1516.         return $this->render(
  1517.             '@PimcoreAdmin/Admin/Asset/imageEditor.html.twig',
  1518.             ['asset' => $asset]
  1519.         );
  1520.     }
  1521.     /**
  1522.      * @Route("/image-editor-save", name="pimcore_admin_asset_imageeditorsave", methods={"PUT"})
  1523.      *
  1524.      * @param Request $request
  1525.      *
  1526.      * @return JsonResponse
  1527.      */
  1528.     public function imageEditorSaveAction(Request $request)
  1529.     {
  1530.         $asset Asset::getById((int) $request->get('id'));
  1531.         if (!$asset) {
  1532.             throw $this->createNotFoundException('Asset not found');
  1533.         }
  1534.         if (!$asset->isAllowed('publish')) {
  1535.             throw $this->createAccessDeniedException('not allowed to publish');
  1536.         }
  1537.         $data $request->get('dataUri');
  1538.         $data substr($datastrpos($data','));
  1539.         $data base64_decode($data);
  1540.         $asset->setData($data);
  1541.         $asset->setUserModification($this->getAdminUser()->getId());
  1542.         $asset->save();
  1543.         return $this->adminJson(['success' => true]);
  1544.     }
  1545.     /**
  1546.      * @Route("/get-folder-content-preview", name="pimcore_admin_asset_getfoldercontentpreview", methods={"GET"})
  1547.      *
  1548.      * @param Request $request
  1549.      *
  1550.      * @return JsonResponse
  1551.      */
  1552.     public function getFolderContentPreviewAction(Request $requestEventDispatcherInterface $eventDispatcher)
  1553.     {
  1554.         $allParams array_merge($request->request->all(), $request->query->all());
  1555.         $filterPrepareEvent = new GenericEvent($this, [
  1556.             'requestParams' => $allParams,
  1557.         ]);
  1558.         $eventDispatcher->dispatch($filterPrepareEventAdminEvents::ASSET_LIST_BEFORE_FILTER_PREPARE);
  1559.         $allParams $filterPrepareEvent->getArgument('requestParams');
  1560.         $folder Asset::getById($allParams['id']);
  1561.         $start 0;
  1562.         $limit 10;
  1563.         if ($allParams['limit']) {
  1564.             $limit $allParams['limit'];
  1565.         }
  1566.         if ($allParams['start']) {
  1567.             $start $allParams['start'];
  1568.         }
  1569.         $conditionFilters = [];
  1570.         $list = new Asset\Listing();
  1571.         $conditionFilters[] = 'path LIKE ' . ($folder->getRealFullPath() == '/' "'/%'" $list->quote(Helper::escapeLike($folder->getRealFullPath()) . '/%')) . " AND type != 'folder'";
  1572.         if (!$this->getAdminUser()->isAdmin()) {
  1573.             $userIds $this->getAdminUser()->getRoles();
  1574.             $userIds[] = $this->getAdminUser()->getId();
  1575.             $conditionFilters[] = ' (
  1576.                                                     (select list from users_workspaces_asset where userId in (' implode(','$userIds) . ') and LOCATE(CONCAT(path, filename),cpath)=1  ORDER BY LENGTH(cpath) DESC LIMIT 1)=1
  1577.                                                     OR
  1578.                                                     (select list from users_workspaces_asset where userId in (' implode(','$userIds) . ') and LOCATE(cpath,CONCAT(path, filename))=1  ORDER BY LENGTH(cpath) DESC LIMIT 1)=1
  1579.                                                  )';
  1580.         }
  1581.         $condition implode(' AND '$conditionFilters);
  1582.         $list->setCondition($condition);
  1583.         $list->setLimit($limit);
  1584.         $list->setOffset($start);
  1585.         $list->setOrderKey('CAST(filename AS CHAR CHARACTER SET utf8) COLLATE utf8_general_ci ASC'false);
  1586.         $beforeListLoadEvent = new GenericEvent($this, [
  1587.             'list' => $list,
  1588.             'context' => $allParams,
  1589.         ]);
  1590.         $eventDispatcher->dispatch($beforeListLoadEventAdminEvents::ASSET_LIST_BEFORE_LIST_LOAD);
  1591.         /** @var Asset\Listing $list */
  1592.         $list $beforeListLoadEvent->getArgument('list');
  1593.         $list->load();
  1594.         $assets = [];
  1595.         foreach ($list as $asset) {
  1596.             $filenameDisplay $asset->getFilename();
  1597.             if (strlen($filenameDisplay) > 32) {
  1598.                 $filenameDisplay substr($filenameDisplay025) . '...' \Pimcore\File::getFileExtension($filenameDisplay);
  1599.             }
  1600.             // Like for treeGetChildsByIdAction, so we respect isAllowed method which can be extended (object DI) for custom permissions, so relying only users_workspaces_asset is insufficient and could lead security breach
  1601.             if ($asset->isAllowed('list')) {
  1602.                 $assets[] = [
  1603.                     'id' => $asset->getId(),
  1604.                     'type' => $asset->getType(),
  1605.                     'filename' => $asset->getFilename(),
  1606.                     'filenameDisplay' => htmlspecialchars($filenameDisplay),
  1607.                     'url' => $this->getThumbnailUrl($asset),
  1608.                     'idPath' => $data['idPath'] = Element\Service::getIdPath($asset),
  1609.                 ];
  1610.             }
  1611.         }
  1612.         // We need to temporary use data key to be compatible with the ASSET_LIST_AFTER_LIST_LOAD global event
  1613.         $result = ['data' => $assets'success' => true'total' => $list->getTotalCount()];
  1614.         $afterListLoadEvent = new GenericEvent($this, [
  1615.             'list' => $result,
  1616.             'context' => $allParams,
  1617.         ]);
  1618.         $eventDispatcher->dispatch($afterListLoadEventAdminEvents::ASSET_LIST_AFTER_LIST_LOAD);
  1619.         $result $afterListLoadEvent->getArgument('list');
  1620.         // Here we revert to assets key
  1621.         return $this->adminJson(['assets' => $result['data'], 'success' => $result['success'], 'total' => $result['total']]);
  1622.     }
  1623.     /**
  1624.      * @Route("/copy-info", name="pimcore_admin_asset_copyinfo", methods={"GET"})
  1625.      *
  1626.      * @param Request $request
  1627.      *
  1628.      * @return JsonResponse
  1629.      */
  1630.     public function copyInfoAction(Request $request)
  1631.     {
  1632.         $transactionId time();
  1633.         $pasteJobs = [];
  1634.         Tool\Session::useSession(function (AttributeBagInterface $session) use ($transactionId) {
  1635.             $session->set((string) $transactionId, []);
  1636.         }, 'pimcore_copy');
  1637.         if ($request->get('type') == 'recursive') {
  1638.             $asset Asset::getById((int) $request->get('sourceId'));
  1639.             if (!$asset) {
  1640.                 throw $this->createNotFoundException('Source not found');
  1641.             }
  1642.             // first of all the new parent
  1643.             $pasteJobs[] = [[
  1644.                 'url' => $this->generateUrl('pimcore_admin_asset_copy'),
  1645.                 'method' => 'POST',
  1646.                 'params' => [
  1647.                     'sourceId' => $request->get('sourceId'),
  1648.                     'targetId' => $request->get('targetId'),
  1649.                     'type' => 'child',
  1650.                     'transactionId' => $transactionId,
  1651.                     'saveParentId' => true,
  1652.                 ],
  1653.             ]];
  1654.             if ($asset->hasChildren()) {
  1655.                 // get amount of children
  1656.                 $list = new Asset\Listing();
  1657.                 $list->setCondition('path LIKE ?', [$list->escapeLike($asset->getRealFullPath()) . '/%']);
  1658.                 $list->setOrderKey('LENGTH(path)'false);
  1659.                 $list->setOrder('ASC');
  1660.                 $childIds $list->loadIdList();
  1661.                 if (count($childIds) > 0) {
  1662.                     foreach ($childIds as $id) {
  1663.                         $pasteJobs[] = [[
  1664.                             'url' => $this->generateUrl('pimcore_admin_asset_copy'),
  1665.                             'method' => 'POST',
  1666.                             'params' => [
  1667.                                 'sourceId' => $id,
  1668.                                 'targetParentId' => $request->get('targetId'),
  1669.                                 'sourceParentId' => $request->get('sourceId'),
  1670.                                 'type' => 'child',
  1671.                                 'transactionId' => $transactionId,
  1672.                             ],
  1673.                         ]];
  1674.                     }
  1675.                 }
  1676.             }
  1677.         } elseif ($request->get('type') == 'child' || $request->get('type') == 'replace') {
  1678.             // the object itself is the last one
  1679.             $pasteJobs[] = [[
  1680.                 'url' => $this->generateUrl('pimcore_admin_asset_copy'),
  1681.                 'method' => 'POST',
  1682.                 'params' => [
  1683.                     'sourceId' => $request->get('sourceId'),
  1684.                     'targetId' => $request->get('targetId'),
  1685.                     'type' => $request->get('type'),
  1686.                     'transactionId' => $transactionId,
  1687.                 ],
  1688.             ]];
  1689.         }
  1690.         return $this->adminJson([
  1691.             'pastejobs' => $pasteJobs,
  1692.         ]);
  1693.     }
  1694.     /**
  1695.      * @Route("/copy", name="pimcore_admin_asset_copy", methods={"POST"})
  1696.      *
  1697.      * @param Request $request
  1698.      *
  1699.      * @return JsonResponse
  1700.      */
  1701.     public function copyAction(Request $request)
  1702.     {
  1703.         $success false;
  1704.         $sourceId = (int)$request->get('sourceId');
  1705.         $source Asset::getById($sourceId);
  1706.         $session Tool\Session::get('pimcore_copy');
  1707.         $sessionBag $session->get($request->get('transactionId'));
  1708.         $targetId = (int)$request->get('targetId');
  1709.         if ($request->get('targetParentId')) {
  1710.             $sourceParent Asset::getById((int) $request->get('sourceParentId'));
  1711.             // this is because the key can get the prefix "_copy" if the target does already exists
  1712.             if ($sessionBag['parentId']) {
  1713.                 $targetParent Asset::getById($sessionBag['parentId']);
  1714.             } else {
  1715.                 $targetParent Asset::getById((int) $request->get('targetParentId'));
  1716.             }
  1717.             $targetPath preg_replace('@^' $sourceParent->getRealFullPath() . '@'$targetParent '/'$source->getRealPath());
  1718.             $target Asset::getByPath($targetPath);
  1719.         } else {
  1720.             $target Asset::getById($targetId);
  1721.         }
  1722.         if (!$target) {
  1723.             throw $this->createNotFoundException('Target not found');
  1724.         }
  1725.         if ($target->isAllowed('create')) {
  1726.             $source Asset::getById($sourceId);
  1727.             if ($source != null) {
  1728.                 if ($request->get('type') == 'child') {
  1729.                     $newAsset $this->_assetService->copyAsChild($target$source);
  1730.                     // this is because the key can get the prefix "_copy" if the target does already exists
  1731.                     if ($request->get('saveParentId')) {
  1732.                         $sessionBag['parentId'] = $newAsset->getId();
  1733.                     }
  1734.                 } elseif ($request->get('type') == 'replace') {
  1735.                     $this->_assetService->copyContents($target$source);
  1736.                 }
  1737.                 $session->set($request->get('transactionId'), $sessionBag);
  1738.                 Tool\Session::writeClose();
  1739.                 $success true;
  1740.             } else {
  1741.                 Logger::debug('prevended copy/paste because asset with same path+key already exists in this location');
  1742.             }
  1743.         } else {
  1744.             Logger::error('could not execute copy/paste because of missing permissions on target [ ' $targetId ' ]');
  1745.             throw $this->createAccessDeniedHttpException();
  1746.         }
  1747.         Tool\Session::writeClose();
  1748.         return $this->adminJson(['success' => $success]);
  1749.     }
  1750.     /**
  1751.      * @Route("/download-as-zip-jobs", name="pimcore_admin_asset_downloadaszipjobs", methods={"GET"})
  1752.      *
  1753.      * @param Request $request
  1754.      *
  1755.      * @return JsonResponse
  1756.      */
  1757.     public function downloadAsZipJobsAction(Request $request)
  1758.     {
  1759.         $jobId uniqid();
  1760.         $filesPerJob 5;
  1761.         $jobs = [];
  1762.         $asset Asset::getById((int) $request->get('id'));
  1763.         if (!$asset) {
  1764.             throw $this->createNotFoundException('Asset not found');
  1765.         }
  1766.         if ($asset->isAllowed('view')) {
  1767.             $parentPath $asset->getRealFullPath();
  1768.             if ($asset->getId() == 1) {
  1769.                 $parentPath '';
  1770.             }
  1771.             $db \Pimcore\Db::get();
  1772.             $conditionFilters = [];
  1773.             $selectedIds explode(','$request->get('selectedIds'''));
  1774.             $quotedSelectedIds = [];
  1775.             foreach ($selectedIds as $selectedId) {
  1776.                 if ($selectedId) {
  1777.                     $quotedSelectedIds[] = $db->quote($selectedId);
  1778.                 }
  1779.             }
  1780.             if (!empty($quotedSelectedIds)) {
  1781.                 //add a condition if id numbers are specified
  1782.                 $conditionFilters[] = 'id IN (' implode(','$quotedSelectedIds) . ')';
  1783.             }
  1784.             $conditionFilters[] = 'path LIKE ' $db->quote(Helper::escapeLike($parentPath) . '/%') . ' AND type != ' $db->quote('folder');
  1785.             if (!$this->getAdminUser()->isAdmin()) {
  1786.                 $userIds $this->getAdminUser()->getRoles();
  1787.                 $userIds[] = $this->getAdminUser()->getId();
  1788.                 $conditionFilters[] = ' (
  1789.                     (select list from users_workspaces_asset where userId in (' implode(','$userIds) . ') and LOCATE(CONCAT(path, filename),cpath)=1  ORDER BY LENGTH(cpath) DESC LIMIT 1)=1
  1790.                     OR
  1791.                     (select list from users_workspaces_asset where userId in (' implode(','$userIds) . ') and LOCATE(cpath,CONCAT(path, filename))=1  ORDER BY LENGTH(cpath) DESC LIMIT 1)=1
  1792.                 )';
  1793.             }
  1794.             $condition implode(' AND '$conditionFilters);
  1795.             $assetList = new Asset\Listing();
  1796.             $assetList->setCondition($condition);
  1797.             $assetList->setOrderKey('LENGTH(path)'false);
  1798.             $assetList->setOrder('ASC');
  1799.             for ($i 0$i ceil($assetList->getTotalCount() / $filesPerJob); $i++) {
  1800.                 $jobs[] = [[
  1801.                     'url' => $this->generateUrl('pimcore_admin_asset_downloadaszipaddfiles'),
  1802.                     'method' => 'GET',
  1803.                     'params' => [
  1804.                         'id' => $asset->getId(),
  1805.                         'selectedIds' => implode(','$selectedIds),
  1806.                         'offset' => $i $filesPerJob,
  1807.                         'limit' => $filesPerJob,
  1808.                         'jobId' => $jobId,
  1809.                     ],
  1810.                 ]];
  1811.             }
  1812.         }
  1813.         return $this->adminJson([
  1814.             'success' => true,
  1815.             'jobs' => $jobs,
  1816.             'jobId' => $jobId,
  1817.         ]);
  1818.     }
  1819.     /**
  1820.      * @Route("/download-as-zip-add-files", name="pimcore_admin_asset_downloadaszipaddfiles", methods={"GET"})
  1821.      *
  1822.      * @param Request $request
  1823.      *
  1824.      * @return JsonResponse
  1825.      */
  1826.     public function downloadAsZipAddFilesAction(Request $request)
  1827.     {
  1828.         $zipFile PIMCORE_SYSTEM_TEMP_DIRECTORY '/download-zip-' $request->get('jobId') . '.zip';
  1829.         $asset Asset::getById((int) $request->get('id'));
  1830.         $success false;
  1831.         if (!$asset) {
  1832.             throw $this->createNotFoundException('Asset not found');
  1833.         }
  1834.         if ($asset->isAllowed('view')) {
  1835.             $zip = new \ZipArchive();
  1836.             if (!is_file($zipFile)) {
  1837.                 $zipState $zip->open($zipFile\ZipArchive::CREATE);
  1838.             } else {
  1839.                 $zipState $zip->open($zipFile);
  1840.             }
  1841.             if ($zipState === true) {
  1842.                 $parentPath $asset->getRealFullPath();
  1843.                 if ($asset->getId() == 1) {
  1844.                     $parentPath '';
  1845.                 }
  1846.                 $db \Pimcore\Db::get();
  1847.                 $conditionFilters = [];
  1848.                 $selectedIds $request->get('selectedIds', []);
  1849.                 if (!empty($selectedIds)) {
  1850.                     $selectedIds explode(','$selectedIds);
  1851.                     $quotedSelectedIds = [];
  1852.                     foreach ($selectedIds as $selectedId) {
  1853.                         if ($selectedId) {
  1854.                             $quotedSelectedIds[] = $db->quote($selectedId);
  1855.                         }
  1856.                     }
  1857.                     //add a condition if id numbers are specified
  1858.                     $conditionFilters[] = 'id IN (' implode(','$quotedSelectedIds) . ')';
  1859.                 }
  1860.                 $conditionFilters[] = "type != 'folder' AND path LIKE " $db->quote(Helper::escapeLike($parentPath) . '/%');
  1861.                 if (!$this->getAdminUser()->isAdmin()) {
  1862.                     $userIds $this->getAdminUser()->getRoles();
  1863.                     $userIds[] = $this->getAdminUser()->getId();
  1864.                     $conditionFilters[] = ' (
  1865.                         (select list from users_workspaces_asset where userId in (' implode(','$userIds) . ') and LOCATE(CONCAT(path, filename),cpath)=1  ORDER BY LENGTH(cpath) DESC LIMIT 1)=1
  1866.                         OR
  1867.                         (select list from users_workspaces_asset where userId in (' implode(','$userIds) . ') and LOCATE(cpath,CONCAT(path, filename))=1  ORDER BY LENGTH(cpath) DESC LIMIT 1)=1
  1868.                     )';
  1869.                 }
  1870.                 $condition implode(' AND '$conditionFilters);
  1871.                 $assetList = new Asset\Listing();
  1872.                 $assetList->setCondition($condition);
  1873.                 $assetList->setOrderKey('LENGTH(path) ASC, id ASC'false);
  1874.                 $assetList->setOffset((int)$request->get('offset'));
  1875.                 $assetList->setLimit((int)$request->get('limit'));
  1876.                 foreach ($assetList as $a) {
  1877.                     if ($a->isAllowed('view')) {
  1878.                         if (!$a instanceof Asset\Folder) {
  1879.                             // add the file with the relative path to the parent directory
  1880.                             $zip->addFile($a->getLocalFile(), preg_replace('@^' preg_quote($asset->getRealPath(), '@') . '@i'''$a->getRealFullPath()));
  1881.                         }
  1882.                     }
  1883.                 }
  1884.                 $zip->close();
  1885.                 $success true;
  1886.             }
  1887.         }
  1888.         return $this->adminJson([
  1889.             'success' => $success,
  1890.         ]);
  1891.     }
  1892.     /**
  1893.      * @Route("/download-as-zip", name="pimcore_admin_asset_downloadaszip", methods={"GET"})
  1894.      *
  1895.      * @param Request $request
  1896.      *
  1897.      * @return BinaryFileResponse
  1898.      * Download all assets contained in the folder with parameter id as ZIP file.
  1899.      * The suggested filename is either [folder name].zip or assets.zip for the root folder.
  1900.      */
  1901.     public function downloadAsZipAction(Request $request)
  1902.     {
  1903.         $asset Asset::getById((int) $request->get('id'));
  1904.         if (!$asset) {
  1905.             throw $this->createNotFoundException('Asset not found');
  1906.         }
  1907.         $zipFile PIMCORE_SYSTEM_TEMP_DIRECTORY '/download-zip-' $request->get('jobId') . '.zip';
  1908.         $suggestedFilename $asset->getFilename();
  1909.         if (empty($suggestedFilename)) {
  1910.             $suggestedFilename 'assets';
  1911.         }
  1912.         $response = new BinaryFileResponse($zipFile);
  1913.         $response->headers->set('Content-Type''application/zip');
  1914.         $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT$suggestedFilename '.zip');
  1915.         $response->deleteFileAfterSend(true);
  1916.         return $response;
  1917.     }
  1918.     /**
  1919.      * @Route("/import-zip", name="pimcore_admin_asset_importzip", methods={"POST"})
  1920.      *
  1921.      * @param Request $request
  1922.      * @param TranslatorInterface $translator
  1923.      *
  1924.      * @return Response
  1925.      */
  1926.     public function importZipAction(Request $requestTranslatorInterface $translator)
  1927.     {
  1928.         $jobId uniqid();
  1929.         $filesPerJob 5;
  1930.         $jobs = [];
  1931.         $asset Asset::getById((int) $request->get('parentId'));
  1932.         if (!is_file($_FILES['Filedata']['tmp_name'])) {
  1933.             return $this->adminJson([
  1934.                 'success' => false,
  1935.                 'message' => 'Something went wrong, please check upload_max_filesize and post_max_size in your php.ini as well as the write permissions on the file system',
  1936.             ]);
  1937.         }
  1938.         if (!$asset) {
  1939.             throw $this->createNotFoundException('Parent asset not found');
  1940.         }
  1941.         if (!$asset->isAllowed('create')) {
  1942.             throw $this->createAccessDeniedException('not allowed to create');
  1943.         }
  1944.         $zipFile PIMCORE_SYSTEM_TEMP_DIRECTORY '/' $jobId '.zip';
  1945.         copy($_FILES['Filedata']['tmp_name'], $zipFile);
  1946.         $zip = new \ZipArchive;
  1947.         $retCode $zip->open($zipFile);
  1948.         if ($retCode === true) {
  1949.             $jobAmount ceil($zip->numFiles $filesPerJob);
  1950.             for ($i 0$i $jobAmount$i++) {
  1951.                 $jobs[] = [[
  1952.                     'url' => $this->generateUrl('pimcore_admin_asset_importzipfiles'),
  1953.                     'method' => 'POST',
  1954.                     'params' => [
  1955.                         'parentId' => $asset->getId(),
  1956.                         'offset' => $i $filesPerJob,
  1957.                         'limit' => $filesPerJob,
  1958.                         'jobId' => $jobId,
  1959.                         'last' => (($i 1) >= $jobAmount) ? 'true' '',
  1960.                     ],
  1961.                 ]];
  1962.             }
  1963.             $zip->close();
  1964.             // here we have to use this method and not the JSON action helper ($this->_helper->json()) because this will add
  1965.             // Content-Type: application/json which fires a download window in most browsers, because this is a normal POST
  1966.             // request and not XHR where the content-type doesn't matter
  1967.             $responseJson $this->encodeJson([
  1968.                 'success' => true,
  1969.                 'jobs' => $jobs,
  1970.                 'jobId' => $jobId,
  1971.             ]);
  1972.             return new Response($responseJson);
  1973.         } else {
  1974.             return $this->adminJson([
  1975.                 'success' => false,
  1976.                 'message' => $translator->trans('could_not_open_zip_file', [], 'admin'),
  1977.             ]);
  1978.         }
  1979.     }
  1980.     /**
  1981.      * @Route("/import-zip-files", name="pimcore_admin_asset_importzipfiles", methods={"POST"})
  1982.      *
  1983.      * @param Request $request
  1984.      *
  1985.      * @return JsonResponse
  1986.      */
  1987.     public function importZipFilesAction(Request $request)
  1988.     {
  1989.         $jobId $request->get('jobId');
  1990.         $limit = (int)$request->get('limit');
  1991.         $offset = (int)$request->get('offset');
  1992.         $importAsset Asset::getById((int) $request->get('parentId'));
  1993.         $zipFile PIMCORE_SYSTEM_TEMP_DIRECTORY '/' $jobId '.zip';
  1994.         $tmpDir PIMCORE_SYSTEM_TEMP_DIRECTORY '/zip-import';
  1995.         if (!is_dir($tmpDir)) {
  1996.             File::mkdir($tmpDir0777true);
  1997.         }
  1998.         $zip = new \ZipArchive;
  1999.         if ($zip->open($zipFile) === true) {
  2000.             for ($i $offset$i < ($offset $limit); $i++) {
  2001.                 $path $zip->getNameIndex($i);
  2002.                 if (str_starts_with($path'__MACOSX/') || str_ends_with($path'/Thumbs.db')) {
  2003.                     continue;
  2004.                 }
  2005.                 if ($path !== false) {
  2006.                     if ($zip->extractTo($tmpDir '/'$path)) {
  2007.                         $tmpFile $tmpDir '/' preg_replace('@^/@'''$path);
  2008.                         $filename Element\Service::getValidKey(basename($path), 'asset');
  2009.                         $relativePath '';
  2010.                         if (dirname($path) != '.') {
  2011.                             $relativePath dirname($path);
  2012.                         }
  2013.                         $parentPath $importAsset->getRealFullPath() . '/' preg_replace('@^/@'''$relativePath);
  2014.                         $parent Asset\Service::createFolderByPath($parentPath);
  2015.                         // check for duplicate filename
  2016.                         $filename $this->getSafeFilename($parent->getRealFullPath(), $filename);
  2017.                         if ($parent->isAllowed('create')) {
  2018.                             $asset Asset::create($parent->getId(), [
  2019.                                 'filename' => $filename,
  2020.                                 'sourcePath' => $tmpFile,
  2021.                                 'userOwner' => $this->getAdminUser()->getId(),
  2022.                                 'userModification' => $this->getAdminUser()->getId(),
  2023.                             ]);
  2024.                             @unlink($tmpFile);
  2025.                         } else {
  2026.                             Logger::debug('prevented creating asset because of missing permissions');
  2027.                         }
  2028.                     }
  2029.                 }
  2030.             }
  2031.             $zip->close();
  2032.         }
  2033.         if ($request->get('last')) {
  2034.             unlink($zipFile);
  2035.         }
  2036.         return $this->adminJson([
  2037.             'success' => true,
  2038.         ]);
  2039.     }
  2040.     /**
  2041.      * @Route("/import-server", name="pimcore_admin_asset_importserver", methods={"POST"})
  2042.      *
  2043.      * @param Request $request
  2044.      *
  2045.      * @return JsonResponse
  2046.      */
  2047.     public function importServerAction(Request $request)
  2048.     {
  2049.         $success true;
  2050.         $filesPerJob 5;
  2051.         $jobs = [];
  2052.         $importDirectory str_replace('/fileexplorer'PIMCORE_PROJECT_ROOT$request->get('serverPath'));
  2053.         if (preg_match('@^' preg_quote(PIMCORE_PROJECT_ROOT'@') . '@'$importDirectory) && is_dir($importDirectory)) {
  2054.             $this->checkForPharStreamWrapper($importDirectory);
  2055.             $files rscandir($importDirectory '/');
  2056.             $count count($files);
  2057.             $jobFiles = [];
  2058.             for ($i 0$i $count$i++) {
  2059.                 if (is_dir($files[$i])) {
  2060.                     continue;
  2061.                 }
  2062.                 $jobFiles[] = preg_replace('@^' preg_quote($importDirectory'@') . '@'''$files[$i]);
  2063.                 if (count($jobFiles) >= $filesPerJob || $i >= ($count 1)) {
  2064.                     $relativeImportDirectory preg_replace('@^' preg_quote(PIMCORE_PROJECT_ROOT'@') . '@'''$importDirectory);
  2065.                     $jobs[] = [[
  2066.                         'url' => $this->generateUrl('pimcore_admin_asset_importserverfiles'),
  2067.                         'method' => 'POST',
  2068.                         'params' => [
  2069.                             'parentId' => $request->get('parentId'),
  2070.                             'serverPath' => $relativeImportDirectory,
  2071.                             'files' => implode('::'$jobFiles),
  2072.                         ],
  2073.                     ]];
  2074.                     $jobFiles = [];
  2075.                 }
  2076.             }
  2077.         }
  2078.         return $this->adminJson([
  2079.             'success' => $success,
  2080.             'jobs' => $jobs,
  2081.         ]);
  2082.     }
  2083.     /**
  2084.      * @Route("/import-server-files", name="pimcore_admin_asset_importserverfiles", methods={"POST"})
  2085.      *
  2086.      * @param Request $request
  2087.      *
  2088.      * @return JsonResponse
  2089.      */
  2090.     public function importServerFilesAction(Request $request)
  2091.     {
  2092.         if(!Tool\Admin::getCurrentUser()->isAdmin()) {
  2093.             throw $this->createAccessDeniedException('Permission denied. You don\'t have the rights to import files from the server!');
  2094.         }
  2095.         $assetFolder Asset::getById((int) $request->get('parentId'));
  2096.         if (!$assetFolder) {
  2097.             throw $this->createNotFoundException('Parent asset not found');
  2098.         }
  2099.         $serverPath realpath(PIMCORE_PROJECT_ROOT $request->get('serverPath'));
  2100.         if(!str_starts_with($serverPathrtrim(str_replace('../'''PIMCORE_PROJECT_ROOT), './'))) {
  2101.             throw $this->createAccessDeniedException('Please do not navigate out of the web root directory!');
  2102.         }
  2103.         $files explode('::'$request->get('files'));
  2104.         // prevent directory traversal
  2105.         foreach ($files as $file) {
  2106.             $absolutePath realpath($serverPath.$file);
  2107.             if (!str_starts_with($absolutePath$serverPath)) {
  2108.                 throw $this->createAccessDeniedException('Please do not navigate out of the web root directory!');
  2109.             }
  2110.         }
  2111.         foreach ($files as $file) {
  2112.             $absolutePath $serverPath $file;
  2113.             $this->checkForPharStreamWrapper($absolutePath);
  2114.             if (is_file($absolutePath)) {
  2115.                 $relFolderPath str_replace('\\''/'dirname($file));
  2116.                 $folder Asset\Service::createFolderByPath($assetFolder->getRealFullPath() . $relFolderPath);
  2117.                 $filename basename($file);
  2118.                 // check for duplicate filename
  2119.                 $filename Element\Service::getValidKey($filename'asset');
  2120.                 $filename $this->getSafeFilename($folder->getRealFullPath(), $filename);
  2121.                 if ($assetFolder->isAllowed('create')) {
  2122.                     $asset Asset::create($folder->getId(), [
  2123.                         'filename' => $filename,
  2124.                         'sourcePath' => $absolutePath,
  2125.                         'userOwner' => $this->getAdminUser()->getId(),
  2126.                         'userModification' => $this->getAdminUser()->getId(),
  2127.                     ]);
  2128.                 } else {
  2129.                     Logger::debug('prevented creating asset because of missing permissions ');
  2130.                 }
  2131.             }
  2132.         }
  2133.         return $this->adminJson([
  2134.             'success' => true,
  2135.         ]);
  2136.     }
  2137.     protected function checkForPharStreamWrapper($path)
  2138.     {
  2139.         if (stripos($path'phar://') !== false) {
  2140.             throw $this->createAccessDeniedException('Using PHAR files is not allowed!');
  2141.         }
  2142.     }
  2143.     /**
  2144.      * @Route("/import-url", name="pimcore_admin_asset_importurl", methods={"POST"})
  2145.      *
  2146.      * @param Request $request
  2147.      *
  2148.      * @return JsonResponse
  2149.      *
  2150.      * @throws \Exception
  2151.      */
  2152.     public function importUrlAction(Request $request)
  2153.     {
  2154.         $success true;
  2155.         $data Tool::getHttpData($request->get('url'));
  2156.         $filename basename($request->get('url'));
  2157.         $parentId $request->get('id');
  2158.         $parentAsset Asset::getById((int)$parentId);
  2159.         if (!$parentAsset) {
  2160.             throw $this->createNotFoundException('Parent asset not found');
  2161.         }
  2162.         $filename Element\Service::getValidKey($filename'asset');
  2163.         $filename $this->getSafeFilename($parentAsset->getRealFullPath(), $filename);
  2164.         if (empty($filename)) {
  2165.             throw new \Exception('The filename of the asset is empty');
  2166.         }
  2167.         // check for duplicate filename
  2168.         $filename $this->getSafeFilename($parentAsset->getRealFullPath(), $filename);
  2169.         if ($parentAsset->isAllowed('create')) {
  2170.             $asset Asset::create($parentId, [
  2171.                 'filename' => $filename,
  2172.                 'data' => $data,
  2173.                 'userOwner' => $this->getAdminUser()->getId(),
  2174.                 'userModification' => $this->getAdminUser()->getId(),
  2175.             ]);
  2176.             $success true;
  2177.         } else {
  2178.             Logger::debug('prevented creating asset because of missing permissions');
  2179.         }
  2180.         return $this->adminJson(['success' => $success]);
  2181.     }
  2182.     /**
  2183.      * @Route("/clear-thumbnail", name="pimcore_admin_asset_clearthumbnail", methods={"POST"})
  2184.      *
  2185.      * @param Request $request
  2186.      *
  2187.      * @return JsonResponse
  2188.      */
  2189.     public function clearThumbnailAction(Request $request)
  2190.     {
  2191.         $success false;
  2192.         if ($asset Asset::getById((int) $request->get('id'))) {
  2193.             if (method_exists($asset'clearThumbnails')) {
  2194.                 if (!$asset->isAllowed('publish')) {
  2195.                     throw $this->createAccessDeniedException('not allowed to publish');
  2196.                 }
  2197.                 $asset->clearThumbnails(true); // force clear
  2198.                 $asset->save();
  2199.                 $success true;
  2200.             }
  2201.         }
  2202.         return $this->adminJson(['success' => $success]);
  2203.     }
  2204.     /**
  2205.      * @Route("/grid-proxy", name="pimcore_admin_asset_gridproxy", methods={"GET", "POST", "PUT"})
  2206.      *
  2207.      * @param Request $request
  2208.      * @param EventDispatcherInterface $eventDispatcher
  2209.      * @param GridHelperService $gridHelperService
  2210.      * @param CsrfProtectionHandler $csrfProtection
  2211.      *
  2212.      * @return JsonResponse
  2213.      */
  2214.     public function gridProxyAction(Request $requestEventDispatcherInterface $eventDispatcherGridHelperService $gridHelperServiceCsrfProtectionHandler $csrfProtection)
  2215.     {
  2216.         $allParams array_merge($request->request->all(), $request->query->all());
  2217.         $filterPrepareEvent = new GenericEvent($this, [
  2218.             'requestParams' => $allParams,
  2219.         ]);
  2220.         $language $request->get('language') != 'default' $request->get('language') : null;
  2221.         $eventDispatcher->dispatch($filterPrepareEventAdminEvents::ASSET_LIST_BEFORE_FILTER_PREPARE);
  2222.         $allParams $filterPrepareEvent->getArgument('requestParams');
  2223.         $loader \Pimcore::getContainer()->get('pimcore.implementation_loader.asset.metadata.data');
  2224.         if (isset($allParams['data']) && $allParams['data']) {
  2225.             $csrfProtection->checkCsrfToken($request);
  2226.             if ($allParams['xaction'] == 'update') {
  2227.                 try {
  2228.                     $data $this->decodeJson($allParams['data']);
  2229.                     $updateEvent = new GenericEvent($this, [
  2230.                         'data' => $data,
  2231.                         'processed' => false,
  2232.                     ]);
  2233.                     $eventDispatcher->dispatch($updateEventAdminEvents::ASSET_LIST_BEFORE_UPDATE);
  2234.                     $processed $updateEvent->getArgument('processed');
  2235.                     if ($processed) {
  2236.                         // update already processed by event handler
  2237.                         return $this->adminJson(['success' => true]);
  2238.                     }
  2239.                     $data $updateEvent->getArgument('data');
  2240.                     // save
  2241.                     $asset Asset::getById($data['id']);
  2242.                     if (!$asset) {
  2243.                         throw $this->createNotFoundException('Asset not found');
  2244.                     }
  2245.                     if (!$asset->isAllowed('publish')) {
  2246.                         throw $this->createAccessDeniedException("Permission denied. You don't have the rights to save this asset.");
  2247.                     }
  2248.                     $metadata $asset->getMetadata(nullnullfalsetrue);
  2249.                     $dirty false;
  2250.                     unset($data['id']);
  2251.                     foreach ($data as $key => $value) {
  2252.                         $fieldDef explode('~'$key);
  2253.                         $key $fieldDef[0];
  2254.                         if (isset($fieldDef[1])) {
  2255.                             $language = ($fieldDef[1] == 'none' '' $fieldDef[1]);
  2256.                         }
  2257.                         foreach ($metadata as $idx => &$em) {
  2258.                             if ($em['name'] == $key && $em['language'] == $language) {
  2259.                                 try {
  2260.                                     $dataImpl $loader->build($em['type']);
  2261.                                     $value $dataImpl->getDataFromListfolderGrid($value$em);
  2262.                                 } catch (UnsupportedException $le) {
  2263.                                     Logger::error('could not resolve metadata implementation for ' $em['type']);
  2264.                                 }
  2265.                                 $em['data'] = $value;
  2266.                                 $dirty true;
  2267.                                 break;
  2268.                             }
  2269.                         }
  2270.                         if (!$dirty) {
  2271.                             $defaulMetadata = ['title''alt''copyright'];
  2272.                             if (in_array($key$defaulMetadata)) {
  2273.                                 $newEm = [
  2274.                                     'name' => $key,
  2275.                                     'language' => $language,
  2276.                                     'type' => 'input',
  2277.                                     'data' => $value,
  2278.                                 ];
  2279.                                 try {
  2280.                                     $dataImpl $loader->build($newEm['type']);
  2281.                                     $newEm['data'] = $dataImpl->getDataFromListfolderGrid($value$newEm);
  2282.                                 } catch (UnsupportedException $le) {
  2283.                                     Logger::error('could not resolve metadata implementation for ' $newEm['type']);
  2284.                                 }
  2285.                                 $metadata[] = $newEm;
  2286.                                 $dirty true;
  2287.                             } else {
  2288.                                 $predefined Model\Metadata\Predefined::getByName($key);
  2289.                                 if ($predefined && (empty($predefined->getTargetSubtype())
  2290.                                         || $predefined->getTargetSubtype() == $asset->getType())) {
  2291.                                     $newEm = [
  2292.                                         'name' => $key,
  2293.                                         'language' => $language,
  2294.                                         'type' => $predefined->getType(),
  2295.                                         'data' => $value,
  2296.                                     ];
  2297.                                     try {
  2298.                                         $dataImpl $loader->build($newEm['type']);
  2299.                                         $newEm['data'] = $dataImpl->getDataFromListfolderGrid($value$newEm);
  2300.                                     } catch (UnsupportedException $le) {
  2301.                                         Logger::error('could not resolve metadata implementation for ' $newEm['type']);
  2302.                                     }
  2303.                                     $metadata[] = $newEm;
  2304.                                     $dirty true;
  2305.                                 }
  2306.                             }
  2307.                         }
  2308.                     }
  2309.                     if ($dirty) {
  2310.                         // $metadata = Asset\Service::minimizeMetadata($metadata, "grid");
  2311.                         $asset->setMetadataRaw($metadata);
  2312.                         $asset->save();
  2313.                         return $this->adminJson(['success' => true]);
  2314.                     }
  2315.                     return $this->adminJson(['success' => false'message' => 'something went wrong.']);
  2316.                 } catch (\Exception $e) {
  2317.                     return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  2318.                 }
  2319.             }
  2320.         } else {
  2321.             $list $gridHelperService->prepareAssetListingForGrid($allParams$this->getAdminUser());
  2322.             $beforeListLoadEvent = new GenericEvent($this, [
  2323.                 'list' => $list,
  2324.                 'context' => $allParams,
  2325.             ]);
  2326.             $eventDispatcher->dispatch($beforeListLoadEventAdminEvents::ASSET_LIST_BEFORE_LIST_LOAD);
  2327.             /** @var Asset\Listing $list */
  2328.             $list $beforeListLoadEvent->getArgument('list');
  2329.             $list->load();
  2330.             $assets = [];
  2331.             foreach ($list->getAssets() as $index => $asset) {
  2332.                 // Like for treeGetChildsByIdAction, so we respect isAllowed method which can be extended (object DI) for custom permissions, so relying only users_workspaces_asset is insufficient and could lead security breach
  2333.                 if ($asset->isAllowed('list')) {
  2334.                     $a Asset\Service::gridAssetData($asset$allParams['fields'], $allParams['language'] ?? '');
  2335.                     $assets[] = $a;
  2336.                 }
  2337.             }
  2338.             $result = ['data' => $assets'success' => true'total' => $list->getTotalCount()];
  2339.             $afterListLoadEvent = new GenericEvent($this, [
  2340.                 'list' => $result,
  2341.                 'context' => $allParams,
  2342.             ]);
  2343.             $eventDispatcher->dispatch($afterListLoadEventAdminEvents::ASSET_LIST_AFTER_LIST_LOAD);
  2344.             $result $afterListLoadEvent->getArgument('list');
  2345.             return $this->adminJson($result);
  2346.         }
  2347.         return $this->adminJson(['success' => false]);
  2348.     }
  2349.     /**
  2350.      * @Route("/get-text", name="pimcore_admin_asset_gettext", methods={"GET"})
  2351.      *
  2352.      * @param Request $request
  2353.      *
  2354.      * @return JsonResponse
  2355.      */
  2356.     public function getTextAction(Request $request)
  2357.     {
  2358.         $asset Asset::getById((int) $request->get('id'));
  2359.         if (!$asset) {
  2360.             throw $this->createNotFoundException('Asset not found');
  2361.         }
  2362.         if (!$asset->isAllowed('view')) {
  2363.             throw $this->createAccessDeniedException('not allowed to view');
  2364.         }
  2365.         $page $request->get('page');
  2366.         $text null;
  2367.         if ($asset instanceof Asset\Document) {
  2368.             $text $asset->getText($page);
  2369.         }
  2370.         return $this->adminJson(['success' => 'true''text' => $text]);
  2371.     }
  2372.     /**
  2373.      * @Route("/detect-image-features", name="pimcore_admin_asset_detectimagefeatures", methods={"GET"})
  2374.      *
  2375.      * @param Request $request
  2376.      *
  2377.      * @return JsonResponse
  2378.      */
  2379.     public function detectImageFeaturesAction(Request $request)
  2380.     {
  2381.         $asset Asset\Image::getById((int)$request->get('id'));
  2382.         if (!$asset instanceof Asset) {
  2383.             return $this->adminJson(['success' => false'message' => "asset doesn't exist"]);
  2384.         }
  2385.         if ($asset->isAllowed('publish')) {
  2386.             $asset->detectFaces();
  2387.             $asset->removeCustomSetting('disableImageFeatureAutoDetection');
  2388.             $asset->save();
  2389.             return $this->adminJson(['success' => true]);
  2390.         }
  2391.         throw $this->createAccessDeniedHttpException();
  2392.     }
  2393.     /**
  2394.      * @Route("/delete-image-features", name="pimcore_admin_asset_deleteimagefeatures", methods={"GET"})
  2395.      *
  2396.      * @param Request $request
  2397.      *
  2398.      * @return JsonResponse
  2399.      */
  2400.     public function deleteImageFeaturesAction(Request $request)
  2401.     {
  2402.         $asset Asset::getById((int)$request->get('id'));
  2403.         if (!$asset instanceof Asset) {
  2404.             return $this->adminJson(['success' => false'message' => "asset doesn't exist"]);
  2405.         }
  2406.         if ($asset->isAllowed('publish')) {
  2407.             $asset->removeCustomSetting('faceCoordinates');
  2408.             $asset->setCustomSetting('disableImageFeatureAutoDetection'true);
  2409.             $asset->save();
  2410.             return $this->adminJson(['success' => true]);
  2411.         }
  2412.         throw $this->createAccessDeniedHttpException();
  2413.     }
  2414.     /**
  2415.      * @param ControllerEvent $event
  2416.      */
  2417.     public function onKernelControllerEvent(ControllerEvent $event)
  2418.     {
  2419.         if (!$event->isMainRequest()) {
  2420.             return;
  2421.         }
  2422.         $this->checkActionPermission($event'assets', [
  2423.             'getImageThumbnailAction''getVideoThumbnailAction''getDocumentThumbnailAction',
  2424.         ]);
  2425.         $this->_assetService = new Asset\Service($this->getAdminUser());
  2426.     }
  2427.     /**
  2428.      * @throws ValidationException
  2429.      */
  2430.     private function validateManyToManyRelationAssetType(array $contextstring $filenamestring $sourcePath): void
  2431.     {
  2432.         if (isset($context['containerType'], $context['objectId'], $context['fieldname'])
  2433.             && 'object' === $context['containerType']
  2434.             && $object Concrete::getById($context['objectId'])
  2435.         ) {
  2436.             $fieldDefinition $object->getClass()?->getFieldDefinition($context['fieldname']);
  2437.             if (!$fieldDefinition instanceof ManyToManyRelation) {
  2438.                 return;
  2439.             }
  2440.             $mimeType MimeTypes::getDefault()->guessMimeType($sourcePath);
  2441.             $type Asset::getTypeFromMimeMapping($mimeType$filename);
  2442.             $allowedAssetTypes $fieldDefinition->getAssetTypes();
  2443.             $allowedAssetTypes array_column($allowedAssetTypes'assetTypes');
  2444.             if (
  2445.                 !(
  2446.                     $fieldDefinition->getAssetsAllowed()
  2447.                     && ($allowedAssetTypes === [] || in_array($type$allowedAssetTypestrue))
  2448.                 )
  2449.             ) {
  2450.                 throw new ValidationException(sprintf('Invalid relation in field `%s` [type: %s]'$context['fieldname'], $type));
  2451.             }
  2452.         }
  2453.     }
  2454. }