]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Merge pull request #727 from ArthurHoaro/api/getlinks
authorArthurHoaro <arthur@hoa.ro>
Sun, 15 Jan 2017 15:49:50 +0000 (16:49 +0100)
committerGitHub <noreply@github.com>
Sun, 15 Jan 2017 15:49:50 +0000 (16:49 +0100)
REST API: implement getLinks service

83 files changed:
.gitattributes
.github/mailmap [new file with mode: 0644]
.gitignore
AUTHORS [new file with mode: 0644]
CHANGELOG.md
COPYING
Makefile
application/ApplicationUtils.php
application/Base64Url.php [new file with mode: 0644]
application/CachedPage.php
application/HttpUtils.php
application/LinkUtils.php
application/PageBuilder.php
application/ThemeUtils.php [new file with mode: 0644]
application/Updater.php
application/api/ApiMiddleware.php
application/api/ApiUtils.php
application/config/ConfigIO.php
application/config/ConfigJson.php
application/config/ConfigManager.php
application/config/ConfigPhp.php
composer.json
docker/development/nginx.conf
docker/production/nginx.conf
docker/production/stable/nginx.conf
images/squiggle.png [deleted file]
index.php
plugins/wallabag/WallabagInstance.php
tests/ApplicationUtilsTest.php
tests/ThemeUtilsTest.php [new file with mode: 0644]
tests/Updater/UpdaterTest.php
tests/Url/UrlTest.php
tests/api/ApiMiddlewareTest.php
tests/api/ApiUtilsTest.php
tests/plugins/PluginAddlinkTest.php
tests/plugins/PluginArchiveorgTest.php
tests/plugins/PluginIssoTest.php
tests/plugins/PluginMarkdownTest.php
tests/plugins/PluginPlayvideosTest.php
tests/plugins/PluginPubsubhubbubTest.php
tests/plugins/PluginQrcodeTest.php [moved from tests/plugins/PlugQrcodeTest.php with 86% similarity]
tests/plugins/PluginReadityourselfTest.php
tests/plugins/PluginWallabagTest.php
tests/plugins/WallabagInstanceTest.php
tests/utils/config/configJson.json.php
tpl/default/404.html [moved from tpl/404.html with 100% similarity]
tpl/default/addlink.html [moved from tpl/addlink.html with 100% similarity]
tpl/default/changepassword.html [moved from tpl/changepassword.html with 100% similarity]
tpl/default/changetag.html [moved from tpl/changetag.html with 94% similarity]
tpl/default/configure.html [moved from tpl/configure.html with 91% similarity]
tpl/default/css/reset.css [moved from inc/reset.css with 100% similarity]
tpl/default/css/shaarli.css [moved from inc/shaarli.css with 98% similarity]
tpl/default/daily.html [moved from tpl/daily.html with 88% similarity]
tpl/default/dailyrss.html [moved from tpl/dailyrss.html with 100% similarity]
tpl/default/editlink.html [moved from tpl/editlink.html with 86% similarity]
tpl/default/export.bookmarks.html [moved from tpl/export.bookmarks.html with 100% similarity]
tpl/default/export.html [moved from tpl/export.html with 100% similarity]
tpl/default/feed.atom.html [moved from tpl/feed.atom.html with 100% similarity]
tpl/default/feed.rss.html [moved from tpl/feed.rss.html with 100% similarity]
tpl/default/images/50pc_transparent.png [moved from images/50pc_transparent.png with 100% similarity]
tpl/default/images/Paper_texture_v5_by_bashcorpo_w1000.jpg [moved from images/Paper_texture_v5_by_bashcorpo_w1000.jpg with 100% similarity]
tpl/default/images/calendar.png [moved from images/calendar.png with 100% similarity]
tpl/default/images/floral_left.png [moved from images/floral_left.png with 100% similarity]
tpl/default/images/floral_right.png [moved from images/floral_right.png with 100% similarity]
tpl/default/images/private.png [moved from images/private.png with 100% similarity]
tpl/default/images/squiggle.png [moved from images/squiggle2.png with 100% similarity]
tpl/default/images/squiggle_closing.png [moved from images/squiggle_closing.png with 100% similarity]
tpl/default/images/tag_blue.png [moved from images/tag_blue.png with 100% similarity]
tpl/default/import.html [moved from tpl/import.html with 100% similarity]
tpl/default/includes.html [moved from tpl/includes.html with 77% similarity]
tpl/default/install.html [moved from tpl/install.html with 100% similarity]
tpl/default/linklist.html [moved from tpl/linklist.html with 98% similarity]
tpl/default/linklist.paging.html [moved from tpl/linklist.paging.html with 100% similarity]
tpl/default/loginform.html [moved from tpl/loginform.html with 100% similarity]
tpl/default/opensearch.html [moved from tpl/opensearch.html with 100% similarity]
tpl/default/page.footer.html [moved from tpl/page.footer.html with 100% similarity]
tpl/default/page.header.html [moved from tpl/page.header.html with 100% similarity]
tpl/default/page.html [moved from tpl/page.html with 100% similarity]
tpl/default/picwall.html [moved from tpl/picwall.html with 100% similarity]
tpl/default/pluginsadmin.html [moved from tpl/pluginsadmin.html with 100% similarity]
tpl/default/readme.txt [moved from tpl/readme.txt with 100% similarity]
tpl/default/tagcloud.html [moved from tpl/tagcloud.html with 100% similarity]
tpl/default/tools.html [moved from tpl/tools.html with 100% similarity]

index d753b1db076ab9fce664b20d15c361a1b5ecefe6..059fbb182a4fb08de293f81daac3b1d4820ebe64 100644 (file)
@@ -19,6 +19,7 @@ Dockerfile      text
 
 # Exclude from Git archives
 .gitattributes  export-ignore
+.github         export-ignore
 .gitignore      export-ignore
 .travis.yml     export-ignore
 doc/**/*.json   export-ignore
