/** * Return all Objects stored in this storage filtered by the given directory / filename pattern * * @param string $pattern A glob compatible directory / filename pattern * @param callable $callback Function called after each object * @return \Generator<StorageObject> */ public function getObjectsByPathPattern($pattern, callable $callback = null) { $directories = []; if (strpos($pattern, '/') !== false) { list($packageKeyPattern, $directoryPattern) = explode('/', $pattern, 2); } else { $packageKeyPattern = $pattern; $directoryPattern = '*'; } // $packageKeyPattern can be used in a future implementation to filter by package key $packages = $this->packageManager->getActivePackages(); foreach ($packages as $packageKey => $package) { /** @var PackageInterface $package */ if ($directoryPattern === '*') { $directories[$packageKey][] = $package->getPackagePath(); } else { $directories[$packageKey] = glob($package->getPackagePath() . $directoryPattern, GLOB_ONLYDIR); } } $iteration = 0; foreach ($directories as $packageKey => $packageDirectories) { foreach ($packageDirectories as $directoryPath) { foreach (Files::getRecursiveDirectoryGenerator($directoryPath) as $resourcePathAndFilename) { $pathInfo = UnicodeFunctions::pathinfo($resourcePathAndFilename); $object = new StorageObject(); $object->setFilename($pathInfo['basename']); $object->setSha1(sha1_file($resourcePathAndFilename)); $object->setMd5(md5_file($resourcePathAndFilename)); $object->setFileSize(filesize($resourcePathAndFilename)); if (isset($pathInfo['dirname'])) { list(, $path) = explode('/', str_replace($packages[$packageKey]->getResourcesPath(), '', $pathInfo['dirname']), 2); $object->setRelativePublicationPath($packageKey . '/' . $path . '/'); } $object->setStream(function () use($resourcePathAndFilename) { return fopen($resourcePathAndFilename, 'r'); }); (yield $object); if (is_callable($callback)) { call_user_func($callback, $iteration, $object); } $iteration++; } } } }
/** * Publishes the given source stream to this target, with the given relative path. * * @param resource $sourceStream Stream of the source to publish * @param string $relativeTargetPathAndFilename relative path and filename in the target directory * @throws TargetException * @throws \Exception */ protected function publishFile($sourceStream, $relativeTargetPathAndFilename) { $pathInfo = UnicodeFunctions::pathinfo($relativeTargetPathAndFilename); if (isset($pathInfo['extension']) && array_key_exists(strtolower($pathInfo['extension']), $this->extensionBlacklist) && $this->extensionBlacklist[strtolower($pathInfo['extension'])] === true) { throw new TargetException(sprintf('Could not publish "%s" into resource publishing target "%s" because the filename extension "%s" is blacklisted.', $sourceStream, $this->name, strtolower($pathInfo['extension'])), 1447152230); } $streamMetaData = stream_get_meta_data($sourceStream); if ($streamMetaData['wrapper_type'] !== 'plainfile' || $streamMetaData['stream_type'] !== 'STDIO') { throw new TargetException(sprintf('Could not publish stream "%s" into resource publishing target "%s" because the source is not a local file.', $streamMetaData['uri'], $this->name), 1416242392); } $sourcePathAndFilename = $streamMetaData['uri']; $targetPathAndFilename = $this->path . $relativeTargetPathAndFilename; if (@stat($sourcePathAndFilename) === false) { throw new TargetException(sprintf('Could not publish "%s" into resource publishing target "%s" because the source file is not accessible (file stat failed).', $sourcePathAndFilename, $this->name), 1415716366); } if (!file_exists(dirname($targetPathAndFilename))) { Files::createDirectoryRecursively(dirname($targetPathAndFilename)); } try { if (Files::is_link($targetPathAndFilename)) { Files::unlink($targetPathAndFilename); } if ($this->relativeSymlinks) { $result = Files::createRelativeSymlink($sourcePathAndFilename, $targetPathAndFilename); } else { $temporaryTargetPathAndFilename = uniqid($targetPathAndFilename . '.') . '.tmp'; symlink($sourcePathAndFilename, $temporaryTargetPathAndFilename); $result = rename($temporaryTargetPathAndFilename, $targetPathAndFilename); } } catch (\Exception $exception) { $result = false; } if ($result === false) { throw new TargetException(sprintf('Could not publish "%s" into resource publishing target "%s" because the source file could not be symlinked at target location.', $sourcePathAndFilename, $this->name), 1415716368, isset($exception) ? $exception : null); } $this->systemLogger->log(sprintf('FileSystemSymlinkTarget: Published file. (target: %s, file: %s)', $this->name, $relativeTargetPathAndFilename), LOG_DEBUG); }
/** * Publishes the given source stream to this target, with the given relative path. * * @param resource $sourceStream Stream of the source to publish * @param string $relativeTargetPathAndFilename relative path and filename in the target directory * @return void * @throws TargetException */ protected function publishFile($sourceStream, $relativeTargetPathAndFilename) { $pathInfo = UnicodeFunctions::pathinfo($relativeTargetPathAndFilename); if (isset($pathInfo['extension']) && array_key_exists(strtolower($pathInfo['extension']), $this->extensionBlacklist) && $this->extensionBlacklist[strtolower($pathInfo['extension'])] === true) { throw new TargetException(sprintf('Could not publish "%s" into resource publishing target "%s" because the filename extension "%s" is blacklisted.', $sourceStream, $this->name, strtolower($pathInfo['extension'])), 1447148472); } $targetPathAndFilename = $this->path . $relativeTargetPathAndFilename; if (@fstat($sourceStream) === false) { throw new TargetException(sprintf('Could not publish "%s" into resource publishing target "%s" because the source file is not accessible (file stat failed).', $sourceStream, $this->name), 1375258499); } if (!file_exists(dirname($targetPathAndFilename))) { Files::createDirectoryRecursively(dirname($targetPathAndFilename)); } if (!is_writable(dirname($targetPathAndFilename))) { throw new Exception(sprintf('Could not publish "%s" into resource publishing target "%s" because the target file "%s" is not writable.', $sourceStream, $this->name, $targetPathAndFilename), 1428917322, isset($exception) ? $exception : null); } try { $targetFileHandle = fopen($targetPathAndFilename, 'w'); $result = stream_copy_to_stream($sourceStream, $targetFileHandle); fclose($targetFileHandle); } catch (\Exception $exception) { $result = false; } if ($result === false) { throw new TargetException(sprintf('Could not publish "%s" into resource publishing target "%s" because the source file could not be copied to the target location.', $sourceStream, $this->name), 1375258399, isset($exception) ? $exception : null); } $this->systemLogger->log(sprintf('FileSystemTarget: Published file. (target: %s, file: %s)', $this->name, $relativeTargetPathAndFilename), LOG_DEBUG); }
/** * Set the suggested filename of this Object * * @param string $filename * @return void */ public function setFilename($filename) { $pathInfo = UnicodeFunctions::pathinfo($filename); $extension = isset($pathInfo['extension']) ? '.' . strtolower($pathInfo['extension']) : ''; $this->filename = $pathInfo['filename'] . $extension; $this->mediaType = MediaTypes::getMediaTypeFromFilename($this->filename); }
/** * @param PersistentResource $originalResource * @param array $adjustments * @return array resource, width, height as keys * @throws ImageFileException * @throws InvalidConfigurationException * @throws Exception */ public function processImage(PersistentResource $originalResource, array $adjustments) { $additionalOptions = array(); $adjustmentsApplied = false; // TODO: Special handling for SVG should be refactored at a later point. if ($originalResource->getMediaType() === 'image/svg+xml') { $originalResourceStream = $originalResource->getStream(); $resource = $this->resourceManager->importResource($originalResourceStream, $originalResource->getCollectionName()); fclose($originalResourceStream); $resource->setFilename($originalResource->getFilename()); return ['width' => null, 'height' => null, 'resource' => $resource]; } $resourceUri = $originalResource->createTemporaryLocalCopy(); $resultingFileExtension = $originalResource->getFileExtension(); $transformedImageTemporaryPathAndFilename = $this->environment->getPathToTemporaryDirectory() . uniqid('ProcessedImage-') . '.' . $resultingFileExtension; if (!file_exists($resourceUri)) { throw new ImageFileException(sprintf('An error occurred while transforming an image: the resource data of the original image does not exist (%s, %s).', $originalResource->getSha1(), $resourceUri), 1374848224); } $imagineImage = $this->imagineService->open($resourceUri); $convertCMYKToRGB = $this->getOptionsMergedWithDefaults()['convertCMYKToRGB']; if ($convertCMYKToRGB && $imagineImage->palette() instanceof CMYK) { $imagineImage->usePalette(new RGB()); } if ($this->imagineService instanceof Imagine && $originalResource->getFileExtension() === 'gif' && $this->isAnimatedGif(file_get_contents($resourceUri)) === true) { $imagineImage->layers()->coalesce(); $layers = $imagineImage->layers(); $newLayers = array(); foreach ($layers as $index => $imagineFrame) { $imagineFrame = $this->applyAdjustments($imagineFrame, $adjustments, $adjustmentsApplied); $newLayers[] = $imagineFrame; } $imagineImage = array_shift($newLayers); $layers = $imagineImage->layers(); foreach ($newLayers as $imagineFrame) { $layers->add($imagineFrame); } $additionalOptions['animated'] = true; } else { $imagineImage = $this->applyAdjustments($imagineImage, $adjustments, $adjustmentsApplied); } if ($adjustmentsApplied === true) { $imagineImage->save($transformedImageTemporaryPathAndFilename, $this->getOptionsMergedWithDefaults($additionalOptions)); $imageSize = $imagineImage->getSize(); // TODO: In the future the collectionName of the new resource should be configurable. $resource = $this->resourceManager->importResource($transformedImageTemporaryPathAndFilename, $originalResource->getCollectionName()); if ($resource === false) { throw new ImageFileException('An error occurred while importing a generated image file as a resource.', 1413562208); } unlink($transformedImageTemporaryPathAndFilename); $pathInfo = UnicodeFunctions::pathinfo($originalResource->getFilename()); $resource->setFilename(sprintf('%s-%ux%u.%s', $pathInfo['filename'], $imageSize->getWidth(), $imageSize->getHeight(), $pathInfo['extension'])); } else { $originalResourceStream = $originalResource->getStream(); $resource = $this->resourceManager->importResource($originalResourceStream, $originalResource->getCollectionName()); fclose($originalResourceStream); $resource->setFilename($originalResource->getFilename()); $imageSize = $this->getImageSize($originalResource); $imageSize = new Box($imageSize['width'], $imageSize['height']); } $this->imageSizeCache->set($resource->getCacheEntryIdentifier(), array('width' => $imageSize->getWidth(), 'height' => $imageSize->getHeight())); $result = array('width' => $imageSize->getWidth(), 'height' => $imageSize->getHeight(), 'resource' => $resource); return $result; }
/** * Prepare an uploaded file to be imported as resource object. Will check the validity of the file, * move it outside of upload folder if open_basedir is enabled and check the filename. * * @param array $uploadInfo * @return array Array of string with the two keys "filepath" (the path to get the filecontent from) and "filename" the filename of the originally uploaded file. * @throws Exception */ protected function prepareUploadedFileForImport(array $uploadInfo) { $openBasedirEnabled = (bool) ini_get('open_basedir'); $temporaryTargetPathAndFilename = $uploadInfo['tmp_name']; $pathInfo = UnicodeFunctions::pathinfo($uploadInfo['name']); if (!is_uploaded_file($temporaryTargetPathAndFilename)) { throw new Exception('The given upload file "' . strip_tags($pathInfo['basename']) . '" was not uploaded through PHP. As it could pose a security risk it cannot be imported.', 1422461503); } if (isset($pathInfo['extension']) && array_key_exists(strtolower($pathInfo['extension']), $this->settings['resource']['uploadExtensionBlacklist']) && $this->settings['resource']['uploadExtensionBlacklist'][strtolower($pathInfo['extension'])] === true) { throw new Exception('The extension of the given upload file "' . strip_tags($pathInfo['basename']) . '" is blacklisted. As it could pose a security risk it cannot be imported.', 1447148472); } if ($openBasedirEnabled === true) { // Move uploaded file to a readable folder before trying to read sha1 value of file $newTemporaryTargetPathAndFilename = $this->environment->getPathToTemporaryDirectory() . 'ResourceUpload.' . uniqid() . '.tmp'; if (move_uploaded_file($temporaryTargetPathAndFilename, $newTemporaryTargetPathAndFilename) === false) { throw new Exception(sprintf('The uploaded file "%s" could not be moved to the temporary location "%s".', $temporaryTargetPathAndFilename, $newTemporaryTargetPathAndFilename), 1375199056); } $temporaryTargetPathAndFilename = $newTemporaryTargetPathAndFilename; } if (!is_file($temporaryTargetPathAndFilename)) { throw new Exception(sprintf('The temporary file "%s" of the file upload does not exist (anymore).', $temporaryTargetPathAndFilename), 1375198998); } return ['filepath' => $temporaryTargetPathAndFilename, 'filename' => $pathInfo['basename']]; }
/** * Sets the filename which is used when this resource is downloaded or saved as a file * * @param string $filename * @return void * @api */ public function setFilename($filename) { $this->throwExceptionIfProtected(); $pathInfo = UnicodeFunctions::pathinfo($filename); $extension = isset($pathInfo['extension']) ? '.' . strtolower($pathInfo['extension']) : ''; $this->filename = $pathInfo['filename'] . $extension; $this->mediaType = Utility\MediaTypes::getMediaTypeFromFilename($this->filename); }
/** * Checks if our version of pathinfo can handle some common special characters * * @test */ public function pathinfoWorksWithCertainSpecialChars() { $testString = 'кириллическийПуть/кириллическоеИмя.расширение'; $this->assertEquals('кириллическийПуть', Functions::pathinfo($testString, PATHINFO_DIRNAME), 'pathinfo() did not return the correct dirname for a unicode path.'); $this->assertEquals('кириллическоеИмя.расширение', Functions::pathinfo($testString, PATHINFO_BASENAME), 'pathinfo() did not return the correct basename for a unicode path.'); $this->assertEquals('расширение', Functions::pathinfo($testString, PATHINFO_EXTENSION), 'pathinfo() did not return the correct extension for a unicode path.'); $this->assertEquals('кириллическоеИмя', Functions::pathinfo($testString, PATHINFO_FILENAME), 'pathinfo() did not return the correct filename for a unicode path.'); }