diff --git a/Gruntfile.js b/Gruntfile.js index 467443bbf3df1af2a63882ced1100d67de693246..01280f1026a1853a70052770c5ef995a79f2799a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -14,6 +14,17 @@ module.exports = function(grunt) { // Iterate through theme.config.php files collecting parent themes in search path: while (config = fs.readFileSync("themes/" + parts[1] + "/theme.config.php", "UTF-8")) { + // First identify mixins: + var mixinMatches = config.match(/["']mixins["']\s*=>\s*\[([^\]]+)\]/); + if (mixinMatches !== null) { + var mixinParts = mixinMatches[1].split(',') + for (var i = 0; i < mixinParts.length; i++) { + parts[1] = mixinParts[i].trim().replace(/['"]/g, ''); + retVal.push(parts.join('/') + '/'); + } + } + + // Now move up to parent theme: var matches = config.match(/["']extends["']\s*=>\s*['"](\w+)['"]/); // "extends" set to "false" or missing entirely? We've hit the end of the line: diff --git a/module/VuFindConsole/config/module.config.php b/module/VuFindConsole/config/module.config.php index 3d8e83c5e2f453047a1cc22a97ad9f8ff9f9f4b9..086187c098d03cdc6336a90403b6cd4bc6174374 100644 --- a/module/VuFindConsole/config/module.config.php +++ b/module/VuFindConsole/config/module.config.php @@ -43,6 +43,7 @@ $routes = [ 'generate/recordroute' => 'generate recordroute [<base>] [<newController>] [<module>]', 'generate/staticroute' => 'generate staticroute [<name>] [<module>]', 'generate/theme' => 'generate theme [<themename>]', + 'generate/thememixin' => 'generate thememixin [<name>]', // harvest/harvest_oai is too complex to represent here; we need to rely on default-route 'harvest/merge-marc' => 'harvest merge-marc [<dir>]', 'import/import-xsl' => 'import import-xsl [--test-only] [--index=] [<xml>] [<properties>]', diff --git a/module/VuFindConsole/src/VuFindConsole/Controller/GenerateController.php b/module/VuFindConsole/src/VuFindConsole/Controller/GenerateController.php index 86b0709357315264e5e030349a37df4f63aceecd..db7ecc6fa2d3e3be580c9c69813d55dc75df8595 100644 --- a/module/VuFindConsole/src/VuFindConsole/Controller/GenerateController.php +++ b/module/VuFindConsole/src/VuFindConsole/Controller/GenerateController.php @@ -320,6 +320,33 @@ class GenerateController extends AbstractBase return $this->getSuccessResponse(); } + /** + * Create a custom theme from the template. + * + * @return \Zend\Console\Response + */ + public function thememixinAction() + { + // Validate command line argument: + $request = $this->getRequest(); + $name = $request->getParam('name'); + if (empty($name)) { + Console::writeLine("\tNo mixin name found, using \"custom\""); + $name = 'custom'; + } + + // Use the theme generator to create and configure the theme: + $generator = $this->serviceLocator->get('VuFindTheme\MixinGenerator'); + if (!$generator->generate($name)) { + Console::writeLine($generator->getLastError()); + return $this->getFailureResponse(); + } + Console::writeLine( + "\tFinished. Add to your theme.config.php 'mixins' setting to activate." + ); + return $this->getSuccessResponse(); + } + /** * Create a new subclass and factory to override a factory-generated * service. diff --git a/module/VuFindTheme/Module.php b/module/VuFindTheme/Module.php index eac58949210c314c42891353181aa3d51411a074..5af6790aace8b4631e0c0ddecf7fe31aea07629a 100644 --- a/module/VuFindTheme/Module.php +++ b/module/VuFindTheme/Module.php @@ -64,6 +64,8 @@ class Module { return [ 'factories' => [ + 'VuFindTheme\MixinGenerator' => + 'VuFindTheme\Module::getMixinGenerator', 'VuFindTheme\ThemeCompiler' => 'VuFindTheme\Module::getThemeCompiler', 'VuFindTheme\ThemeGenerator' => @@ -98,6 +100,18 @@ class Module ]; } + /** + * Factory function for MixinGenerator object. + * + * @param ServiceManager $sm Service manager + * + * @return MixinGenerator + */ + public static function getMixinGenerator(ServiceManager $sm) + { + return new MixinGenerator($sm->get('VuFindTheme\ThemeInfo')); + } + /** * Factory function for ThemeCompiler object. * diff --git a/module/VuFindTheme/src/VuFindTheme/MixinGenerator.php b/module/VuFindTheme/src/VuFindTheme/MixinGenerator.php new file mode 100644 index 0000000000000000000000000000000000000000..957820399658542c0a0c51388bd6bcc886521f15 --- /dev/null +++ b/module/VuFindTheme/src/VuFindTheme/MixinGenerator.php @@ -0,0 +1,67 @@ +<?php +/** + * Class to generate a new mixin from a template. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2017. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Theme + * @author Chris Hallberg <challber@villanova.edu> + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Site + */ +namespace VuFindTheme; +use Zend\Console\Console; + +/** + * Class to generate a new mixin from a template. + * + * @category VuFind + * @package Theme + * @author Chris Hallberg <challber@villanova.edu> + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Site + */ +class MixinGenerator extends AbstractThemeUtility +{ + /** + * Generate a new mixin from a template. + * + * @param string $name Name of mixin to generate. + * @param string $template Name of template mixin directory + * + * @return bool + */ + public function generate($name, $template = 'local_mixin_example') + { + // Check for existing theme + $baseDir = $this->info->getBaseDir() . '/'; + if (realpath($baseDir . $name)) { + return $this->setLastError('Mixin "' . $name . '" already exists'); + } + Console::writeLine('Creating new mixin: "' . $name . '"'); + $source = $baseDir . $template; + $dest = $baseDir . $name; + Console::writeLine("\tCopying $template"); + Console::writeLine("\t\tFrom: " . $source); + Console::writeLine("\t\tTo: " . $dest); + return $this->copyDir($source, $dest); + } +} diff --git a/module/VuFindTheme/src/VuFindTheme/ThemeCompiler.php b/module/VuFindTheme/src/VuFindTheme/ThemeCompiler.php index 89d3d2b497a01f717fd31e643b7b7848aa38fef5..7070d65a2cecff8211800b0e4dc3d1a26e53b542 100644 --- a/module/VuFindTheme/src/VuFindTheme/ThemeCompiler.php +++ b/module/VuFindTheme/src/VuFindTheme/ThemeCompiler.php @@ -124,6 +124,9 @@ class ThemeCompiler extends AbstractThemeUtility $dest[$key] = $this ->mergeConfig($value, isset($dest[$key]) ? $dest[$key] : []); break; + case 'mixins': + // Omit mixin settings entirely + break; default: // Default behavior: merge arrays, let existing flat settings // trump new incoming ones: diff --git a/module/VuFindTheme/src/VuFindTheme/ThemeInfo.php b/module/VuFindTheme/src/VuFindTheme/ThemeInfo.php index 43e0731c11643c2c683ce64cd67ef2af1dcac214..70f76d5141490e44bfa420fba89677fbbbb641a2 100644 --- a/module/VuFindTheme/src/VuFindTheme/ThemeInfo.php +++ b/module/VuFindTheme/src/VuFindTheme/ThemeInfo.php @@ -92,6 +92,18 @@ class ThemeInfo return $this->baseDir; } + /** + * Get the configuration file for the specified mixin. + * + * @param string $mixin Mixin name + * + * @return string + */ + protected function getMixinConfig($mixin) + { + return $this->baseDir . "/$mixin/mixin.config.php"; + } + /** * Get the configuration file for the specified theme. * @@ -136,6 +148,26 @@ class ThemeInfo return $this->currentTheme; } + /** + * Load configuration for the specified theme (and its mixins, if any) into the + * allThemeInfo property. + * + * @param string $theme Name of theme to load + * + * @return void + */ + protected function loadThemeConfig($theme) + { + // Load theme configuration... + $this->allThemeInfo[$theme] = include $this->getThemeConfig($theme); + // ..and if there are mixins, load those too! + if (isset($this->allThemeInfo[$theme]['mixins'])) { + foreach ($this->allThemeInfo[$theme]['mixins'] as $mix) { + $this->allThemeInfo[$mix] = include $this->getMixinConfig($mix); + } + } + } + /** * Get all the configuration details related to the current theme. * @@ -149,8 +181,7 @@ class ThemeInfo $this->allThemeInfo = []; $currentTheme = $this->getTheme(); do { - $this->allThemeInfo[$currentTheme] - = include $this->getThemeConfig($currentTheme); + $this->loadThemeConfig($currentTheme); $currentTheme = $this->allThemeInfo[$currentTheme]['extends']; } while ($currentTheme); } @@ -180,16 +211,21 @@ class ThemeInfo $allThemeInfo = $this->getThemeInfo(); while (!empty($currentTheme)) { - foreach ($allPaths as $currentPath) { - $file = "$basePath/$currentTheme/$currentPath"; - if (file_exists($file)) { - if (true === $returnType) { - return $file; - } else if (self::RETURN_ALL_DETAILS === $returnType) { - return ['path' => $file, 'theme' => $currentTheme]; + $currentThemeSet = array_merge( + (array) $currentTheme, + isset($allThemeInfo[$currentTheme]['mixins']) + ? $allThemeInfo[$currentTheme]['mixins'] : [] + ); + foreach ($currentThemeSet as $theme) { + foreach ($allPaths as $currentPath) { + $path = "$basePath/$theme/$currentPath"; + if (file_exists($path)) { + // Depending on return type, send back the requested data: + if (self::RETURN_ALL_DETAILS === $returnType) { + return compact('path', 'theme'); + } + return $returnType ? $path : $theme; } - // Default return type: - return $currentTheme; } } $currentTheme = $allThemeInfo[$currentTheme]['extends']; diff --git a/module/VuFindTheme/tests/unit-tests/fixtures/themes/mixin/js/hello.js b/module/VuFindTheme/tests/unit-tests/fixtures/themes/mixin/js/hello.js new file mode 100644 index 0000000000000000000000000000000000000000..d91d6f72456f9d12f9b9a7d8d2e9c39f8e24f8e7 --- /dev/null +++ b/module/VuFindTheme/tests/unit-tests/fixtures/themes/mixin/js/hello.js @@ -0,0 +1 @@ +alert('hello, mixin'); \ No newline at end of file diff --git a/module/VuFindTheme/tests/unit-tests/fixtures/themes/mixin/js/mixin.js b/module/VuFindTheme/tests/unit-tests/fixtures/themes/mixin/js/mixin.js new file mode 100644 index 0000000000000000000000000000000000000000..1efc4f412e973d7b1ce0d375b5a2251a0e4d66d5 --- /dev/null +++ b/module/VuFindTheme/tests/unit-tests/fixtures/themes/mixin/js/mixin.js @@ -0,0 +1 @@ +// no code in demo file \ No newline at end of file diff --git a/module/VuFindTheme/tests/unit-tests/fixtures/themes/mixin/mixin.config.php b/module/VuFindTheme/tests/unit-tests/fixtures/themes/mixin/mixin.config.php new file mode 100644 index 0000000000000000000000000000000000000000..cd8e12f22a446523339f864a30a92e0a6324c25e --- /dev/null +++ b/module/VuFindTheme/tests/unit-tests/fixtures/themes/mixin/mixin.config.php @@ -0,0 +1,4 @@ +<?php +return [ + 'js' => ['mixin.js'], +]; diff --git a/module/VuFindTheme/tests/unit-tests/fixtures/themes/mixin_user/theme.config.php b/module/VuFindTheme/tests/unit-tests/fixtures/themes/mixin_user/theme.config.php new file mode 100644 index 0000000000000000000000000000000000000000..93c5a8d527a33fd4fa18f5721d401e77c9f4a537 --- /dev/null +++ b/module/VuFindTheme/tests/unit-tests/fixtures/themes/mixin_user/theme.config.php @@ -0,0 +1,5 @@ +<?php +return [ + 'extends' => 'child', + 'mixins' => ['mixin'], +]; diff --git a/module/VuFindTheme/tests/unit-tests/src/VuFindTest/ThemeCompilerTest.php b/module/VuFindTheme/tests/unit-tests/src/VuFindTest/ThemeCompilerTest.php index 6640bd8271afc3b612d1764d0e9840d442cfb006..dcaedb3b18b572f8c25dc4786fe8db03575cf873 100644 --- a/module/VuFindTheme/tests/unit-tests/src/VuFindTest/ThemeCompilerTest.php +++ b/module/VuFindTheme/tests/unit-tests/src/VuFindTest/ThemeCompilerTest.php @@ -136,6 +136,64 @@ class ThemeCompilerTest extends Unit\TestCase $this->assertEquals($expectedConfig, $mergedConfig); } + /** + * Test the compiler with a mixin. + * + * @return void + */ + public function testStandardCompilationWithMixin() + { + $baseDir = $this->info->getBaseDir(); + $parentDir = $baseDir . '/parent'; + $childDir = $baseDir . '/child'; + $mixinDir = $baseDir . '/mixin'; + $compiler = $this->getThemeCompiler(); + $result = $compiler->compile('mixin_user', 'compiled'); + + // Did the compiler report success? + $this->assertEquals('', $compiler->getLastError()); + $this->assertTrue($result); + + // Was the target directory created with the expected files? + $this->assertTrue(is_dir($this->targetPath)); + $this->assertTrue(file_exists("{$this->targetPath}/parent.txt")); + $this->assertTrue(file_exists("{$this->targetPath}/child.txt")); + $this->assertTrue(file_exists("{$this->targetPath}/js/mixin.js")); + + // Did the right version of the file that exists in both parent and child + // get copied over? + $this->assertEquals( + file_get_contents("$mixinDir/js/hello.js"), + file_get_contents("{$this->targetPath}/js/hello.js") + ); + $this->assertNotEquals( + file_get_contents("$childDir/js/hello.js"), + file_get_contents("{$this->targetPath}/js/hello.js") + ); + $this->assertNotEquals( + file_get_contents("$parentDir/js/hello.js"), + file_get_contents("{$this->targetPath}/js/hello.js") + ); + + // Did the configuration merge correctly? + $expectedConfig = [ + 'extends' => false, + 'css' => ['child.css'], + 'js' => ['hello.js', 'extra.js', 'mixin.js'], + 'helpers' => [ + 'factories' => [ + 'foo' => 'fooOverrideFactory', + 'bar' => 'barFactory', + ], + 'invokables' => [ + 'xyzzy' => 'Xyzzy', + ] + ], + ]; + $mergedConfig = include "{$this->targetPath}/theme.config.php"; + $this->assertEquals($expectedConfig, $mergedConfig); + } + /** * Test overwrite protection. * diff --git a/module/VuFindTheme/tests/unit-tests/src/VuFindTest/ThemeInfoTest.php b/module/VuFindTheme/tests/unit-tests/src/VuFindTest/ThemeInfoTest.php index 35c17dff5bdb95393dc0bb524030968c0b3a4583..0cf095e7eea96691f7e14ef637de353d4dc9d821 100644 --- a/module/VuFindTheme/tests/unit-tests/src/VuFindTest/ThemeInfoTest.php +++ b/module/VuFindTheme/tests/unit-tests/src/VuFindTest/ThemeInfoTest.php @@ -109,6 +109,33 @@ class ThemeInfoTest extends Unit\TestCase ); } + /** + * Test theme info with a mixin + * + * @return void + */ + public function testGetThemeInfoWithMixin() + { + $ti = $this->getThemeInfo(); + $ti->setTheme('mixin_user'); + $expectedChild = include "{$this->fixturePath}/child/theme.config.php"; + $expectedParent = include "{$this->fixturePath}/parent/theme.config.php"; + $expectedMixin = include "{$this->fixturePath}/mixin/mixin.config.php"; + $expectedMixinUser + = include "{$this->fixturePath}/mixin_user/theme.config.php"; + $this->assertEquals('parent', $expectedChild['extends']); + $this->assertEquals(false, $expectedParent['extends']); + $this->assertEquals( + [ + 'mixin' => $expectedMixin, + 'mixin_user' => $expectedMixinUser, + 'child' => $expectedChild, + 'parent' => $expectedParent + ], + $ti->getThemeInfo() + ); + } + /** * Test unfindable item. * @@ -135,6 +162,19 @@ class ThemeInfoTest extends Unit\TestCase $this->assertEquals($expected, $ti->findContainingTheme('parent.txt', ThemeInfo::RETURN_ALL_DETAILS)); } + /** + * Test findContainingTheme() with a mixin + * + * @return void + */ + public function testFindContainingThemeWithMixin() + { + $ti = $this->getThemeInfo(); + $ti->setTheme('mixin_user'); + $this->assertEquals('mixin', $ti->findContainingTheme('js/mixin.js')); + $this->assertEquals('child', $ti->findContainingTheme('child.txt')); + } + /** * Get a test object * diff --git a/themes/local_mixin_example/js/mixin-popup.js b/themes/local_mixin_example/js/mixin-popup.js new file mode 100644 index 0000000000000000000000000000000000000000..4006f884be462cb8d3306021492b0152af5e9c51 --- /dev/null +++ b/themes/local_mixin_example/js/mixin-popup.js @@ -0,0 +1 @@ +alert('Your mixin is loaded; please customize it to remove this annoying example script.'); \ No newline at end of file diff --git a/themes/local_mixin_example/mixin.config.php b/themes/local_mixin_example/mixin.config.php new file mode 100644 index 0000000000000000000000000000000000000000..b7b10fe6ebc0cd025ff2a31e9956f49c304e8933 --- /dev/null +++ b/themes/local_mixin_example/mixin.config.php @@ -0,0 +1,4 @@ +<?php +return [ + 'js' => ['mixin-popup.js'], +]; \ No newline at end of file