diff --git a/.github/mailmap b/.github/mailmap
new file mode 100644 (file)
index 0000000..41d91e4
--- /dev/null
@@ -0,0 +1,13 @@
+ArthurHoaro <arthur@hoa.ro>
+Florian Eula <eula.florian@gmail.com> feula
+Florian Eula <eula.florian@gmail.com> <mr.pikzen@gmail.com>
+Nicolas Danelon <hi@nicolasmd.com.ar> nicolasm
+Nicolas Danelon <hi@nicolasmd.com.ar> <nda@3818.com.ar>
+Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@gmail.com>
+Nicolas Danelon <hi@nicolasmd.com.ar> <nicolasdanelon@users.noreply.github.com>
+Sébastien Sauvage <sebsauvage@sebsauvage.net>
+Timo Van Neerden <fire@lehollandaisvolant.net>
+Timo Van Neerden <fire@lehollandaisvolant.net> lehollandaisvolant <levoltigeurhollandais@gmail.com>
+VirtualTam <virtualtam@flibidi.net> <tamisier.aurelien@gmail.com>
+VirtualTam <virtualtam@flibidi.net> <virtualtam+github@flibidi.net>
+VirtualTam <virtualtam@flibidi.net> <virtualtam@flibidi.org>
index 9121905d84603c1943753a4e7bd8d981af6f9aec..19f3dc836873ea5196e2bdece1477de4d4d937c8 100644 (file)
@@ -28,3 +28,7 @@ phpmd.html
 
 # User plugin configuration
 plugins/*/config.php
+
+# 3rd party themes
+tpl/*
+!tpl/default
diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..aa041ae
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,40 @@
+   327 ArthurHoaro <arthur@hoa.ro>
+   188 VirtualTam <virtualtam@flibidi.net>
+   132 nodiscc <nodiscc@gmail.com>
+    56 Sébastien Sauvage <sebsauvage@sebsauvage.net>
+    15 Florian Eula <eula.florian@gmail.com>
+    13 Emilien Klein <emilien@klein.st>
+    12 Nicolas Danelon <hi@nicolasmd.com.ar>
+     7 Christophe HENRY <christophe.henry@sbgodin.fr>
+     4 Alexandre Alapetite <alexandre@alapetite.fr>
+     4 David Sferruzza <david.sferruzza@gmail.com>
+     3 Teromene <teromene@teromene.fr>
+     2 Chris Kuethe <chris.kuethe@gmail.com>
+     2 Knah Tsaeb <Knah-Tsaeb@knah-tsaeb.org>
+     2 Mathieu Chabanon <git@matchab.fr>
+     2 MiloÅ¡ Jovanović <mjovanovic@gmail.com>
+     2 Qwerty <champlywood@free.fr>
+     2 Timo Van Neerden <fire@lehollandaisvolant.net>
+     2 julienCXX <software@chmodplusx.eu>
+     2 kalvn <kalvnthereal@gmail.com>
+     1 Adrien Oliva <adrien.oliva@yapbreak.fr>
+     1 Alexis J <alexis@effingo.be>
+     1 BoboTiG <bobotig@gmail.com>
+     1 Bronco <bronco@warriordudimanche.net>
+     1 D Low <daniellowtw@gmail.com>
+     1 Dimtion <zizou.xena@gmail.com>
+     1 Fanch <fanch-github@qth.fr>
+     1 Felix Bartels <felix@host-consultants.de>
+     1 Felix Kästner <github.com-fpunktk@fpunktk.de>
+     1 Florian Voigt <flvoigt@me.com>
+     1 Gary Marigliano <gmarigliano93@gmail.com>
+     1 Guillaume Virlet <github@virlet.org>
+     1 Jonathan Druart <jonathan.druart@gmail.com>
+     1 Julien Pivotto <roidelapluie@inuits.eu>
+     1 Kevin Canévet <kevin@streamroot.io>
+     1 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org>
+     1 Lionel Martin <renarddesmers@gmail.com>
+     1 Marsup <marsup@gmail.com>
+     1 Sbgodin <Sbgodin@users.noreply.github.com>
+     1 TsT <tst2005@gmail.com>
+     1 dimtion <zizou.xena@gmail.com>
index fe775b3e61de9c8c426ce637466b5f420261cef1..d3ecc1e61a9c2d9e8825d81c84b82f9994d49281 100644 (file)
@@ -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 22929463e48ee2a32c7caf44649903c813a96483..4bbdf2b3c95bb18bf984bb0f6084793231bb40fb 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -1,33 +1,7 @@
 Files: *
 License: zlib/libpng
 Copyright: (c) 2011-2015 Sébastien SAUVAGE <sebsauvage@sebsauvage.net>
-           (c) 2011-2015 Alexandre Alapetite <alexandre@alapetite.fr>
-           (c) 2011-2015 David Sferruzza <david.sferruzza@gmail.com>
-           (c) 2011-2015 Christophe HENRY <christophe.henry@sbgodin.fr>
-           (c) 2011-2015 Mathieu Chabanon <git@matchab.fr>
-           (c) 2011-2015 BoboTiG <bobotig@gmail.com>
-           (c) 2011-2015 Bronco <bronco@warriordudimanche.net>
-           (c) 2011-2015 Emilien Klein <emilien@klein.st>
-           (c) 2011-2015 Knah Tsaeb <knah-tsaeb@knah-tsaeb.org>
-           (c) 2011-2015 Lionel Martin <renarddesmers@gmail.com>
-           (c) 2011-2015 lehollandaisvolant <levoltigeurhollandais@gmail.com>
-           (c) 2011-2015 timo van neerden <fire@lehollandaisvolant.net>
-           (c) 2011-2015 nodiscc <nodiscc@gmail.com>
-           (c) 2011-2015 Florian Eula <mr.pikzen@gmail.com>
-           (c) 2011-2015 Arthur Hoaro <arthur@hoa.ro>
-           (c) 2011-2015 Aurélien "VirtualTam" Tamisier <virtualtam@flibidi.net>
-           (c) 2011-2015 qwertygc <champlywood@free.fr>
-           (c) 2011-2015 idleman <idleman@idleman.fr>
-           (c) 2015 Alexis Ju <alexis@effingo.be>
-           (c) 2015 dimtion <zizou.xena@gmail.com>
-           (c) 2015 Fanch <fanch-github@qth.fr>
-           (c) 2015 Guillaume Virlet <github@virlet.org>
-           (c) 2015 Felix Bartels <felix@host-consultants.de>
-           (c) 2015 Marsup <marsup@gmail.com>
-           (c) 2015 MiloÅ¡ Jovanović <mjovanovic@gmail.com>
-           (c) 2015 Nicolás Danelón <hola@nicolasdanelon.com.ar>
-           (c) 2015 TsT <tst2005@gmail.com>
-
+           (c) 2011-2017 The Shaarli Community, see AUTHORS
 
 Files: inc/reset.css
 License: BSD (http://opensource.org/licenses/BSD-3-Clause)
@@ -43,7 +17,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
 
index 60aec9a08b6049328ee031dd7e19fbaeafc8664e..f3065b77ed2658856018372444844dc00e4575da 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -169,6 +169,12 @@ clean:
        @git clean -df
        @rm -rf sandbox
 
+### generate the AUTHORS file from Git commit information
+authors:
+       @cp .github/mailmap .mailmap
+       @git shortlog -sne > AUTHORS
+       @rm .mailmap
+
 ### generate Doxygen documentation
 doxygen: clean
        @rm -rf doxygen
@@ -214,4 +220,4 @@ htmlpages:
                        -o doc/$$base.html $$file; \
        done;
 
-htmldoc: doc htmlsidebar htmlpages
+htmldoc: authors doc htmlsidebar htmlpages
index 7f963e9786cc50901863789341bc802ed1e22403..a0f482b0b9791e0f4c1e28b5406dc4895459a239 100644 (file)
@@ -150,6 +150,7 @@ class ApplicationUtils
             '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/Base64Url.php b/application/Base64Url.php
new file mode 100644 (file)
index 0000000..61590e4
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+namespace Shaarli;
+
+
+/**
+ * URL-safe Base64 operations
+ *
+ * @see https://en.wikipedia.org/wiki/Base64#URL_applications
+ */
+class Base64Url
+{
+    /**
+     * Base64Url-encodes data
+     *
+     * @param string $data Data to encode
+     *
+     * @return string Base64Url-encoded data
+     */
+    public static function encode($data) {
+        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
+    }
+
+    /**
+     * Decodes Base64Url-encoded data
+     *
+     * @param string $data Data to decode
+     *
+     * @return string Decoded data
+     */
+    public static function decode($data) {
+        return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
+    }
+}
index 5087d0c4b6b1fd6182fcd4564497d6531c78754b..e11cc52d01e3aeac07142c476d76e35bf7b88229 100644 (file)
@@ -7,9 +7,6 @@ class CachedPage
     // Directory containing page caches
     private $cacheDir;
 
-    // Full URL of the page to cache -typically the value returned by pageUrl()
-    private $url;
-
     // Should this URL be cached (boolean)?
     private $shouldBeCached;
 
@@ -27,7 +24,6 @@ class CachedPage
     {
         // TODO: check write access to the cache directory
         $this->cacheDir = $cacheDir;
-        $this->url = $url;
         $this->filename = $this->cacheDir.'/'.sha1($url).'.cache';
         $this->shouldBeCached = $shouldBeCached;
     }
