nextcloud-gallery/lib/Service/ConfigService.php

364 lines
10 KiB
PHP

<?php
/**
* Nextcloud - Gallery
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Olivier Paroz <galleryapps@oparoz.com>
*
* @copyright Olivier Paroz 2017
*/
namespace OCA\Gallery\Service;
use OCP\Files\Folder;
use OCP\IPreview;
use OCP\ILogger;
use OCA\Gallery\Config\ConfigParser;
use OCA\Gallery\Config\ConfigException;
use OCA\Gallery\Environment\Environment;
/**
* Finds configurations files and returns a configuration array
*
* Checks the current and parent folders for configuration files and to see if we're allowed to
* look for media file
* Supports explicit inheritance
*
* @package OCA\Gallery\Service
*/
class ConfigService extends FilesService {
/** @var string */
private $configName = 'gallery.cnf';
/** @var array <string,bool> */
private $completionStatus = ['design' => false, 'information' => false, 'sorting' => false];
/** @var ConfigParser */
private $configParser;
/** @var IPreview */
private $previewManager;
/**
* @todo This hard-coded array could be replaced by admin settings
*
* @var string[]
*/
private $baseMimeTypes = [
'image/png',
'image/jpeg',
'image/gif',
'image/x-xbitmap',
'image/bmp',
'image/tiff',
'image/x-dcraw',
'application/x-photoshop',
'application/illustrator',
'application/postscript',
];
/**
* These types are useful for files preview in the files app, but
* not for the gallery side
*
* @var string[]
*/
private $slideshowMimeTypes = [
'application/font-sfnt',
'application/x-font',
];
/**
* Constructor
*
* @param string $appName
* @param Environment $environment
* @param ConfigParser $configParser
* @param IPreview $previewManager
* @param ILogger $logger
*/
public function __construct(
$appName,
Environment $environment,
ConfigParser $configParser,
IPreview $previewManager,
ILogger $logger
) {
parent::__construct($appName, $environment, $logger);
$this->configParser = $configParser;
$this->previewManager = $previewManager;
}
/**
* Returns a list of supported features
*
* @return string[]
*/
public function getFeaturesList() {
$featuresList = [];
/** @var Folder $rootFolder */
$rootFolder = $this->environment->getVirtualRootFolder();
if ($this->isAllowedAndAvailable($rootFolder) && $this->configExists($rootFolder)) {
try {
$featuresList =
$this->configParser->getFeaturesList($rootFolder, $this->configName);
} catch (ConfigException $exception) {
$featuresList = $this->buildErrorMessage($exception, $rootFolder);
}
}
return $featuresList;
}
/**
* This builds and returns a list of all supported media types
*
* @todo Native SVG could be disabled via admin settings
*
* @param bool $extraMediaTypes
* @param bool $nativeSvgSupport
*
* @return string[] all supported media types
*/
public function getSupportedMediaTypes($extraMediaTypes, $nativeSvgSupport) {
$supportedMimes = [];
$wantedMimes = $this->baseMimeTypes;
if ($extraMediaTypes) {
$wantedMimes = array_merge($wantedMimes, $this->slideshowMimeTypes);
}
foreach ($wantedMimes as $wantedMime) {
// Let's see if a preview of files of that media type can be generated
if ($this->isMimeSupported($wantedMime)) {
// We store the media type
$supportedMimes[] = $wantedMime;
}
}
$supportedMimes = $this->addSvgSupport($supportedMimes, $nativeSvgSupport);
//$this->logger->debug("Supported Mimes: {mimes}", ['mimes' => $supportedMimes]);
return $supportedMimes;
}
/**
* Returns the configuration of the currently selected folder
*
* * information (description, copyright)
* * sorting (date, name, inheritance)
* * design (colour)
* * if the album should be ignored
*
* @param Folder $folderNode the current folder
* @param array $features the list of features retrieved fro the configuration file
*
* @return array|null
* @throws ForbiddenServiceException
*/
public function getConfig($folderNode, $features) {
$this->features = $features;
list ($albumConfig, $ignored) =
$this->collectConfig($folderNode, $this->ignoreAlbum, $this->configName);
if ($ignored) {
throw new ForbiddenServiceException(
'The owner has placed a restriction or the storage location is unavailable'
);
}
return $albumConfig;
}
/**
* Throws an exception if the media type of the file is not part of what the app allows
*
* @param $mimeType
*
* @throws ForbiddenServiceException
*/
public function validateMimeType($mimeType) {
if (!in_array($mimeType, $this->getSupportedMediaTypes(true, true))) {
throw new ForbiddenServiceException('Media type not allowed');
}
}
/**
* Determines if we have a configuration file to work with
*
* @param Folder $rootFolder the virtual root folder
*
* @return bool
*/
private function configExists($rootFolder) {
return $rootFolder && $rootFolder->nodeExists($this->configName);
}
/**
* Adds the SVG media type if it's not already there
*
* If it's enabled, but doesn't work, an exception will be raised when trying to generate a
* preview. If it's disabled, we support it via the browser's native support
*
* @param string[] $supportedMimes
* @param bool $nativeSvgSupport
*
* @return string[]
*/
private function addSvgSupport($supportedMimes, $nativeSvgSupport) {
if (!in_array('image/svg+xml', $supportedMimes) && $nativeSvgSupport) {
$supportedMimes[] = 'image/svg+xml';
}
return $supportedMimes;
}
/**
* Returns true if the passed mime type is supported
*
* In case of a failure, we just return that the media type is not supported
*
* @param string $mimeType
*
* @return boolean
*/
private function isMimeSupported($mimeType = '*') {
try {
return $this->previewManager->isMimeSupported($mimeType);
} catch (\Exception $exception) {
unset($exception);
return false;
}
}
/**
* Returns an album configuration array
*
* Goes through all the parent folders until either we're told the album is private or we've
* reached the root folder
*
* @param Folder $folder the current folder
* @param string $ignoreAlbum name of the file which blacklists folders
* @param string $configName name of the configuration file
* @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
* @param array $configSoFar the configuration collected so far
*
* @return array <null|array,bool>
*/
private function collectConfig(
$folder, $ignoreAlbum, $configName, $level = 0, $configSoFar = []
) {
if ($folder->nodeExists($ignoreAlbum)) {
// Cancel as soon as we find out that the folder is private or external
return [null, true];
}
$isRootFolder = $this->isRootFolder($folder, $level);
if ($folder->nodeExists($configName)) {
$configSoFar = $this->buildFolderConfig($folder, $configName, $configSoFar, $level);
}
if (!$isRootFolder) {
return $this->getParentConfig($folder, $ignoreAlbum, $configName, $level, $configSoFar);
}
$configSoFar = $this->validatesInfoConfig($configSoFar);
// We have reached the root folder
return [$configSoFar, false];
}
/**
* Returns a parsed configuration if one was found in the current folder or generates an error
* message to send back
*
* @param Folder $folder the current folder
* @param string $configName name of the configuration file
* @param array $collectedConfig the configuration collected so far
* @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
*
* @return array
*/
private function buildFolderConfig($folder, $configName, $collectedConfig, $level) {
try {
list($collectedConfig, $completionStatus) = $this->configParser->getFolderConfig(
$folder, $configName, $collectedConfig, $this->completionStatus, $level
);
$this->completionStatus = $completionStatus;
} catch (ConfigException $exception) {
$collectedConfig = $this->buildErrorMessage($exception, $folder);
}
return $collectedConfig;
}
/**
* Builds the error message to send back when there is an error
*
* @fixme Missing translation
*
* @param ConfigException $exception
* @param Folder $folder the current folder
*
* @return array<array<string,string>,bool>
*/
private function buildErrorMessage($exception, $folder) {
$configPath = $this->environment->getPathFromVirtualRoot($folder);
$errorMessage = $exception->getMessage() . ". Config location: /$configPath";
$this->logger->error($errorMessage);
$config = ['error' => ['message' => $errorMessage]];
$completionStatus = $this->completionStatus;
foreach ($completionStatus as $key) {
$completionStatus[$key] = true;
}
$this->completionStatus = $completionStatus;
return [$config];
}
/**
* Removes links if they were collected outside of the virtual root
*
* This is for shared folders which have a virtual root
*
* @param array $albumConfig
*
* @return array
*/
private function validatesInfoConfig($albumConfig) {
$this->virtualRootLevel;
if (array_key_exists('information', $albumConfig)) {
$info = $albumConfig['information'];
if (array_key_exists('level', $info)) {
$level = $info['level'];
if ($level > $this->virtualRootLevel) {
$albumConfig['information']['description_link'] = null;
$albumConfig['information']['copyright_link'] = null;
}
}
}
return $albumConfig;
}
/**
* Looks for an album configuration in the parent folder
*
* We will look up to the virtual root of a shared folder, for privacy reasons
*
* @param Folder $folder the current folder
* @param string $privacyChecker name of the file which blacklists folders
* @param string $configName name of the configuration file
* @param int $level the starting level is 0 and we add 1 each time we visit a parent folder
* @param array $collectedConfig the configuration collected so far
*
* @return array<null|array,bool>
*/
private function getParentConfig($folder, $privacyChecker, $configName, $level, $collectedConfig
) {
$parentFolder = $folder->getParent();
$level++;
return $this->collectConfig(
$parentFolder, $privacyChecker, $configName, $level, $collectedConfig
);
}
}