*/ class taoItems_actions_ItemContent extends tao_actions_CommonModule { use HttpJsonResponseTrait; use OntologyAwareTrait; /** * @throws MissingParameterException|TaoMediaException */ public function files(): void { $params = $this->getRequiredQueryParams('uri', 'lang', 'path'); ['uri' => $uri, 'lang' => $lang, 'path' => $path] = $params; $depth = (int)($params['depth'] ?? 1); $childrenOffset = (int)($params['childrenOffset'] ?? AssetTreeBuilder::DEFAULT_PAGINATION_OFFSET); $filters = $this->buildFilters($params); $searchQuery = new DirectorySearchQuery( $this->resolveAsset($uri, $path, $lang), $uri, $lang, $filters, $depth, $childrenOffset ); $this->setSuccessJsonResponse($this->getAssetTreeBuilder()->build($searchQuery)); } /** * Returns whenever or not a file exists at the indicated path * @throws MissingParameterException|TaoMediaException */ public function fileExists(): void { try { $params = $this->getRequiredQueryParams('uri', 'lang', 'path'); $asset = $this->resolveAsset($params['uri'], $params['path'], $params['lang']); $asset->getMediaSource()->getFileInfo($asset->getMediaIdentifier()); $found = true; } catch (FileNotFoundException $exception) { $found = false; } $formatter = $this->getResponseFormatter() ->withJsonHeader() ->withBody(['exists' => $found]); $this->setResponse($formatter->format($this->getPsrResponse())); } /** * Upload a file to the item directory */ public function upload(): void { $formatter = $this->getResponseFormatter() ->withJsonHeader(); //as upload may be called multiple times, we remove the session lock as soon as possible try { session_write_close(); $params = $this->getRequiredQueryParams('uri', 'lang', 'relPath', 'filters'); ['filters' => $filters] = $params; $asset = $this->getMediaAsset('uploadAsset'); $file = tao_helpers_Http::getUploadedFile('content'); $fileTmpName = $file['tmp_name'] . '_' . $file['name']; if (!tao_helpers_File::copy($file['tmp_name'], $fileTmpName)) { throw new common_exception_Error('impossible to copy ' . $file['tmp_name'] . ' to ' . $fileTmpName); } $mime = tao_helpers_File::getMimeType($fileTmpName); if (is_string($filters)) { // the mime type is part of the $filters $filters = explode(',', $filters); if ((in_array($mime, $filters))) { $fileData = $asset->getMediaSource()->add( $fileTmpName, $file['name'], $asset->getMediaIdentifier() ); } else { throw new FileUploadException(__('The file you tried to upload is not valid')); } } else { $valid = false; // OR the extension is part of the filter and it correspond to the mime type $fileExtension = tao_helpers_File::getFileExtention($fileTmpName); foreach ($filters as $filter) { if ( $filter['mime'] === $mime && (!isset($filter['extension']) || $filter['extension'] === $fileExtension) ) { $valid = true; } } if ($valid) { $fileData = $asset->getMediaSource()->add( $fileTmpName, $file['name'], $asset->getMediaIdentifier() ); } else { throw new FileUploadException(__('The file you tried to upload is not valid')); } } $formatter->withBody($fileData); } catch (PermissionException | FileUploadException $e) { $formatter->withBody(['error' => $e->getMessage()]); } catch (common_Exception $e) { $this->logWarning($e->getMessage()); $formatter->withBody(['error' => _('Unable to upload file')]); } $this->setResponse($formatter->format($this->getPsrResponse())); } /** * @throws MissingParameterException|FileNotFoundException|TaoMediaException */ public function download(): void { $asset = $this->getMediaAsset('previewAsset'); $mediaSource = $asset->getMediaSource(); $stream = $this->getMediaSourceFileStream($mediaSource, $asset); $info = $mediaSource->getFileInfo($asset->getMediaIdentifier()); $mime = $info['mime'] !== 'application/qti+xml' ? $info['mime'] : null; tao_helpers_Http::returnStream($stream, $mime, $this->getPsrRequest()); } /** * Delete a file from the item directory * * @throws MissingParameterException|TaoMediaException */ public function delete(): void { $asset = $this->getMediaAsset('deleteAsset'); $deleted = $asset->getMediaSource()->delete($asset->getMediaIdentifier()); $formatter = $this->getResponseFormatter() ->withJsonHeader() ->withBody(['deleted' => $deleted]); $this->setResponse($formatter->format($this->getPsrResponse())); } private function getMediaAsset(string $action): ?MediaAsset { $isWrite = in_array($action, ['deleteAsset', 'uploadAsset'], true); $params = $this->getRequiredQueryParams('uri', 'lang'); $queryParams = $this->getPsrRequest()->getQueryParams(); $path = $queryParams['relPath'] ?? $queryParams['path'] ?? null; $asset = $this->resolveAsset($params['uri'], $path, $params['lang']); $resourceUri = $params['uri']; $hasAccess = true; // We do not want to validate access for item gallery if (!$asset->getMediaSource() instanceof LocalItemSource) { $resourceUri = tao_helpers_Uri::decode($asset->getMediaIdentifier()); $context = new Context( [ Context::PARAM_CONTROLLER => taoItems_actions_ItemContent::class, Context::PARAM_ACTION => $action, ] ); $hasAccess = $isWrite ? $this->hasWriteAccessByContext($context) : $this->hasReadAccessByContext($context); } if (!$isWrite) { $hasAccess = $hasAccess && $this->getPermissionChecker()->hasReadAccess($resourceUri); } if ($isWrite) { $hasAccess = $hasAccess && $this->getPermissionChecker()->hasWriteAccess($resourceUri); } if (!$hasAccess) { throw new ResourceAccessDeniedException($resourceUri); } return $asset; } /** * @throws TaoMediaException */ private function resolveAsset(string $url, string $path, string $lang): MediaAsset { $item = $this->getResource($url); $resolver = new ItemMediaResolver($item, $lang); return $resolver->resolve($path); } /** * Returns Query params if mandatory params not empty * * @throws MissingParameterException */ private function getRequiredQueryParams(string ...$requiredKeys): array { $params = $this->getPsrRequest()->getQueryParams(); foreach ($requiredKeys as $key) { if (!array_key_exists($key, $params) || empty($params[$key])) { throw new MissingParameterException($key, __METHOD__); } } return $params; } private function buildFilters(array $params): array { $filters = []; if (isset($params['filters'])) { $filterParameter = $params['filters']; if (is_array($filterParameter)) { foreach ($filterParameter as $filter) { if (preg_match('/\/\*/', $filter['mime'])) { $this->logWarning( 'Stars mime type are not yet supported, filter "' . $filter['mime'] . '" will fail' ); } $filters[] = $filter['mime']; } } else { if (preg_match('/\/\*/', $filterParameter)) { $this->logWarning( 'Stars mime type are not yet supported, filter "' . $filterParameter . '" will fail' ); } $filters = array_map('trim', explode(',', $filterParameter)); } } return $filters; } private function getAssetTreeBuilder(): AssetTreeBuilderInterface { return $this->getServiceLocator()->get(AssetTreeBuilder::SERVICE_ID); } /** * @throws FileNotFoundException */ private function getMediaSourceFileStream(MediaBrowser $mediaSource, MediaAsset $asset): StreamInterface { if ($mediaSource instanceof ProcessedFileStreamAware) { return $mediaSource->getProcessedFileStream($asset->getMediaIdentifier()); } return $mediaSource->getFileStream($asset->getMediaIdentifier()); } private function getPermissionChecker(): PermissionCheckerInterface { return $this->getServiceLocator()->get(PermissionChecker::class); } }