index e8fc1f5db45fc4ca11da2793068c373013339614..a81f90565fb3b79349981cadb049de3a2fe2d6aa 100644 (file)
@@ -122,7 +122,7 @@ function get_http_response($url, $timeout = 30, $maxBytes = 4194304)
     $content = substr($response, $headSize);
     $headers = array();
     foreach (preg_split('~[\r\n]+~', $rawHeadersLastRedir) as $line) {
-        if (empty($line) or ctype_space($line)) {
+        if (empty($line) || ctype_space($line)) {
             continue;
         }
         $splitLine = explode(': ', $line, 2);
index cf58f8083355af37e0a9271cf1168823252708dc..976474de721ad14636b9b431f0cec06a8920e120 100644 (file)
@@ -89,7 +89,9 @@ function count_private($links)
 {
     $cpt = 0;
     foreach ($links as $link) {
-        $cpt = $link['private'] == true ? $cpt + 1 : $cpt;
+        if ($link['private']) {
+            $cpt += 1;
+        }
     }
 
     return $cpt;
index 32c7f9f18b01ba131be61bb778dc3d7a9239b727..544aba7ca3f3c1504a093a4f2483b29474af9268 100644 (file)
@@ -25,7 +25,7 @@ class PageBuilder
      *
      * @param ConfigManager $conf Configuration Manager instance (reference).
      */
-    function __construct(&$conf)
+    public function __construct(&$conf)
     {
         $this->tpl = false;
         $this->conf = $conf;
diff --git a/application/ThemeUtils.php b/application/ThemeUtils.php
new file mode 100644 (file)
index 0000000..2718ed1
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+namespace Shaarli;
+
+/**
+ * Class ThemeUtils
+ *
+ * Utility functions related to theme management.
+ *
+ * @package Shaarli
+ */
+class ThemeUtils
+{
+    /**
+     * Get a list of available themes.
+     *
+     * It will return the name of any directory present in the template folder.
+     *
+     * @param string $tplDir Templates main directory.
+     *
+     * @return array List of theme names.
+     */
+    public static function getThemes($tplDir)
+    {
+        $allTheme = glob($tplDir.'/*', GLOB_ONLYDIR);
+        $themes = [];
+        foreach ($allTheme as $value) {
+            $themes[] = str_replace($tplDir.'/', '', $value);
+        }
+
+        return $themes;
+    }
+}
index 38de33503a417ac06c9b186298c7621a471d3667..eb03c6d3afe6cc6be66062fb2050ac6dfe3170b5 100644 (file)
@@ -69,7 +69,7 @@ class Updater
             return $updatesRan;
         }
 
-        if ($this->methods == null) {
+        if ($this->methods === null) {
             throw new UpdaterException('Couldn\'t retrieve Updater class methods.');
         }
 
@@ -279,6 +279,51 @@ class Updater
         $this->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;
+    }
+
+    /**
+     * Move the file to inc/user.css to data/user.css.
+     *
+     * Note: Due to hardcoded paths, it's not unit testable. But one line of code should be fine.
+     *
+     * @return bool true if the update is successful, false otherwise.
+     */
+    public function updateMethodMoveUserCss()
+    {
+        if (! is_file('inc/user.css')) {
+            return true;
+        }
+
+        return rename('inc/user.css', 'data/user.css');
+    }
 }
 
 /**
index 162e88e0bc06ea50a43e6cc1cc289212962b1edf..522091cac39328222d790b441f29195e2d64fe78 100644 (file)
@@ -98,8 +98,7 @@ class ApiMiddleware
      * @throws ApiAuthorizationException The token couldn't be validated.
      */
     protected function checkToken($request) {
-        $jwt = $request->getHeaderLine('jwt');
-        if (empty($jwt)) {
+        if (! $request->hasHeader('Authorization')) {
             throw new ApiAuthorizationException('JWT token not provided');
         }
 
@@ -107,7 +106,13 @@ class ApiMiddleware
             throw new ApiAuthorizationException('Token secret must be set in Shaarli\'s administration');
         }
 
-        ApiUtils::validateJwtToken($jwt, $this->conf->get('api.secret'));
+        $authorization = $request->getHeaderLine('Authorization');
+
+        if (! preg_match('/^Bearer (.*)/i', $authorization, $matches)) {
+            throw new ApiAuthorizationException('Invalid JWT header');
+        }
+
+        ApiUtils::validateJwtToken($matches[1], $this->conf->get('api.secret'));
     }
 
     /**
index d024291988db8b7a439e3a0e7986749fa629467a..d40158652d1f66680cfd5ceea17623b9a8eef5a8 100644 (file)
@@ -1,13 +1,11 @@
 <?php
-
 namespace Shaarli\Api;
 
+use Shaarli\Base64Url;
 use Shaarli\Api\Exceptions\ApiAuthorizationException;
 
 /**
- * Class ApiUtils
- *
- * Utility functions for the API.
+ * REST API utilities
  */
 class ApiUtils
 {
@@ -26,17 +24,17 @@ class ApiUtils
             throw new ApiAuthorizationException('Malformed JWT token');
         }
 
-        $genSign = hash_hmac('sha512', $parts[0] .'.'. $parts[1], $secret);
+        $genSign = Base64Url::encode(hash_hmac('sha512', $parts[0] .'.'. $parts[1], $secret, true));
         if ($parts[2] != $genSign) {
             throw new ApiAuthorizationException('Invalid JWT signature');
         }
 
-        $header = json_decode(base64_decode($parts[0]));
+        $header = json_decode(Base64Url::decode($parts[0]));
         if ($header === null) {
             throw new ApiAuthorizationException('Invalid JWT header');
         }
 
-        $payload = json_decode(base64_decode($parts[1]));
+        $payload = json_decode(Base64Url::decode($parts[1]));
         if ($payload === null) {
             throw new ApiAuthorizationException('Invalid JWT payload');
         }
index 2b68fe6ab7b4af924371bf7b1403a0f39d2f784f..be78b1c71cf52a798198cdfa44c9f5220b9bc9f8 100644 (file)
@@ -14,7 +14,7 @@ interface ConfigIO
      *
      * @return array All configuration in an array.
      */
-    function read($filepath);
+    public function read($filepath);
 
     /**
      * Write configuration.
@@ -22,12 +22,12 @@ interface ConfigIO
      * @param string $filepath Config file absolute path.
      * @param array  $conf   All configuration in an array.
      */
-    function write($filepath, $conf);
+    public function write($filepath, $conf);
 
     /**
      * Get config file extension according to config type.
      *
      * @return string Config file extension.
      */
-    function getExtension();
+    public function getExtension();
 }
index 30007eb4cfd550fc106d7ace97fca00545519cff..6b5d73f159440cc1276ff984d48ef0473c668859 100644 (file)
@@ -10,7 +10,7 @@ class ConfigJson implements ConfigIO
     /**
      * @inheritdoc
      */
-    function read($filepath)
+    public function read($filepath)
     {
         if (! is_readable($filepath)) {
             return array();
@@ -29,7 +29,7 @@ class ConfigJson implements ConfigIO
     /**
      * @inheritdoc
      */
-    function write($filepath, $conf)
+    public function write($filepath, $conf)
     {
         // JSON_PRETTY_PRINT is available from PHP 5.4.
         $print = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
@@ -46,7 +46,7 @@ class ConfigJson implements ConfigIO
     /**
      * @inheritdoc
      */
-    function getExtension()
+    public function getExtension()
     {
         return '.json.php';
     }
index ca8918b52b2be8693bc53397cad247e72f3661b4..a401887c5367389e34785a8dd7d7c5c906238f41 100644 (file)
@@ -299,6 +299,7 @@ class ConfigManager
         $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');
index 27187b662f34c3e984d1097776017050e88c4d6a..d7fd4baffa34d2fa3cec5bcb83f108c3571af97a 100644 (file)
@@ -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',
@@ -71,7 +72,7 @@ class ConfigPhp implements ConfigIO
     /**
      * @inheritdoc
      */
-    function read($filepath)
+    public function read($filepath)
     {
         if (! file_exists($filepath) || ! is_readable($filepath)) {
             return array();
@@ -91,7 +92,7 @@ class ConfigPhp implements ConfigIO
     /**
      * @inheritdoc
      */
-    function write($filepath, $conf)
+    public function write($filepath, $conf)
     {
         $configStr = '<?php '. PHP_EOL;
         foreach (self::$ROOT_KEYS as $key) {
@@ -99,7 +100,7 @@ class ConfigPhp implements ConfigIO
                 $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;
@@ -125,7 +126,7 @@ class ConfigPhp implements ConfigIO
     /**
      * @inheritdoc
      */
-    function getExtension()
+    public function getExtension()
     {
         return '.php';
     }
index cfbde1a0695ee7a58eb86ee93e340e36baa6e9d4..2fed0df7700bc5211d9e515d9d0b60f9c2c19e3b 100644 (file)
@@ -24,6 +24,7 @@
     },
     "autoload": {
         "psr-4": {
+            "Shaarli\\": "application",
             "Shaarli\\Api\\": "application/api/",
             "Shaarli\\Api\\Controllers\\": "application/api/controllers",
             "Shaarli\\Api\\Exceptions\\": "application/api/exceptions"
index ac0c6c61ce165976b7572bb96eb8ba90b5f4d2a8..79c45bfe9049dad77de492648260cb283a5859c7 100644 (file)
@@ -56,7 +56,16 @@ http {
             alias /var/www/shaarli/images/favicon.ico;
         }
 
+        location / {
+            # Slim - rewrite URLs
+            try_files $uri /index.php$is_args$args;
+        }
+
         location ~ (index)\.php$ {
+            # Slim - split URL path into (script_filename, path_info)
+            try_files $uri =404;
+            fastcgi_split_path_info ^(.+\.php)(/.+)$;
+
             # filter and proxy PHP requests to PHP-FPM
             fastcgi_pass   unix:/var/run/php5-fpm.sock;
             fastcgi_index  index.php;
index 5ffa02d0a9d570136d4ab7ea8f8bef4107f03cb7..e8754d9b478f85cbeb033756d2114759f1809d37 100644 (file)
@@ -48,7 +48,16 @@ http {
             alias /var/www/shaarli/images/favicon.ico;
         }
 
+        location / {
+            # Slim - rewrite URLs
+            try_files $uri /index.php$is_args$args;
+        }
+
         location ~ (index)\.php$ {
+            # Slim - split URL path into (script_filename, path_info)
+            try_files $uri =404;
+            fastcgi_split_path_info ^(.+\.php)(/.+)$;
+
             # filter and proxy PHP requests to PHP-FPM
             fastcgi_pass   unix:/var/run/php5-fpm.sock;
             fastcgi_index  index.php;
index 5ffa02d0a9d570136d4ab7ea8f8bef4107f03cb7..e8754d9b478f85cbeb033756d2114759f1809d37 100644 (file)
@@ -48,7 +48,16 @@ http {
             alias /var/www/shaarli/images/favicon.ico;
         }
 
+        location / {
+            # Slim - rewrite URLs
+            try_files $uri /index.php$is_args$args;
+        }
+
         location ~ (index)\.php$ {
+            # Slim - split URL path into (script_filename, path_info)
+            try_files $uri =404;
+            fastcgi_split_path_info ^(.+\.php)(/.+)$;
+
             # filter and proxy PHP requests to PHP-FPM
             fastcgi_pass   unix:/var/run/php5-fpm.sock;
             fastcgi_index  index.php;
diff --git a/images/squiggle.png b/images/squiggle.png
deleted file mode 100644 (file)
index a6ce218..0000000
Binary files a/images/squiggle.png and /dev/null differ
index ff24ed7eca2762b4c531570efd2aa97b3ee37753..beb1cbca592aefae1071254c9ab95c04ea635526 100644 (file)
--- a/index.php
+++ b/index.php
@@ -79,6 +79,7 @@ require_once 'application/Utils.php';
 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 @@ if (isset($_COOKIE['shaarli']) && !is_session_id_valid($_COOKIE['shaarli'])) {
 $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);
@@ -203,7 +204,7 @@ function setup_login_state($conf)
        }
        // If session does not exist on server side, or IP address has changed, or session has expired, logout.
        if (empty($_SESSION['uid'])
-        || ($conf->get('security.session_protection_disabled') == false && $_SESSION['ip'] != allIPs())
+        || ($conf->get('security.session_protection_disabled') === false && $_SESSION['ip'] != allIPs())
         || time() >= $_SESSION['expires_on'])
        {
            logout();
@@ -617,7 +618,7 @@ function showDailyRSS($conf) {
         $tpl->assign('links', $links);
         $tpl->assign('rssdate', escape($dayDate->format(DateTime::RSS)));
         $tpl->assign('hide_timestamps', $conf->get('privacy.hide_timestamps', false));
-        $html = $tpl->draw('dailyrss', $return_string=true);
+        $html = $tpl->draw('dailyrss', true);
 
         echo $html . PHP_EOL;
     }
@@ -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);
index 72cc2e5efb56bc474233aaa95d6d94c14b10d7a3..eb8ab618f14f72a768b5851d4e804f1b5f0c2974 100644 (file)
@@ -35,7 +35,7 @@ class WallabagInstance
      */
     private $apiVersion;
 
-    function __construct($instance, $version)
+    public function __construct($instance, $version)
     {
         if ($this->isVersionAllowed($version)) {
             $this->apiVersion = self::$wallabagVersions[$version];
index 861b8d4e506533c1f155f6542018692f64aaea67..634bd0eda01a76848ef80058aadcfc4db71e6d1c 100644 (file)
@@ -289,6 +289,7 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
         $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 @@ class ApplicationUtilsTest extends PHPUnit_Framework_TestCase
         $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 (file)
index 0000000..e44564b
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+namespace Shaarli;
+
+/**
+ * Class ThemeUtilsTest
+ *
+ * @package Shaarli
+ */
+class ThemeUtilsTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * Test getThemes() with existing theme directories.
+     */
+    public function testGetThemes()
+    {
+        $themes = ['theme1', 'default', 'Bl1p_- bL0p'];
+        foreach ($themes as $theme) {
+            mkdir('sandbox/tpl/'. $theme, 0755, true);
+        }
+
+        // include a file which should be ignored
+        touch('sandbox/tpl/supertheme');
+
+        $res = ThemeUtils::getThemes('sandbox/tpl/');
+        foreach ($res as $theme) {
+            $this->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'));
+    }
+}
index 0171daada17ca4d424fc3ceb811c692b4da7d63c..1d15cfaaace698d825a9b711258ee504f0927dc0 100644 (file)
@@ -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 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
         $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);
+    }
 }
index 0586237211b7a402ea2a9a0f7c0b3749af6b516a..aa2f2234529b4878cdb3e926918a13d004f7d9bc 100644 (file)
@@ -157,7 +157,7 @@ class UrlTest extends PHPUnit_Framework_TestCase
     /**
      * Test add trailing slash.
      */
-    function testAddTrailingSlash()
+    public function testAddTrailingSlash()
     {
         $strOn = 'http://randomstr.com/test/';
         $strOff = 'http://randomstr.com/test';
@@ -168,7 +168,7 @@ class UrlTest extends PHPUnit_Framework_TestCase
     /**
      * Test valid HTTP url.
      */
-    function testUrlIsHttp()
+    public function testUrlIsHttp()
     {
         $url = new Url(self::$baseUrl);
         $this->assertTrue($url->isHttp());
@@ -177,7 +177,7 @@ class UrlTest extends PHPUnit_Framework_TestCase
     /**
      * Test non HTTP url.
      */
-    function testUrlIsNotHttp()
+    public function testUrlIsNotHttp()
     {
         $url = new Url('ftp://save.tld/mysave');
         $this->assertFalse($url->isHttp());
@@ -186,7 +186,7 @@ class UrlTest extends PHPUnit_Framework_TestCase
     /**
      * Test International Domain Name to ASCII conversion
      */
-    function testIdnToAscii()
+    public function testIdnToAscii()
     {
         $ind = 'http://www.académie-française.fr/';
         $expected = 'http://www.xn--acadmie-franaise-npb1a.fr/';
index 4d4dd9b979c8180b3ffd05c816ba4e6c1600287e..d9753b1d6bdafc6b92aa8771e3e748ae7e605d66 100644 (file)
@@ -143,7 +143,7 @@ class ApiMiddlewareTest extends \PHPUnit_Framework_TestCase
         $env = Environment::mock([
             'REQUEST_METHOD' => 'GET',
             'REQUEST_URI' => '/echo',
-            'HTTP_JWT'=> 'jwt',
+            'HTTP_AUTHORIZATION'=> 'Bearer jwt',
         ]);
         $request = Request::createFromEnvironment($env);
         $response = new Response();
@@ -157,7 +157,30 @@ class ApiMiddlewareTest extends \PHPUnit_Framework_TestCase
     }
 
     /**
-     * Invoke the middleware without an invalid JWT token (debug):
+     * Invoke the middleware with an invalid JWT token header
+     */
+    public function testInvalidJwtAuthHeaderDebug()
+    {
+        $this->conf->set('dev.debug', true);
+        $mw = new ApiMiddleware($this->container);
+        $env = Environment::mock([
+            'REQUEST_METHOD' => 'GET',
+            'REQUEST_URI' => '/echo',
+            'HTTP_AUTHORIZATION'=> 'PolarBearer jwt',
+        ]);
+        $request = Request::createFromEnvironment($env);
+        $response = new Response();
+        /** @var Response $response */
+        $response = $mw($request, $response, null);
+
+        $this->assertEquals(401, $response->getStatusCode());
+        $body = json_decode((string) $response->getBody());
+        $this->assertEquals('Not authorized: Invalid JWT header', $body->message);
+        $this->assertContains('ApiAuthorizationException', $body->stacktrace);
+    }
+
+    /**
+     * Invoke the middleware with an invalid JWT token (debug):
      * should return a 401 error Unauthorized - with a specific message and a stacktrace.
      *
      * Note: specific JWT errors tests are handled in ApiUtilsTest.
@@ -169,7 +192,7 @@ class ApiMiddlewareTest extends \PHPUnit_Framework_TestCase
         $env = Environment::mock([
             'REQUEST_METHOD' => 'GET',
             'REQUEST_URI' => '/echo',
-            'HTTP_JWT'=> 'bad jwt',
+            'HTTP_AUTHORIZATION'=> 'Bearer jwt',
         ]);
         $request = Request::createFromEnvironment($env);
         $response = new Response();
index 516ee6860edd169ba2cf6ea962669e59a0296896..b4431d1be66d8ed34e75ffdaed18965625717140 100644 (file)
@@ -2,6 +2,9 @@
 
 namespace Shaarli\Api;
 
+use Shaarli\Base64Url;
+
+
 /**
  * Class ApiUtilsTest
  */
@@ -24,14 +27,14 @@ class ApiUtilsTest extends \PHPUnit_Framework_TestCase
      */
     public static function generateValidJwtToken($secret)
     {
-        $header = base64_encode('{
+        $header = Base64Url::encode('{
             "typ": "JWT",
             "alg": "HS512"
         }');
-        $payload = base64_encode('{
+        $payload = Base64Url::encode('{
             "iat": '. time() .'
         }');
-        $signature = hash_hmac('sha512', $header .'.'. $payload , $secret);
+        $signature = Base64Url::encode(hash_hmac('sha512', $header .'.'. $payload , $secret, true));
         return $header .'.'. $payload .'.'. $signature;
     }
 
@@ -46,9 +49,9 @@ class ApiUtilsTest extends \PHPUnit_Framework_TestCase
      */
     public static function generateCustomJwtToken($header, $payload, $secret)
     {
-        $header = base64_encode($header);
-        $payload = base64_encode($payload);
-        $signature = hash_hmac('sha512', $header . '.' . $payload, $secret);
+        $header = Base64Url::encode($header);
+        $payload = Base64Url::encode($payload);
+        $signature = Base64Url::encode(hash_hmac('sha512', $header . '.' . $payload, $secret, true));
         return $header . '.' . $payload . '.' . $signature;
     }
 
index a2f25becbf4793a9657556151c55c908b7a18560..b77fe12a314bbb979b1cf806ef9c2c80ee126afd 100644 (file)
@@ -16,7 +16,7 @@ class PluginAddlinkTest extends PHPUnit_Framework_TestCase
     /**
      * Reset plugin path.
      */
-    function setUp()
+    public function setUp()
     {
         PluginManager::$PLUGINS_PATH = 'plugins';
     }
@@ -24,7 +24,7 @@ class PluginAddlinkTest extends PHPUnit_Framework_TestCase
     /**
      * Test render_header hook while logged in.
      */
-    function testAddlinkHeaderLoggedIn()
+    public function testAddlinkHeaderLoggedIn()
     {
         $str = 'stuff';
         $data = array($str => $str);
@@ -46,7 +46,7 @@ class PluginAddlinkTest extends PHPUnit_Framework_TestCase
     /**
      * Test render_header hook while logged out.
      */
-    function testAddlinkHeaderLoggedOut()
+    public function testAddlinkHeaderLoggedOut()
     {
         $str = 'stuff';
         $data = array($str => $str);
@@ -61,7 +61,7 @@ class PluginAddlinkTest extends PHPUnit_Framework_TestCase
     /**
      * Test render_includes hook while logged in.
      */
-    function testAddlinkIncludesLoggedIn()
+    public function testAddlinkIncludesLoggedIn()
     {
         $str = 'stuff';
         $data = array($str => $str);
@@ -86,7 +86,7 @@ class PluginAddlinkTest extends PHPUnit_Framework_TestCase
      * Test render_includes hook.
      * Should not affect css files while logged out.
      */
-    function testAddlinkIncludesLoggedOut()
+    public function testAddlinkIncludesLoggedOut()
     {
         $str = 'stuff';
         $data = array($str => $str);
index 4daa4c9d63deb491ca702cdadfa893f56f4122b4..fecd5f2c21488b3cf35390b663879766c0a1a0e8 100644 (file)
@@ -15,7 +15,7 @@ class PluginArchiveorgTest extends PHPUnit_Framework_TestCase
     /**
      * Reset plugin path
      */
-    function setUp()
+    public function setUp()
     {
         PluginManager::$PLUGINS_PATH = 'plugins';
     }
@@ -23,7 +23,7 @@ class PluginArchiveorgTest extends PHPUnit_Framework_TestCase
     /**
      * Test render_linklist hook on external links.
      */
-    function testArchiveorgLinklistOnExternalLinks()
+    public function testArchiveorgLinklistOnExternalLinks()
     {
         $str = 'http://randomstr.com/test';
 
@@ -48,13 +48,12 @@ class PluginArchiveorgTest extends PHPUnit_Framework_TestCase
         // plugin data
         $this->assertEquals(1, count($link['link_plugin']));
         $this->assertNotFalse(strpos($link['link_plugin'][0], $str));
-
     }
 
     /**
      * Test render_linklist hook on internal links.
      */
-    function testArchiveorgLinklistOnInternalLinks()
+    public function testArchiveorgLinklistOnInternalLinks()
     {
         $internalLink1 = 'http://shaarli.shaarli/?qvMAqg';
         $internalLinkRealURL1 = '?qvMAqg';
@@ -101,7 +100,6 @@ class PluginArchiveorgTest extends PHPUnit_Framework_TestCase
             )
         );
 
-
         $data = hook_archiveorg_render_linklist($data);
 
         // Case n°1: first link type, public
@@ -136,7 +134,5 @@ class PluginArchiveorgTest extends PHPUnit_Framework_TestCase
         $link = $data['links'][5];
 
         $this->assertArrayNotHasKey('link_plugin', $link);
-
     }
-    
 }
index 6b7904dd595fcbc09e64c5ec5c99fe66773b7cbc..03def208eea6c647356d2ca5db2c13906a1ff97d 100644 (file)
@@ -12,7 +12,7 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
     /**
      * Reset plugin path
      */
-    function setUp()
+    public function setUp()
     {
         PluginManager::$PLUGINS_PATH = 'plugins';
     }
@@ -20,7 +20,7 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
     /**
      * Test Isso init without errors.
      */
-    function testWallabagInitNoError()
+    public function testWallabagInitNoError()
     {
         $conf = new ConfigManager('');
         $conf->set('plugins.ISSO_SERVER', 'value');
@@ -31,7 +31,7 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
     /**
      * Test Isso init with errors.
      */
-    function testWallabagInitError()
+    public function testWallabagInitError()
     {
         $conf = new ConfigManager('');
         $errors = isso_init($conf);
@@ -41,7 +41,7 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
     /**
      * Test render_linklist hook with valid settings to display the comment form.
      */
-    function testIssoDisplayed()
+    public function testIssoDisplayed()
     {
         $conf = new ConfigManager('');
         $conf->set('plugins.ISSO_SERVER', 'value');
@@ -81,7 +81,7 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
     /**
      * Test isso plugin when multiple links are displayed (shouldn't be displayed).
      */
-    function testIssoMultipleLinks()
+    public function testIssoMultipleLinks()
     {
         $conf = new ConfigManager('');
         $conf->set('plugins.ISSO_SERVER', 'value');
@@ -113,7 +113,7 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
     /**
      * Test isso plugin when using search (shouldn't be displayed).
      */
-    function testIssoNotDisplayedWhenSearch()
+    public function testIssoNotDisplayedWhenSearch()
     {
         $conf = new ConfigManager('');
         $conf->set('plugins.ISSO_SERVER', 'value');
@@ -141,7 +141,7 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
     /**
      * Test isso plugin without server configuration (shouldn't be displayed).
      */
-    function testIssoWithoutConf()
+    public function testIssoWithoutConf()
     {
         $data = 'abc';
         $conf = new ConfigManager('');
index 17ef228031331fba63fbb5ef567a3fc1fa06c04c..d359b2a164548f87ae282ec89c4b97ca84f8fbd7 100644 (file)
@@ -16,7 +16,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
     /**
      * Reset plugin path
      */
-    function setUp()
+    public function setUp()
     {
         PluginManager::$PLUGINS_PATH = 'plugins';
     }
@@ -25,7 +25,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
      * Test render_linklist hook.
      * Only check that there is basic markdown rendering.
      */
-    function testMarkdownLinklist()
+    public function testMarkdownLinklist()
     {
         $markdown = '# My title' . PHP_EOL . 'Very interesting content.';
         $data = array(
@@ -45,7 +45,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
      * Test render_daily hook.
      * Only check that there is basic markdown rendering.
      */
-    function testMarkdownDaily()
+    public function testMarkdownDaily()
     {
         $markdown = '# My title' . PHP_EOL . 'Very interesting content.';
         $data = array(
@@ -69,7 +69,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
     /**
      * Test reverse_text2clickable().
      */
-    function testReverseText2clickable()
+    public function testReverseText2clickable()
     {
         $text = 'stuff http://hello.there/is=someone#here otherstuff';
         $clickableText = text2clickable($text, '');
@@ -80,7 +80,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
     /**
      * Test reverse_nl2br().
      */
-    function testReverseNl2br()
+    public function testReverseNl2br()
     {
         $text = 'stuff' . PHP_EOL . 'otherstuff';
         $processedText = nl2br($text);
@@ -91,7 +91,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
     /**
      * Test reverse_space2nbsp().
      */
-    function testReverseSpace2nbsp()
+    public function testReverseSpace2nbsp()
     {
         $text = ' stuff' . PHP_EOL . '  otherstuff  and another';
         $processedText = space2nbsp($text);
@@ -102,7 +102,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
     /**
      * Test sanitize_html().
      */
-    function testSanitizeHtml()
+    public function testSanitizeHtml()
     {
         $input = '< script src="js.js"/>';
         $input .= '< script attr>alert(\'xss\');</script>';
@@ -119,7 +119,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
     /**
      * Test the no markdown tag.
      */
-    function testNoMarkdownTag()
+    public function testNoMarkdownTag()
     {
         $str = 'All _work_ and `no play` makes Jack a *dull* boy.';
         $data = array(
@@ -158,7 +158,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
     /**
      * Test that a close value to nomarkdown is not understand as nomarkdown (previous value `.nomarkdown`).
      */
-    function testNoMarkdownNotExcactlyMatching()
+    public function testNoMarkdownNotExcactlyMatching()
     {
         $str = 'All _work_ and `no play` makes Jack a *dull* boy.';
         $data = array(
@@ -176,7 +176,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
     /**
      * Test hashtag links processed with markdown.
      */
-    function testMarkdownHashtagLinks()
+    public function testMarkdownHashtagLinks()
     {
         $md = file_get_contents('tests/plugins/resources/markdown.md');
         $md = format_description($md);
index be1ef774796862ee2fbba32b6a7301365959254f..29ad047f4a10ae608dd1d1b59bd057f40429ff9a 100644 (file)
@@ -16,7 +16,7 @@ class PluginPlayvideosTest extends PHPUnit_Framework_TestCase
     /**
      * Reset plugin path
      */
-    function setUp()
+    public function setUp()
     {
         PluginManager::$PLUGINS_PATH = 'plugins';
     }
@@ -24,7 +24,7 @@ class PluginPlayvideosTest extends PHPUnit_Framework_TestCase
     /**
      * Test render_linklist hook.
      */
-    function testPlayvideosHeader()
+    public function testPlayvideosHeader()
     {
         $str = 'stuff';
         $data = array($str => $str);
@@ -43,7 +43,7 @@ class PluginPlayvideosTest extends PHPUnit_Framework_TestCase
     /**
      * Test render_footer hook.
      */
-    function testPlayvideosFooter()
+    public function testPlayvideosFooter()
     {
         $str = 'stuff';
         $data = array($str => $str);
index 24dd7a11b0d957da6bfcb39f9ecebfd86b3286de..1bd8793568c358127db6f5ff9b6a4313ba35e341 100644 (file)
@@ -17,7 +17,7 @@ class PluginPubsubhubbubTest extends PHPUnit_Framework_TestCase
     /**
      * Reset plugin path
      */
-    function setUp()
+    public function setUp()
     {
         PluginManager::$PLUGINS_PATH = 'plugins';
     }
@@ -25,7 +25,7 @@ class PluginPubsubhubbubTest extends PHPUnit_Framework_TestCase
     /**
      * Test render_feed hook with an RSS feed.
      */
-    function testPubSubRssRenderFeed()
+    public function testPubSubRssRenderFeed()
     {
         $hub = 'http://domain.hub';
         $conf = new ConfigManager(self::$configFile);
@@ -40,7 +40,7 @@ class PluginPubsubhubbubTest extends PHPUnit_Framework_TestCase
     /**
      * Test render_feed hook with an ATOM feed.
      */
-    function testPubSubAtomRenderFeed()
+    public function testPubSubAtomRenderFeed()
     {
         $hub = 'http://domain.hub';
         $conf = new ConfigManager(self::$configFile);
similarity index 86%
rename from tests/plugins/PlugQrcodeTest.php
rename to tests/plugins/PluginQrcodeTest.php
index 86dc7f293059a94830f0cbec3f0f7cc74229becc..211ee89c4a0755bb7ed66f360c474e3b120d14ad 100644 (file)
@@ -1,29 +1,29 @@
 <?php
 
 /**
- * PlugQrcodeTest.php
+ * PluginQrcodeTest.php
  */
 
 require_once 'plugins/qrcode/qrcode.php';
 require_once 'application/Router.php';
 
 /**
- * Class PlugQrcodeTest
+ * Class PluginQrcodeTest
  * Unit test for the QR-Code plugin
  */
-class PlugQrcodeTest extends PHPUnit_Framework_TestCase
+class PluginQrcodeTest extends PHPUnit_Framework_TestCase
 {
     /**
      * Reset plugin path
      */
-    function setUp() {
+    public function setUp() {
         PluginManager::$PLUGINS_PATH = 'plugins';
     }
 
     /**
      * Test render_linklist hook.
      */
-    function testQrcodeLinklist()
+    public function testQrcodeLinklist()
     {
         $str = 'http://randomstr.com/test';
         $data = array(
@@ -49,7 +49,7 @@ class PlugQrcodeTest extends PHPUnit_Framework_TestCase
     /**
      * Test render_footer hook.
      */
-    function testQrcodeFooter()
+    public function testQrcodeFooter()
     {
         $str = 'stuff';
         $data = array($str => $str);
index 532db14626d5e177566ddd8c352f5fab621f91a2..30470ab72fba4ee9dd6b48089a21fc642398ff3a 100644 (file)
@@ -15,7 +15,7 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
     /**
      * Reset plugin path
      */
-    function setUp()
+    public function setUp()
     {
         PluginManager::$PLUGINS_PATH = 'plugins';
     }
@@ -23,7 +23,7 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
     /**
      * Test Readityourself init without errors.
      */
-    function testReadityourselfInitNoError()
+    public function testReadityourselfInitNoError()
     {
         $conf = new ConfigManager('');
         $conf->set('plugins.READITYOUSELF_URL', 'value');
@@ -34,7 +34,7 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
     /**
      * Test Readityourself init with errors.
      */
-    function testReadityourselfInitError()
+    public function testReadityourselfInitError()
     {
         $conf = new ConfigManager('');
         $errors = readityourself_init($conf);
@@ -44,7 +44,7 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
     /**
      * Test render_linklist hook.
      */
-    function testReadityourselfLinklist()
+    public function testReadityourselfLinklist()
     {
         $conf = new ConfigManager('');
         $conf->set('plugins.READITYOUSELF_URL', 'value');
@@ -72,7 +72,7 @@ class PluginReadityourselfTest extends PHPUnit_Framework_TestCase
     /**
      * Test without config: nothing should happened.
      */
-    function testReadityourselfLinklistWithoutConfig()
+    public function testReadityourselfLinklistWithoutConfig()
     {
         $conf = new ConfigManager('');
         $conf->set('plugins.READITYOUSELF_URL', null);
index 2c268cbd1b714b3b9aa1c29891bbf40e8592b24a..30351f4651dba8972c9533a66f2579ee87c0ce1c 100644 (file)
@@ -15,7 +15,7 @@ class PluginWallabagTest extends PHPUnit_Framework_TestCase
     /**
      * Reset plugin path
      */
-    function setUp()
+    public function setUp()
     {
         PluginManager::$PLUGINS_PATH = 'plugins';
     }
@@ -23,7 +23,7 @@ class PluginWallabagTest extends PHPUnit_Framework_TestCase
     /**
      * Test wallabag init without errors.
      */
-    function testWallabagInitNoError()
+    public function testWallabagInitNoError()
     {
         $conf = new ConfigManager('');
         $conf->set('plugins.WALLABAG_URL', 'value');
@@ -34,7 +34,7 @@ class PluginWallabagTest extends PHPUnit_Framework_TestCase
     /**
      * Test wallabag init with errors.
      */
-    function testWallabagInitError()
+    public function testWallabagInitError()
     {
         $conf = new ConfigManager('');
         $errors = wallabag_init($conf);
@@ -44,7 +44,7 @@ class PluginWallabagTest extends PHPUnit_Framework_TestCase
     /**
      * Test render_linklist hook.
      */
-    function testWallabagLinklist()
+    public function testWallabagLinklist()
     {
         $conf = new ConfigManager('');
         $conf->set('plugins.WALLABAG_URL', 'value');
index 7c14c1df0b8cb36dc897be9ad9b4f71776ab4a9a..2c46687100c13495408e80ac68dc31523283fd78 100644 (file)
@@ -15,7 +15,7 @@ class WallabagInstanceTest extends PHPUnit_Framework_TestCase
     /**
      * Reset plugin path
      */
-    function setUp()
+    public function setUp()
     {
         $this->instance = 'http://some.url';
     }
@@ -23,7 +23,7 @@ class WallabagInstanceTest extends PHPUnit_Framework_TestCase
     /**
      * Test WallabagInstance with API V1.
      */
-    function testWallabagInstanceV1()
+    public function testWallabagInstanceV1()
     {
         $instance = new WallabagInstance($this->instance, 1);
         $expected = $this->instance . '/?plainurl=';
@@ -34,7 +34,7 @@ class WallabagInstanceTest extends PHPUnit_Framework_TestCase
     /**
      * Test WallabagInstance with API V2.
      */
-    function testWallabagInstanceV2()
+    public function testWallabagInstanceV2()
     {
         $instance = new WallabagInstance($this->instance, 2);
         $expected = $this->instance . '/bookmarklet?url=';
@@ -45,7 +45,7 @@ class WallabagInstanceTest extends PHPUnit_Framework_TestCase
     /**
      * Test WallabagInstance with an invalid API version.
      */
-    function testWallabagInstanceInvalidVersion()
+    public function testWallabagInstanceInvalidVersion()
     {
         $instance = new WallabagInstance($this->instance, false);
         $expected = $this->instance . '/?plainurl=';
index 06a302e875b8e61779a0981c85731c63ebb18723..13d38c6680b4a4515192ee0045adcfea05c45b0e 100644 (file)
@@ -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
similarity index 100%
rename from tpl/404.html
rename to tpl/default/404.html
similarity index 100%
rename from tpl/addlink.html
rename to tpl/default/addlink.html
similarity index 94%
rename from tpl/changetag.html
rename to tpl/default/changetag.html
index 13cc5cf1ede43a62815f4f18c225face60ce154b..a0df3328ed67d1529b8d1a81c0ab2eaae25c72fc 100644 (file)
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>{include="includes"}
-    <link type="text/css" rel="stylesheet" href="../inc/awesomplete.css" />
+    <link type="text/css" rel="stylesheet" href="inc/awesomplete.css#" />
     <script src="inc/awesomplete.min.js#"></script>
 </head>
 <body onload="document.changetag.fromtag.focus();">
similarity index 91%
rename from tpl/configure.html
rename to tpl/default/configure.html
index b4197bf9faee7d6b07b552c0250cd6cd757b1222..5820e6e4022d902f7284205405ab1e0495687b1a 100644 (file)
         <td><input type="text" name="titleLink" id="titleLink" size="50" value="{$titleLink}"><br/><label
             for="titleLink">(default value is: ?)</label></td>
       </tr>
+
+      <tr>
+        <td><b>Theme:</b></td>
+        <td>
+          <select name="theme" id="theme">
+            {loop="$theme_available"}
+              <option value="{$value}" {if="$value===$theme"}selected{/if}>
+                {$value|ucfirst}
+              </option>
+            {/loop}
+          </select>
+        </td>
+      </tr>
+
       <tr>
         <td><b>Timezone:</b></td>
         <td>{$timezone_form}</td>
similarity index 100%
rename from inc/reset.css
rename to tpl/default/css/reset.css
similarity index 98%
rename from inc/shaarli.css
rename to tpl/default/css/shaarli.css
index a24d4b7c1e384cfc72a03000e462263063c77fb0..6d73af3ea115ca027bd9173d6428921deb068c8c 100644 (file)
@@ -42,7 +42,7 @@ strong {
 }
 
 /* Buttons */
-.bigbutton {
+.bigbutton, #pageheader a.bigbutton  {
     background-color: #c0c0c0;
     background: -moz-linear-gradient(#c0c0c0, #ffffff) repeat scroll 0 0 transparent;
     background: -webkit-gradient(linear, 0 0, 0 bottom, from(#c0c0c0), to(#ffffff));
@@ -54,11 +54,17 @@ strong {
     box-shadow: 0 1px 1px rgba(0, 0, 0, 0.5);
     cursor: pointer;
     height: 24px;
-    margin-left: 5px;
     padding: 0 5px;
+    margin: 5px 5px 0 0;
     color: #606060;
     border-style: outset;
     border-width: 1px;
+    display: inline-block;
+}
+
+a.bigbutton, #pageheader a.bigbutton {
+    height: 22px;
+    line-height: 22px;
 }
 
 .smallbutton {
@@ -103,7 +109,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 +809,10 @@ div.dailyAbout img {
     height: 14px;
 }
 
+div.dailyEntryPermalink {
+    float: right;
+}
+
 div.dailyTitle {
     font-weight: bold;
     font-size: 44pt;
@@ -1005,7 +1015,7 @@ div.dailyNoEntry {
        display: inline !important;
     }
 
-    .tagfilter input.bigbutton, .searchform input.bigbutton, .addform input.bigbutton {
+    .tagfilter input.bigbutton, .searchform input.bigbutton, .addform input.bigbutton, a.bigbutton {
        width: 30%;
        font-size: smaller;
     }
similarity index 88%
rename from tpl/daily.html
rename to tpl/default/daily.html
index eba0af3bfcb290a114874ac4f7625a8260aa146d..e86e90b1e0bd3c13e12a7d4286d62600ed917808 100644 (file)
@@ -28,9 +28,9 @@
     </div>
 
     <div class="dailyTitle">
-        <img src="../images/floral_left.png" width="51" height="50" class="nomobile" alt="floral_left">
+        <img src="images/floral_left.png" width="51" height="50" class="nomobile" alt="floral_left">
         The Daily Shaarli
-        <img src="../images/floral_right.png" width="51" height="50" class="nomobile" alt="floral_right">
+        <img src="images/floral_right.png" width="51" height="50" class="nomobile" alt="floral_right">
     </div>
 
     <div class="dailyDate">
@@ -50,7 +50,7 @@
                     <div class="dailyEntry">
                         <div class="dailyEntryPermalink">
                             <a href="?{$value.shorturl}">
-                                <img src="../images/squiggle2.png" width="25" height="26" title="permalink" alt="permalink">
+                                <img src="images/squiggle.png" width="25" height="26" title="permalink" alt="permalink">
                             </a>
                         </div>
                         {if="!$hide_timestamps || isLoggedIn()"}
@@ -94,7 +94,7 @@
             {$value}
         {/loop}
     </div>
-    <div id="closing"><img src="../images/squiggle_closing.png" width="66" height="61" alt="-"></div>
+    <div id="closing"><img src="images/squiggle_closing.png" width="66" height="61" alt="-"></div>
 </div>
 {include="page.footer"}
 </body>
similarity index 100%
rename from tpl/dailyrss.html
rename to tpl/default/dailyrss.html
similarity index 86%
rename from tpl/editlink.html
rename to tpl/default/editlink.html
index 870cc1688ac4d3ffb29f4b17bbb65b658a910b02..a2d9b78f96a61bcecf9d293892dcccefab70637f 100644 (file)
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>{include="includes"}
-    <link type="text/css" rel="stylesheet" href="../inc/awesomplete.css" />
+    <link type="text/css" rel="stylesheet" href="inc/awesomplete.css#" />
 </head>
 <body
 {if="$link.title==''"}onload="document.linkform.lf_title.focus();"
             {/if}
             <input type="submit" value="Save" name="save_edit" class="bigbutton">
             <input type="submit" value="Cancel" name="cancel_edit" class="bigbutton">
-            {if="!$link_is_new"}<input type="submit" value="Delete" name="delete_link" class="bigbutton delete" onClick="return confirmDeleteLink();">{/if}
+            {if="!$link_is_new && isset($link.id)"}
+              <a href="?delete_link&amp;lf_linkdate={$link.id}&amp;token={$token}"
+                 name="delete_link" class="bigbutton"
+                 onClick="return confirmDeleteLink();">
+                {'Delete'|t}
+              </a>
+            {/if}
             <input type="hidden" name="token" value="{$token}">
             {if="$http_referer"}<input type="hidden" name="returnurl" value="{$http_referer}">{/if}
         </form>
similarity index 100%
rename from tpl/export.html
rename to tpl/default/export.html
similarity index 100%
rename from tpl/feed.rss.html
rename to tpl/default/feed.rss.html
similarity index 100%
rename from tpl/import.html
rename to tpl/default/import.html
similarity index 77%
rename from tpl/includes.html
rename to tpl/default/includes.html
index 7b2997ce45e73b8dd8ce7df72dd3ba93a08d3864..17b78b17ccda034cf074c4a175e07ce843b9b15a 100644 (file)
@@ -6,9 +6,9 @@
 <link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" />
 <link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" />
 <link href="images/favicon.ico#" rel="shortcut icon" type="image/x-icon" />
-<link type="text/css" rel="stylesheet" href="../inc/reset.css" />
-<link type="text/css" rel="stylesheet" href="../inc/shaarli.css" />
-{if="is_file('inc/user.css')"}<link type="text/css" rel="stylesheet" href="../inc/user.css" />{/if}
+<link type="text/css" rel="stylesheet" href="css/reset.css" />
+<link type="text/css" rel="stylesheet" href="css/shaarli.css" />
+{if="is_file('data/user.css')"}<link type="text/css" rel="stylesheet" href="data/user.css#" />{/if}
 {loop="$plugins_includes.css_files"}
 <link type="text/css" rel="stylesheet" href="{$value}#"/>
 {/loop}
similarity index 100%
rename from tpl/install.html
rename to tpl/default/install.html
similarity index 98%
rename from tpl/linklist.html
rename to tpl/default/linklist.html
index d42323423bc3411bc75de98ecda8bf0ead6cb6ac..5accc92fa4eed0367a187c3509d27e943ffd50fd 100644 (file)
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
-    <link type="text/css" rel="stylesheet" href="../inc/awesomplete.css" />
+    <link type="text/css" rel="stylesheet" href="inc/awesomplete.css#" />
     {include="includes"}
 </head>
 <body>
similarity index 100%
rename from tpl/page.html
rename to tpl/default/page.html
similarity index 100%
rename from tpl/picwall.html
rename to tpl/default/picwall.html
similarity index 100%
rename from tpl/readme.txt
rename to tpl/default/readme.txt
similarity index 100%
rename from tpl/tagcloud.html
rename to tpl/default/tagcloud.html
similarity index 100%
rename from tpl/tools.html
rename to tpl/default/tools.html