diff --git a/.gitignore b/.gitignore index 9121905d..19f3dc83 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,7 @@ phpmd.html # User plugin configuration plugins/*/config.php + +# 3rd party themes +tpl/* +!tpl/default diff --git a/CHANGELOG.md b/CHANGELOG.md index fe775b3e..d3ecc1e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,14 +7,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [v0.9.0](https://github.com/shaarli/Shaarli/releases/tag/v0.9.0) - UNPUBLISHED -**WARNING**: Shaarli now requires PHP 5.5+. +**WARNING**: Shaarli now requires PHP 5.5+. ### Added - REST API: see [Shaarli API documentation](http://shaarli.github.io/api-documentation/) +- The theme can now be selected in the administration page. ### Changed +- Default template files are moved to a subfolder (`default`). + ### Fixed diff --git a/COPYING b/COPYING index 22929463..547ea570 100644 --- a/COPYING +++ b/COPYING @@ -43,7 +43,7 @@ License: CC-BY (http://creativecommons.org/licenses/by/3.0/) Copyright: (c) 2014 Designmodo Source: http://designmodo.com/linecons-free/ -Files: images/floral_left.png, images/floral_right.png, images/squiggle.png, images/squiggle2.png, images/squiggle_closing.png +Files: images/floral_left.png, images/floral_right.png, images/squiggle.png, images/squiggle_closing.png Licence: Public Domain Source: https://openclipart.org/people/j4p4n/j4p4n_ornimental_bookend_-_left.svg diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php index 7f963e97..a0f482b0 100644 --- a/application/ApplicationUtils.php +++ b/application/ApplicationUtils.php @@ -150,6 +150,7 @@ public static function checkResourcePermissions($conf) 'inc', 'plugins', $conf->get('resource.raintpl_tpl'), + $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme'), ) as $path) { if (! is_readable(realpath($path))) { $errors[] = '"'.$path.'" directory is not readable'; diff --git a/application/ThemeUtils.php b/application/ThemeUtils.php new file mode 100644 index 00000000..2718ed13 --- /dev/null +++ b/application/ThemeUtils.php @@ -0,0 +1,33 @@ +conf->write($this->isLoggedIn); return true; } + + /** + * New setting: theme name. If the default theme is used, nothing to do. + * + * If the user uses a custom theme, raintpl_tpl dir is updated to the parent directory, + * and the current theme is set as default in the theme setting. + * + * @return bool true if the update is successful, false otherwise. + */ + public function updateMethodDefaultTheme() + { + // raintpl_tpl isn't the root template directory anymore. + // We run the update only if this folder still contains the template files. + $tplDir = $this->conf->get('resource.raintpl_tpl'); + $tplFile = $tplDir . '/linklist.html'; + if (! file_exists($tplFile)) { + return true; + } + + $parent = dirname($tplDir); + $this->conf->set('resource.raintpl_tpl', $parent); + $this->conf->set('resource.theme', trim(str_replace($parent, '', $tplDir), '/')); + $this->conf->write($this->isLoggedIn); + + // Dependency injection gore + RainTPL::$tpl_dir = $tplDir; + + return true; + } } /** diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php index ca8918b5..a401887c 100644 --- a/application/config/ConfigManager.php +++ b/application/config/ConfigManager.php @@ -299,6 +299,7 @@ protected function setDefaultValues() $this->setEmpty('resource.log', 'data/log.txt'); $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt'); $this->setEmpty('resource.raintpl_tpl', 'tpl/'); + $this->setEmpty('resource.theme', 'default'); $this->setEmpty('resource.raintpl_tmp', 'tmp/'); $this->setEmpty('resource.thumbnails_cache', 'cache'); $this->setEmpty('resource.page_cache', 'pagecache'); diff --git a/application/config/ConfigPhp.php b/application/config/ConfigPhp.php index 9dd9a65d..d7fd4baf 100644 --- a/application/config/ConfigPhp.php +++ b/application/config/ConfigPhp.php @@ -41,6 +41,7 @@ class ConfigPhp implements ConfigIO 'resource.log' => 'config.LOG_FILE', 'resource.update_check' => 'config.UPDATECHECK_FILENAME', 'resource.raintpl_tpl' => 'config.RAINTPL_TPL', + 'resource.theme' => 'config.theme', 'resource.raintpl_tmp' => 'config.RAINTPL_TMP', 'resource.thumbnails_cache' => 'config.CACHEDIR', 'resource.page_cache' => 'config.PAGECACHE', @@ -99,7 +100,7 @@ public function write($filepath, $conf) $configStr .= '$GLOBALS[\'' . $key . '\'] = ' . var_export($conf[$key], true) . ';' . PHP_EOL; } } - + // Store all $conf['config'] foreach ($conf['config'] as $key => $value) { $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($conf['config'][$key], true).';'. PHP_EOL; diff --git a/images/squiggle.png b/images/squiggle.png deleted file mode 100644 index a6ce218c..00000000 Binary files a/images/squiggle.png and /dev/null differ diff --git a/index.php b/index.php index 0639e85f..e553d1dd 100644 --- a/index.php +++ b/index.php @@ -79,6 +79,7 @@ require_once 'application/PluginManager.php'; require_once 'application/Router.php'; require_once 'application/Updater.php'; +use \Shaarli\ThemeUtils; // Ensure the PHP version is supported try { @@ -122,7 +123,7 @@ $conf = new ConfigManager(); $conf->setEmpty('general.timezone', date_default_timezone_get()); $conf->setEmpty('general.title', 'Shared links on '. escape(index_url($_SERVER))); -RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl'); // template directory +RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme').'/'; // template directory RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory $pluginManager = new PluginManager($conf); @@ -1124,6 +1125,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) $conf->set('general.timezone', $tz); $conf->set('general.title', escape($_POST['title'])); $conf->set('general.header_link', escape($_POST['titleLink'])); + $conf->set('resource.theme', escape($_POST['theme'])); $conf->set('redirector.url', escape($_POST['redirector'])); $conf->set('security.session_protection_disabled', !empty($_POST['disablesessionprotection'])); $conf->set('privacy.default_private_links', !empty($_POST['privateLinkByDefault'])); @@ -1134,6 +1136,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) $conf->set('api.secret', escape($_POST['apiSecret'])); try { $conf->write(isLoggedIn()); + invalidateCaches($conf->get('resource.page_cache')); } catch(Exception $e) { error_log( @@ -1151,6 +1154,8 @@ function renderPage($conf, $pluginManager, $LINKSDB) else // Show the configuration form. { $PAGE->assign('title', $conf->get('general.title')); + $PAGE->assign('theme', $conf->get('resource.theme')); + $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl'))); $PAGE->assign('redirector', $conf->get('redirector.url')); list($timezone_form, $timezone_js) = generateTimeZoneForm($conf->get('general.timezone')); $PAGE->assign('timezone_form', $timezone_form); diff --git a/tests/ApplicationUtilsTest.php b/tests/ApplicationUtilsTest.php index 861b8d4e..634bd0ed 100644 --- a/tests/ApplicationUtilsTest.php +++ b/tests/ApplicationUtilsTest.php @@ -289,6 +289,7 @@ public function testCheckCurrentResourcePermissions() $conf->set('resource.page_cache', 'pagecache'); $conf->set('resource.raintpl_tmp', 'tmp'); $conf->set('resource.raintpl_tpl', 'tpl'); + $conf->set('resource.theme', 'default'); $conf->set('resource.update_check', 'data/lastupdatecheck.txt'); $this->assertEquals( @@ -312,10 +313,12 @@ public function testCheckCurrentResourcePermissionsErrors() $conf->set('resource.page_cache', 'null/pagecache'); $conf->set('resource.raintpl_tmp', 'null/tmp'); $conf->set('resource.raintpl_tpl', 'null/tpl'); + $conf->set('resource.raintpl_theme', 'null/tpl/default'); $conf->set('resource.update_check', 'null/data/lastupdatecheck.txt'); $this->assertEquals( array( '"null/tpl" directory is not readable', + '"null/tpl/default" directory is not readable', '"null/cache" directory is not readable', '"null/cache" directory is not writable', '"null/data" directory is not readable', diff --git a/tests/ThemeUtilsTest.php b/tests/ThemeUtilsTest.php new file mode 100644 index 00000000..e44564be --- /dev/null +++ b/tests/ThemeUtilsTest.php @@ -0,0 +1,55 @@ +assertTrue(in_array($theme, $themes)); + } + $this->assertFalse(in_array('supertheme', $res)); + + foreach ($themes as $theme) { + rmdir('sandbox/tpl/'. $theme); + } + unlink('sandbox/tpl/supertheme'); + rmdir('sandbox/tpl'); + } + + /** + * Test getThemes() without any theme dir. + */ + public function testGetThemesEmpty() + { + mkdir('sandbox/tpl/', 0755, true); + $this->assertEquals([], ThemeUtils::getThemes('sandbox/tpl/')); + rmdir('sandbox/tpl/'); + } + + /** + * Test getThemes() with an invalid path. + */ + public function testGetThemesInvalid() + { + $this->assertEquals([], ThemeUtils::getThemes('nope')); + } +} diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php index 0171daad..1d15cfaa 100644 --- a/tests/Updater/UpdaterTest.php +++ b/tests/Updater/UpdaterTest.php @@ -2,6 +2,7 @@ require_once 'application/config/ConfigManager.php'; require_once 'tests/Updater/DummyUpdater.php'; +require_once 'inc/rain.tpl.class.php'; /** * Class UpdaterTest. @@ -421,4 +422,48 @@ public function testDatastoreIdsNothingToDo() $this->assertTrue($updater->updateMethodDatastoreIds()); $this->assertEquals($checksum, hash_file('sha1', self::$testDatastore)); } + + /** + * Test defaultTheme update with default settings: nothing to do. + */ + public function testDefaultThemeWithDefaultSettings() + { + $sandbox = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandbox . '.json.php'); + $this->conf = new ConfigManager($sandbox); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodDefaultTheme()); + + $this->assertEquals('tpl/', $this->conf->get('resource.raintpl_tpl')); + $this->assertEquals('default', $this->conf->get('resource.theme')); + $this->conf = new ConfigManager($sandbox); + $this->assertEquals('tpl/', $this->conf->get('resource.raintpl_tpl')); + $this->assertEquals('default', $this->conf->get('resource.theme')); + unlink($sandbox . '.json.php'); + } + + /** + * Test defaultTheme update with a custom theme in a subfolder + */ + public function testDefaultThemeWithCustomTheme() + { + $theme = 'iamanartist'; + $sandbox = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandbox . '.json.php'); + $this->conf = new ConfigManager($sandbox); + mkdir('sandbox/'. $theme); + touch('sandbox/'. $theme .'/linklist.html'); + $this->conf->set('resource.raintpl_tpl', 'sandbox/'. $theme .'/'); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodDefaultTheme()); + + $this->assertEquals('sandbox', $this->conf->get('resource.raintpl_tpl')); + $this->assertEquals($theme, $this->conf->get('resource.theme')); + $this->conf = new ConfigManager($sandbox); + $this->assertEquals('sandbox', $this->conf->get('resource.raintpl_tpl')); + $this->assertEquals($theme, $this->conf->get('resource.theme')); + unlink($sandbox . '.json.php'); + unlink('sandbox/'. $theme .'/linklist.html'); + rmdir('sandbox/'. $theme); + } } diff --git a/tests/utils/config/configJson.json.php b/tests/utils/config/configJson.json.php index 06a302e8..13d38c66 100644 --- a/tests/utils/config/configJson.json.php +++ b/tests/utils/config/configJson.json.php @@ -24,7 +24,8 @@ }, "resource": { "datastore": "tests\/utils\/config\/datastore.php", - "data_dir": "tests\/utils\/config" + "data_dir": "tests\/utils\/config", + "raintpl_tpl": "tpl/" }, "plugins": { "WALLABAG_VERSION": 1 diff --git a/tpl/404.html b/tpl/default/404.html similarity index 100% rename from tpl/404.html rename to tpl/default/404.html diff --git a/tpl/addlink.html b/tpl/default/addlink.html similarity index 100% rename from tpl/addlink.html rename to tpl/default/addlink.html diff --git a/tpl/changepassword.html b/tpl/default/changepassword.html similarity index 100% rename from tpl/changepassword.html rename to tpl/default/changepassword.html diff --git a/tpl/changetag.html b/tpl/default/changetag.html similarity index 94% rename from tpl/changetag.html rename to tpl/default/changetag.html index 13cc5cf1..a0df3328 100644 --- a/tpl/changetag.html +++ b/tpl/default/changetag.html @@ -1,7 +1,7 @@ {include="includes"} - + diff --git a/tpl/configure.html b/tpl/default/configure.html similarity index 91% rename from tpl/configure.html rename to tpl/default/configure.html index b4197bf9..5820e6e4 100644 --- a/tpl/configure.html +++ b/tpl/default/configure.html @@ -19,6 +19,20 @@
+ + + Theme: + + + + + Timezone: {$timezone_form} diff --git a/inc/reset.css b/tpl/default/css/reset.css similarity index 100% rename from inc/reset.css rename to tpl/default/css/reset.css diff --git a/inc/shaarli.css b/tpl/default/css/shaarli.css similarity index 99% rename from inc/shaarli.css rename to tpl/default/css/shaarli.css index a24d4b7c..10709b6a 100644 --- a/inc/shaarli.css +++ b/tpl/default/css/shaarli.css @@ -103,7 +103,7 @@ strong { } #pageheader #logo { - background-image: url('../images/logo.png'); + background-image: url('../../../images/logo.png'); background-repeat: no-repeat; float: left; margin: 0 10px 0 10px; @@ -803,6 +803,10 @@ div.dailyAbout img { height: 14px; } +div.dailyEntryPermalink { + float: right; +} + div.dailyTitle { font-weight: bold; font-size: 44pt; diff --git a/tpl/daily.html b/tpl/default/daily.html similarity index 88% rename from tpl/daily.html rename to tpl/default/daily.html index eba0af3b..e86e90b1 100644 --- a/tpl/daily.html +++ b/tpl/default/daily.html @@ -28,9 +28,9 @@
- floral_left + floral_left The Daily Shaarli - floral_right + floral_right
@@ -50,7 +50,7 @@
{if="!$hide_timestamps || isLoggedIn()"} @@ -94,7 +94,7 @@ {$value} {/loop}
-
-
+
-
{include="page.footer"} diff --git a/tpl/dailyrss.html b/tpl/default/dailyrss.html similarity index 100% rename from tpl/dailyrss.html rename to tpl/default/dailyrss.html diff --git a/tpl/editlink.html b/tpl/default/editlink.html similarity index 97% rename from tpl/editlink.html rename to tpl/default/editlink.html index 870cc168..d3f99fe6 100644 --- a/tpl/editlink.html +++ b/tpl/default/editlink.html @@ -1,7 +1,7 @@ {include="includes"} - + - - -{if="is_file('inc/user.css')"}{/if} + + +{if="is_file('inc/user.css')"}{/if} {loop="$plugins_includes.css_files"} {/loop} diff --git a/tpl/install.html b/tpl/default/install.html similarity index 100% rename from tpl/install.html rename to tpl/default/install.html diff --git a/tpl/linklist.html b/tpl/default/linklist.html similarity index 98% rename from tpl/linklist.html rename to tpl/default/linklist.html index d4232342..5accc92f 100644 --- a/tpl/linklist.html +++ b/tpl/default/linklist.html @@ -1,7 +1,7 @@ - + {include="includes"} diff --git a/tpl/linklist.paging.html b/tpl/default/linklist.paging.html similarity index 100% rename from tpl/linklist.paging.html rename to tpl/default/linklist.paging.html diff --git a/tpl/loginform.html b/tpl/default/loginform.html similarity index 100% rename from tpl/loginform.html rename to tpl/default/loginform.html diff --git a/tpl/opensearch.html b/tpl/default/opensearch.html similarity index 100% rename from tpl/opensearch.html rename to tpl/default/opensearch.html diff --git a/tpl/page.footer.html b/tpl/default/page.footer.html similarity index 100% rename from tpl/page.footer.html rename to tpl/default/page.footer.html diff --git a/tpl/page.header.html b/tpl/default/page.header.html similarity index 100% rename from tpl/page.header.html rename to tpl/default/page.header.html diff --git a/tpl/page.html b/tpl/default/page.html similarity index 100% rename from tpl/page.html rename to tpl/default/page.html diff --git a/tpl/picwall.html b/tpl/default/picwall.html similarity index 100% rename from tpl/picwall.html rename to tpl/default/picwall.html diff --git a/tpl/pluginsadmin.html b/tpl/default/pluginsadmin.html similarity index 100% rename from tpl/pluginsadmin.html rename to tpl/default/pluginsadmin.html diff --git a/tpl/readme.txt b/tpl/default/readme.txt similarity index 100% rename from tpl/readme.txt rename to tpl/default/readme.txt diff --git a/tpl/tagcloud.html b/tpl/default/tagcloud.html similarity index 100% rename from tpl/tagcloud.html rename to tpl/default/tagcloud.html diff --git a/tpl/tools.html b/tpl/default/tools.html similarity index 100% rename from tpl/tools.html rename to tpl/default/tools.html