Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
13 / 13
CRAP
100.00% covered (success)
100.00%
73 / 73
ConfigService
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
13 / 13
33
100.00% covered (success)
100.00%
73 / 73
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 getFeaturesList
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
7 / 7
 getSupportedMediaTypes
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
9 / 9
 getConfig
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
 validateMimeType
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 configExists
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 addSvgSupport
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
3 / 3
 isMimeSupported
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
4 / 4
 collectConfig
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
9 / 9
 buildFolderConfig
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
 buildErrorMessage
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
9 / 9
 validatesInfoConfig
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
9 / 9
 getParentConfig
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
<?php
/**
 * 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 2016
 */
namespace OCA\Gallery\Service;
use OCP\Files\Folder;
use OCP\ILogger;
use OCA\Gallery\Config\ConfigParser;
use OCA\Gallery\Config\ConfigException;
use OCA\Gallery\Environment\Environment;
use OCA\Gallery\Preview\Preview;
/**
 * 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 Preview */
    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 Preview $previewManager
     * @param ILogger $logger
     */
    public function __construct(
        $appName,
        Environment $environment,
        ConfigParser $configParser,
        Preview $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
        );
    }
}