Commit 2e9d4521 authored by Philipp Schaffrath's avatar Philipp Schaffrath Committed by GitHub
Browse files

Filetype icons from themes (#26805)

* corrected comment

* provide information about the current enabled theme and save it in OC.currentTheme

* *wip*: refactor mimeType icon render process, implement theme-app support

* extracted alias logic into separate method, use core icons if theme has no icon

* extracted comments into readme.md file, that way we don't need to filter them out in our code

* make sure an app-theme uses the app name as its own name

* refactored code, added methods to get filetype icons by directory and get legacy and app theme information

* use the new OC.MimeTypeList.themes object which has icons

* adding directory and icons was unnecessary since js gets the theme directory from the active theme itself

* test app themes get app name as theme name, test theme name setter

* added OC.getRootPath since it might be necessary for some special configurations (and tests suggest this)

* mimetypes now use OC.currentTheme which contains both the name and the directory of the active theme
also replaced deprecated webroot with getRootPath()

* updated mimetypelist to the latest generated one, since the one in the repository was missing the example theme, an alias and had an error in its comment above

* fixed type annotation

* removed redundant wording, improved grammatic

* return 1 and print error message if file could not be written
parent f9c1e90e
......@@ -23,19 +23,18 @@
namespace OC\Core\Command\Maintenance\Mimetype;
use OC\Files\Type\Detection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use OCP\Files\IMimeTypeDetector;
class UpdateJS extends Command {
/** @var IMimeTypeDetector */
/** @var Detection */
protected $mimetypeDetector;
public function __construct(
IMimeTypeDetector $mimetypeDetector
Detection $mimetypeDetector
) {
parent::__construct();
$this->mimetypeDetector = $mimetypeDetector;
......@@ -47,83 +46,148 @@ class UpdateJS extends Command {
->setDescription('Update mimetypelist.js');
}
protected function execute(InputInterface $input, OutputInterface $output) {
// Fetch all the aliases
$aliases = $this->mimetypeDetector->getAllAliases();
// Remove comments
$keys = array_filter(array_keys($aliases), function($k) {
return $k[0] === '_';
});
foreach($keys as $key) {
unset($aliases[$key]);
}
// Fetch all files
$dir = new \DirectoryIterator(\OC::$SERVERROOT.'/core/img/filetypes');
/**
* @return array
*/
private function getFiles() {
$dir = new \DirectoryIterator(\OC::$SERVERROOT . '/core/img/filetypes');
$files = [];
foreach($dir as $fileInfo) {
if ($fileInfo->isFile()) {
$file = preg_replace('/.[^.]*$/', '', $fileInfo->getFilename());
$files[] = $file;
$files[] = preg_replace('/.[^.]*$/', '', $fileInfo->getFilename());
}
}
//Remove duplicates
$files = array_values(array_unique($files));
sort($files);
return $files;
}
/**
* @param $themeDirectory
* @return array
*/
private function getFileTypeIcons($themeDirectory) {
$fileTypeIcons = [];
$fileTypeIconDirectory = $themeDirectory . '/core/img/filetypes';
if (is_dir($fileTypeIconDirectory)) {
$fileTypeIconFiles = new \DirectoryIterator($fileTypeIconDirectory);
foreach ($fileTypeIconFiles as $fileTypeIconFile) {
if ($fileTypeIconFile->isFile()) {
$fileTypeIconName = preg_replace('/.[^.]*$/', '', $fileTypeIconFile->getFilename());
$fileTypeIcons[] = $fileTypeIconName;
}
}
}
$fileTypeIcons = array_values(array_unique($fileTypeIcons));
sort($fileTypeIcons);
return $fileTypeIcons;
}
// Fetch all themes!
/**
* @return array
*/
private function getThemes() {
return array_merge(
$this->getAppThemes(),
$this->getLegacyThemes()
);
}
/**
* @return array
*/
private function getAppThemes() {
$themes = [];
$dirs = new \DirectoryIterator(\OC::$SERVERROOT.'/themes/');
foreach($dirs as $dir) {
//Valid theme dir
if ($dir->isFile() || $dir->isDot()) {
continue;
$apps = \OC_App::getEnabledApps();
foreach ($apps as $app) {
if(\OC_App::isType($app, 'theme')) {
$themes[$app] = $this->getFileTypeIcons(\OC_App::getAppPath($app));
}
}
return $themes;
}
$theme = $dir->getFilename();
$themeDir = $dir->getPath() . '/' . $theme . '/core/img/filetypes/';
// Check if this theme has its own filetype icons
if (!file_exists($themeDir)) {
/**
* @return array
*/
private function getLegacyThemes() {
$themes = [];
$legacyThemeDirectories = new \DirectoryIterator(\OC::$SERVERROOT . '/themes/');
foreach($legacyThemeDirectories as $legacyThemeDirectory) {
if ($legacyThemeDirectory->isFile() || $legacyThemeDirectory->isDot()) {
continue;
}
$themes[$legacyThemeDirectory->getFilename()] = $this->getFileTypeIcons(
$legacyThemeDirectory->getPathname()
);
}
$themes[$theme] = [];
// Fetch all the theme icons!
$themeIt = new \DirectoryIterator($themeDir);
foreach ($themeIt as $fileInfo) {
if ($fileInfo->isFile()) {
$file = preg_replace('/.[^.]*$/', '', $fileInfo->getFilename());
$themes[$theme][] = $file;
}
}
return $themes;
}
//Remove Duplicates
$themes[$theme] = array_values(array_unique($themes[$theme]));
sort($themes[$theme]);
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$fileName = \OC::$SERVERROOT.'/core/js/mimetypelist.js';
$success = file_put_contents(
$fileName,
$this->generateMimeTypeListContent(
$this->mimetypeDetector->getAllAliases(),
$this->getFiles(),
$this->getThemes()
)
);
if ($success === false) {
$output->writeln("<error>could not write mimetypelist to $fileName.</error>");
return 1;
}
//Generate the JS
$js = '/**
$output->writeln('<info>mimetypelist.js is updated</info>');
return 0;
}
/**
* @param array $aliases
* @param array $files
* @param array $themes
* @return string
*/
private function generateMimeTypeListContent($aliases, $files, $themes) {
$aliasesJson = json_encode($aliases, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$filesJson = json_encode($files, JSON_PRETTY_PRINT);
$themesJson = json_encode($themes, JSON_PRETTY_PRINT);
$content = <<< MTLC
/**
* This file is automatically generated
* DO NOT EDIT MANUALLY!
*
* You can update the list of MimeType Aliases in config/mimetypealiases.json
* The list of files is fetched from core/img/filetypes
* To regenerate this file run ./occ maintenance:mimetypesjs
* To regenerate this file run ./occ maintenance:mimetype:update-js
*/
OC.MimeTypeList={
aliases: ' . json_encode($aliases, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . ',
files: ' . json_encode($files, JSON_PRETTY_PRINT) . ',
themes: ' . json_encode($themes, JSON_PRETTY_PRINT) . '
aliases: $aliasesJson,
files: $filesJson,
themes: $themesJson
};
';
//Output the JS
file_put_contents(\OC::$SERVERROOT.'/core/js/mimetypelist.js', $js);
MTLC;
$output->writeln('<info>mimetypelist.js is updated');
return $content;
}
}
......@@ -186,7 +186,13 @@ $array = [
'logoClaim' => $defaults->getLogoClaim(),
'shortFooter' => $defaults->getShortFooter(),
'longFooter' => $defaults->getLongFooter(),
'folder' => OC_Util::getTheme(),
'folder' => OC_Util::getTheme()->getName()
]
),
'theme' => json_encode(
[
'name' => OC_Util::getTheme()->getName(),
'directory' => OC_Util::getTheme()->getDirectory()
]
)
];
......
......@@ -82,6 +82,7 @@ var OC={
coreApps:['', 'admin','log','core/search','settings','core','3rdparty'],
requestToken: oc_requesttoken,
menuSpeed: 50,
currentTheme: window.theme || {},
/**
* Get an absolute url to a file in an app
......
......@@ -72,37 +72,44 @@ OC.MimeType = {
return undefined;
}
while (mimeType in OC.MimeTypeList.aliases) {
mimeType = OC.MimeTypeList.aliases[mimeType];
}
mimeType = this.getMimeTypeAliasTarget(mimeType);
if (mimeType in OC.MimeType._mimeTypeIcons) {
return OC.MimeType._mimeTypeIcons[mimeType];
}
// First try to get the correct icon from the current theme
var gotIcon = null;
var path = '';
if (OC.theme.folder !== '' && $.isArray(OC.MimeTypeList.themes[OC.theme.folder])) {
path = OC.webroot + '/themes/' + OC.theme.folder + '/core/img/filetypes/';
var icon = OC.MimeType._getFile(mimeType, OC.MimeTypeList.themes[OC.theme.folder]);
var path, icon = null;
if (icon !== null) {
gotIcon = true;
path += icon;
}
// First try to get the correct icon from the current theme
if (OC.currentTheme.name !== '' && $.isArray(OC.MimeTypeList.themes[OC.currentTheme.name])) {
path = '/' + OC.currentTheme.directory + 'core/img/filetypes/';
icon = OC.MimeType._getFile(mimeType, OC.MimeTypeList.themes[OC.currentTheme.name]);
}
// If we do not yet have an icon fall back to the default
if (gotIcon === null) {
path = OC.webroot + '/core/img/filetypes/';
path += OC.MimeType._getFile(mimeType, OC.MimeTypeList.files);
if (icon === null) {
path = '/core/img/filetypes/';
icon = OC.MimeType._getFile(mimeType, OC.MimeTypeList.files);
}
path += '.svg';
var mimeTypeIcon = OC.getRootPath() + path + icon + '.svg';
// Cache the result
OC.MimeType._mimeTypeIcons[mimeType] = path;
return path;
}
OC.MimeType._mimeTypeIcons[mimeType] = mimeTypeIcon;
return mimeTypeIcon;
},
/**
* If the given mimeType is an alias, this method returns its target,
* else it returns the given mimeType.
*
* @param {string} mimeType The mimeType to get the icon for
* @returns {string} mimeType The mimeType to get the icon for
*/
getMimeTypeAliasTarget: function (mimeType) {
while (mimeType in OC.MimeTypeList.aliases) {
mimeType = OC.MimeTypeList.aliases[mimeType];
}
return mimeType;
}
};
......@@ -4,7 +4,7 @@
*
* You can update the list of MimeType Aliases in config/mimetypealiases.json
* The list of files is fetched from core/img/filetypes
* To regenerate this file run ./occ maintenance:mimetypesjs
* To regenerate this file run ./occ maintenance:mimetype:update-js
*/
OC.MimeTypeList={
aliases: {
......@@ -56,6 +56,7 @@ OC.MimeTypeList={
"application/vnd.visio": "x-office/document",
"application/vnd.wordperfect": "x-office/document",
"application/x-7z-compressed": "package/x-generic",
"application/x-bzip2": "package/x-generic",
"application/x-cbr": "text",
"application/x-compressed": "package/x-generic",
"application/x-dcraw": "image",
......@@ -107,5 +108,7 @@ OC.MimeTypeList={
"x-office-presentation",
"x-office-spreadsheet"
],
themes: []
};
themes: {
"example": []
}
};
\ No newline at end of file
......@@ -100,7 +100,7 @@ describe('MimeType tests', function() {
it('return the url for the mimetype file', function() {
var res = OC.MimeType.getIconUrl('file');
expect(res).toEqual(OC.webroot + '/core/img/filetypes/file.svg');
expect(res).toEqual(OC.getRootPath() + '/core/img/filetypes/file.svg');
});
it('test if the cache works correctly', function() {
......@@ -118,7 +118,7 @@ describe('MimeType tests', function() {
it('test if alaiases are converted correctly', function() {
var res = OC.MimeType.getIconUrl('app/foobar');
expect(res).toEqual(OC.webroot + '/core/img/filetypes/foo-bar.svg');
expect(res).toEqual(OC.getRootPath() + '/core/img/filetypes/foo-bar.svg');
expect(OC.MimeType._mimeTypeIcons['foo/bar']).toEqual(res);
});
});
......@@ -127,24 +127,25 @@ describe('MimeType tests', function() {
var _themeFolder;
beforeEach(function() {
_themeFolder = OC.theme.folder;
OC.theme.folder = 'abc';
_themeFolder = OC.currentTheme.name;
OC.currentTheme.name = 'abc';
OC.currentTheme.directory = 'themes/abc/';
//Clear mimetypeIcons caches
OC.MimeType._mimeTypeIcons = {};
});
afterEach(function() {
OC.theme.folder = _themeFolder;
OC.currentTheme.name = _themeFolder;
});
it('test if theme path is used if a theme icon is availble', function() {
var res = OC.MimeType.getIconUrl('dir');
expect(res).toEqual(OC.webroot + '/themes/abc/core/img/filetypes/folder.svg');
expect(res).toEqual(OC.getRootPath() + '/themes/abc/core/img/filetypes/folder.svg');
});
it('test if we fallback to the default theme if no icon is available in the theme', function() {
var res = OC.MimeType.getIconUrl('dir-shared');
expect(res).toEqual(OC.webroot + '/core/img/filetypes/folder-shared.svg');
expect(res).toEqual(OC.getRootPath() + '/core/img/filetypes/folder-shared.svg');
});
});
});
......
......@@ -32,6 +32,13 @@ class Theme {
return $this->name;
}
/**
* @param $name
*/
public function setName($name) {
$this->name = $name;
}
/**
* @return string
*/
......
......@@ -83,6 +83,7 @@ class ThemeService {
$this->theme->setDirectory(
ltrim(\OC_App::getAppWebPath($appName), '/') . '/'
);
$this->theme->setName($appName);
}
}
}
{
"_comment" : "Array of mimetype aliases.",
"_comment2": "Any changes you make here will be overwritten on an update of ownCloud.",
"_comment3": "Put any custom mappings in a new file mimetypealiases.json in the config/ folder of ownCloud",
"_comment4": "After any change to mimetypealiases.json run:",
"_comment5": "./occ maintenance:mimetype:update-js",
"_comment6": "Otherwise your update won't propagate through the system.",
"application/coreldraw": "image",
"application/epub+zip": "text",
"application/font-sfnt": "image",
......@@ -85,5 +76,4 @@
"text/x-python": "text/code",
"text/x-shellscript": "text/code",
"web": "text/code"
}
}
\ No newline at end of file
{
"_comment" : "Array mapping file extensions to mimetypes (in alphabetical order]",
"_comment2": "The first index in the mime type array is the assumed correct mimetype",
"_comment3": "and the second (if present] is a secure alternative",
"_comment4": "Any changes you make here will be overwritten on an update of ownCloud",
"_comment5": "Put any custom mappings in a new file mimetypemapping.json in the config/ folder of ownCloud",
"3gp": ["video/3gpp"],
"7z": ["application/x-7z-compressed"],
"accdb": ["application/msaccess"],
......@@ -182,4 +174,4 @@
"yaml": ["application/yaml", "text/plain"],
"yml": ["application/yaml", "text/plain"],
"zip": ["application/zip"]
}
}
\ No newline at end of file
# resources/config
Useful information about the files contained in this directory.
## mimetypealiases.dist.json
Contains mimetype aliases. Any changes to this file might get overwritten by an ownCloud update.
To add custom mimetype aliases, create a `mimetypealiases.json` file in the `/config` directory.
Any changes to `mimetypealiases.json` require you to run `./occ maintenance:mimetype:update-js`.
## mimetypemapping.dist.json
Maps file extensions to mimetypes in alphabetical order. Any changes to this file might get overwritten by an ownCloud update.
The first index in the mapped array is assumed to be the correct mimetype. The second one is a secure alternative.
To add a custom mimetype mapping, create a `mimetypemapping.json` file in the `/config` directory.
\ No newline at end of file
......@@ -51,4 +51,11 @@ class ThemeServiceTest extends \PHPUnit\Framework\TestCase {
$this->assertEquals('default', $theme->getName());
$this->assertEquals('themes/default/', $theme->getDirectory());
}
public function testSetAppThemeSetsName() {
$themeService = new ThemeService();
$this->assertEmpty($themeService->getTheme()->getName());
$themeService->setAppTheme('some-app-theme');
$this->assertEquals('some-app-theme', $themeService->getTheme()->getName());
}
}
......@@ -29,4 +29,10 @@ class ThemeTest extends \PHPUnit\Framework\TestCase {
$this->sut->setDirectory('test/directory');
$this->assertEquals('test/directory', $this->sut->getDirectory());
}
public function testNameCanBeSet() {
$this->assertEmpty($this->sut->getName());
$this->sut->setName('some-name');
$this->assertEquals('some-name', $this->sut->getName());
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment