From 6d03a9b2b38dd27c6df3127fcd2e24c686cab9df Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sat, 14 May 2016 12:25:31 +0200 Subject: PHP endtag in shaarli_version.php --- shaarli_version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shaarli_version.php b/shaarli_version.php index 184e5220..3c89e94f 100644 --- a/shaarli_version.php +++ b/shaarli_version.php @@ -1 +1 @@ - -- cgit v1.2.3 From 725ca094f8e4b7460869097b6a2a2bd6a4a420f4 Mon Sep 17 00:00:00 2001 From: nodiscc Date: Fri, 20 May 2016 02:06:43 +0200 Subject: Url.php: remove unwanted ?PHPSESSID= URL parameters, update test case --- application/Url.php | 1 + tests/Url/UrlTest.php | 1 + 2 files changed, 2 insertions(+) diff --git a/application/Url.php b/application/Url.php index 77447c8d..c166ff6e 100644 --- a/application/Url.php +++ b/application/Url.php @@ -99,6 +99,7 @@ class Url 'action_type_map=', 'fb_', 'fb=', + 'PHPSESSID=', // Scoop.it '__scoop', diff --git a/tests/Url/UrlTest.php b/tests/Url/UrlTest.php index ce82265e..4bf53b2d 100644 --- a/tests/Url/UrlTest.php +++ b/tests/Url/UrlTest.php @@ -85,6 +85,7 @@ class UrlTest extends PHPUnit_Framework_TestCase $this->assertUrlIsCleaned('?utm_term=1n4l'); $this->assertUrlIsCleaned('?xtor=some-url'); + $this->assertUrlIsCleaned('?PHPSESSID=012345678910111213'); } /** -- cgit v1.2.3 From c7227c5dcaf319ff91a4fa453610cad77a3a0566 Mon Sep 17 00:00:00 2001 From: VirtualTam Date: Fri, 20 May 2016 22:01:54 +0200 Subject: README: add link to the upgrade and migration wiki page Relates to #575 Signed-off-by: VirtualTam --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d67c10ac..d8328d83 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Login: `demo`; Password: `demo` ### Installation & upgrade - [Download](https://github.com/shaarli/Shaarli/wiki/Download) +- [Upgrade and migration](https://github.com/shaarli/Shaarli/wiki/Upgrade-and-migration) - [Server requirements](https://github.com/shaarli/Shaarli/wiki/Server-requirements) - [Server configuration](https://github.com/shaarli/Shaarli/wiki/Server-configuration) - [Shaarli configuration](https://github.com/shaarli/Shaarli/wiki/Shaarli-configuration) -- cgit v1.2.3 From 8c4e60186d393a7c42b6bc09e81ba3051092076e Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Mon, 30 May 2016 18:51:00 +0200 Subject: The tag is no longer private A private tag is never loaded for visitor, making this feature useless. --- plugins/markdown/markdown.php | 19 ++++++++++++++++--- tests/plugins/PluginMarkdownTest.php | 6 ++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/plugins/markdown/markdown.php b/plugins/markdown/markdown.php index 57fcce32..5f56ecc2 100644 --- a/plugins/markdown/markdown.php +++ b/plugins/markdown/markdown.php @@ -10,9 +10,8 @@ require_once 'Parsedown.php'; /* * If this tag is used on a shaare, the description won't be processed by Parsedown. - * Using a private tag so it won't appear for visitors. */ -define('NO_MD_TAG', '.nomarkdown'); +define('NO_MD_TAG', 'nomarkdown'); /** * Parse linklist descriptions. @@ -25,11 +24,11 @@ function hook_markdown_render_linklist($data) { foreach ($data['links'] as &$value) { if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { + $value['taglist'] = stripNoMarkdownTag($value['taglist']); continue; } $value['description'] = process_markdown($value['description']); } - return $data; } @@ -44,6 +43,7 @@ function hook_markdown_render_feed($data) { foreach ($data['links'] as &$value) { if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { + $value['tags'] = stripNoMarkdownTag($value['tags']); continue; } $value['description'] = process_markdown($value['description']); @@ -86,6 +86,19 @@ function noMarkdownTag($tags) return strpos($tags, NO_MD_TAG) !== false; } +/** + * Remove the no-markdown meta tag so it won't be displayed. + * + * @param string $tags Tag list. + * + * @return string tag list without no markdown tag. + */ +function stripNoMarkdownTag($tags) +{ + unset($tags[array_search(NO_MD_TAG, $tags)]); + return array_values($tags); +} + /** * When link list is displayed, include markdown CSS. * diff --git a/tests/plugins/PluginMarkdownTest.php b/tests/plugins/PluginMarkdownTest.php index fa7e1d52..3593a556 100644 --- a/tests/plugins/PluginMarkdownTest.php +++ b/tests/plugins/PluginMarkdownTest.php @@ -125,7 +125,8 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase $data = array( 'links' => array(array( 'description' => $str, - 'tags' => NO_MD_TAG + 'tags' => NO_MD_TAG, + 'taglist' => array(NO_MD_TAG), )) ); @@ -140,7 +141,8 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase // nth link 0 => array( 'formatedDescription' => $str, - 'tags' => NO_MD_TAG + 'tags' => NO_MD_TAG, + 'taglist' => array(), ), ), ), -- cgit v1.2.3 From b1eb5d1d31e3ea256501c08a3ed9aa7183b27466 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 14 Apr 2016 17:59:37 +0200 Subject: Fixes #497: ignore case difference between tags While retrieving all tags, case differences will be ignored. This affects: * tag cloud * tag autocompletion --- application/LinkDB.php | 11 +++++++++-- tests/FeedBuilderTest.php | 2 +- tests/LinkDBTest.php | 6 ++++-- tests/utils/ReferenceLinkDB.php | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/application/LinkDB.php b/application/LinkDB.php index a62341fc..4c1a45b5 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php @@ -417,11 +417,18 @@ You use the community supported version of the original Shaarli project, by Seba public function allTags() { $tags = array(); + $caseMapping = array(); foreach ($this->_links as $link) { foreach (explode(' ', $link['tags']) as $tag) { - if (!empty($tag)) { - $tags[$tag] = (empty($tags[$tag]) ? 1 : $tags[$tag] + 1); + if (empty($tag)) { + continue; } + // The first case found will be displayed. + if (!isset($caseMapping[strtolower($tag)])) { + $caseMapping[strtolower($tag)] = $tag; + $tags[$caseMapping[strtolower($tag)]] = 0; + } + $tags[$caseMapping[strtolower($tag)]]++; } } // Sort tags by usage (most used tag first) diff --git a/tests/FeedBuilderTest.php b/tests/FeedBuilderTest.php index 069b1581..647b2db2 100644 --- a/tests/FeedBuilderTest.php +++ b/tests/FeedBuilderTest.php @@ -93,7 +93,7 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase $this->assertContains('Permalink', $link['description']); $this->assertContains('http://host.tld/?WDWyig', $link['description']); $this->assertEquals(1, count($link['taglist'])); - $this->assertEquals('stuff', $link['taglist'][0]); + $this->assertEquals('sTuff', $link['taglist'][0]); // Test URL with external link. $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links']['20150310_114633']['url']); diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php index 52d31400..8ffb1512 100644 --- a/tests/LinkDBTest.php +++ b/tests/LinkDBTest.php @@ -290,7 +290,9 @@ class LinkDBTest extends PHPUnit_Framework_TestCase 'stallman' => 1, 'free' => 1, '-exclude' => 1, - 'stuff' => 2, + // The DB contains a link with `sTuff` and another one with `stuff` tag. + // They need to be grouped with the first case found (`sTuff`). + 'sTuff' => 2, ), self::$publicLinkDB->allTags() ); @@ -310,7 +312,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase 'w3c' => 1, 'css' => 1, 'Mercurial' => 1, - 'stuff' => 2, + 'sTuff' => 2, '-exclude' => 1, '.hidden' => 1, ), diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php index dc4f5dfa..46165b4d 100644 --- a/tests/utils/ReferenceLinkDB.php +++ b/tests/utils/ReferenceLinkDB.php @@ -21,7 +21,7 @@ class ReferenceLinkDB 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this.', 0, '20150310_114651', - 'stuff' + 'sTuff' ); $this->addLink( -- cgit v1.2.3 From 9ccca40189652e529732683abcdf54fcf775c9ec Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Tue, 10 May 2016 23:18:04 +0200 Subject: Hashtag system * Hashtag are auto-linked with a filter search * Supports unicode * Compatible with markdown (excluded in code blocks) --- application/LinkDB.php | 2 +- application/LinkFilter.php | 33 +++++++++++++++- application/LinkUtils.php | 75 +++++++++++++++++++++++++++++++++++ application/Utils.php | 55 +------------------------- plugins/markdown/markdown.php | 41 ++++++++++++++++++- tests/LinkDBTest.php | 4 +- tests/LinkFilterTest.php | 26 ++++++++++++ tests/LinkUtilsTest.php | 88 +++++++++++++++++++++++++++++++++++++++++ tests/UtilsTest.php | 37 ----------------- tests/utils/ReferenceLinkDB.php | 14 +++---- 10 files changed, 271 insertions(+), 104 deletions(-) diff --git a/application/LinkDB.php b/application/LinkDB.php index b1072e07..929a6b0f 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php @@ -409,7 +409,7 @@ You use the community supported version of the original Shaarli project, by Seba $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : ''; // Search tags + fullsearch. - if (empty($type) && ! empty($searchtags) && ! empty($searchterm)) { + if (! empty($searchtags) && ! empty($searchterm)) { $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT; $request = array($searchtags, $searchterm); } diff --git a/application/LinkFilter.php b/application/LinkFilter.php index e693b284..d4fe28df 100644 --- a/application/LinkFilter.php +++ b/application/LinkFilter.php @@ -27,6 +27,11 @@ class LinkFilter */ public static $FILTER_DAY = 'FILTER_DAY'; + /** + * @var string Allowed characters for hashtags (regex syntax). + */ + public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}'; + /** * @var array all available links. */ @@ -263,8 +268,10 @@ class LinkFilter for ($i = 0 ; $i < count($searchtags) && $found; $i++) { // Exclusive search, quit if tag found. // Or, tag not found in the link, quit. - if (($searchtags[$i][0] == '-' && in_array(substr($searchtags[$i], 1), $linktags)) - || ($searchtags[$i][0] != '-') && ! in_array($searchtags[$i], $linktags) + if (($searchtags[$i][0] == '-' + && $this->searchTagAndHashTag(substr($searchtags[$i], 1), $linktags, $link['description'])) + || ($searchtags[$i][0] != '-') + && ! $this->searchTagAndHashTag($searchtags[$i], $linktags, $link['description']) ) { $found = false; } @@ -306,6 +313,28 @@ class LinkFilter return $filtered; } + /** + * Check if a tag is found in the taglist, or as an hashtag in the link description. + * + * @param string $tag Tag to search. + * @param array $taglist List of tags for the current link. + * @param string $description Link description. + * + * @return bool True if found, false otherwise. + */ + protected function searchTagAndHashTag($tag, $taglist, $description) + { + if (in_array($tag, $taglist)) { + return true; + } + + if (preg_match('/(^| )#'. $tag .'([^'. self::$HASHTAG_CHARS .']|$)/mui', $description) > 0) { + return true; + } + + return false; + } + /** * Convert a list of tags (str) to an array. Also * - handle case sensitivity. diff --git a/application/LinkUtils.php b/application/LinkUtils.php index da04ca97..eeca631f 100644 --- a/application/LinkUtils.php +++ b/application/LinkUtils.php @@ -91,5 +91,80 @@ function count_private($links) foreach ($links as $link) { $cpt = $link['private'] == true ? $cpt + 1 : $cpt; } + return $cpt; } + +/** + * In a string, converts URLs to clickable links. + * + * @param string $text input string. + * @param string $redirector if a redirector is set, use it to gerenate links. + * + * @return string returns $text with all links converted to HTML links. + * + * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722 + */ +function text2clickable($text, $redirector = '') +{ + $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si'; + + if (empty($redirector)) { + return preg_replace($regex, '$1', $text); + } + // Redirector is set, urlencode the final URL. + return preg_replace_callback( + $regex, + function ($matches) use ($redirector) { + return ''. $matches[1] .''; + }, + $text + ); +} + +/** + * Auto-link hashtags. + * + * @param string $description Given description. + * @param string $indexUrl Root URL. + * + * @return string Description with auto-linked hashtags. + */ +function hashtag_autolink($description, $indexUrl = '') +{ + /* + * To support unicode: http://stackoverflow.com/a/35498078/1484919 + * \p{Pc} - to match underscore + * \p{N} - numeric character in any script + * \p{L} - letter from any language + * \p{Mn} - any non marking space (accents, umlauts, etc) + */ + $regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui'; + $replacement = '$1#$2'; + return preg_replace($regex, $replacement, $description); +} + +/** + * This function inserts   where relevant so that multiple spaces are properly displayed in HTML + * even in the absence of
  (This is used in description to keep text formatting).
+ *
+ * @param string $text input text.
+ *
+ * @return string formatted text.
+ */
+function space2nbsp($text)
+{
+    return preg_replace('/(^| ) /m', '$1 ', $text);
+}
+
+/**
+ * Format Shaarli's description
+ *
+ * @param string $description shaare's description.
+ * @param string $redirector  if a redirector is set, use it to gerenate links.
+ *
+ * @return string formatted description.
+ */
+function format_description($description, $redirector = '', $indexUrl = '') {
+    return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $indexUrl)));
+}
diff --git a/application/Utils.php b/application/Utils.php
index da521cce..7d7eaffd 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -197,59 +197,6 @@ function is_session_id_valid($sessionId)
     return true;
 }
 
-/**
- * In a string, converts URLs to clickable links.
- *
- * @param string $text       input string.
- * @param string $redirector if a redirector is set, use it to gerenate links.
- *
- * @return string returns $text with all links converted to HTML links.
- *
- * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
- */
-function text2clickable($text, $redirector)
-{
-    $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si';
-
-    if (empty($redirector)) {
-        return preg_replace($regex, '$1', $text);
-    }
-    // Redirector is set, urlencode the final URL.
-    return preg_replace_callback(
-        $regex,
-        function ($matches) use ($redirector) {
-            return ''. $matches[1] .'';
-        },
-        $text
-    );
-}
-
-/**
- * This function inserts   where relevant so that multiple spaces are properly displayed in HTML
- * even in the absence of 
  (This is used in description to keep text formatting).
- *
- * @param string $text input text.
- *
- * @return string formatted text.
- */
-function space2nbsp($text)
-{
-    return preg_replace('/(^| ) /m', '$1 ', $text);
-}
-
-/**
- * Format Shaarli's description
- * TODO: Move me to ApplicationUtils when it's ready.
- *
- * @param string $description shaare's description.
- * @param string $redirector  if a redirector is set, use it to gerenate links.
- *
- * @return string formatted description.
- */
-function format_description($description, $redirector = false) {
-    return nl2br(space2nbsp(text2clickable($description, $redirector)));
-}
-
 /**
  * Sniff browser language to set the locale automatically.
  * Note that is may not work on your server if the corresponding locale is not installed.
@@ -273,4 +220,4 @@ function autoLocale($headerLocale)
         }
     }
     setlocale(LC_ALL, $attempts);
-}
\ No newline at end of file
+}
diff --git a/plugins/markdown/markdown.php b/plugins/markdown/markdown.php
index 5f56ecc2..6b1c1d44 100644
--- a/plugins/markdown/markdown.php
+++ b/plugins/markdown/markdown.php
@@ -151,7 +151,44 @@ function hook_markdown_render_editlink($data)
  */
 function reverse_text2clickable($description)
 {
-    return preg_replace('![^ ]+!m', '$1', $description);
+    $descriptionLines = explode(PHP_EOL, $description);
+    $descriptionOut = '';
+    $codeBlockOn = false;
+    $lineCount = 0;
+
+    foreach ($descriptionLines as $descriptionLine) {
+        // Detect line of code
+        $codeLineOn = preg_match('/^    /', $descriptionLine) > 0;
+        // Detect and toggle block of code
+        if (!$codeBlockOn) {
+            $codeBlockOn = preg_match('/^```/', $descriptionLine) > 0;
+        }
+        elseif (preg_match('/^```/', $descriptionLine) > 0) {
+            $codeBlockOn = false;
+        }
+
+        $hashtagTitle = ' title="Hashtag [^"]+"';
+        // Reverse `inline code` hashtags.
+        $descriptionLine = preg_replace(
+            '!(`[^`\n]*)([^<]+)([^`\n]*`)!m',
+            '$1$2$3',
+            $descriptionLine
+        );
+
+        // Reverse hashtag links if we're in a code block.
+        $hashtagFilter = ($codeBlockOn || $codeLineOn) ? $hashtagTitle : '';
+        $descriptionLine = preg_replace(
+            '!([^<]+)!m',
+            '$1',
+            $descriptionLine
+        );
+
+        $descriptionOut .= $descriptionLine;
+        if ($lineCount++ < count($descriptionLines) - 1) {
+            $descriptionOut .= PHP_EOL;
+        }
+    }
+    return $descriptionOut;
 }
 
 /**
@@ -226,9 +263,9 @@ function process_markdown($description)
     $parsedown = new Parsedown();
 
     $processedDescription = $description;
-    $processedDescription = reverse_text2clickable($processedDescription);
     $processedDescription = reverse_nl2br($processedDescription);
     $processedDescription = reverse_space2nbsp($processedDescription);
+    $processedDescription = reverse_text2clickable($processedDescription);
     $processedDescription = unescape($processedDescription);
     $processedDescription = $parsedown
         ->setMarkupEscaped(false)
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index 30ea4e01..0db81fd6 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -256,7 +256,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
         $link = self::$publicLinkDB->getLinkFromUrl('http://mediagoblin.org/');
 
         $this->assertNotEquals(false, $link);
-        $this->assertEquals(
+        $this->assertContains(
             'A free software media publishing platform',
             $link['description']
         );
@@ -293,6 +293,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
                 // The DB contains a link with `sTuff` and another one with `stuff` tag.
                 // They need to be grouped with the first case found (`sTuff`).
                 'sTuff' => 2,
+                'hashtag' => 2,
             ),
             self::$publicLinkDB->allTags()
         );
@@ -315,6 +316,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
                 'sTuff' => 2,
                 '-exclude' => 1,
                 '.hidden' => 1,
+                'hashtag' => 2,
             ),
             self::$privateLinkDB->allTags()
         );
diff --git a/tests/LinkFilterTest.php b/tests/LinkFilterTest.php
index 1620bb78..7d45fc59 100644
--- a/tests/LinkFilterTest.php
+++ b/tests/LinkFilterTest.php
@@ -387,4 +387,30 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
             ))
         );
     }
+
+    /**
+     * Filter links by #hashtag.
+     */
+    public function testFilterByHashtag()
+    {
+        $hashtag = 'hashtag';
+        $this->assertEquals(
+            3,
+            count(self::$linkFilter->filter(
+                LinkFilter::$FILTER_TAG,
+                $hashtag
+            ))
+        );
+
+        $hashtag = 'private';
+        $this->assertEquals(
+            1,
+            count(self::$linkFilter->filter(
+                LinkFilter::$FILTER_TAG,
+                $hashtag,
+                false,
+                true
+            ))
+        );
+    }
 }
diff --git a/tests/LinkUtilsTest.php b/tests/LinkUtilsTest.php
index d1b022fd..7c0d4b0b 100644
--- a/tests/LinkUtilsTest.php
+++ b/tests/LinkUtilsTest.php
@@ -93,4 +93,92 @@ class LinkUtilsTest extends PHPUnit_Framework_TestCase
         $refDB = new ReferenceLinkDB();
         $this->assertEquals($refDB->countPrivateLinks(), count_private($refDB->getLinks()));
     }
+
+    /**
+     * Test text2clickable without a redirector being set.
+     */
+    public function testText2clickableWithoutRedirector()
+    {
+        $text = 'stuff http://hello.there/is=someone#here otherstuff';
+        $expectedText = 'stuff http://hello.there/is=someone#here otherstuff';
+        $processedText = text2clickable($text, '');
+        $this->assertEquals($expectedText, $processedText);
+    }
+
+    /**
+     * Test text2clickable a redirector set.
+     */
+    public function testText2clickableWithRedirector()
+    {
+        $text = 'stuff http://hello.there/is=someone#here otherstuff';
+        $redirector = 'http://redirector.to';
+        $expectedText = 'stuff http://hello.there/is=someone#here otherstuff';
+        $processedText = text2clickable($text, $redirector);
+        $this->assertEquals($expectedText, $processedText);
+    }
+
+    /**
+     * Test testSpace2nbsp.
+     */
+    public function testSpace2nbsp()
+    {
+        $text = '  Are you   thrilled  by flags   ?'. PHP_EOL .' Really?';
+        $expectedText = '  Are you   thrilled  by flags   ?'. PHP_EOL .' Really?';
+        $processedText = space2nbsp($text);
+        $this->assertEquals($expectedText, $processedText);
+    }
+
+    /**
+     * Test hashtags auto-link.
+     */
+    public function testHashtagAutolink()
+    {
+        $index = 'http://domain.tld/';
+        $rawDescription = '#hashtag\n
+            # nothashtag\n
+            test#nothashtag #hashtag \#nothashtag\n
+            test #hashtag #hashtag test #hashtag.test\n
+            #hashtag #hashtag-nothashtag #hashtag_hashtag\n
+            What is #ашок anyway?\n
+            カタカナ #カタカナ」カタカナ\n';
+        $autolinkedDescription = hashtag_autolink($rawDescription, $index);
+
+        $this->assertContains($this->getHashtagLink('hashtag', $index), $autolinkedDescription);
+        $this->assertNotContains(' #hashtag', $autolinkedDescription);
+        $this->assertNotContains('>#nothashtag', $autolinkedDescription);
+        $this->assertContains($this->getHashtagLink('ашок', $index), $autolinkedDescription);
+        $this->assertContains($this->getHashtagLink('カタカナ', $index), $autolinkedDescription);
+        $this->assertContains($this->getHashtagLink('hashtag_hashtag', $index), $autolinkedDescription);
+        $this->assertNotContains($this->getHashtagLink('hashtag-nothashtag', $index), $autolinkedDescription);
+    }
+
+    /**
+     * Test hashtags auto-link without index URL.
+     */
+    public function testHashtagAutolinkNoIndex()
+    {
+        $rawDescription = 'blabla #hashtag x#nothashtag';
+        $autolinkedDescription = hashtag_autolink($rawDescription);
+
+        $this->assertContains($this->getHashtagLink('hashtag'), $autolinkedDescription);
+        $this->assertNotContains(' #hashtag', $autolinkedDescription);
+        $this->assertNotContains('>#nothashtag', $autolinkedDescription);
+    }
+
+    /**
+     * Util function to build an hashtag link.
+     *
+     * @param string $hashtag Hashtag name.
+     * @param string $index   Index URL.
+     *
+     * @return string HTML hashtag link.
+     */
+    private function getHashtagLink($hashtag, $index = '')
+    {
+        $hashtagLink = '#$1';
+        return str_replace('$1', $hashtag, $hashtagLink);
+    }
 }
diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php
index 3073b5eb..6a7870c4 100644
--- a/tests/UtilsTest.php
+++ b/tests/UtilsTest.php
@@ -253,41 +253,4 @@ class UtilsTest extends PHPUnit_Framework_TestCase
             is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
         );
     }
-
-    /**
-     * Test text2clickable without a redirector being set.
-     */
-    public function testText2clickableWithoutRedirector()
-    {
-        $text = 'stuff http://hello.there/is=someone#here otherstuff';
-        $expectedText = 'stuff http://hello.there/is=someone#here otherstuff';
-        $processedText = text2clickable($text, '');
-        $this->assertEquals($expectedText, $processedText);
-    }
-
-    /**
-     * Test text2clickable a redirector set.
-     */
-    public function testText2clickableWithRedirector()
-    {
-        $text = 'stuff http://hello.there/is=someone#here otherstuff';
-        $redirector = 'http://redirector.to';
-        $expectedText = 'stuff http://hello.there/is=someone#here otherstuff';
-        $processedText = text2clickable($text, $redirector);
-        $this->assertEquals($expectedText, $processedText);
-    }
-
-    /**
-     * Test testSpace2nbsp.
-     */
-    public function testSpace2nbsp()
-    {
-        $text = '  Are you   thrilled  by flags   ?'. PHP_EOL .' Really?';
-        $expectedText = '  Are you   thrilled  by flags   ?'. PHP_EOL .' Really?';
-        $processedText = space2nbsp($text);
-        $this->assertEquals($expectedText, $processedText);
-    }
 }
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php
index 46165b4d..fe16baac 100644
--- a/tests/utils/ReferenceLinkDB.php
+++ b/tests/utils/ReferenceLinkDB.php
@@ -18,7 +18,7 @@ class ReferenceLinkDB
         $this->addLink(
             'Link title: @website',
             '?WDWyig',
-            'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this.',
+            'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag',
             0,
             '20150310_114651',
             'sTuff'
@@ -27,25 +27,25 @@ class ReferenceLinkDB
         $this->addLink(
             'Free as in Freedom 2.0 @website',
             'https://static.fsf.org/nosvn/faif-2.0.pdf',
-            'Richard Stallman and the Free Software Revolution. Read this.',
+            'Richard Stallman and the Free Software Revolution. Read this. #hashtag',
             0,
             '20150310_114633',
-            'free gnu software stallman -exclude stuff'
+            'free gnu software stallman -exclude stuff hashtag'
         );
 
         $this->addLink(
             'MediaGoblin',
             'http://mediagoblin.org/',
-            'A free software media publishing platform',
+            'A free software media publishing platform #hashtagOther',
             0,
             '20130614_184135',
-            'gnu media web .hidden'
+            'gnu media web .hidden hashtag'
         );
 
         $this->addLink(
             'w3c-markup-validator',
             'https://dvcs.w3.org/hg/markup-validator/summary',
-            'Mercurial repository for the W3C Validator',
+            'Mercurial repository for the W3C Validator #private',
             1,
             '20141125_084734',
             'css html w3c web Mercurial'
@@ -54,7 +54,7 @@ class ReferenceLinkDB
         $this->addLink(
             'UserFriendly - Web Designer',
             'http://ars.userfriendly.org/cartoons/?id=20121206',
-            'Naming conventions...',
+            'Naming conventions... #private',
             0,
             '20121206_142300',
             'dev cartoon web'
-- 
cgit v1.2.3


From 823a363c3b2e10008a607c8b69c1a3d4e9b44ea1 Mon Sep 17 00:00:00 2001
From: ArthurHoaro 
Date: Mon, 16 May 2016 08:54:03 +0200
Subject: Configuration template indenting

---
 tpl/configure.html | 114 ++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 78 insertions(+), 36 deletions(-)

diff --git a/tpl/configure.html b/tpl/configure.html
index 77c8b7d9..b7f87c68 100644
--- a/tpl/configure.html
+++ b/tpl/configure.html
@@ -3,48 +3,90 @@
 {include="includes"}
 
 
 {include="page.footer"}
 
-- 
cgit v1.2.3


From 59404d7909b21682ec0782778452a8a70e38b25e Mon Sep 17 00:00:00 2001
From: ArthurHoaro 
Date: Wed, 18 May 2016 21:43:59 +0200
Subject: Introduce a configuration manager (not plugged yet)

---
 application/config/ConfigIO.php      |  33 ++++
 application/config/ConfigManager.php | 363 +++++++++++++++++++++++++++++++++++
 application/config/ConfigPhp.php     |  93 +++++++++
 application/config/ConfigPlugin.php  | 118 ++++++++++++
 tests/config/ConfigManagerTest.php   |  48 +++++
 tests/config/ConfigPhpTest.php       |  82 ++++++++
 tests/config/ConfigPluginTest.php    | 121 ++++++++++++
 tests/config/php/configOK.php        |  14 ++
 8 files changed, 872 insertions(+)
 create mode 100644 application/config/ConfigIO.php
 create mode 100644 application/config/ConfigManager.php
 create mode 100644 application/config/ConfigPhp.php
 create mode 100644 application/config/ConfigPlugin.php
 create mode 100644 tests/config/ConfigManagerTest.php
 create mode 100644 tests/config/ConfigPhpTest.php
 create mode 100644 tests/config/ConfigPluginTest.php
 create mode 100644 tests/config/php/configOK.php

diff --git a/application/config/ConfigIO.php b/application/config/ConfigIO.php
new file mode 100644
index 00000000..2b68fe6a
--- /dev/null
+++ b/application/config/ConfigIO.php
@@ -0,0 +1,33 @@
+initialize();
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * Rebuild the loaded config array from config files.
+     */
+    public function reload()
+    {
+        $this->initialize();
+    }
+
+    /**
+     * Initialize loaded conf in ConfigManager.
+     */
+    protected function initialize()
+    {
+        /*if (! file_exists(self::$CONFIG_FILE .'.php')) {
+            $this->configIO = new ConfigJson();
+        } else {
+            $this->configIO = new ConfigPhp();
+        }*/
+        $this->configIO = new ConfigPhp();
+        $this->loadedConfig = $this->configIO->read(self::$CONFIG_FILE);
+        $this->setDefaultValues();
+    }
+
+    /**
+     * Get a setting.
+     *
+     * Supports nested settings with dot separated keys.
+     * Eg. 'config.stuff.option' will find $conf[config][stuff][option],
+     * or in JSON:
+     *   { "config": { "stuff": {"option": "mysetting" } } } }
+     *
+     * @param string $setting Asked setting, keys separated with dots.
+     * @param string $default Default value if not found.
+     *
+     * @return mixed Found setting, or the default value.
+     */
+    public function get($setting, $default = '')
+    {
+        $settings = explode('.', $setting);
+        $value = self::getConfig($settings, $this->loadedConfig);
+        if ($value === self::$NOT_FOUND) {
+            return $default;
+        }
+        return $value;
+    }
+
+    /**
+     * Set a setting, and eventually write it.
+     *
+     * Supports nested settings with dot separated keys.
+     *
+     * @param string $setting    Asked setting, keys separated with dots.
+     * @param string $value      Value to set.
+     * @param bool   $write      Write the new setting in the config file, default false.
+     * @param bool   $isLoggedIn User login state, default false.
+     */
+    public function set($setting, $value, $write = false, $isLoggedIn = false)
+    {
+        $settings = explode('.', $setting);
+        self::setConfig($settings, $value, $this->loadedConfig);
+        if ($write) {
+            $this->write($isLoggedIn);
+        }
+    }
+
+    /**
+     * Check if a settings exists.
+     *
+     * Supports nested settings with dot separated keys.
+     *
+     * @param string $setting    Asked setting, keys separated with dots.
+     *
+     * @return bool true if the setting exists, false otherwise.
+     */
+    public function exists($setting)
+    {
+        $settings = explode('.', $setting);
+        $value = self::getConfig($settings, $this->loadedConfig);
+        if ($value === self::$NOT_FOUND) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Call the config writer.
+     *
+     * @param bool $isLoggedIn User login state.
+     *
+     * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
+     * @throws UnauthorizedConfigException: user is not authorize to change configuration.
+     * @throws IOException: an error occurred while writing the new config file.
+     */
+    public function write($isLoggedIn)
+    {
+        // These fields are required in configuration.
+        $mandatoryFields = array(
+            'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',
+            'redirector', 'disablesessionprotection', 'privateLinkByDefault'
+        );
+
+        // Only logged in user can alter config.
+        if (is_file(self::$CONFIG_FILE) && !$isLoggedIn) {
+            throw new UnauthorizedConfigException();
+        }
+
+        // Check that all mandatory fields are provided in $conf.
+        foreach ($mandatoryFields as $field) {
+            if (! $this->exists($field)) {
+                throw new MissingFieldConfigException($field);
+            }
+        }
+
+        $this->configIO->write(self::$CONFIG_FILE, $this->loadedConfig);
+    }
+
+    /**
+     * Get the configuration file path.
+     *
+     * @return string Config file path.
+     */
+    public function getConfigFile()
+    {
+        return self::$CONFIG_FILE . $this->configIO->getExtension();
+    }
+
+    /**
+     * Recursive function which find asked setting in the loaded config.
+     *
+     * @param array $settings Ordered array which contains keys to find.
+     * @param array $conf   Loaded settings, then sub-array.
+     *
+     * @return mixed Found setting or NOT_FOUND flag.
+     */
+    protected static function getConfig($settings, $conf)
+    {
+        if (!is_array($settings) || count($settings) == 0) {
+            return self::$NOT_FOUND;
+        }
+
+        $setting = array_shift($settings);
+        if (!isset($conf[$setting])) {
+            return self::$NOT_FOUND;
+        }
+
+        if (count($settings) > 0) {
+            return self::getConfig($settings, $conf[$setting]);
+        }
+        return $conf[$setting];
+    }
+
+    /**
+     * Recursive function which find asked setting in the loaded config.
+     *
+     * @param array $settings Ordered array which contains keys to find.
+     * @param mixed $value
+     * @param array $conf   Loaded settings, then sub-array.
+     *
+     * @return mixed Found setting or NOT_FOUND flag.
+     */
+    protected static function setConfig($settings, $value, &$conf)
+    {
+        if (!is_array($settings) || count($settings) == 0) {
+            return self::$NOT_FOUND;
+        }
+
+        $setting = array_shift($settings);
+        if (count($settings) > 0) {
+            return self::setConfig($settings, $value, $conf[$setting]);
+        }
+        $conf[$setting] = $value;
+    }
+
+    /**
+     * Set a bunch of default values allowing Shaarli to start without a config file.
+     */
+    protected function setDefaultValues()
+    {
+        // Data subdirectory
+        $this->setEmpty('config.DATADIR', 'data');
+
+        // Main configuration file
+        $this->setEmpty('config.CONFIG_FILE', 'data/config.php');
+
+        // Link datastore
+        $this->setEmpty('config.DATASTORE', 'data/datastore.php');
+
+        // Banned IPs
+        $this->setEmpty('config.IPBANS_FILENAME', 'data/ipbans.php');
+
+        // Processed updates file.
+        $this->setEmpty('config.UPDATES_FILE', 'data/updates.txt');
+
+        // Access log
+        $this->setEmpty('config.LOG_FILE', 'data/log.txt');
+
+        // For updates check of Shaarli
+        $this->setEmpty('config.UPDATECHECK_FILENAME', 'data/lastupdatecheck.txt');
+
+        // Set ENABLE_UPDATECHECK to disabled by default.
+        $this->setEmpty('config.ENABLE_UPDATECHECK', false);
+
+        // RainTPL cache directory (keep the trailing slash!)
+        $this->setEmpty('config.RAINTPL_TMP', 'tmp/');
+        // Raintpl template directory (keep the trailing slash!)
+        $this->setEmpty('config.RAINTPL_TPL', 'tpl/');
+
+        // Thumbnail cache directory
+        $this->setEmpty('config.CACHEDIR', 'cache');
+
+        // Atom & RSS feed cache directory
+        $this->setEmpty('config.PAGECACHE', 'pagecache');
+
+        // Ban IP after this many failures
+        $this->setEmpty('config.BAN_AFTER', 4);
+        // Ban duration for IP address after login failures (in seconds)
+        $this->setEmpty('config.BAN_DURATION', 1800);
+
+        // Feed options
+        // Enable RSS permalinks by default.
+        // This corresponds to the default behavior of shaarli before this was added as an option.
+        $this->setEmpty('config.ENABLE_RSS_PERMALINKS', true);
+        // If true, an extra "ATOM feed" button will be displayed in the toolbar
+        $this->setEmpty('config.SHOW_ATOM', false);
+
+        // Link display options
+        $this->setEmpty('config.HIDE_PUBLIC_LINKS', false);
+        $this->setEmpty('config.HIDE_TIMESTAMPS', false);
+        $this->setEmpty('config.LINKS_PER_PAGE', 20);
+
+        // Open Shaarli (true): anyone can add/edit/delete links without having to login
+        $this->setEmpty('config.OPEN_SHAARLI', false);
+
+        // Thumbnails
+        // Display thumbnails in links
+        $this->setEmpty('config.ENABLE_THUMBNAILS', true);
+        // Store thumbnails in a local cache
+        $this->setEmpty('config.ENABLE_LOCALCACHE', true);
+
+        // Update check frequency for Shaarli. 86400 seconds=24 hours
+        $this->setEmpty('config.UPDATECHECK_BRANCH', 'stable');
+        $this->setEmpty('config.UPDATECHECK_INTERVAL', 86400);
+
+        $this->setEmpty('redirector', '');
+        $this->setEmpty('config.REDIRECTOR_URLENCODE', true);
+
+        // Enabled plugins.
+        $this->setEmpty('config.ENABLED_PLUGINS', array('qrcode'));
+
+        // Initialize plugin parameters array.
+        $this->setEmpty('plugins', array());
+    }
+
+    /**
+     * Set only if the setting does not exists.
+     *
+     * @param string $key   Setting key.
+     * @param mixed  $value Setting value.
+     */
+    protected function setEmpty($key, $value)
+    {
+        if (! $this->exists($key)) {
+            $this->set($key, $value);
+        }
+    }
+}
+
+/**
+ * Exception used if a mandatory field is missing in given configuration.
+ */
+class MissingFieldConfigException extends Exception
+{
+    public $field;
+
+    /**
+     * Construct exception.
+     *
+     * @param string $field field name missing.
+     */
+    public function __construct($field)
+    {
+        $this->field = $field;
+        $this->message = 'Configuration value is required for '. $this->field;
+    }
+}
+
+/**
+ * Exception used if an unauthorized attempt to edit configuration has been made.
+ */
+class UnauthorizedConfigException extends Exception
+{
+    /**
+     * Construct exception.
+     */
+    public function __construct()
+    {
+        $this->message = 'You are not authorized to alter config.';
+    }
+}
diff --git a/application/config/ConfigPhp.php b/application/config/ConfigPhp.php
new file mode 100644
index 00000000..311aeb81
--- /dev/null
+++ b/application/config/ConfigPhp.php
@@ -0,0 +1,93 @@
+getExtension();
+        if (! file_exists($filepath) || ! is_readable($filepath)) {
+            return array();
+        }
+
+        include $filepath;
+
+        $out = array();
+        foreach (self::$ROOT_KEYS as $key) {
+            $out[$key] = $GLOBALS[$key];
+        }
+        $out['config'] = $GLOBALS['config'];
+        $out['plugins'] = !empty($GLOBALS['plugins']) ? $GLOBALS['plugins'] : array();
+        return $out;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    function write($filepath, $conf)
+    {
+        $filepath .= $this->getExtension();
+
+        $configStr = ' $value) {
+            $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($conf['config'][$key], true).';'. PHP_EOL;
+        }
+
+        if (isset($conf['plugins'])) {
+            foreach ($conf['plugins'] as $key => $value) {
+                $configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($conf['plugins'][$key], true).';'. PHP_EOL;
+            }
+        }
+
+        // FIXME!
+        //$configStr .= 'date_default_timezone_set('.var_export($conf['timezone'], true).');'. PHP_EOL;
+
+        if (!file_put_contents($filepath, $configStr)
+            || strcmp(file_get_contents($filepath), $configStr) != 0
+        ) {
+            throw new IOException(
+                $filepath,
+                'Shaarli could not create the config file.
+                Please make sure Shaarli has the right to write in the folder is it installed in.'
+            );
+        }
+    }
+
+    /**
+     * @inheritdoc
+     */
+    function getExtension()
+    {
+        return '.php';
+    }
+}
diff --git a/application/config/ConfigPlugin.php b/application/config/ConfigPlugin.php
new file mode 100644
index 00000000..8af89d04
--- /dev/null
+++ b/application/config/ConfigPlugin.php
@@ -0,0 +1,118 @@
+ $data) {
+        if (startsWith($key, 'order')) {
+            continue;
+        }
+
+        // If there is no order, it means a disabled plugin has been enabled.
+        if (isset($formData['order_' . $key])) {
+            $plugins[(int) $formData['order_' . $key]] = $key;
+        }
+        else {
+            $newEnabledPlugins[] = $key;
+        }
+    }
+
+    // New enabled plugins will be added at the end of order.
+    $plugins = array_merge($plugins, $newEnabledPlugins);
+
+    // Sort plugins by order.
+    if (!ksort($plugins)) {
+        throw new PluginConfigOrderException();
+    }
+
+    $finalPlugins = array();
+    // Make plugins order continuous.
+    foreach ($plugins as $plugin) {
+        $finalPlugins[] = $plugin;
+    }
+
+    return $finalPlugins;
+}
+
+/**
+ * Validate plugin array submitted.
+ * Will fail if there is duplicate orders value.
+ *
+ * @param array $formData Data from submitted form.
+ *
+ * @return bool true if ok, false otherwise.
+ */
+function validate_plugin_order($formData)
+{
+    $orders = array();
+    foreach ($formData as $key => $value) {
+        // No duplicate order allowed.
+        if (in_array($value, $orders)) {
+            return false;
+        }
+
+        if (startsWith($key, 'order')) {
+            $orders[] = $value;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Affect plugin parameters values into plugins array.
+ *
+ * @param mixed $plugins Plugins array ($plugins[]['parameters']['param_name'] = .
+ * @param mixed $conf  Plugins configuration.
+ *
+ * @return mixed Updated $plugins array.
+ */
+function load_plugin_parameter_values($plugins, $conf)
+{
+    $out = $plugins;
+    foreach ($plugins as $name => $plugin) {
+        if (empty($plugin['parameters'])) {
+            continue;
+        }
+
+        foreach ($plugin['parameters'] as $key => $param) {
+            if (!empty($conf[$key])) {
+                $out[$name]['parameters'][$key] = $conf[$key];
+            }
+        }
+    }
+
+    return $out;
+}
+
+/**
+ * Exception used if an error occur while saving plugin configuration.
+ */
+class PluginConfigOrderException extends Exception
+{
+    /**
+     * Construct exception.
+     */
+    public function __construct()
+    {
+        $this->message = 'An error occurred while trying to save plugins loading order.';
+    }
+}
diff --git a/tests/config/ConfigManagerTest.php b/tests/config/ConfigManagerTest.php
new file mode 100644
index 00000000..1b6358f3
--- /dev/null
+++ b/tests/config/ConfigManagerTest.php
@@ -0,0 +1,48 @@
+conf = ConfigManager::getInstance();
+    }
+
+    public function tearDown()
+    {
+        @unlink($this->conf->getConfigFile());
+    }
+
+    public function testSetWriteGet()
+    {
+        // This won't work with ConfigPhp.
+        $this->markTestIncomplete();
+
+        $this->conf->set('paramInt', 42);
+        $this->conf->set('paramString', 'value1');
+        $this->conf->set('paramBool', false);
+        $this->conf->set('paramArray', array('foo' => 'bar'));
+        $this->conf->set('paramNull', null);
+
+        $this->conf->write(true);
+        $this->conf->reload();
+
+        $this->assertEquals(42, $this->conf->get('paramInt'));
+        $this->assertEquals('value1', $this->conf->get('paramString'));
+        $this->assertFalse($this->conf->get('paramBool'));
+        $this->assertEquals(array('foo' => 'bar'), $this->conf->get('paramArray'));
+        $this->assertEquals(null, $this->conf->get('paramNull'));
+    }
+    
+}
\ No newline at end of file
diff --git a/tests/config/ConfigPhpTest.php b/tests/config/ConfigPhpTest.php
new file mode 100644
index 00000000..0f849bd5
--- /dev/null
+++ b/tests/config/ConfigPhpTest.php
@@ -0,0 +1,82 @@
+configIO = new ConfigPhp();
+    }
+
+    /**
+     * Read a simple existing config file.
+     */
+    public function testRead()
+    {
+        $conf = $this->configIO->read('tests/config/php/configOK');
+        $this->assertEquals('root', $conf['login']);
+        $this->assertEquals('lala', $conf['redirector']);
+        $this->assertEquals('data/datastore.php', $conf['config']['DATASTORE']);
+        $this->assertEquals('1', $conf['plugins']['WALLABAG_VERSION']);
+    }
+
+    /**
+     * Read a non existent config file -> empty array.
+     */
+    public function testReadNonExistent()
+    {
+        $this->assertEquals(array(), $this->configIO->read('nope'));
+    }
+
+    /**
+     * Write a new config file.
+     */
+    public function testWriteNew()
+    {
+        $dataFile = 'tests/config/php/configWrite';
+        $data = array(
+            'login' => 'root',
+            'redirector' => 'lala',
+            'config' => array(
+                'DATASTORE' => 'data/datastore.php',
+            ),
+            'plugins' => array(
+                'WALLABAG_VERSION' => '1',
+            )
+        );
+        $this->configIO->write($dataFile, $data);
+        $expected = 'assertEquals($expected, file_get_contents($dataFile .'.php'));
+        unlink($dataFile .'.php');
+    }
+
+    /**
+     * Overwrite an existing setting.
+     */
+    public function testOverwrite()
+    {
+        $source = 'tests/config/php/configOK.php';
+        $dest = 'tests/config/php/configOverwrite';
+        copy($source, $dest . '.php');
+        $conf = $this->configIO->read($dest);
+        $conf['redirector'] = 'blabla';
+        $this->configIO->write($dest, $conf);
+        $conf = $this->configIO->read($dest);
+        $this->assertEquals('blabla', $conf['redirector']);
+        unlink($dest .'.php');
+    }
+}
diff --git a/tests/config/ConfigPluginTest.php b/tests/config/ConfigPluginTest.php
new file mode 100644
index 00000000..716631b0
--- /dev/null
+++ b/tests/config/ConfigPluginTest.php
@@ -0,0 +1,121 @@
+ 2,   // no plugin related
+            'plugin2' => 0,         // new - at the end
+            'plugin3' => 0,         // 2nd
+            'order_plugin3' => 8,
+            'plugin4' => 0,         // 1st
+            'order_plugin4' => 5,
+        );
+
+        $expected = array(
+            'plugin3',
+            'plugin4',
+            'plugin2',
+        );
+
+        $out = save_plugin_config($data);
+        $this->assertEquals($expected, $out);
+    }
+
+    /**
+     * Test save_plugin_config with invalid data.
+     *
+     * @expectedException              PluginConfigOrderException
+     */
+    public function testSavePluginConfigInvalid()
+    {
+        $data = array(
+            'plugin2' => 0,
+            'plugin3' => 0,
+            'order_plugin3' => 0,
+            'plugin4' => 0,
+            'order_plugin4' => 0,
+        );
+
+        save_plugin_config($data);
+    }
+
+    /**
+     * Test save_plugin_config without data.
+     */
+    public function testSavePluginConfigEmpty()
+    {
+        $this->assertEquals(array(), save_plugin_config(array()));
+    }
+
+    /**
+     * Test validate_plugin_order with valid data.
+     */
+    public function testValidatePluginOrderValid()
+    {
+        $data = array(
+            'order_plugin1' => 2,
+            'plugin2' => 0,
+            'plugin3' => 0,
+            'order_plugin3' => 1,
+            'plugin4' => 0,
+            'order_plugin4' => 5,
+        );
+
+        $this->assertTrue(validate_plugin_order($data));
+    }
+
+    /**
+     * Test validate_plugin_order with invalid data.
+     */
+    public function testValidatePluginOrderInvalid()
+    {
+        $data = array(
+            'order_plugin1' => 2,
+            'order_plugin3' => 1,
+            'order_plugin4' => 1,
+        );
+
+        $this->assertFalse(validate_plugin_order($data));
+    }
+
+    /**
+     * Test load_plugin_parameter_values.
+     */
+    public function testLoadPluginParameterValues()
+    {
+        $plugins = array(
+            'plugin_name' => array(
+                'parameters' => array(
+                    'param1' => true,
+                    'param2' => false,
+                    'param3' => '',
+                )
+            )
+        );
+
+        $parameters = array(
+            'param1' => 'value1',
+            'param2' => 'value2',
+        );
+
+        $result = load_plugin_parameter_values($plugins, $parameters);
+        $this->assertEquals('value1', $result['plugin_name']['parameters']['param1']);
+        $this->assertEquals('value2', $result['plugin_name']['parameters']['param2']);
+        $this->assertEquals('', $result['plugin_name']['parameters']['param3']);
+    }
+}
diff --git a/tests/config/php/configOK.php b/tests/config/php/configOK.php
new file mode 100644
index 00000000..b91ad293
--- /dev/null
+++ b/tests/config/php/configOK.php
@@ -0,0 +1,14 @@
+
Date: Wed, 18 May 2016 21:48:24 +0200
Subject: Replace $GLOBALS configuration with the configuration manager in the
 whole code base

---
 application/ApplicationUtils.php     |  26 +-
 application/Config.php               | 221 -----------------
 application/FileUtils.php            |   8 +-
 application/PageBuilder.php          |  28 ++-
 application/Updater.php              |  30 +--
 application/Utils.php                |   2 +-
 application/config/ConfigIO.php      |   2 +
 application/config/ConfigManager.php |  49 +++-
 application/config/ConfigPhp.php     |   3 -
 application/config/ConfigPlugin.php  |   4 +-
 index.php                            | 443 ++++++++++++++++-------------------
 tests/ApplicationUtilsTest.php       |  54 ++---
 tests/ConfigTest.php                 | 244 -------------------
 tests/FeedBuilderTest.php            |   6 +-
 tests/LinkDBTest.php                 |   2 +-
 tests/Updater/DummyUpdater.php       |   5 +-
 tests/Updater/UpdaterTest.php        |  80 ++++---
 tests/config/ConfigPhpTest.php       |  16 +-
 tests/config/php/configOK.php        |  14 --
 tests/utils/config/configPhp.php     |  14 ++
 tests/utils/config/configUpdater.php |  15 ++
 tpl/configure.html                   |   8 +-
 tpl/page.header.html                 |   4 +-
 23 files changed, 422 insertions(+), 856 deletions(-)
 delete mode 100644 application/Config.php
 delete mode 100644 tests/ConfigTest.php
 delete mode 100644 tests/config/php/configOK.php
 create mode 100644 tests/utils/config/configPhp.php
 create mode 100644 tests/utils/config/configUpdater.php

diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php
index 978fc9da..ed9abc39 100644
--- a/application/ApplicationUtils.php
+++ b/application/ApplicationUtils.php
@@ -132,32 +132,32 @@ class ApplicationUtils
     /**
      * Checks Shaarli has the proper access permissions to its resources
      *
-     * @param array $globalConfig The $GLOBALS['config'] array
-     *
      * @return array A list of the detected configuration issues
      */
-    public static function checkResourcePermissions($globalConfig)
+    public static function checkResourcePermissions()
     {
         $errors = array();
+        $conf = ConfigManager::getInstance();
 
         // Check script and template directories are readable
         foreach (array(
             'application',
             'inc',
             'plugins',
-            $globalConfig['RAINTPL_TPL']
+            $conf->get('config.RAINTPL_TPL'),
         ) as $path) {
             if (! is_readable(realpath($path))) {
                 $errors[] = '"'.$path.'" directory is not readable';
             }
         }
 
+        $datadir = $conf->get('config.DATADIR');
         // Check cache and data directories are readable and writeable
         foreach (array(
-            $globalConfig['CACHEDIR'],
-            $globalConfig['DATADIR'],
-            $globalConfig['PAGECACHE'],
-            $globalConfig['RAINTPL_TMP']
+            $conf->get('config.CACHEDIR'),
+            $datadir,
+            $conf->get('config.PAGECACHE'),
+            $conf->get('config.RAINTPL_TMP'),
         ) as $path) {
             if (! is_readable(realpath($path))) {
                 $errors[] = '"'.$path.'" directory is not readable';
@@ -169,11 +169,11 @@ class ApplicationUtils
 
         // Check configuration files are readable and writeable
         foreach (array(
-            $globalConfig['CONFIG_FILE'],
-            $globalConfig['DATASTORE'],
-            $globalConfig['IPBANS_FILENAME'],
-            $globalConfig['LOG_FILE'],
-            $globalConfig['UPDATECHECK_FILENAME']
+            $conf->getConfigFile(),
+            $conf->get('config.DATASTORE'),
+            $conf->get('config.IPBANS_FILENAME'),
+            $conf->get('config.LOG_FILE'),
+            $conf->get('config.UPDATECHECK_FILENAME'),
         ) as $path) {
             if (! is_file(realpath($path))) {
                 # the file may not exist yet
diff --git a/application/Config.php b/application/Config.php
deleted file mode 100644
index 05a59452..00000000
--- a/application/Config.php
+++ /dev/null
@@ -1,221 +0,0 @@
- $value) {
-        $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL;
-    }
-
-    if (isset($config['plugins'])) {
-        foreach ($config['plugins'] as $key => $value) {
-            $configStr .= '$GLOBALS[\'plugins\'][\''. $key .'\'] = '.var_export($config['plugins'][$key], true).';'. PHP_EOL;
-        }
-    }
-
-    if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr)
-        || strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0
-    ) {
-        throw new Exception(
-            'Shaarli could not create the config file.
-            Please make sure Shaarli has the right to write in the folder is it installed in.'
-        );
-    }
-}
-
-/**
- * Process plugin administration form data and save it in an array.
- *
- * @param array $formData Data sent by the plugin admin form.
- *
- * @return array New list of enabled plugin, ordered.
- *
- * @throws PluginConfigOrderException Plugins can't be sorted because their order is invalid.
- */
-function save_plugin_config($formData)
-{
-    // Make sure there are no duplicates in orders.
-    if (!validate_plugin_order($formData)) {
-        throw new PluginConfigOrderException();
-    }
-
-    $plugins = array();
-    $newEnabledPlugins = array();
-    foreach ($formData as $key => $data) {
-        if (startsWith($key, 'order')) {
-            continue;
-        }
-
-        // If there is no order, it means a disabled plugin has been enabled.
-        if (isset($formData['order_' . $key])) {
-            $plugins[(int) $formData['order_' . $key]] = $key;
-        }
-        else {
-            $newEnabledPlugins[] = $key;
-        }
-    }
-
-    // New enabled plugins will be added at the end of order.
-    $plugins = array_merge($plugins, $newEnabledPlugins);
-
-    // Sort plugins by order.
-    if (!ksort($plugins)) {
-        throw new PluginConfigOrderException();
-    }
-
-    $finalPlugins = array();
-    // Make plugins order continuous.
-    foreach ($plugins as $plugin) {
-        $finalPlugins[] = $plugin;
-    }
-
-    return $finalPlugins;
-}
-
-/**
- * Validate plugin array submitted.
- * Will fail if there is duplicate orders value.
- *
- * @param array $formData Data from submitted form.
- *
- * @return bool true if ok, false otherwise.
- */
-function validate_plugin_order($formData)
-{
-    $orders = array();
-    foreach ($formData as $key => $value) {
-        // No duplicate order allowed.
-        if (in_array($value, $orders)) {
-            return false;
-        }
-
-        if (startsWith($key, 'order')) {
-            $orders[] = $value;
-        }
-    }
-
-    return true;
-}
-
-/**
- * Affect plugin parameters values into plugins array.
- *
- * @param mixed $plugins Plugins array ($plugins[]['parameters']['param_name'] = .
- * @param mixed $config  Plugins configuration.
- *
- * @return mixed Updated $plugins array.
- */
-function load_plugin_parameter_values($plugins, $config)
-{
-    $out = $plugins;
-    foreach ($plugins as $name => $plugin) {
-        if (empty($plugin['parameters'])) {
-            continue;
-        }
-
-        foreach ($plugin['parameters'] as $key => $param) {
-            if (!empty($config[$key])) {
-                $out[$name]['parameters'][$key] = $config[$key];
-            }
-        }
-    }
-
-    return $out;
-}
-
-/**
- * Exception used if a mandatory field is missing in given configuration.
- */
-class MissingFieldConfigException extends Exception
-{
-    public $field;
-
-    /**
-     * Construct exception.
-     *
-     * @param string $field field name missing.
-     */
-    public function __construct($field)
-    {
-        $this->field = $field;
-        $this->message = 'Configuration value is required for '. $this->field;
-    }
-}
-
-/**
- * Exception used if an unauthorized attempt to edit configuration has been made.
- */
-class UnauthorizedConfigException extends Exception
-{
-    /**
-     * Construct exception.
-     */
-    public function __construct()
-    {
-        $this->message = 'You are not authorized to alter config.';
-    }
-}
-
-/**
- * Exception used if an error occur while saving plugin configuration.
- */
-class PluginConfigOrderException extends Exception
-{
-    /**
-     * Construct exception.
-     */
-    public function __construct()
-    {
-        $this->message = 'An error occurred while trying to save plugins loading order.';
-    }
-}
diff --git a/application/FileUtils.php b/application/FileUtils.php
index 6a12ef0e..6cac9825 100644
--- a/application/FileUtils.php
+++ b/application/FileUtils.php
@@ -9,11 +9,13 @@ class IOException extends Exception
     /**
      * Construct a new IOException
      *
-     * @param string $path path to the ressource that cannot be accessed
+     * @param string $path    path to the resource that cannot be accessed
+     * @param string $message Custom exception message.
      */
-    public function __construct($path)
+    public function __construct($path, $message = '')
     {
         $this->path = $path;
-        $this->message = 'Error accessing '.$this->path;
+        $this->message = empty($message) ? 'Error accessing' : $message;
+        $this->message .= PHP_EOL . $this->path;
     }
 }
diff --git a/application/PageBuilder.php b/application/PageBuilder.php
index 82580787..cf13c714 100644
--- a/application/PageBuilder.php
+++ b/application/PageBuilder.php
@@ -29,21 +29,22 @@ class PageBuilder
     private function initialize()
     {
         $this->tpl = new RainTPL();
+        $conf = ConfigManager::getInstance();
 
         try {
             $version = ApplicationUtils::checkUpdate(
                 shaarli_version,
-                $GLOBALS['config']['UPDATECHECK_FILENAME'],
-                $GLOBALS['config']['UPDATECHECK_INTERVAL'],
-                $GLOBALS['config']['ENABLE_UPDATECHECK'],
+                $conf->get('config.UPDATECHECK_FILENAME'),
+                $conf->get('config.UPDATECHECK_INTERVAL'),
+                $conf->get('config.ENABLE_UPDATECHECK'),
                 isLoggedIn(),
-                $GLOBALS['config']['UPDATECHECK_BRANCH']
+                $conf->get('config.UPDATECHECK_BRANCH')
             );
             $this->tpl->assign('newVersion', escape($version));
             $this->tpl->assign('versionError', '');
 
         } catch (Exception $exc) {
-            logm($GLOBALS['config']['LOG_FILE'], $_SERVER['REMOTE_ADDR'], $exc->getMessage());
+            logm($conf->get('config.LOG_FILE'), $_SERVER['REMOTE_ADDR'], $exc->getMessage());
             $this->tpl->assign('newVersion', '');
             $this->tpl->assign('versionError', escape($exc->getMessage()));
         }
@@ -62,16 +63,19 @@ class PageBuilder
         $this->tpl->assign('scripturl', index_url($_SERVER));
         $this->tpl->assign('pagetitle', 'Shaarli');
         $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links?
-        if (!empty($GLOBALS['title'])) {
-            $this->tpl->assign('pagetitle', $GLOBALS['title']);
+        if ($conf->exists('title')) {
+            $this->tpl->assign('pagetitle', $conf->get('title'));
         }
-        if (!empty($GLOBALS['titleLink'])) {
-            $this->tpl->assign('titleLink', $GLOBALS['titleLink']);
+        if ($conf->exists('titleLink')) {
+            $this->tpl->assign('titleLink', $conf->get('titleLink'));
         }
-        if (!empty($GLOBALS['pagetitle'])) {
-            $this->tpl->assign('pagetitle', $GLOBALS['pagetitle']);
+        if ($conf->exists('pagetitle')) {
+            $this->tpl->assign('pagetitle', $conf->get('pagetitle'));
         }
-        $this->tpl->assign('shaarlititle', empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title']);
+        $this->tpl->assign('shaarlititle', $conf->get('title', 'Shaarli'));
+        $this->tpl->assign('openshaarli', $conf->get('config.OPEN_SHAARLI', false));
+        $this->tpl->assign('showatom', $conf->get('config.SHOW_ATOM', false));
+        // FIXME! Globals
         if (!empty($GLOBALS['plugin_errors'])) {
             $this->tpl->assign('plugin_errors', $GLOBALS['plugin_errors']);
         }
diff --git a/application/Updater.php b/application/Updater.php
index 58c13c07..6b92af3d 100644
--- a/application/Updater.php
+++ b/application/Updater.php
@@ -12,11 +12,6 @@ class Updater
      */
     protected $doneUpdates;
 
-    /**
-     * @var array Shaarli's configuration array.
-     */
-    protected $config;
-
     /**
      * @var LinkDB instance.
      */
@@ -36,14 +31,12 @@ class Updater
      * Object constructor.
      *
      * @param array   $doneUpdates Updates which are already done.
-     * @param array   $config      Shaarli's configuration array.
      * @param LinkDB  $linkDB      LinkDB instance.
      * @param boolean $isLoggedIn  True if the user is logged in.
      */
-    public function __construct($doneUpdates, $config, $linkDB, $isLoggedIn)
+    public function __construct($doneUpdates, $linkDB, $isLoggedIn)
     {
         $this->doneUpdates = $doneUpdates;
-        $this->config = $config;
         $this->linkDB = $linkDB;
         $this->isLoggedIn = $isLoggedIn;
 
@@ -114,19 +107,21 @@ class Updater
      */
     public function updateMethodMergeDeprecatedConfigFile()
     {
-        $config_file = $this->config['config']['CONFIG_FILE'];
+        $conf = ConfigManager::getInstance();
 
-        if (is_file($this->config['config']['DATADIR'].'/options.php')) {
-            include $this->config['config']['DATADIR'].'/options.php';
+        if (is_file($conf->get('config.DATADIR') . '/options.php')) {
+            include $conf->get('config.DATADIR') . '/options.php';
 
             // Load GLOBALS into config
+            $allowedKeys = array_merge(ConfigPhp::$ROOT_KEYS);
+            $allowedKeys[] = 'config';
             foreach ($GLOBALS as $key => $value) {
-                $this->config[$key] = $value;
+                if (in_array($key, $allowedKeys)) {
+                    $conf->set($key, $value);
+                }
             }
-            $this->config['config']['CONFIG_FILE'] = $config_file;
-            writeConfig($this->config, $this->isLoggedIn);
-
-            unlink($this->config['config']['DATADIR'].'/options.php');
+            $conf->write($this->isLoggedIn);
+            unlink($conf->get('config.DATADIR').'/options.php');
         }
 
         return true;
@@ -137,13 +132,14 @@ class Updater
      */
     public function updateMethodRenameDashTags()
     {
+        $conf = ConfigManager::getInstance();
         $linklist = $this->linkDB->filterSearch();
         foreach ($linklist as $link) {
             $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
             $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
             $this->linkDB[$link['linkdate']] = $link;
         }
-        $this->linkDB->savedb($this->config['config']['PAGECACHE']);
+        $this->linkDB->savedb($conf->get('config.PAGECACHE'));
         return true;
     }
 }
diff --git a/application/Utils.php b/application/Utils.php
index da521cce..9a8ca6d1 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -273,4 +273,4 @@ function autoLocale($headerLocale)
         }
     }
     setlocale(LC_ALL, $attempts);
-}
\ No newline at end of file
+}
diff --git a/application/config/ConfigIO.php b/application/config/ConfigIO.php
index 2b68fe6a..4b1c9901 100644
--- a/application/config/ConfigIO.php
+++ b/application/config/ConfigIO.php
@@ -21,6 +21,8 @@ interface ConfigIO
      *
      * @param string $filepath Config file absolute path.
      * @param array  $conf   All configuration in an array.
+     *
+     * @return bool True if the configuration has been successfully written, false otherwise.
      */
     function write($filepath, $conf);
 
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
index dfe9eeb9..212aac05 100644
--- a/application/config/ConfigManager.php
+++ b/application/config/ConfigManager.php
@@ -62,16 +62,25 @@ class ConfigManager
         return self::$instance;
     }
 
+    /**
+     * Reset the ConfigManager instance.
+     */
+    public static function reset()
+    {
+        self::$instance = null;
+        return self::getInstance();
+    }
+
     /**
      * Rebuild the loaded config array from config files.
      */
     public function reload()
     {
-        $this->initialize();
+        $this->load();
     }
 
     /**
-     * Initialize loaded conf in ConfigManager.
+     * Initialize the ConfigIO and loaded the conf.
      */
     protected function initialize()
     {
@@ -81,7 +90,15 @@ class ConfigManager
             $this->configIO = new ConfigPhp();
         }*/
         $this->configIO = new ConfigPhp();
-        $this->loadedConfig = $this->configIO->read(self::$CONFIG_FILE);
+        $this->load();
+    }
+
+    /**
+     * Load configuration in the ConfigurationManager.
+     */
+    protected function load()
+    {
+        $this->loadedConfig = $this->configIO->read($this->getConfigFile());
         $this->setDefaultValues();
     }
 
@@ -117,9 +134,15 @@ class ConfigManager
      * @param string $value      Value to set.
      * @param bool   $write      Write the new setting in the config file, default false.
      * @param bool   $isLoggedIn User login state, default false.
+     *
+     * @throws Exception Invalid
      */
     public function set($setting, $value, $write = false, $isLoggedIn = false)
     {
+        if (empty($setting) || ! is_string($setting)) {
+            throw new Exception('Invalid setting key parameter. String expected, got: '. gettype($setting));
+        }
+
         $settings = explode('.', $setting);
         self::setConfig($settings, $value, $this->loadedConfig);
         if ($write) {
@@ -151,6 +174,8 @@ class ConfigManager
      *
      * @param bool $isLoggedIn User login state.
      *
+     * @return bool True if the configuration has been successfully written, false otherwise.
+     *
      * @throws MissingFieldConfigException: a mandatory field has not been provided in $conf.
      * @throws UnauthorizedConfigException: user is not authorize to change configuration.
      * @throws IOException: an error occurred while writing the new config file.
@@ -175,7 +200,7 @@ class ConfigManager
             }
         }
 
-        $this->configIO->write(self::$CONFIG_FILE, $this->loadedConfig);
+        return $this->configIO->write($this->getConfigFile(), $this->loadedConfig);
     }
 
     /**
@@ -327,6 +352,22 @@ class ConfigManager
             $this->set($key, $value);
         }
     }
+
+    /**
+     * @return ConfigIO
+     */
+    public function getConfigIO()
+    {
+        return $this->configIO;
+    }
+
+    /**
+     * @param ConfigIO $configIO
+     */
+    public function setConfigIO($configIO)
+    {
+        $this->configIO = $configIO;
+    }
 }
 
 /**
diff --git a/application/config/ConfigPhp.php b/application/config/ConfigPhp.php
index 311aeb81..19fecf2b 100644
--- a/application/config/ConfigPhp.php
+++ b/application/config/ConfigPhp.php
@@ -28,7 +28,6 @@ class ConfigPhp implements ConfigIO
      */
     function read($filepath)
     {
-        $filepath .= $this->getExtension();
         if (! file_exists($filepath) || ! is_readable($filepath)) {
             return array();
         }
@@ -49,8 +48,6 @@ class ConfigPhp implements ConfigIO
      */
     function write($filepath, $conf)
     {
-        $filepath .= $this->getExtension();
-
         $configStr = ' /shaarli/
-define('WEB_PATH', substr($_SERVER["REQUEST_URI"], 0, 1+strrpos($_SERVER["REQUEST_URI"], '/', 0)));
+define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0)));
 
 // High execution time in case of problematic imports/exports.
 ini_set('max_input_time','60');
@@ -144,12 +43,6 @@ error_reporting(E_ALL^E_WARNING);
 // See all errors (for debugging only)
 //error_reporting(-1);
 
-/*
- * User configuration
- */
-if (is_file($GLOBALS['config']['CONFIG_FILE'])) {
-    require_once $GLOBALS['config']['CONFIG_FILE'];
-}
 
 // Shaarli library
 require_once 'application/ApplicationUtils.php';
@@ -166,10 +59,12 @@ require_once 'application/PageBuilder.php';
 require_once 'application/TimeZone.php';
 require_once 'application/Url.php';
 require_once 'application/Utils.php';
-require_once 'application/Config.php';
+require_once 'application/config/ConfigManager.php';
+require_once 'application/config/ConfigPlugin.php';
 require_once 'application/PluginManager.php';
 require_once 'application/Router.php';
 require_once 'application/Updater.php';
+require_once 'inc/rain.tpl.class.php';
 
 // Ensure the PHP version is supported
 try {
@@ -210,16 +105,16 @@ if (isset($_COOKIE['shaarli']) && !is_session_id_valid($_COOKIE['shaarli'])) {
     $_COOKIE['shaarli'] = session_id();
 }
 
-include "inc/rain.tpl.class.php"; //include Rain TPL
-raintpl::$tpl_dir = $GLOBALS['config']['RAINTPL_TPL']; // template directory
-raintpl::$cache_dir = $GLOBALS['config']['RAINTPL_TMP']; // cache directory
+$conf = ConfigManager::getInstance();
+
+RainTPL::$tpl_dir = $conf->get('config.RAINTPL_TPL'); // template directory
+RainTPL::$cache_dir = $conf->get('config.RAINTPL_TMP'); // cache directory
 
 $pluginManager = PluginManager::getInstance();
-$pluginManager->load($GLOBALS['config']['ENABLED_PLUGINS']);
+$pluginManager->load($conf->get('config.ENABLED_PLUGINS'));
 
 ob_start();  // Output buffering for the page cache.
 
-
 // In case stupid admin has left magic_quotes enabled in php.ini:
 if (get_magic_quotes_gpc())
 {
@@ -236,17 +131,25 @@ header("Cache-Control: post-check=0, pre-check=0", false);
 header("Pragma: no-cache");
 
 // Handling of old config file which do not have the new parameters.
-if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.escape(index_url($_SERVER));
-if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get();
-if (empty($GLOBALS['redirector'])) $GLOBALS['redirector']='';
-if (empty($GLOBALS['disablesessionprotection'])) $GLOBALS['disablesessionprotection']=false;
-if (empty($GLOBALS['privateLinkByDefault'])) $GLOBALS['privateLinkByDefault']=false;
-if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?';
-// I really need to rewrite Shaarli with a proper configuation manager.
-
-if (! is_file($GLOBALS['config']['CONFIG_FILE'])) {
+if (! $conf->exists('title')) {
+    $conf->set('title', 'Shared links on '. escape(index_url($_SERVER)));
+}
+if (! $conf->exists('timezone')) {
+    $conf->set('timezone', date_default_timezone_get());
+}
+if (! $conf->exists('disablesessionprotection')) {
+    $conf->set('disablesessionprotection', false);
+}
+if (! $conf->exists('privateLinkByDefault')) {
+    $conf->set('privateLinkByDefault', false);
+}
+if (! $conf->exists('titleLink')) {
+    $conf->set('titleLink', '?');
+}
+
+if (! is_file($conf->getConfigFile())) {
     // Ensure Shaarli has proper access to its resources
-    $errors = ApplicationUtils::checkResourcePermissions($GLOBALS['config']);
+    $errors = ApplicationUtils::checkResourcePermissions();
 
     if ($errors != array()) {
         $message = '

Insufficient permissions:

Themes

See Theming for the list of community-contributed themes, and an installation guide.

diff --git a/doc/Community-&-Related-software.md b/doc/Community-&-Related-software.md index 03a3dea9..3945d005 100644 --- a/doc/Community-&-Related-software.md +++ b/doc/Community-&-Related-software.md @@ -21,8 +21,9 @@ _TODO: contact repos owners to see if they'd like to standardize their work with * [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.[](.html) * [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.[](.html) * [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.[](.html) - * [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie/emojione): Add colorful emojis to your Shaarli.[](.html) + * [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.[](.html) * [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.[](.html) + * [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.[](.html) ### Themes diff --git a/doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html b/doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html index edb1555f..9efb1ad6 100644 --- a/doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html +++ b/doc/Copy-an-existing-installation-over-SSH-and-serve-it-locally.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf @@ -111,55 +109,55 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf #Usage: ./local-shaarli.sh #Author: nodiscc (nodiscc@gmail.com) #License: MIT (http://opensource.org/licenses/MIT) -set -o errexit -set -o nounset +set -o errexit +set -o nounset ##### CONFIG ################# #The port used by php's local server -php_local_port=7431 +php_local_port=7431 #Name of the SSH server and path where Shaarli is installed #TODO: pass these as command-line arguments -remotehost="my.ssh.server" -remote_shaarli_dir="/var/www/shaarli" +remotehost="my.ssh.server" +remote_shaarli_dir="/var/www/shaarli" ###### FUNCTIONS ############# _main() { - _CBSyncShaarli - _CBServeShaarli + _CBSyncShaarli + _CBServeShaarli } _CBSyncShaarli() { - remote_temp_dir=$(ssh $remotehost mktemp -d) - remote_ssh_user=$(ssh $remotehost whoami) - ssh -t "$remotehost" sudo cp -r "$remote_shaarli_dir" "$remote_temp_dir" - ssh -t "$remotehost" sudo chown -R "$remote_ssh_user":"$remote_ssh_user" "$remote_temp_dir" - scp -rq "$remotehost":"$remote_temp_dir" local-shaarli - ssh "$remotehost" rm -r "$remote_temp_dir" + remote_temp_dir=$(ssh $remotehost mktemp -d) + remote_ssh_user=$(ssh $remotehost whoami) + ssh -t "$remotehost" sudo cp -r "$remote_shaarli_dir" "$remote_temp_dir" + ssh -t "$remotehost" sudo chown -R "$remote_ssh_user":"$remote_ssh_user" "$remote_temp_dir" + scp -rq "$remotehost":"$remote_temp_dir" local-shaarli + ssh "$remotehost" rm -r "$remote_temp_dir" } _CBServeShaarli() { #TODO: allow serving a previously downloaded Shaarli #TODO: ask before overwriting local copy, if it exists - cd local-shaarli/ - php -S localhost:${php_local_port} - echo "Please go to http://localhost:${php_local_port}" + cd local-shaarli/ + php -S localhost:${php_local_port} + echo "Please go to http://localhost:${php_local_port}" } ##### MAIN ################# -_main
+_main

This outputs:

-
$ ./local-shaarli.sh
-PHP 5.6.0RC4 Development Server started at Mon Sep  1 21:56:19 2014
-Listening on http://localhost:7431
-Document root is /home/user/local-shaarli/shaarli
-Press Ctrl-C to quit.
+
$ ./local-shaarli.sh
+PHP 5.6.0RC4 Development Server started at Mon Sep  1 21:56:19 2014
+Listening on http://localhost:7431
+Document root is /home/user/local-shaarli/shaarli
+Press Ctrl-C to quit.
 
-[Mon Sep  1 21:56:27 2014] ::1:57868 [200]: /[](.html)
-[Mon Sep  1 21:56:27 2014] ::1:57869 [200]: /index.html[](.html)
-[Mon Sep  1 21:56:37 2014] ::1:57881 [200]: /...[](.html)
+[Mon Sep 1 21:56:27 2014] ::1:57868 [200]: /[](.html) +[Mon Sep 1 21:56:27 2014] ::1:57869 [200]: /index.html[](.html) +[Mon Sep 1 21:56:37 2014] ::1:57881 [200]: /...[](.html)
diff --git a/doc/Create-and-serve-multiple-Shaarlis-(farm).html b/doc/Create-and-serve-multiple-Shaarlis-(farm).html index 933144e4..672e4bf3 100644 --- a/doc/Create-and-serve-multiple-Shaarlis-(farm).html +++ b/doc/Create-and-serve-multiple-Shaarlis-(farm).html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf

Create and serve multiple Shaarlis (farm)

Example bash script (creates multiple shaarli instances and generates an HTML index of them)

#!/bin/bash
-set -o errexit
-set -o nounset
+set -o errexit
+set -o nounset
 
 #config
-shaarli_base_dir='/var/www/shaarli'
-accounts='bob john whatever username'
-shaarli_repo_url='https://github.com/shaarli/Shaarli'
-ref="master"
+shaarli_base_dir='/var/www/shaarli'
+accounts='bob john whatever username'
+shaarli_repo_url='https://github.com/shaarli/Shaarli'
+ref="master"
 
 #clone multiple shaarli instances
-if [ ! -d "$shaarli_base_dir" ]; then mkdir "$shaarli_base_dir"; fi[](.html)
+if [ ! -d "$shaarli_base_dir" ]; then mkdir "$shaarli_base_dir"; fi[](.html)
    
-for account in $accounts; do
-    if [ -d "$shaarli_base_dir/$account" ];[](.html)
-    then echo "[info] account $account already exists, skipping";[](.html)
-    else echo "[info] creating new account $account ..."; git clone --quiet "$shaarli_repo_url" -b "$ref" "$shaarli_base_dir/$account"; fi[](.html)
+for account in $accounts; do
+    if [ -d "$shaarli_base_dir/$account" ];[](.html)
+    then echo "[info] account $account already exists, skipping";[](.html)
+    else echo "[info] creating new account $account ..."; git clone --quiet "$shaarli_repo_url" -b "$ref" "$shaarli_base_dir/$account"; fi[](.html)
 done
 
 #generate html index of shaarlis
-htmlhead='<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+htmlhead='<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
 <!-- Minimal html template thanks to http://www.sitepoint.com/a-minimal-html-document/ -->
 <html lang="en">
     <head>
@@ -136,9 +134,9 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
     <h1>My Shaarli farm</h1>
     <ul style="list-style-type: none;">'
 
-accountlinks=''
+accountlinks=''
     
-htmlfooter='
+htmlfooter='
     </ul>
     </div>
     </body>
@@ -146,14 +144,14 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
     
 
 
-for account in $accounts; do accountlinks="$accountlinks\n<li><a href=\"$account\">$account</a></li>"; done
-if [ -d "$shaarli_base_dir/index.html" ]; then echo "[removing old index.html]"; rm "$shaarli_base_dir/index.html" ]; fi[](.html)
-echo "[info] generating new index of shaarlis"[](.html)
-echo -e "$htmlhead $accountlinks $htmlfooter" > "$shaarli_base_dir/index.html"
-echo '[info] done.'[](.html)
-echo "[info] list of accounts: $accounts"[](.html)
-echo "[info] contents of $shaarli_base_dir:"[](.html)
-tree -a -L 1 "$shaarli_base_dir"
+for account in $accounts; do accountlinks="$accountlinks\n<li><a href=\"$account\">$account</a></li>"; done +if [ -d "$shaarli_base_dir/index.html" ]; then echo "[removing old index.html]"; rm "$shaarli_base_dir/index.html" ]; fi[](.html) +echo "[info] generating new index of shaarlis"[](.html) +echo -e "$htmlhead $accountlinks $htmlfooter" > "$shaarli_base_dir/index.html" +echo '[info] done.'[](.html) +echo "[info] list of accounts: $accounts"[](.html) +echo "[info] contents of $shaarli_base_dir:"[](.html) +tree -a -L 1 "$shaarli_base_dir"

This script just serves as an example. More precise or complex (applying custom configuration, etc) automation is possible using configuration management software like Ansible

diff --git a/doc/Datastore-hacks.html b/doc/Datastore-hacks.html index 88639402..15da09d4 100644 --- a/doc/Datastore-hacks.html +++ b/doc/Datastore-hacks.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf diff --git a/doc/Development.html b/doc/Development.html index 2eacff94..c5776413 100644 --- a/doc/Development.html +++ b/doc/Development.html @@ -15,13 +15,13 @@ diff --git a/doc/Directory-structure.html b/doc/Directory-structure.html index 003d4d94..404ff7c8 100644 --- a/doc/Directory-structure.html +++ b/doc/Directory-structure.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf

Directory structure

Here is the directory structure of Shaarli and the purpose of the different files:

-
    index.php        # Main program
-    application/     # Shaarli classes
-        ├── LinkDB.php
-        └── Utils.php
-    tests/       # Shaarli unitary & functional tests
-        ├── LinkDBTest.php
-        ├── utils  # utilities to ease testing
-        │   └── ReferenceLinkDB.php
-        └── UtilsTest.php
-    COPYING          # Shaarli license
-    inc/             # static assets and 3rd party libraries
-        ├── awesomplete.*          # tags autocompletion library
-        ├── blazy.*                # picture wall lazy image loading library
-        ├── shaarli.css, reset.css # Shaarli stylesheet.
-        ├── qr.*                   # qr code generation library
-        └──rain.tpl.class.php      # RainTPL templating library
-    tpl/             # RainTPL templates for Shaarli. They are used to build the pages.
-    images/          # Images and icons used in Shaarli
-    data/            # data storage: bookmark database, configuration, logs, banlist…
-        ├── config.php             # Shaarli configuration (login, password, timezone, title…)
-        ├── datastore.php          # Your link database (compressed).
-        ├── ipban.php              # IP address ban system data
-        ├── lastupdatecheck.txt    # Update check timestamp file
-        └──log.txt                 # login/IPban log.
-    cache/           # thumbnails cache
+
    index.php        # Main program
+    application/     # Shaarli classes
+        ├── LinkDB.php
+        └── Utils.php
+    tests/       # Shaarli unitary & functional tests
+        ├── LinkDBTest.php
+        ├── utils  # utilities to ease testing
+        │   └── ReferenceLinkDB.php
+        └── UtilsTest.php
+    COPYING          # Shaarli license
+    inc/             # static assets and 3rd party libraries
+        ├── awesomplete.*          # tags autocompletion library
+        ├── blazy.*                # picture wall lazy image loading library
+        ├── shaarli.css, reset.css # Shaarli stylesheet.
+        ├── qr.*                   # qr code generation library
+        └──rain.tpl.class.php      # RainTPL templating library
+    tpl/             # RainTPL templates for Shaarli. They are used to build the pages.
+    images/          # Images and icons used in Shaarli
+    data/            # data storage: bookmark database, configuration, logs, banlist…
+        ├── config.php             # Shaarli configuration (login, password, timezone, title…)
+        ├── datastore.php          # Your link database (compressed).
+        ├── ipban.php              # IP address ban system data
+        ├── lastupdatecheck.txt    # Update check timestamp file
+        └──log.txt                 # login/IPban log.
+    cache/           # thumbnails cache
                      # This directory is automatically created. You can erase it anytime you want.
-    tmp/             # Temporary directory for compiled RainTPL templates.
+    tmp/             # Temporary directory for compiled RainTPL templates.
                      # This directory is automatically created. You can erase it anytime you want.
diff --git a/doc/Docker.html b/doc/Docker.html index a443d100..e89c90fb 100644 --- a/doc/Docker.html +++ b/doc/Docker.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf @@ -112,18 +110,18 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf

Install Docker, by following the instructions relevant
to your OS / distribution, and start the service.

Search an image on DockerHub

-
$ docker search debian
+
$ docker search debian
 
-NAME            DESCRIPTION                                     STARS   OFFICIAL   AUTOMATED
-ubuntu          Ubuntu is a Debian-based Linux operating s...   2065    [OK][](.html)
-debian          Debian is a Linux distribution that's comp...   603     [OK][](.html)
+NAME            DESCRIPTION                                     STARS   OFFICIAL   AUTOMATED
+ubuntu          Ubuntu is a Debian-based Linux operating s...   2065    [OK][](.html)
+debian          Debian is a Linux distribution that's comp...   603     [OK][](.html)
 google/debian                                                   47                 [OK][](.html)

Show available tags for a repository

-
$ curl https://index.docker.io/v1/repositories/debian/tags | python -m json.tool
+
$ curl https://index.docker.io/v1/repositories/debian/tags | python -m json.tool
 
-% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
-Dload  Upload   Total   Spent    Left  Speed
-100  1283    0  1283    0     0    433      0 --:--:--  0:00:02 --:--:--   433
+% Total % Received % Xferd Average Speed Time Time Time Current +Dload Upload Total Spent Left Speed +100 1283 0 1283 0 0 433 0 --:--:-- 0:00:02 --:--:-- 433

Sample output:

[[](.html)
     {
@@ -148,14 +146,14 @@ to your OS / distribution, and start the service.

} ]

Pull an image from DockerHub

-
$ docker pull repository[:tag][](.html)
+
$ docker pull repository[:tag][](.html)
 
-$ docker pull debian:wheezy
-wheezy: Pulling from debian
-4c8cbfd2973e: Pull complete
-60c52dbe9d91: Pull complete
-Digest: sha256:c584131da2ac1948aa3e66468a4424b6aea2f33acba7cec0b631bdb56254c4fe
-Status: Downloaded newer image for debian:wheezy
+$ docker pull debian:wheezy +wheezy: Pulling from debian +4c8cbfd2973e: Pull complete +60c52dbe9d91: Pull complete +Digest: sha256:c584131da2ac1948aa3e66468a4424b6aea2f33acba7cec0b631bdb56254c4fe +Status: Downloaded newer image for debian:wheezy

Get and run a Shaarli image

DockerHub repository

The images can be found in the shaarli/shaarli
@@ -173,53 +171,53 @@ repository.

  • Nginx
  • Download from DockerHub

    -
    $ docker pull shaarli/shaarli
    -latest: Pulling from shaarli/shaarli
    -32716d9fcddb: Pull complete
    -84899d045435: Pull complete
    -4b6ad7444763: Pull complete
    -e0345ef7a3e0: Pull complete
    -5c1dd344094f: Pull complete
    -6422305a200b: Pull complete
    -7d63f861dbef: Pull complete
    -3eb97210645c: Pull complete
    -869319d746ff: Already exists
    -869319d746ff: Pulling fs layer
    -902b87aaaec9: Already exists
    -Digest: sha256:f836b4627b958b3f83f59c332f22f02fcd495ace3056f2be2c4912bd8704cc98
    -Status: Downloaded newer image for shaarli/shaarli:latest
    +
    $ docker pull shaarli/shaarli
    +latest: Pulling from shaarli/shaarli
    +32716d9fcddb: Pull complete
    +84899d045435: Pull complete
    +4b6ad7444763: Pull complete
    +e0345ef7a3e0: Pull complete
    +5c1dd344094f: Pull complete
    +6422305a200b: Pull complete
    +7d63f861dbef: Pull complete
    +3eb97210645c: Pull complete
    +869319d746ff: Already exists
    +869319d746ff: Pulling fs layer
    +902b87aaaec9: Already exists
    +Digest: sha256:f836b4627b958b3f83f59c332f22f02fcd495ace3056f2be2c4912bd8704cc98
    +Status: Downloaded newer image for shaarli/shaarli:latest

    Create and start a new container from the image

    # map the host's :8000 port to the container's :80 port
    -$ docker create -p 8000:80 shaarli/shaarli
    -d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101
    +$ docker create -p 8000:80 shaarli/shaarli
    +d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101
     
     # launch the container in the background
    -$ docker start d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101
    -d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101
    +$ docker start d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101
    +d40b7af693d678958adedfb88f87d6ea0237186c23de5c4102a55a8fcb499101
     
     # list active containers
    -$ docker ps
    -CONTAINER ID  IMAGE            COMMAND               CREATED         STATUS        PORTS                 NAMES
    -d40b7af693d6  shaarli/shaarli  /usr/bin/supervisor  15 seconds ago  Up 4 seconds  0.0.0.0:8000->80/tcp  backstabbing_galileo
    +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +d40b7af693d6 shaarli/shaarli /usr/bin/supervisor 15 seconds ago Up 4 seconds 0.0.0.0:8000->80/tcp backstabbing_galileo

    Stop and destroy a container

    -
    $ docker stop backstabbing_galileo  # those docker guys are really rude to physicists!
    -backstabbing_galileo
    +
    $ docker stop backstabbing_galileo  # those docker guys are really rude to physicists!
    +backstabbing_galileo
     
     # check the container is stopped
    -$ docker ps
    -CONTAINER ID  IMAGE            COMMAND               CREATED         STATUS        PORTS                 NAMES
    +$ docker ps
    +CONTAINER ID  IMAGE            COMMAND               CREATED         STATUS        PORTS                 NAMES
     
     # list ALL containers
    -$ docker ps -a
    -CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                      PORTS               NAMES
    -d40b7af693d6        shaarli/shaarli     /usr/bin/supervisor   5 minutes ago       Exited (0) 48 seconds ago                       backstabbing_galileo
    +$ docker ps -a
    +CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                      PORTS               NAMES
    +d40b7af693d6        shaarli/shaarli     /usr/bin/supervisor   5 minutes ago       Exited (0) 48 seconds ago                       backstabbing_galileo
     
     # destroy the container
    -$ docker rm backstabbing_galileo  # let's put an end to these barbarian practices
    -backstabbing_galileo
    +$ docker rm backstabbing_galileo  # let's put an end to these barbarian practices
    +backstabbing_galileo
     
    -$ docker ps -a
    -CONTAINER ID  IMAGE            COMMAND               CREATED         STATUS        PORTS                 NAMES
    +$ docker ps -a +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

    Resources

    Docker

      diff --git a/doc/Download-CSS-styles-from-an-OPML-list.html b/doc/Download-CSS-styles-from-an-OPML-list.html index 22771502..a4f68ac6 100644 --- a/doc/Download-CSS-styles-from-an-OPML-list.html +++ b/doc/Download-CSS-styles-from-an-OPML-list.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf @@ -209,8 +207,8 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf /** * Reading directory list, courtesy of http://www.laughing-buddha.net/php/dirlist/ - * @param directory the directory we want to list files of - * @return a simple array containing the list of absolute file paths. Notice that current file (".") and parent one("..") + * @param directory the directory we want to list files of + * @return a simple array containing the list of absolute file paths. Notice that current file (".") and parent one("..") * are not listed here */ function getDirectoryList ($directory) { diff --git a/doc/Download-and-Installation.html b/doc/Download-and-Installation.html new file mode 100644 index 00000000..17c7b69e --- /dev/null +++ b/doc/Download-and-Installation.html @@ -0,0 +1,165 @@ + + + + + + + Shaarli – Download and Installation + + + + + + + +

      Download and Installation

      +

      Get Shaarli!

      +

      To install Shaarli, simply place the files in a directory under your webserver's Document Root (or directly at the document root). Make sure your server is properly configured.

      +

      Several releases are available:

      +
      + +

      Get the latest released version from the releases page.

      +

      The current latest released version is v0.7.0.

      +

      Download as an archive

      +

      As a .zip archive:

      +
      $ wget https://github.com/shaarli/Shaarli/archive/v0.7.0.zip
      +$ unzip Shaarli-v0.7.0.zip
      +$ mv Shaarli-v0.7.0 /path/to/shaarli/
      + ++++ + + + + + + + + +
      !In most cases, download Shaarli from the releases page. Cloning using git or downloading Github branches as zip files requires additional steps (see below).
      +
      +

      Stable version

      +

      The stable version has been experienced by Shaarli users, and will receive security updates.

      +

      Download as an archive

      +

      As a .zip archive:

      +
      $ wget https://github.com/shaarli/Shaarli/archive/stable.zip
      +$ unzip stable.zip
      +$ mv Shaarli-stable /path/to/shaarli/
      +

      As a .tar.gz archive :

      +
      $ wget https://github.com/shaarli/Shaarli/archive/stable.tar.gz
      +$ tar xvf stable.tar.gz
      +$ mv Shaarli-stable /path/to/shaarli/
      +

      Clone with Git

      +

      Composer is required to build a functional Shaarli installation when pulling from git.

      +
      $ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/
      +# install/update third-party dependencies
      +$ cd /path/to/shaarli/
      +$ composer update --no-dev
      +
      +

      Development version (mainline)

      +

      Use at your own risk!

      +

      To get the latest changes from the master branch:

      +
      # clone the repository  
      +$ git clone https://github.com/shaarli/Shaarli.git master /path/to/shaarli/
      +# install/update third-party dependencies
      +$ cd /path/to/shaarli
      +$ composer update --no-dev
      +
      +

      Finish Installation

      +

      Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser.

      +

      install screenshot

      +

      Setup your Shaarli installation, and it's ready to use!

      +
      +

      Updating Shaarli

      +

      See Upgrade and Migration

      + + diff --git a/doc/Download-and-Installation.md b/doc/Download-and-Installation.md new file mode 100644 index 00000000..77af25eb --- /dev/null +++ b/doc/Download-and-Installation.md @@ -0,0 +1,97 @@ +#Download and Installation +# Get Shaarli! + +To install Shaarli, simply place the files in a directory under your webserver's Document Root (or directly at the document root). Make sure your [server](Server-requirements) is properly [configured](Server-configuration).[](.html) + +Several releases are available: + +-------------------------------------------------------- + +## Latest release (recommended) + +Get the latest released version from the [releases](https://github.com/shaarli/Shaarli/releases) page.[](.html) + +The current latest released version is `v0.7.0`. + +### Download as an archive + +As a .zip archive: + +```bash +$ wget https://github.com/shaarli/Shaarli/archive/v0.7.0.zip +$ unzip Shaarli-v0.7.0.zip +$ mv Shaarli-v0.7.0 /path/to/shaarli/ +``` + + +| ! |In most cases, download Shaarli from the [releases](https://github.com/shaarli/Shaarli/releases) page. Cloning using `git` or downloading Github branches as zip files requires additional steps (see below).|[](.html) +|-----|--------------------------| + + + +-------------------------------------------------------- + +## Stable version + +The stable version has been experienced by Shaarli users, and will receive security updates. + +### Download as an archive + +As a .zip archive: + +```bash +$ wget https://github.com/shaarli/Shaarli/archive/stable.zip +$ unzip stable.zip +$ mv Shaarli-stable /path/to/shaarli/ +``` + +As a .tar.gz archive : + +```bash +$ wget https://github.com/shaarli/Shaarli/archive/stable.tar.gz +$ tar xvf stable.tar.gz +$ mv Shaarli-stable /path/to/shaarli/ +``` + +### Clone with Git + +[Composer](https://getcomposer.org/) is required to build a functional Shaarli installation when pulling from git.[](.html) + +```bash +$ git clone https://github.com/shaarli/Shaarli.git -b stable /path/to/shaarli/ +# install/update third-party dependencies +$ cd /path/to/shaarli/ +$ composer update --no-dev +``` + +-------------------------------------------------------- + +## Development version (mainline) + +_Use at your own risk!_ + +To get the latest changes from the `master` branch: + +```bash +# clone the repository +$ git clone https://github.com/shaarli/Shaarli.git master /path/to/shaarli/ +# install/update third-party dependencies +$ cd /path/to/shaarli +$ composer update --no-dev +``` + +-------------------------------------------------------- + +## Finish Installation + +Once Shaarli is downloaded and files have been placed at the correct location, open it this location your favorite browser. + +![install screenshot](http://i.imgur.com/wuMpDSN.png)[](.html) + +Setup your Shaarli installation, and it's ready to use! + +-------------------------------------------------------- + +## Updating Shaarli + +See [Upgrade and Migration](Upgrade-and-migration)[](.html) diff --git a/doc/Download.html b/doc/Download.html deleted file mode 100644 index 9f9f5117..00000000 --- a/doc/Download.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - Shaarli – Download - - - - - - - -

      Download

      -

      Get Shaarli!

      -

      Latest stable revision

      -

      This revision has been released and tested.

      - -
      $ git clone https://github.com/shaarli/Shaarli.git -b stable shaarli
      -

      Download as an archive

      -
      $ wget https://github.com/shaarli/Shaarli/archive/stable.zip
      -$ unzip stable.zip
      -$ mv Shaarli-stable shaarli
      -

      Tarballs are also available:

      -
      $ wget https://github.com/shaarli/Shaarli/archive/stable.tar.gz
      -$ tar xvf stable.tar.gz
      -$ mv Shaarli-stable shaarli
      -

      Development (mainline)

      -

      Use at your own risk!

      -

      To get the latest changes:

      -
      $ git clone https://github.com/shaarli/Shaarli.git shaarli
      - - diff --git a/doc/Download.md b/doc/Download.md deleted file mode 100644 index 7930f541..00000000 --- a/doc/Download.md +++ /dev/null @@ -1,31 +0,0 @@ -#Download -## Get Shaarli! -### Latest stable revision -This revision has been [released](https://github.com/shaarli/Shaarli/releases) and tested.[](.html) - -#### Clone with Git (recommended) -```bash -$ git clone https://github.com/shaarli/Shaarli.git -b stable shaarli -``` - -#### Download as an archive -```bash -$ wget https://github.com/shaarli/Shaarli/archive/stable.zip -$ unzip stable.zip -$ mv Shaarli-stable shaarli -``` - -Tarballs are also available: -```bash -$ wget https://github.com/shaarli/Shaarli/archive/stable.tar.gz -$ tar xvf stable.tar.gz -$ mv Shaarli-stable shaarli -``` - -### Development (mainline) -_Use at your own risk!_ - -To get the latest changes: -```bash -$ git clone https://github.com/shaarli/Shaarli.git shaarli -``` diff --git a/doc/Example-patch---add-new-via-field-for-links.html b/doc/Example-patch---add-new-via-field-for-links.html index 7db43107..133224e2 100644 --- a/doc/Example-patch---add-new-via-field-for-links.html +++ b/doc/Example-patch---add-new-via-field-for-links.html @@ -15,13 +15,13 @@ diff --git a/doc/FAQ.html b/doc/FAQ.html index 3b6b956d..61f3475f 100644 --- a/doc/FAQ.html +++ b/doc/FAQ.html @@ -15,13 +15,13 @@ diff --git a/doc/Firefox-share.html b/doc/Firefox-share.html index add6d4e8..d7dcc282 100644 --- a/doc/Firefox-share.html +++ b/doc/Firefox-share.html @@ -15,13 +15,13 @@ diff --git a/doc/GnuPG-signature.html b/doc/GnuPG-signature.html index c431f9ad..50b904d5 100644 --- a/doc/GnuPG-signature.html +++ b/doc/GnuPG-signature.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf @@ -129,26 +127,26 @@ Keys
    • Generating a GPG key (GitHub)

    gpg - provide identity information

    -
    $ gpg --gen-key
    +
    $ gpg --gen-key
     
    -gpg (GnuPG) 2.1.6; Copyright (C) 2015 Free Software Foundation, Inc.
    -This is free software: you are free to change and redistribute it.
    -There is NO WARRANTY, to the extent permitted by law.
    +gpg (GnuPG) 2.1.6; Copyright (C) 2015 Free Software Foundation, Inc.
    +This is free software: you are free to change and redistribute it.
    +There is NO WARRANTY, to the extent permitted by law.
     
    -Note: Use "gpg2 --full-gen-key" for a full featured key generation dialog.
    +Note: Use "gpg2 --full-gen-key" for a full featured key generation dialog.
     
    -GnuPG needs to construct a user ID to identify your key.
    +GnuPG needs to construct a user ID to identify your key.
     
    -Real name: Marvin the Paranoid Android
    -Email address: marvin@h2g2.net
    -You selected this USER-ID:
    +Real name: Marvin the Paranoid Android
    +Email address: marvin@h2g2.net
    +You selected this USER-ID:
         "Marvin the Paranoid Android <marvin@h2g2.net>"
     
    -Change (N)ame, (E)mail, or (O)kay/(Q)uit? o
    -We need to generate a lot of random bytes. It is a good idea to perform
    -some other action (type on the keyboard, move the mouse, utilize the
    -disks) during the prime generation; this gives the random number
    -generator a better chance to gain enough entropy.
    +Change (N)ame, (E)mail, or (O)kay/(Q)uit? o +We need to generate a lot of random bytes. It is a good idea to perform +some other action (type on the keyboard, move the mouse, utilize the +disks) during the prime generation; this gives the random number +generator a better chance to gain enough entropy.

    gpg - entropy interlude

    At this point, you will:

      @@ -156,19 +154,19 @@ Keys
    • be asked to use your machine's input devices (mouse, keyboard, etc.) to generate random entropy; this step may take some time

    gpg - key creation confirmation

    -
    gpg: key A9D53A3E marked as ultimately trusted
    -public and secret key created and signed.
    +
    gpg: key A9D53A3E marked as ultimately trusted
    +public and secret key created and signed.
     
    -gpg: checking the trustdb
    -gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
    -gpg: depth: 0  valid:   2  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 2u
    -pub   rsa2048/A9D53A3E 2015-07-31
    -      Key fingerprint = AF2A 5381 E54B 2FD2 14C4  A9A3 0E35 ACA4 A9D5 3A3E
    -uid       [ultimate] Marvin the Paranoid Android <marvin@h2g2.net>[](.html)
    -sub   rsa2048/8C0EACF1 2015-07-31
    +gpg: checking the trustdb +gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model +gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u +pub rsa2048/A9D53A3E 2015-07-31 + Key fingerprint = AF2A 5381 E54B 2FD2 14C4 A9A3 0E35 ACA4 A9D5 3A3E +uid [ultimate] Marvin the Paranoid Android <marvin@h2g2.net>[](.html) +sub rsa2048/8C0EACF1 2015-07-31

    gpg - submit your public key to a PGP server (Optional)

    -
    $ gpg --keyserver pgp.mit.edu --send-keys A9D53A3E
    -gpg: sending key A9D53A3E to hkp server pgp.mit.edu
    +
    $ gpg --keyserver pgp.mit.edu --send-keys A9D53A3E
    +gpg: sending key A9D53A3E to hkp server pgp.mit.edu

    Create and push a GPG-signed tag

    See Release Shaarli.

    diff --git a/doc/Home.html b/doc/Home.html index 442503c5..970f547e 100644 --- a/doc/Home.html +++ b/doc/Home.html @@ -15,13 +15,13 @@ diff --git a/doc/Plugin-System.html b/doc/Plugin-System.html index 37b26152..655536c6 100644 --- a/doc/Plugin-System.html +++ b/doc/Plugin-System.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf diff --git a/doc/Plugins.html b/doc/Plugins.html index e7df6aed..435a836f 100644 --- a/doc/Plugins.html +++ b/doc/Plugins.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf diff --git a/doc/RSS-feeds.html b/doc/RSS-feeds.html index 1b38e4e8..0f332b3d 100644 --- a/doc/RSS-feeds.html +++ b/doc/RSS-feeds.html @@ -15,13 +15,13 @@ diff --git a/doc/Release-Shaarli.html b/doc/Release-Shaarli.html index cfaa663b..cdefd3d6 100644 --- a/doc/Release-Shaarli.html +++ b/doc/Release-Shaarli.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf

    Release Shaarli

    See Git - Maintaining a project - Tagging your [](.html)
    releases
    .

    -

    Prerequisites

    +

    Prerequisites

    This guide assumes that you have:

    • a GPG key matching your GitHub authentication credentials @@ -118,53 +116,70 @@ releases.

    • upstream pointing to the main Shaarli repository
  • maintainer permissions on the main Shaarli repository (to push the signed tag)
  • -
  • Pandoc needs to be installed.
  • +
  • Composer and Pandoc need to be installed
  • +

    Increment the version code, create and push a signed tag

    Bump Shaarli's version

    -
    $ cd /path/to/shaarli
    +
    $ cd /path/to/shaarli
     
     # create a new branch
    -$ git fetch upstream
    -$ git checkout upstream/master -b v0.5.0
    +$ git fetch upstream
    +$ git checkout upstream/master -b v0.5.0
     
     # bump the version number
    -$ vim index.php shaarli_version.php
    +$ vim index.php shaarli_version.php
     
     # rebuild the documentation from the wiki
    -$ make htmldoc
    +$ make htmldoc
     
     # commit the changes
    -$ git add index.php shaarli_version.php doc
    -$ git commit -s -m "Bump version to v0.5.0"
    +$ git add index.php shaarli_version.php doc
    +$ git commit -s -m "Bump version to v0.5.0"
     
     # push the commit on your GitHub fork
    -$ git push origin v0.5.0
    +$ git push origin v0.5.0

    Create and merge a Pull Request

    This one is pretty straightforward ;-)

    Create and push a signed tag

    # update your local copy
    -$ git checkout master
    -$ git fetch upstream
    -$ git pull upstream master
    +$ git checkout master
    +$ git fetch upstream
    +$ git pull upstream master
     
     # create a signed tag
    -$ git tag -s -m "Release v0.5.0" v0.5.0
    +$ git tag -s -m "Release v0.5.0" v0.5.0
     
     # push it to "upstream"
    -$ git push --tags upstream
    +$ git push --tags upstream

    Verify a signed tag

    v0.5.0 is the first GPG-signed tag pushed on the Community Shaarli.

    Let's have a look at its signature!

    -
    $ cd /path/to/shaarli
    -$ git fetch upstream
    +
    $ cd /path/to/shaarli
    +$ git fetch upstream
     
     # get the SHA1 reference of the tag
    -$ git show-ref tags/v0.5.0
    -f7762cf803f03f5caf4b8078359a63783d0090c1 refs/tags/v0.5.0
    +$ git show-ref tags/v0.5.0
    +f7762cf803f03f5caf4b8078359a63783d0090c1 refs/tags/v0.5.0
     
     # verify the tag signature information
    -$ git verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1
    -gpg: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F
    -gpg: Good signature from "VirtualTam <virtualtam@flibidi.net>" [ultimate][](.html)
    +$ git verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1 +gpg: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F +gpg: Good signature from "VirtualTam <virtualtam@flibidi.net>" [ultimate][](.html)
    +

    Generate and upload all-in-one release archives

    +

    Users with a shared hosting may have:

    +
      +
    • no SSH access
    • +
    • no possibility to install PHP packages or server extensions
    • +
    • no possibility to run scripts
    • +
    +

    To ease Shaarli installations, it is possible to generate and upload additional release archives,
    +that will contain Shaarli code plus all required third-party libraries:

    +
    $ make release_archive
    +

    This will create the following archives:

    +
      +
    • shaarli-vX.Y.Z-full.tar
    • +
    • shaarli-vX.Y.Z-full.zip
    • +
    +

    The archives need to be manually uploaded on the previously created GitHub release.

    diff --git a/doc/Release-Shaarli.md b/doc/Release-Shaarli.md index d5044fe9..5cbcd79a 100644 --- a/doc/Release-Shaarli.md +++ b/doc/Release-Shaarli.md @@ -2,7 +2,7 @@ See [Git - Maintaining a project - Tagging your [](.html) releases](http://git-scm.com/book/en/v2/Distributed-Git-Maintaining-a-Project#Tagging-Your-Releases). -### Prerequisites +## Prerequisites This guide assumes that you have: - a GPG key matching your GitHub authentication credentials - i.e., the email address identified by the GPG key is the same as the one in your `~/.gitconfig` @@ -11,8 +11,9 @@ This guide assumes that you have: - `origin` pointing to your GitHub fork - `upstream` pointing to the main Shaarli repository - maintainer permissions on the main Shaarli repository (to push the signed tag) -- [Pandoc](http://pandoc.org/) needs to be installed.[](.html) +- [Composer](https://getcomposer.org/) and [Pandoc](http://pandoc.org/) need to be installed[](.html) +## Increment the version code, create and push a signed tag ### Bump Shaarli's version ```bash $ cd /path/to/shaarli @@ -70,3 +71,22 @@ $ git verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1 gpg: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F gpg: Good signature from "VirtualTam " [ultimate][](.html) ``` + +## Generate and upload all-in-one release archives +Users with a shared hosting may have: +- no SSH access +- no possibility to install PHP packages or server extensions +- no possibility to run scripts + +To ease Shaarli installations, it is possible to generate and upload additional release archives, +that will contain Shaarli code plus all required third-party libraries: + +```bash +$ make release_archive +``` + +This will create the following archives: +- `shaarli-vX.Y.Z-full.tar` +- `shaarli-vX.Y.Z-full.zip` + +The archives need to be manually uploaded on the previously created GitHub release. diff --git a/doc/Security.html b/doc/Security.html index b1969a4c..cec20590 100644 --- a/doc/Security.html +++ b/doc/Security.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf diff --git a/doc/Server-configuration.html b/doc/Server-configuration.html index 1d2276df..068900b8 100644 --- a/doc/Server-configuration.html +++ b/doc/Server-configuration.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf @@ -133,7 +131,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf

    See also proxy-related issues.

    Apache

    Minimal

    -
    <VirtualHost *:80>
    +
    <VirtualHost *:80>
         ServerName   shaarli.my-domain.org
         DocumentRoot /absolute/path/to/shaarli/
     </VirtualHost>
    @@ -144,11 +142,11 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
  • Apache/PHP - error log per VirtualHost (StackOverflow)
  • PHP: php_value vs php_admin_value and the use of php_flag explained
  • -
    <VirtualHost *:80>
    +
    <VirtualHost *:80>
         ServerName   shaarli.my-domain.org
         DocumentRoot /absolute/path/to/shaarli/
     
    -    LogLevel  warn
    +    LogLevel  warn
         ErrorLog  /var/log/apache2/shaarli-error.log
         CustomLog /var/log/apache2/shaarli-access.log combined
     
    @@ -158,40 +156,40 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
         php_value error_log /var/log/apache2/shaarli-php-error.log
     </VirtualHost>

    Standard - Keep access and error logs

    -
    <VirtualHost *:80>
    +
    <VirtualHost *:80>
         ServerName   shaarli.my-domain.org
         DocumentRoot /absolute/path/to/shaarli/
     
    -    LogLevel  warn
    +    LogLevel  warn
         ErrorLog  /var/log/apache2/shaarli-error.log
         CustomLog /var/log/apache2/shaarli-access.log combined
     </VirtualHost>

    Paranoid - Redirect HTTP (:80) to HTTPS (:443)

    See Server-side TLS (Mozilla).

    -
    <VirtualHost *:443>
    +
    <VirtualHost *:443>
         ServerName   shaarli.my-domain.org
         DocumentRoot /absolute/path/to/shaarli/
     
    -    SSLEngine             on
    +    SSLEngine             on
         SSLCertificateFile    /absolute/path/to/the/website/certificate.pem
         SSLCertificateKeyFile /absolute/path/to/the/website/key.key
     
    -    <Directory /absolute/path/to/shaarli/>
    -        AllowOverride All
    -        Options Indexes FollowSymLinks MultiViews
    -        Order allow,deny
    +    <Directory /absolute/path/to/shaarli/>
    +        AllowOverride All
    +        Options Indexes FollowSymLinks MultiViews
    +        Order allow,deny
             allow from all
         </Directory>
     
    -    LogLevel  warn
    +    LogLevel  warn
         ErrorLog  /var/log/apache2/shaarli-error.log
         CustomLog /var/log/apache2/shaarli-access.log combined
     </VirtualHost>
    -<VirtualHost *:80>
    +<VirtualHost *:80>
         ServerName   shaarli.my-domain.org
         Redirect 301 / https://shaarli.my-domain.org
     
    -    LogLevel  warn
    +    LogLevel  warn
         ErrorLog  /var/log/apache2/shaarli-error.log
         CustomLog /var/log/apache2/shaarli-access.log combined
     </VirtualHost>
    @@ -410,10 +408,5 @@ http { include php.conf; } }
    -

    Restricting search engines and web crawler traffic

    -

    Creating a robots.txt witht he following contents at the root of your Shaarli installation will prevent "honest" web crawlers from indexing each and every link and Daily page from a Shaarli instance, thus getting rid of a certain amount of unsollicited network traffic.

    -
    User-agent: *
    -Disallow: /
    -

    See: http://www.robotstxt.org/, http://www.robotstxt.org/robotstxt.html, http://www.robotstxt.org/meta.html

    diff --git a/doc/Server-configuration.md b/doc/Server-configuration.md index fd98a608..1ab57a0a 100644 --- a/doc/Server-configuration.md +++ b/doc/Server-configuration.md @@ -334,15 +334,3 @@ http { } } ``` - -## Restricting search engines and web crawler traffic - -Creating a `robots.txt` witht he following contents at the root of your Shaarli installation will prevent "honest" web crawlers from indexing each and every link and Daily page from a Shaarli instance, thus getting rid of a certain amount of unsollicited network traffic. - -``` -User-agent: * -Disallow: / -``` - -See: http://www.robotstxt.org/, http://www.robotstxt.org/robotstxt.html, http://www.robotstxt.org/meta.html - diff --git a/doc/Server-requirements.html b/doc/Server-requirements.html index 8e4deeb8..2c2545bb 100644 --- a/doc/Server-requirements.html +++ b/doc/Server-requirements.html @@ -15,13 +15,13 @@ @@ -96,18 +94,18 @@ 5.5 -Supported +EOL: 2016-07-10 ✅ 5.4 EOL: 2015-09-14 -✅ +✅ (up to Shaarli 0.8.x) 5.3 EOL: 2014-08-14 -✅ +✅ (up to Shaarli 0.8.x) @@ -115,6 +113,25 @@ +

    Dependency management

    +

    Starting with Shaarli v0.8.x, Composer is used to resolve,
    +download and install third-party PHP dependencies.

    + + + + + + + + + + + + + + + +
    LibraryRequired?Usage
    shaarli/netscape-bookmark-parserAllImport bookmarks from Netscape files

    Extensions

    @@ -142,13 +159,18 @@ - + - - - + + + + + + + +
    php-gd-optional thumbnail resizing
    php-intlOptionalTag cloud intelligent sorting (eg. e->è->f)php-intloptionallocalized text sorting (e.g. e->è->f)
    php-curloptionalusing cURL for fetching webpages and thumbnails in a more robust way
    diff --git a/doc/Server-requirements.md b/doc/Server-requirements.md index 7955fddf..4962193e 100644 --- a/doc/Server-requirements.md +++ b/doc/Server-requirements.md @@ -12,17 +12,26 @@ Version | Status | Shaarli compatibility :---:|:---:|:---: 7.0 | Supported | :white_check_mark: 5.6 | Supported | :white_check_mark: -5.5 | Supported | :white_check_mark: -5.4 | EOL: 2015-09-14 | :white_check_mark: -5.3 | EOL: 2014-08-14 | :white_check_mark: +5.5 | EOL: 2016-07-10 | :white_check_mark: +5.4 | EOL: 2015-09-14 | :white_check_mark: (up to Shaarli 0.8.x) +5.3 | EOL: 2014-08-14 | :white_check_mark: (up to Shaarli 0.8.x) See also: - [Travis configuration](https://github.com/shaarli/Shaarli/blob/master/.travis.yml)[](.html) +### Dependency management +Starting with Shaarli `v0.8.x`, [Composer](https://getcomposer.org/) is used to resolve,[](.html) +download and install third-party PHP dependencies. + +Library | Required? | Usage +---|:---:|--- +[`shaarli/netscape-bookmark-parser`](https://packagist.org/packages/shaarli/netscape-bookmark-parser) | All | Import bookmarks from Netscape files[](.html) + ### Extensions Extension | Required? | Usage ---|:---:|--- [`openssl`](http://php.net/manual/en/book.openssl.php) | All | OpenSSL, HTTPS[](.html) [`php-mbstring`](http://php.net/manual/en/book.mbstring.php) | CentOS, Fedora, RHEL, Windows | multibyte (Unicode) string support[](.html) -[`php-gd`](http://php.net/manual/en/book.image.php) | - | thumbnail resizing[](.html) -[`php-intl`](http://php.net/manual/fr/book.intl.php) | Optional | Tag cloud intelligent sorting (eg. `e->è->f`)[](.html) +[`php-gd`](http://php.net/manual/en/book.image.php) | optional | thumbnail resizing[](.html) +[`php-intl`](http://php.net/manual/en/book.intl.php) | optional | localized text sorting (e.g. `e->è->f`)[](.html) +[`php-curl`](http://php.net/manual/en/book.curl.php) | optional | using cURL for fetching webpages and thumbnails in a more robust way[](.html) diff --git a/doc/Server-security.html b/doc/Server-security.html index 6b44a133..3551deff 100644 --- a/doc/Server-security.html +++ b/doc/Server-security.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf @@ -118,11 +116,11 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf

    Locate .ini files

    Console environment

    -
    $ php --ini
    -Configuration File (php.ini) Path: /etc/php
    -Loaded Configuration File:         /etc/php/php.ini
    -Scan for additional .ini files in: /etc/php/conf.d
    -Additional .ini files parsed:      /etc/php/conf.d/xdebug.ini
    +
    $ php --ini
    +Configuration File (php.ini) Path: /etc/php
    +Loaded Configuration File:         /etc/php/php.ini
    +Scan for additional .ini files in: /etc/php/conf.d
    +Additional .ini files parsed:      /etc/php/conf.d/xdebug.ini

    Server environment

    • create a phpinfo.php script located in a path supported by the web server, e.g. @@ -161,5 +159,15 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf [Definition][](.html) failregex = \s-\s<HOST>\s-\sLogin failed for user.*$ ignoreregex =
    +

    Robots - Restricting search engines and web crawler traffic

    +

    Creating a robots.txt with the following contents at the root of your Shaarli installation will prevent honest web crawlers from indexing each and every link and Daily page from a Shaarli instance, thus getting rid of a certain amount of unsollicited network traffic.

    +
    User-agent: *
    +Disallow: /
    +

    See:

    + diff --git a/doc/Server-security.md b/doc/Server-security.md index 0d16e284..50549a21 100644 --- a/doc/Server-security.md +++ b/doc/Server-security.md @@ -58,3 +58,17 @@ before = common.conf failregex = \s-\s\s-\sLogin failed for user.*$ ignoreregex = ``` + +## Robots - Restricting search engines and web crawler traffic + +Creating a `robots.txt` with the following contents at the root of your Shaarli installation will prevent _honest_ web crawlers from indexing each and every link and Daily page from a Shaarli instance, thus getting rid of a certain amount of unsollicited network traffic. + +``` +User-agent: * +Disallow: / +``` + +See: +- http://www.robotstxt.org/ +- http://www.robotstxt.org/robotstxt.html +- http://www.robotstxt.org/meta.html diff --git a/doc/Shaarli-configuration.html b/doc/Shaarli-configuration.html index 74947578..6d717c65 100644 --- a/doc/Shaarli-configuration.html +++ b/doc/Shaarli-configuration.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf

    Shaarli configuration

    +

    Shaarli configuration

    Foreword

    Do not edit configuration options in index.php! Your changes would be lost.

    -

    Once your Shaarli instance is installed, the file data/config.php is generated:

    +

    Once your Shaarli instance is installed, the file data/config.json.php is generated:

      -
    • it contains all settings, and can be edited to customize values
    • -
    • it defines which plugins are enabled
    • +
    • it contains all settings in JSON format, and can be edited to customize values
    • +
    • it defines which plugins are enabled(.html)
    • its values override those defined in index.php
    • +
    • it is wrap in a PHP comment to prevent anyone accessing it, regardless of server configuration

    File and directory permissions

    The server process running Shaarli must have:

    @@ -141,120 +141,155 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
  • unzip Shaarli in the default web server location (usually /var/www/) and set the web server user as the owner
  • put users in the same group as the web server, and set the appropriate access rights
  • -
  • if you have a domain / subdomain to serve Shaarli, configure the server accordingly
  • +
  • if you have a domain / subdomain to serve Shaarli, configure the server accordingly(.html)
  • -

    Example data/config.php

    -

    See also Plugin System.

    -
    <?php 
    -// User login
    -$GLOBALS['login'] = '<login>';[](.html)
    -
    -// User password hash
    -$GLOBALS['hash'] = '200c452da46c2f889e5e48c49ef044bcacdcb095';[](.html)
    -
    -// Password salt
    -$GLOBALS['salt'] = '13b654102321576033d8473b63a275a1bf94c0f0'; [](.html)
    -
    -// Local timezone
    -$GLOBALS['timezone'] = 'Africa/Abidjan';[](.html)
    -date_default_timezone_set('Africa/Abidjan');
    -
    -// Shaarli title
    -$GLOBALS['title'] = 'My Little Shaarly';[](.html)
    -
    -// Link the Shaarli title points to
    -$GLOBALS['titleLink'] = '?';[](.html)
    -
    -// HTTP referer redirector
    -$GLOBALS['redirector'] = '';[](.html)
    -
    -// Disable session hijacking
    -$GLOBALS['disablesessionprotection'] = false; [](.html)
    -
    -// Whether new links are private by default
    -$GLOBALS['privateLinkByDefault'] = false;[](.html)
    -
    -// Enabled plugins
    -// Note: each plugin may provide further settings through its own "config.php"
    -$GLOBALS['config'['ENABLED_PLUGINS'] = array('addlink_toolbar', 'qrcode');]('ENABLED_PLUGINS']-=-array('addlink_toolbar',-'qrcode');.html)
    -
    -// Subdirectory where Shaarli stores its data files.
    -// You can change it for better security.
    -$GLOBALS['config'['DATADIR'] = 'data';]('DATADIR']-=-'data';.html)
    -
    -// File used to store settings
    -$GLOBALS['config'['CONFIG_FILE'] = 'data/config.php';]('CONFIG_FILE']-=-'data/config.php';.html)
    -
    -// File containing the link database
    -$GLOBALS['config'['DATASTORE'] = 'data/datastore.php';]('DATASTORE']-=-'data/datastore.php';.html)
    -
    -// Number of links displayed per page
    -$GLOBALS['config'['LINKS_PER_PAGE'] = 20;]('LINKS_PER_PAGE']-=-20;.html)
    -
    -// File recording failed login attempts and IP bans
    -$GLOBALS['config'['IPBANS_FILENAME'] = 'data/ipbans.php';]('IPBANS_FILENAME']-=-'data/ipbans.php';.html)
    -
    -// Failed login attempts before being banned
    -$GLOBALS['config'['BAN_AFTER'] = 4;]('BAN_AFTER']-=-4;.html)
    -
    -// Duration of an IP ban, in seconds (30 minutes)
    -$GLOBALS['config'['BAN_DURATION'] = 1800;]('BAN_DURATION']-=-1800;.html)
    -
    -// If set to true, everyone will be able to add, edit and remove links,
    -// as well as change configuration
    -$GLOBALS['config'['OPEN_SHAARLI'] = false;]('OPEN_SHAARLI']-=-false;.html)
    -
    -// Do not show link timestamps
    -$GLOBALS['config'['HIDE_TIMESTAMPS'] = false;]('HIDE_TIMESTAMPS']-=-false;.html)
    -
    -// Set to false to disable local thumbnail cache, e.g. due to limited disk quotas
    -$GLOBALS['config'['ENABLE_THUMBNAILS'] = true;]('ENABLE_THUMBNAILS']-=-true;.html)
    -
    -// Thumbnail cache directory
    -$GLOBALS['config'['CACHEDIR'] = 'cache';]('CACHEDIR']-=-'cache';.html)
    -
    -// Enable feed (rss, atom, dailyrss) cache
    -$GLOBALS['config'['ENABLE_LOCALCACHE'] = true;]('ENABLE_LOCALCACHE']-=-true;.html)
    -
    -// Feed cache directory
    -$GLOBALS['config'['PAGECACHE'] = 'pagecache';]('PAGECACHE']-=-'pagecache';.html)
    -
    -// RainTPL cache directory (keep the trailing slash!)
    -$GLOBALS['config'['RAINTPL_TMP'] = 'tmp/';]('RAINTPL_TMP']-=-'tmp/';.html)
    -
    -// RainTPL template directory (keep the trailing slash!)
    -$GLOBALS['config'['RAINTPL_TPL'] = 'tpl/';]('RAINTPL_TPL']-=-'tpl/';.html)
    -
    -// Whether Shaarli checks for new releases at https://github.com/shaarli/Shaarli
    -$GLOBALS['config'['ENABLE_UPDATECHECK'] = true;]('ENABLE_UPDATECHECK']-=-true;.html)
    -
    -// File to store the latest Shaarli version
    -$GLOBALS['config'['UPDATECHECK_FILENAME'] = 'data/lastupdatecheck.txt';]('UPDATECHECK_FILENAME']-=-'data/lastupdatecheck.txt';.html)
    -
    -// Delay between version checks (requires to be logged in) (24 hours)
    -$GLOBALS['config'['UPDATECHECK_INTERVAL'] = 86400;]('UPDATECHECK_INTERVAL']-=-86400;.html)
    -
    -// For each link, display a link to an archived version on archive.org
    -$GLOBALS['config'['ARCHIVE_ORG'] = false;]('ARCHIVE_ORG']-=-false;.html)
    -
    -// The RSS item links point:
    -// true   =>  directly to the link
    -// false  =>  to the entry on Shaarli (permalink)
    -$GLOBALS['config'['ENABLE_RSS_PERMALINKS'] = true;]('ENABLE_RSS_PERMALINKS']-=-true;.html)
    -
    -// Hide all links to non-logged users
    -$GLOBALS['config'['HIDE_PUBLIC_LINKS'] = false;]('HIDE_PUBLIC_LINKS']-=-false;.html)
    -
    -$GLOBALS['config'['PUBSUBHUB_URL'] = '';]('PUBSUBHUB_URL']-=-'';.html)
    -
    -// Show an ATOM Feed button next to the Subscribe (RSS) button.
    -// ATOM feeds are available at the address ?do=atom regardless of this option.
    -$GLOBALS['config'['SHOW_ATOM'] = false;]('SHOW_ATOM']-=-false;.html)
    -
    -// Set this to true if the redirector requires encoded URL, false otherwise.
    -$GLOBALS['config'['REDIRECTOR_URLENCODE'] = true;]('REDIRECTOR_URLENCODE']-=-true;.html)
    -?>
    +

    Configuration

    +

    In data/config.json.php.

    +

    See also Plugin System.

    +

    Credentials

    +
    +

    You shouldn't edit those.

    +
    +

    login: Login username.
    +hash: Generated password hash.
    +salt: Password salt.

    +

    General

    +

    title: Shaarli's instance title.
    +header_link: Link to the homepage.
    +links_per_page: Number of shaares displayed per page.
    +timezone: See the list of supported timezones.
    +enabled_plugins: List of enabled plugins.

    +

    Security

    +

    session_protection_disabled: Disable session cookie hijacking protection (not recommended).
    +It might be useful if your IP adress often changes.
    +ban_after: Failed login attempts before being IP banned.
    +ban_duration: IP ban duration in seconds.
    +open_shaarli: Anyone can add a new link while logged out if enabled.
    +trusted_proxies: List of trusted IP which won't be banned after failed login attemps. Useful if Shaarli is behind a reverse proxy.

    +

    Resources

    +

    data_dir: Data directory.
    +datastore: Shaarli's links database file path.
    +updates: File path for the ran updates file.
    +log: Log file path.
    +update_check: Last update check file path.
    +raintpl_tpl: Templates directory.
    +raintpl_tmp: Template engine cache directory.
    +thumbnails_cache: Thumbnails cache directory.
    +page_cache: Shaarli's internal cache directory.
    +ban_file: Banned IP file path.

    +

    Updates

    +

    check_updates: Enable or disable update check to the git repository.
    +check_updates_branch: Git branch used to check updates (e.g. stable or master).
    +check_updates_interval: Look for new version every N seconds (default: every day).

    +

    Privacy

    +

    default_private_links: Check the private checkbox by default for every new link.
    +hide_public_links: All links are hidden while logged out.
    +hide_timestamps: Timestamps are hidden.

    +

    Feed

    +

    rss_permalinks: Enable this to redirect RSS links to Shaarli's permalinks instead of shaared URL.
    +show_atom: Display ATOM feed button.

    +

    Thumbnail

    +

    enable_thumbnails: Enable or disable thumbnail display.
    +enable_localcache: Enable or disable local cache.

    +

    Redirector

    +

    url: Redirector URL, such as anonym.to.
    +encode_url: Enable this if the redirector needs encoded URL to work properly.

    +

    Configuration file example

    +
    <?php /*
    +{
    +    "credentials": {
    +        "login": "<login>",
    +        "hash": "<password hash>",
    +        "salt": "<password salt>"
    +    },
    +    "security": {
    +        "ban_after": 4,
    +        "session_protection_disabled": false,
    +        "ban_duration": 1800,
    +        "trusted_proxies": [[](.html)
    +            "1.2.3.4",
    +            "5.6.7.8"
    +        ]
    +    },
    +    "resources": {
    +        "data_dir": "data",
    +        "config": "data\/config.php",
    +        "datastore": "data\/datastore.php",
    +        "ban_file": "data\/ipbans.php",
    +        "updates": "data\/updates.txt",
    +        "log": "data\/log.txt",
    +        "update_check": "data\/lastupdatecheck.txt",
    +        "raintpl_tmp": "tmp\/",
    +        "raintpl_tpl": "tpl\/",
    +        "thumbnails_cache": "cache",
    +        "page_cache": "pagecache"
    +    },
    +    "general": {
    +        "check_updates": true,
    +        "rss_permalinks": true,
    +        "links_per_page": 20,
    +        "default_private_links": true,
    +        "enable_thumbnails": true,
    +        "enable_localcache": true,
    +        "check_updates_branch": "stable",
    +        "check_updates_interval": 86400,
    +        "enabled_plugins": [[](.html)
    +            "markdown",
    +            "wallabag",
    +            "archiveorg"
    +        ],
    +        "timezone": "Europe\/Paris",
    +        "title": "My Shaarli",
    +        "header_link": "?"
    +    },
    +    "extras": {
    +        "show_atom": false,
    +        "hide_public_links": false,
    +        "hide_timestamps": false,
    +        "open_shaarli": false,
    +        "redirector": "http://anonym.to/?",
    +        "redirector_encode_url": false
    +    },
    +    "general": {
    +        "header_link": "?",
    +        "links_per_page": 20,
    +        "enabled_plugins": [[](.html)
    +            "markdown",
    +            "wallabag"
    +        ],
    +        "timezone": "Europe\/Paris",
    +        "title": "My Shaarli"
    +    },
    +    "updates": {
    +        "check_updates": true,
    +        "check_updates_branch": "stable",
    +        "check_updates_interval": 86400
    +    },
    +    "feed": {
    +        "rss_permalinks": true,
    +        "show_atom": false
    +    },
    +    "privacy": {
    +        "default_private_links": true,
    +        "hide_public_links": false,
    +        "hide_timestamps": false
    +    },
    +    "thumbnail": {
    +        "enable_thumbnails": true,
    +        "enable_localcache": true
    +    },
    +    "redirector": {
    +        "url": "http://anonym.to/?",
    +        "encode_url": false
    +    },
    +    "plugins": {
    +        "WALLABAG_URL": "http://demo.wallabag.org",
    +        "WALLABAG_VERSION": "1"
    +    }
    +} ?>

    Additional configuration

    -

    The playvideos plugin may require that you adapt your server's Content Security Policy configuration to work properly.

    +

    The playvideos plugin may require that you adapt your server's
    +Content Security Policy
    +configuration to work properly.(.html)

    diff --git a/doc/Shaarli-configuration.md b/doc/Shaarli-configuration.md index d0560d79..4a783c0e 100644 --- a/doc/Shaarli-configuration.md +++ b/doc/Shaarli-configuration.md @@ -1,14 +1,18 @@ #Shaarli configuration +# Shaarli configuration + ## Foreword **Do not edit configuration options in index.php! Your changes would be lost.** -Once your Shaarli instance is installed, the file `data/config.php` is generated: -* it contains all settings, and can be edited to customize values -* it defines which [plugins](Plugin-System) are enabled[](.html) +Once your Shaarli instance is installed, the file `data/config.json.php` is generated: +* it contains all settings in JSON format, and can be edited to customize values +* it defines which [plugins](Plugin-System) are enabled[(.html)]((.html).html) * its values override those defined in `index.php` +* it is wrap in a PHP comment to prevent anyone accessing it, regardless of server configuration ## File and directory permissions + The server process running Shaarli must have: - `read` access to the following resources: - PHP scripts: `index.php`, `application/*.php`, `plugins/*.php` @@ -29,123 +33,179 @@ On a Linux distribution: - to give it access to Shaarli, either: - unzip Shaarli in the default web server location (usually `/var/www/`) and set the web server user as the owner - put users in the same group as the web server, and set the appropriate access rights -- if you have a domain / subdomain to serve Shaarli, [configure the server](Server-configuration) accordingly[](.html) - -## Example `data/config.php` -See also [Plugin System](Plugin-System.html). - -```php -';[](.html) - -// User password hash -$GLOBALS['hash'] = '200c452da46c2f889e5e48c49ef044bcacdcb095';[](.html) - -// Password salt -$GLOBALS['salt'] = '13b654102321576033d8473b63a275a1bf94c0f0'; [](.html) - -// Local timezone -$GLOBALS['timezone'] = 'Africa/Abidjan';[](.html) -date_default_timezone_set('Africa/Abidjan'); - -// Shaarli title -$GLOBALS['title'] = 'My Little Shaarly';[](.html) - -// Link the Shaarli title points to -$GLOBALS['titleLink'] = '?';[](.html) - -// HTTP referer redirector -$GLOBALS['redirector'] = '';[](.html) - -// Disable session hijacking -$GLOBALS['disablesessionprotection'] = false; [](.html) - -// Whether new links are private by default -$GLOBALS['privateLinkByDefault'] = false;[](.html) - -// Enabled plugins -// Note: each plugin may provide further settings through its own "config.php" -$GLOBALS['config'['ENABLED_PLUGINS'] = array('addlink_toolbar', 'qrcode');]('ENABLED_PLUGINS']-=-array('addlink_toolbar',-'qrcode');.html) - -// Subdirectory where Shaarli stores its data files. -// You can change it for better security. -$GLOBALS['config'['DATADIR'] = 'data';]('DATADIR']-=-'data';.html) - -// File used to store settings -$GLOBALS['config'['CONFIG_FILE'] = 'data/config.php';]('CONFIG_FILE']-=-'data/config.php';.html) - -// File containing the link database -$GLOBALS['config'['DATASTORE'] = 'data/datastore.php';]('DATASTORE']-=-'data/datastore.php';.html) - -// Number of links displayed per page -$GLOBALS['config'['LINKS_PER_PAGE'] = 20;]('LINKS_PER_PAGE']-=-20;.html) - -// File recording failed login attempts and IP bans -$GLOBALS['config'['IPBANS_FILENAME'] = 'data/ipbans.php';]('IPBANS_FILENAME']-=-'data/ipbans.php';.html) - -// Failed login attempts before being banned -$GLOBALS['config'['BAN_AFTER'] = 4;]('BAN_AFTER']-=-4;.html) - -// Duration of an IP ban, in seconds (30 minutes) -$GLOBALS['config'['BAN_DURATION'] = 1800;]('BAN_DURATION']-=-1800;.html) - -// If set to true, everyone will be able to add, edit and remove links, -// as well as change configuration -$GLOBALS['config'['OPEN_SHAARLI'] = false;]('OPEN_SHAARLI']-=-false;.html) - -// Do not show link timestamps -$GLOBALS['config'['HIDE_TIMESTAMPS'] = false;]('HIDE_TIMESTAMPS']-=-false;.html) - -// Set to false to disable local thumbnail cache, e.g. due to limited disk quotas -$GLOBALS['config'['ENABLE_THUMBNAILS'] = true;]('ENABLE_THUMBNAILS']-=-true;.html) - -// Thumbnail cache directory -$GLOBALS['config'['CACHEDIR'] = 'cache';]('CACHEDIR']-=-'cache';.html) - -// Enable feed (rss, atom, dailyrss) cache -$GLOBALS['config'['ENABLE_LOCALCACHE'] = true;]('ENABLE_LOCALCACHE']-=-true;.html) - -// Feed cache directory -$GLOBALS['config'['PAGECACHE'] = 'pagecache';]('PAGECACHE']-=-'pagecache';.html) - -// RainTPL cache directory (keep the trailing slash!) -$GLOBALS['config'['RAINTPL_TMP'] = 'tmp/';]('RAINTPL_TMP']-=-'tmp/';.html) - -// RainTPL template directory (keep the trailing slash!) -$GLOBALS['config'['RAINTPL_TPL'] = 'tpl/';]('RAINTPL_TPL']-=-'tpl/';.html) - -// Whether Shaarli checks for new releases at https://github.com/shaarli/Shaarli -$GLOBALS['config'['ENABLE_UPDATECHECK'] = true;]('ENABLE_UPDATECHECK']-=-true;.html) - -// File to store the latest Shaarli version -$GLOBALS['config'['UPDATECHECK_FILENAME'] = 'data/lastupdatecheck.txt';]('UPDATECHECK_FILENAME']-=-'data/lastupdatecheck.txt';.html) - -// Delay between version checks (requires to be logged in) (24 hours) -$GLOBALS['config'['UPDATECHECK_INTERVAL'] = 86400;]('UPDATECHECK_INTERVAL']-=-86400;.html) - -// For each link, display a link to an archived version on archive.org -$GLOBALS['config'['ARCHIVE_ORG'] = false;]('ARCHIVE_ORG']-=-false;.html) - -// The RSS item links point: -// true => directly to the link -// false => to the entry on Shaarli (permalink) -$GLOBALS['config'['ENABLE_RSS_PERMALINKS'] = true;]('ENABLE_RSS_PERMALINKS']-=-true;.html) - -// Hide all links to non-logged users -$GLOBALS['config'['HIDE_PUBLIC_LINKS'] = false;]('HIDE_PUBLIC_LINKS']-=-false;.html) - -$GLOBALS['config'['PUBSUBHUB_URL'] = '';]('PUBSUBHUB_URL']-=-'';.html) - -// Show an ATOM Feed button next to the Subscribe (RSS) button. -// ATOM feeds are available at the address ?do=atom regardless of this option. -$GLOBALS['config'['SHOW_ATOM'] = false;]('SHOW_ATOM']-=-false;.html) - -// Set this to true if the redirector requires encoded URL, false otherwise. -$GLOBALS['config'['REDIRECTOR_URLENCODE'] = true;]('REDIRECTOR_URLENCODE']-=-true;.html) -?> +- if you have a domain / subdomain to serve Shaarli, [configure the server](Server-configuration) accordingly[(.html)]((.html).html) + +## Configuration + +In `data/config.json.php`. + +See also [Plugin System](Plugin-System.html).[](.html) + +### Credentials + +> You shouldn't edit those. + +**login**: Login username. +**hash**: Generated password hash. +**salt**: Password salt. + +### General + +**title**: Shaarli's instance title. +**header_link**: Link to the homepage. +**links_per_page**: Number of shaares displayed per page. +**timezone**: See [the list of supported timezones](http://php.net/manual/en/timezones.php). [](.html) +**enabled_plugins**: List of enabled plugins. + +### Security + +**session_protection_disabled**: Disable session cookie hijacking protection (not recommended). +It might be useful if your IP adress often changes. +**ban_after**: Failed login attempts before being IP banned. +**ban_duration**: IP ban duration in seconds. +**open_shaarli**: Anyone can add a new link while logged out if enabled. +**trusted_proxies**: List of trusted IP which won't be banned after failed login attemps. Useful if Shaarli is behind a reverse proxy. + +### Resources + +**data_dir**: Data directory. +**datastore**: Shaarli's links database file path. +**updates**: File path for the ran updates file. +**log**: Log file path. +**update_check**: Last update check file path. +**raintpl_tpl**: Templates directory. +**raintpl_tmp**: Template engine cache directory. +**thumbnails_cache**: Thumbnails cache directory. +**page_cache**: Shaarli's internal cache directory. +**ban_file**: Banned IP file path. + +### Updates + +**check_updates**: Enable or disable update check to the git repository. +**check_updates_branch**: Git branch used to check updates (e.g. `stable` or `master`). +**check_updates_interval**: Look for new version every N seconds (default: every day). + +### Privacy + +**default_private_links**: Check the private checkbox by default for every new link. +**hide_public_links**: All links are hidden while logged out. +**hide_timestamps**: Timestamps are hidden. + +### Feed + +**rss_permalinks**: Enable this to redirect RSS links to Shaarli's permalinks instead of shaared URL. +**show_atom**: Display ATOM feed button. + +### Thumbnail + +**enable_thumbnails**: Enable or disable thumbnail display. +**enable_localcache**: Enable or disable local cache. + +### Redirector + +**url**: Redirector URL, such as `anonym.to`. +**encode_url**: Enable this if the redirector needs encoded URL to work properly. + +## Configuration file example + +```json +", + "hash": "", + "salt": "" + }, + "security": { + "ban_after": 4, + "session_protection_disabled": false, + "ban_duration": 1800, + "trusted_proxies": [[](.html) + "1.2.3.4", + "5.6.7.8" + ] + }, + "resources": { + "data_dir": "data", + "config": "data\/config.php", + "datastore": "data\/datastore.php", + "ban_file": "data\/ipbans.php", + "updates": "data\/updates.txt", + "log": "data\/log.txt", + "update_check": "data\/lastupdatecheck.txt", + "raintpl_tmp": "tmp\/", + "raintpl_tpl": "tpl\/", + "thumbnails_cache": "cache", + "page_cache": "pagecache" + }, + "general": { + "check_updates": true, + "rss_permalinks": true, + "links_per_page": 20, + "default_private_links": true, + "enable_thumbnails": true, + "enable_localcache": true, + "check_updates_branch": "stable", + "check_updates_interval": 86400, + "enabled_plugins": [[](.html) + "markdown", + "wallabag", + "archiveorg" + ], + "timezone": "Europe\/Paris", + "title": "My Shaarli", + "header_link": "?" + }, + "extras": { + "show_atom": false, + "hide_public_links": false, + "hide_timestamps": false, + "open_shaarli": false, + "redirector": "http://anonym.to/?", + "redirector_encode_url": false + }, + "general": { + "header_link": "?", + "links_per_page": 20, + "enabled_plugins": [[](.html) + "markdown", + "wallabag" + ], + "timezone": "Europe\/Paris", + "title": "My Shaarli" + }, + "updates": { + "check_updates": true, + "check_updates_branch": "stable", + "check_updates_interval": 86400 + }, + "feed": { + "rss_permalinks": true, + "show_atom": false + }, + "privacy": { + "default_private_links": true, + "hide_public_links": false, + "hide_timestamps": false + }, + "thumbnail": { + "enable_thumbnails": true, + "enable_localcache": true + }, + "redirector": { + "url": "http://anonym.to/?", + "encode_url": false + }, + "plugins": { + "WALLABAG_URL": "http://demo.wallabag.org", + "WALLABAG_VERSION": "1" + } +} ?> ``` ## Additional configuration -The playvideos plugin may require that you adapt your server's [Content Security Policy](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md#troubleshooting) configuration to work properly.[](.html) +The playvideos plugin may require that you adapt your server's +[Content Security Policy](https://github.com/shaarli/Shaarli/blob/master/plugins/playvideos/README.md#troubleshooting) [](.html) +configuration to work properly.[(.html)]((.html).html) + diff --git a/doc/Shaarli-installation.html b/doc/Shaarli-installation.html deleted file mode 100644 index 487ec1db..00000000 --- a/doc/Shaarli-installation.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - Shaarli – Shaarli installation - - - - - - -

    Shaarli installation

    -

    Once Shaarli is downloaded and installed behind a web server, open it in your favorite browser.

    -

    install screenshot

    -

    Setup your Shaarli installation, and it's ready to use!

    - - diff --git a/doc/Shaarli-installation.md b/doc/Shaarli-installation.md deleted file mode 100644 index be9726e0..00000000 --- a/doc/Shaarli-installation.md +++ /dev/null @@ -1,6 +0,0 @@ -#Shaarli installation -Once Shaarli is downloaded and installed behind a web server, open it in your favorite browser. - -![install screenshot](http://i.imgur.com/wuMpDSN.png)[](.html) - -Setup your Shaarli installation, and it's ready to use! diff --git a/doc/Sharing-button.html b/doc/Sharing-button.html index 3770d8ad..93710efe 100644 --- a/doc/Sharing-button.html +++ b/doc/Sharing-button.html @@ -15,13 +15,13 @@ diff --git a/doc/Static-analysis.html b/doc/Static-analysis.html index 86cb4696..d964e917 100644 --- a/doc/Static-analysis.html +++ b/doc/Static-analysis.html @@ -15,13 +15,13 @@ diff --git a/doc/TODO.html b/doc/TODO.html deleted file mode 100644 index 04224dbf..00000000 --- a/doc/TODO.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - Shaarli – TODO - - - - - - -

    TODO

    -
      -
    • add more screenshots
    • -
    • improve developer documentation: storage architecture, classes and functions, security handling...
    • -
    • add server configuration examples: lighthttpd
    • -
    - - diff --git a/doc/TODO.md b/doc/TODO.md deleted file mode 100644 index fb72fd57..00000000 --- a/doc/TODO.md +++ /dev/null @@ -1,4 +0,0 @@ -#TODO -* add more screenshots -* improve developer documentation: storage architecture, classes and functions, security handling... -* add server configuration examples: lighthttpd diff --git a/doc/Theming.html b/doc/Theming.html index 27c5d863..13e6acf0 100644 --- a/doc/Theming.html +++ b/doc/Theming.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf @@ -132,6 +130,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
  • kalvn/shaarli-blocks - A template/theme for Shaarli
  • kalvn/Shaarli-Material - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.
  • misterair/Limonade - A fork of (legacy) Shaarli with a new template
  • +
  • mrjovanovic/serious-theme-shaarli - A serious theme for SHaarli.
  • Vinm/Blue-theme-for Shaarli - A template/theme for Shaarli (unmaintained, compatibility unknown)
  • vivienhaese/shaarlitheme - A Shaarli fork meant to be run in an openshift instance
  • @@ -142,17 +141,17 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
  • user sites are enabled, e.g. /home/user/public_html/somedir is served as http://localhost/~user/somedir
  • http is the name of the Apache user
  • -
    $ cd ~/public_html
    +
    $ cd ~/public_html
     
     # clone repositories
    -$ git clone https://github.com/shaarli/Shaarli.git shaarli
    -$ pushd shaarli/tpl
    -$ git clone https://github.com/alexisju/albinomouse-template.git
    -$ popd
    +$ git clone https://github.com/shaarli/Shaarli.git shaarli
    +$ pushd shaarli/tpl
    +$ git clone https://github.com/alexisju/albinomouse-template.git
    +$ popd
     
     # set access rights for Apache
    -$ chgrp -R http shaarli
    -$ chmod g+rwx shaarli shaarli/cache shaarli/data shaarli/pagecache shaarli/tmp
    +$ chgrp -R http shaarli +$ chmod g+rwx shaarli shaarli/cache shaarli/data shaarli/pagecache shaarli/tmp

    Get config written:

    • go to the freshly installed site
    • @@ -161,6 +160,6 @@ $ chmod g+rwx shaarli shaarli/cache shaarli/data shaarli

    Edit Shaarli's configuration|Shaarli configuration:

    # the file should be owned by Apache, thus not writeable => sudo
    -$ sudo sed -i s=tpl=tpl/albinomouse-template=g shaarli/data/config.php
    +$ sudo sed -i s=tpl=tpl/albinomouse-template=g shaarli/data/config.php
    diff --git a/doc/Theming.md b/doc/Theming.md index 9dfdcf9f..7fb8d927 100644 --- a/doc/Theming.md +++ b/doc/Theming.md @@ -28,6 +28,7 @@ $GLOBALS['config'['RAINTPL_TPL'] = 'tpl/my-template/';]('RAINTPL_TPL']-=-'tpl/my - [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli[](.html) - [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.[](.html) - [misterair/Limonade](https://github.com/misterair/limonade) - A fork of (legacy) Shaarli with a new template[](.html) +- [mrjovanovic/serious-theme-shaarli](https://github.com/mrjovanovic/serious-theme-shaarli) - A serious theme for SHaarli.[](.html) - [Vinm/Blue-theme-for Shaarli](https://github.com/Vinm/Blue-theme-for-Shaarli) - A template/theme for Shaarli ([unmaintained](https://github.com/Vinm/Blue-theme-for-Shaarli/issues/2), compatibility unknown)[](.html) - [vivienhaese/shaarlitheme](https://github.com/vivienhaese/shaarlitheme) - A Shaarli fork meant to be run in an openshift instance[](.html) diff --git a/doc/Troubleshooting.html b/doc/Troubleshooting.html index 3de8ad1e..ed1c6f09 100644 --- a/doc/Troubleshooting.html +++ b/doc/Troubleshooting.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf @@ -132,6 +130,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
    • false (default): real referer
    • true: spoof referer (use target URI as referer)
    • +
    • known to break some functionality in Shaarli

    network.http.referer.trimmingPolicy - trim the URI not to send a full Referer

      @@ -140,7 +139,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
    • 2: scheme+host+port

    Firefox, localhost and redirections

    -

    localhost is not a proper Fully Qualified Domain Name (FQDN); if Firefox has been set up to spoof referers, or anly accept requests from the same base domain/host, Shaarli redirections will not work properly.

    +

    localhost is not a proper Fully Qualified Domain Name (FQDN); if Firefox has been set up to spoof referers, or only accept requests from the same base domain/host, Shaarli redirections will not work properly.

    To solve this, assign a local domain to your host, e.g.

    127.0.0.1 localhost desktop localhost.lan
     ::1       localhost desktop localhost.lan
    diff --git a/doc/Troubleshooting.md b/doc/Troubleshooting.md index e91fe846..8e30fce5 100644 --- a/doc/Troubleshooting.md +++ b/doc/Troubleshooting.md @@ -25,6 +25,7 @@ HTTP settings are available by browsing `about:config`, here are the available s `network.http.referer.spoofSource` - Referer spoofing (~faking) - false (default): real referer - true: spoof referer (use target URI as referer) + - known to break some functionality in Shaarli `network.http.referer.trimmingPolicy` - trim the URI not to send a full Referer - 0 (default): send full URI @@ -32,7 +33,7 @@ HTTP settings are available by browsing `about:config`, here are the available s - 2: scheme+host+port ### Firefox, localhost and redirections -`localhost` is not a proper Fully Qualified Domain Name (FQDN); if Firefox has been set up to spoof referers, or anly accept requests from the same base domain/host, Shaarli redirections will not work properly. +`localhost` is not a proper Fully Qualified Domain Name (FQDN); if Firefox has been set up to spoof referers, or only accept requests from the same base domain/host, Shaarli redirections will not work properly. To solve this, assign a local domain to your host, e.g. ``` diff --git a/doc/Unit-tests.html b/doc/Unit-tests.html index 7934e346..266fd33a 100644 --- a/doc/Unit-tests.html +++ b/doc/Unit-tests.html @@ -52,13 +52,13 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf @@ -111,87 +109,87 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf

    Sample usage

    # system-wide version
    -$ composer install
    -$ composer update
    +$ composer install
    +$ composer update
     
     # local version
    -$ php composer.phar self-update
    -$ php composer.phar install
    -$ php composer.phar update
    +$ php composer.phar self-update +$ php composer.phar install +$ php composer.phar update

    Install Shaarli dev dependencies

    -
    $ cd /path/to/shaarli
    -$ composer update
    +
    $ cd /path/to/shaarli
    +$ composer update

    Install and enable Xdebug to generate PHPUnit coverage reports

    For Debian-based distros:

    -
    $ aptitude install php5-xdebug
    +
    $ aptitude install php5-xdebug

    For ArchLinux:

    -
    $ pacman -S xdebug
    +
    $ pacman -S xdebug

    Then add the following line to /etc/php/php.ini:

    zend_extension=xdebug.so

    Run unit tests

    Successful test suite:

    -
    $ make test
    +
    $ make test
     
    --------
    -PHPUNIT
    --------
    -PHPUnit 4.6.9 by Sebastian Bergmann and contributors.
    +-------
    +PHPUNIT
    +-------
    +PHPUnit 4.6.9 by Sebastian Bergmann and contributors.
     
    -Configuration read from /home/virtualtam/public_html/shaarli/phpunit.xml
    +Configuration read from /home/virtualtam/public_html/shaarli/phpunit.xml
     
    -....................................
    +....................................
     
    -Time: 759 ms, Memory: 8.25Mb
    +Time: 759 ms, Memory: 8.25Mb
     
    -OK (36 tests, 65 assertions)
    +OK (36 tests, 65 assertions)

    Test suite with failures and errors:

    -
    $ make test
    --------
    -PHPUNIT
    --------
    -PHPUnit 4.6.9 by Sebastian Bergmann and contributors.
    +
    $ make test
    +-------
    +PHPUNIT
    +-------
    +PHPUnit 4.6.9 by Sebastian Bergmann and contributors.
     
    -Configuration read from /home/virtualtam/public_html/shaarli/phpunit.xml
    +Configuration read from /home/virtualtam/public_html/shaarli/phpunit.xml
     
    -E..FF...............................
    +E..FF...............................
     
    -Time: 802 ms, Memory: 8.25Mb
    +Time: 802 ms, Memory: 8.25Mb
     
    -There was 1 error:
    +There was 1 error:
     
    -1) LinkDBTest::testConstructLoggedIn
    -Missing argument 2 for LinkDB::__construct(), called in /home/virtualtam/public_html/shaarli/tests/Link\
    +1) LinkDBTest::testConstructLoggedIn
    +Missing argument 2 for LinkDB::__construct(), called in /home/virtualtam/public_html/shaarli/tests/Link\
     DBTest.php on line 79 and defined
     
    -/home/virtualtam/public_html/shaarli/application/LinkDB.php:58
    -/home/virtualtam/public_html/shaarli/tests/LinkDBTest.php:79
    +/home/virtualtam/public_html/shaarli/application/LinkDB.php:58
    +/home/virtualtam/public_html/shaarli/tests/LinkDBTest.php:79
     
    ---
    +--
     
    -There were 2 failures:
    +There were 2 failures:
     
    -1) LinkDBTest::testCheckDBNew
    -Failed asserting that two strings are equal.
    ---- Expected
    -+++ Actual
    -@@ @@
    --'e3edea8ea7bb50be4bcb404df53fbb4546a7156e'
    -+'85eab0c610d4f68025f6ed6e6b6b5fabd4b55834'
    +1) LinkDBTest::testCheckDBNew
    +Failed asserting that two strings are equal.
    +--- Expected
    ++++ Actual
    +@@ @@
    +-'e3edea8ea7bb50be4bcb404df53fbb4546a7156e'
    ++'85eab0c610d4f68025f6ed6e6b6b5fabd4b55834'
     
    -/home/virtualtam/public_html/shaarli/tests/LinkDBTest.php:121
    +/home/virtualtam/public_html/shaarli/tests/LinkDBTest.php:121
     
    -2) LinkDBTest::testCheckDBLoad
    -Failed asserting that two strings are equal.
    ---- Expected
    -+++ Actual
    -@@ @@
    --'e3edea8ea7bb50be4bcb404df53fbb4546a7156e'
    -+'85eab0c610d4f68025f6ed6e6b6b5fabd4b55834'
    +2) LinkDBTest::testCheckDBLoad
    +Failed asserting that two strings are equal.
    +--- Expected
    ++++ Actual
    +@@ @@
    +-'e3edea8ea7bb50be4bcb404df53fbb4546a7156e'
    ++'85eab0c610d4f68025f6ed6e6b6b5fabd4b55834'
     
    -/home/virtualtam/public_html/shaarli/tests/LinkDBTest.php:133
    +/home/virtualtam/public_html/shaarli/tests/LinkDBTest.php:133
     
    -FAILURES!
    -Tests: 36, Assertions: 63, Errors: 1, Failures: 2.
    +FAILURES! +Tests: 36, Assertions: 63, Errors: 1, Failures: 2.

    Test results and coverage

    By default, PHPUnit will run all suitable tests found under the tests directory.

    Each test has 3 possible outcomes:

    diff --git a/doc/Upgrade-and-migration.html b/doc/Upgrade-and-migration.html new file mode 100644 index 00000000..a5b041d5 --- /dev/null +++ b/doc/Upgrade-and-migration.html @@ -0,0 +1,242 @@ + + + + + + + Shaarli – Upgrade and migration + + + + + + + +

    Upgrade and migration

    +

    Preparation

    +

    Backup your data

    +

    Shaarli stores all user data under the data directory:

    +
      +
    • data/config.php - main configuration file
    • +
    • data/datastore.php - bookmarked links
    • +
    • data/ipbans.php - banned IP addresses
    • +
    +

    See Shaarli configuration for more information about Shaarli resources.

    +

    It is recommended to backup this repository before starting updating/upgrading Shaarli:

    +
      +
    • users with SSH access: copy or archive the directory to a temporary location
    • +
    • users with FTP access: download a local copy of your Shaarli installation using your favourite client
    • +
    +

    Migrating data from a previous installation

    +

    As all user data is kept under data, this is the only directory you need to worry about when migrating to a new installation, which corresponds to the following steps:

    +
      +
    • backup the data directory
    • +
    • install or update Shaarli: +
    • +
    • check or restore the data directory
    • +
    +

    Upgrading from release archives

    +

    All tagged revisions can be downloaded as tarballs or ZIP archives from the releases page.

    +

    We recommend using the releases from the stable branch, which are available as:

    + +

    Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the contents of the data directory!

    +

    After upgrading, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to data/config.php (see Shaarli configuration for more details).

    +

    Upgrading with Git

    +

    Updating a community Shaarli

    +

    If you have installed Shaarli from the community Git repository, simply pull new changes from your local clone:

    +
    $ cd /path/to/shaarli
    +$ git pull
    +
    +From github.com:shaarli/Shaarli
    + * branch            master     -> FETCH_HEAD
    +Updating ebd67c6..521f0e6
    +Fast-forward
    + application/Url.php   | 1 +
    + shaarli_version.php   | 2 +-
    + tests/Url/UrlTest.php | 1 +
    + 3 files changed, 3 insertions(+), 1 deletion(-)
    +

    Shaarli >= v0.8.x: install/update third-party PHP dependencies using Composer:

    +
    $ composer update --no-dev
    +
    +Loading composer repositories with package information
    +Updating dependencies
    +  - Installing shaarli/netscape-bookmark-parser (v1.0.1)
    +    Downloading: 100%
    +

    Migrating and upgrading from Sebsauvage's repository

    +

    If you have installed Shaarli from Sebsauvage's original Git repository, you can use Git remotes to update your working copy.

    +

    The following guide assumes that:

    +
      +
    • you have a basic knowledge of Git branching and remote repositories
    • +
    • the default remote is named origin and points to Sebsauvage's repository
    • +
    • the current branch is master +
        +
      • if you have personal branches containing customizations, you will need to rebase them after the upgrade; beware though, a lot of changes have been made since the community fork has been created, so things are very likely to break
      • +
    • +
    • the working copy is clean: +
        +
      • no versioned file has been locally modified
      • +
      • no untracked files are present
      • +
    • +
    +

    Step 0: show repository information

    +
    $ cd /path/to/shaarli
    +
    +$ git remote -v
    +origin  https://github.com/sebsauvage/Shaarli (fetch)
    +origin  https://github.com/sebsauvage/Shaarli (push)
    +
    +$ git branch -vv
    +* master 029f75f [origin/master] Update README.md[](.html)
    +
    +$ git status
    +On branch master
    +Your branch is up-to-date with 'origin/master'.
    +nothing to commit, working directory clean
    +

    Step 1: update Git remotes

    +
    $ git remote rename origin sebsauvage
    +$ git remote -v
    +sebsauvage  https://github.com/sebsauvage/Shaarli (fetch)
    +sebsauvage  https://github.com/sebsauvage/Shaarli (push)
    +
    +$ git remote add origin https://github.com/shaarli/Shaarli
    +$ git fetch origin
    +
    +remote: Counting objects: 3015, done.
    +remote: Compressing objects: 100% (19/19), done.
    +remote: Total 3015 (delta 446), reused 457 (delta 446), pack-reused 2550
    +Receiving objects: 100% (3015/3015), 2.59 MiB | 918.00 KiB/s, done.
    +Resolving deltas: 100% (1899/1899), completed with 48 local objects.
    +From https://github.com/shaarli/Shaarli
    + * [new branch]      master     -> origin/master[](.html)
    + * [new branch]      stable     -> origin/stable[](.html)
    +[...][](.html)
    + * [new tag]         v0.6.4     -> v0.6.4[](.html)
    + * [new tag]         v0.7.0     -> v0.7.0[](.html)
    +

    Step 2: use the stable community branch

    +
    $ git checkout origin/stable -b stable
    +Branch stable set up to track remote branch stable from origin.
    +Switched to a new branch 'stable'
    +
    +$ git branch -vv
    +  master 029f75f [sebsauvage/master] Update README.md[](.html)
    +* stable 890afc3 [origin/stable] Merge pull request #509 from ArthurHoaro/v0.6.5[](.html)
    +

    Shaarli >= v0.8.x: install/update third-party PHP dependencies using Composer:

    +
    $ composer update --no-dev
    +
    +Loading composer repositories with package information
    +Updating dependencies
    +  - Installing shaarli/netscape-bookmark-parser (v1.0.1)
    +    Downloading: 100%
    +

    Optionally, you can delete information related to the legacy version:

    +
    $ git branch -D master
    +Deleted branch master (was 029f75f).
    +
    +$ git remote remove sebsauvage
    +
    +$ git remote -v
    +origin  https://github.com/shaarli/Shaarli (fetch)
    +origin  https://github.com/shaarli/Shaarli (push)
    +
    +$ git gc
    +Counting objects: 3317, done.
    +Delta compression using up to 8 threads.
    +Compressing objects: 100% (1237/1237), done.
    +Writing objects: 100% (3317/3317), done.
    +Total 3317 (delta 2050), reused 3301 (delta 2034)to
    +

    Step 3: configuration

    +

    After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to data/config.php (see Shaarli configuration for more details).

    + + diff --git a/doc/Upgrade-and-migration.md b/doc/Upgrade-and-migration.md new file mode 100644 index 00000000..0bc33824 --- /dev/null +++ b/doc/Upgrade-and-migration.md @@ -0,0 +1,161 @@ +#Upgrade and migration +## Preparation +### Backup your data + +Shaarli stores all user data under the `data` directory: +- `data/config.php` - main configuration file +- `data/datastore.php` - bookmarked links +- `data/ipbans.php` - banned IP addresses + +See [Shaarli configuration](Shaarli-configuration.html) for more information about Shaarli resources. + +It is recommended to backup this repository _before_ starting updating/upgrading Shaarli: +- users with SSH access: copy or archive the directory to a temporary location +- users with FTP access: download a local copy of your Shaarli installation using your favourite client + +### Migrating data from a previous installation +As all user data is kept under `data`, this is the only directory you need to worry about when migrating to a new installation, which corresponds to the following steps: + +- backup the `data` directory +- install or update Shaarli: + - fresh installation - see [Download and installation](Download-and-installation.html) + - update - see the following sections +- check or restore the `data` directory + +## Upgrading from release archives +All tagged revisions can be downloaded as tarballs or ZIP archives from the [releases](https://github.com/shaarli/Shaarli/releases) page.[](.html) + +We _recommend_ using the releases from the `stable` branch, which are available as: +- gzipped tarball - https://github.com/shaarli/Shaarli/archive/stable.tar.gz +- ZIP archive - https://github.com/shaarli/Shaarli/archive/stable.zip + +Once downloaded, extract the archive locally and update your remote installation (e.g. via FTP) -be sure you keep the contents of the `data` directory! + +After upgrading, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration.html) for more details). + +## Upgrading with Git +### Updating a community Shaarli +If you have installed Shaarli from the [community Git repository](Download#clone-with-git-recommended), simply [pull new changes](https://www.git-scm.com/docs/git-pull) from your local clone:[](.html) + +```bash +$ cd /path/to/shaarli +$ git pull + +From github.com:shaarli/Shaarli + * branch master -> FETCH_HEAD +Updating ebd67c6..521f0e6 +Fast-forward + application/Url.php | 1 + + shaarli_version.php | 2 +- + tests/Url/UrlTest.php | 1 + + 3 files changed, 3 insertions(+), 1 deletion(-) +``` + +Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):[](.html) + +```bash +$ composer update --no-dev + +Loading composer repositories with package information +Updating dependencies + - Installing shaarli/netscape-bookmark-parser (v1.0.1) + Downloading: 100% +``` + +### Migrating and upgrading from Sebsauvage's repository +If you have installed Shaarli from [Sebsauvage's original Git repository](https://github.com/sebsauvage/Shaarli), you can use [Git remotes](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) to update your working copy.[](.html) + +The following guide assumes that: +- you have a basic knowledge of Git [branching](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) and [remote repositories](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes)[](.html) +- the default remote is named `origin` and points to Sebsauvage's repository +- the current branch is `master` + - if you have personal branches containing customizations, you will need to [rebase them](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) after the upgrade; beware though, a lot of changes have been made since the community fork has been created, so things are very likely to break![](.html) +- the working copy is clean: + - no versioned file has been locally modified + - no untracked files are present + +#### Step 0: show repository information +```bash +$ cd /path/to/shaarli + +$ git remote -v +origin https://github.com/sebsauvage/Shaarli (fetch) +origin https://github.com/sebsauvage/Shaarli (push) + +$ git branch -vv +* master 029f75f [origin/master] Update README.md[](.html) + +$ git status +On branch master +Your branch is up-to-date with 'origin/master'. +nothing to commit, working directory clean +``` + +#### Step 1: update Git remotes +``` +$ git remote rename origin sebsauvage +$ git remote -v +sebsauvage https://github.com/sebsauvage/Shaarli (fetch) +sebsauvage https://github.com/sebsauvage/Shaarli (push) + +$ git remote add origin https://github.com/shaarli/Shaarli +$ git fetch origin + +remote: Counting objects: 3015, done. +remote: Compressing objects: 100% (19/19), done. +remote: Total 3015 (delta 446), reused 457 (delta 446), pack-reused 2550 +Receiving objects: 100% (3015/3015), 2.59 MiB | 918.00 KiB/s, done. +Resolving deltas: 100% (1899/1899), completed with 48 local objects. +From https://github.com/shaarli/Shaarli + * [new branch] master -> origin/master[](.html) + * [new branch] stable -> origin/stable[](.html) +[...][](.html) + * [new tag] v0.6.4 -> v0.6.4[](.html) + * [new tag] v0.7.0 -> v0.7.0[](.html) +``` + +#### Step 2: use the stable community branch + +```bash +$ git checkout origin/stable -b stable +Branch stable set up to track remote branch stable from origin. +Switched to a new branch 'stable' + +$ git branch -vv + master 029f75f [sebsauvage/master] Update README.md[](.html) +* stable 890afc3 [origin/stable] Merge pull request #509 from ArthurHoaro/v0.6.5[](.html) +``` + +Shaarli >= `v0.8.x`: install/update third-party PHP dependencies using [Composer](https://getcomposer.org/):[](.html) + +```bash +$ composer update --no-dev + +Loading composer repositories with package information +Updating dependencies + - Installing shaarli/netscape-bookmark-parser (v1.0.1) + Downloading: 100% +``` + +Optionally, you can delete information related to the legacy version: + +```bash +$ git branch -D master +Deleted branch master (was 029f75f). + +$ git remote remove sebsauvage + +$ git remote -v +origin https://github.com/shaarli/Shaarli (fetch) +origin https://github.com/shaarli/Shaarli (push) + +$ git gc +Counting objects: 3317, done. +Delta compression using up to 8 threads. +Compressing objects: 100% (1237/1237), done. +Writing objects: 100% (3317/3317), done. +Total 3317 (delta 2050), reused 3301 (delta 2034)to +``` + +#### Step 3: configuration +After migrating, access your fresh Shaarli installation from a web browser; the configuration will then be automatically updated, and new settings added to `data/config.php` (see [Shaarli configuration](Shaarli-configuration.html) for more details). diff --git a/doc/Upgrade-from-original-sebsauvage-Shaarli.html b/doc/Upgrade-from-original-sebsauvage-Shaarli.html deleted file mode 100644 index db69a0ed..00000000 --- a/doc/Upgrade-from-original-sebsauvage-Shaarli.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - Shaarli – Upgrade from original sebsauvage Shaarli - - - - - - -

    Upgrade from original sebsauvage Shaarli

    -
      -
    • Backup your original data/ directory.
    • -
    • Install and setup the Shaarli community fork.
    • -
    • Copy your original data directory over the new installation.
    • -
    - - diff --git a/doc/Upgrade-from-original-sebsauvage-Shaarli.md b/doc/Upgrade-from-original-sebsauvage-Shaarli.md deleted file mode 100644 index 6ae0c67b..00000000 --- a/doc/Upgrade-from-original-sebsauvage-Shaarli.md +++ /dev/null @@ -1,4 +0,0 @@ -#Upgrade from original sebsauvage Shaarli - * Backup your original `data/` directory. - * [Install](https://github.com/shaarli/Shaarli#installation--upgrade) and setup the Shaarli community fork.[](.html) - * Copy your original `data` directory over the new installation. diff --git a/doc/Usage.html b/doc/Usage.html index 2befaa02..63f21d93 100644 --- a/doc/Usage.html +++ b/doc/Usage.html @@ -15,13 +15,13 @@ diff --git a/doc/_Footer.html b/doc/_Footer.html index a054cc53..e8a62d2a 100644 --- a/doc/_Footer.html +++ b/doc/_Footer.html @@ -15,13 +15,13 @@ -

    _Footer
    -Shaarli, the personal, minimalist, super-fast, no-database delicious clone

    +

    _Footer
    +Shaarli, the personal, minimalist, super-fast, database-free bookmarking service

    diff --git a/doc/_Footer.md b/doc/_Footer.md index 29c39bb6..50fa4f56 100644 --- a/doc/_Footer.md +++ b/doc/_Footer.md @@ -1,2 +1,2 @@ #_Footer -_Shaarli, the personal, minimalist, super-fast, no-database delicious clone_ +_Shaarli, the personal, minimalist, super-fast, database-free bookmarking service_ diff --git a/doc/_Sidebar.html b/doc/_Sidebar.html index 89c2cf8a..bb6dad93 100644 --- a/doc/_Sidebar.html +++ b/doc/_Sidebar.html @@ -15,13 +15,13 @@

    _Sidebar

    -

    For all following examples, a development configuration will be used:

    +

    For all following configuration examples, this user/group pair will be used:

    • user:group = john:users,
    @@ -251,6 +254,24 @@ user john users; http { [...][](.html) } +

    (Optional) Increase the maximum file upload size

    +

    Some bookmark dumps generated by web browsers can be huge due to the presence of Base64-encoded images and favicons, as well as extra verbosity when nesting links in (sub-)folders.

    +

    To increase upload size, you will need to modify both nginx and PHP configuration:

    +
    # /etc/nginx/nginx.conf
    +
    +http {
    +    [...][](.html)
    +
    +    client_max_body_size 10m;
    +
    +    [...][](.html)
    +}
    +
    # /etc/php5/fpm/php.ini
    +
    +[...][](.html)
    +post_max_size = 10M
    +[...][](.html)
    +upload_max_filesize = 10M

    Minimal

    WARNING: Use for development only!

    user john users;
    @@ -350,6 +371,11 @@ http {
                 error_log   /var/log/nginx/shaarli.error.log;
             }
     
    +        location = /shaarli/favicon.ico {
    +            # serve the Shaarli favicon from its custom location
    +            alias /var/www/shaarli/images/favicon.ico;
    +        }
    +
             include deny.conf;
             include static_assets.conf;
             include php.conf;
    @@ -403,6 +429,11 @@ http {
                 error_log   /var/log/nginx/shaarli.error.log;
             }
     
    +        location = /shaarli/favicon.ico {
    +            # serve the Shaarli favicon from its custom location
    +            alias /var/www/shaarli/images/favicon.ico;
    +        }
    +
             include deny.conf;
             include static_assets.conf;
             include php.conf;
    diff --git a/doc/Server-configuration.md b/doc/Server-configuration.md
    index 1ab57a0a..df10feb2 100644
    --- a/doc/Server-configuration.md
    +++ b/doc/Server-configuration.md
    @@ -102,6 +102,12 @@ See [Server-side TLS](https://wiki.mozilla.org/Security/Server_Side_TLS#Apache)
     
     ```
     
    +### .htaccess
    +
    +Shaarli use `.htaccess` Apache files to deny access to files that shouldn't be directly accessed (datastore, config, etc.). You need the directive `AllowOverride All` in your virtual host configuration for them to work.
    +
    +**Warning**: If you use Apache 2.2 or lower, you need [mod_version](https://httpd.apache.org/docs/current/mod/mod_version.html) to be installed and enabled.[](.html)
    +
     ## LightHttpd
     
     ## Nginx
    @@ -136,7 +142,7 @@ On a development server:
     - files may be located in a user's home directory
     - in this case, make sure both Nginx and PHP-FPM are running as the local user/group!
     
    -For all following examples, a development configuration will be used:
    +For all following configuration examples, this user/group pair will be used:
     - `user:group = john:users`,
     
     which corresponds to the following service configuration:
    @@ -160,6 +166,32 @@ http {
     }
     ```
     
    +### (Optional) Increase the maximum file upload size
    +Some bookmark dumps generated by web browsers can be _huge_ due to the presence of Base64-encoded images and favicons, as well as extra verbosity when nesting links in (sub-)folders.
    +
    +To increase upload size, you will need to modify both nginx and PHP configuration:
    +
    +```nginx
    +# /etc/nginx/nginx.conf
    +
    +http {
    +    [...][](.html)
    +
    +    client_max_body_size 10m;
    +
    +    [...][](.html)
    +}
    +```
    +
    +```ini
    +# /etc/php5/fpm/php.ini
    +
    +[...][](.html)
    +post_max_size = 10M
    +[...][](.html)
    +upload_max_filesize = 10M
    +```
    +
     ### Minimal
     _WARNING: Use for development only!_ 
     
    @@ -271,6 +303,11 @@ http {
                 error_log   /var/log/nginx/shaarli.error.log;
             }
     
    +        location = /shaarli/favicon.ico {
    +            # serve the Shaarli favicon from its custom location
    +            alias /var/www/shaarli/images/favicon.ico;
    +        }
    +
             include deny.conf;
             include static_assets.conf;
             include php.conf;
    @@ -328,6 +365,11 @@ http {
                 error_log   /var/log/nginx/shaarli.error.log;
             }
     
    +        location = /shaarli/favicon.ico {
    +            # serve the Shaarli favicon from its custom location
    +            alias /var/www/shaarli/images/favicon.ico;
    +        }
    +
             include deny.conf;
             include static_assets.conf;
             include php.conf;
    diff --git a/doc/Theming.html b/doc/Theming.html
    index 13e6acf0..7cbf7aef 100644
    --- a/doc/Theming.html
    +++ b/doc/Theming.html
    @@ -119,19 +119,20 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
     
    • There should now be a my-template/ directory under the tpl/ dir, containing directly all the template files.
    -
  • Edit data/config.php to have Shaarli use this template, e.g.

    -
    $GLOBALS['config'['RAINTPL_TPL'] = 'tpl/my-template/';]('RAINTPL_TPL']-=-'tpl/my-template/';.html)
  • +
  • Edit data/config.json.php to have Shaarli use this template, in "resource" e.g.

    +
    "raintpl_tpl": "tpl\/my-template\/",
  • Community themes & templates

    Example installation: AlbinoMouse template

    diff --git a/doc/Theming.md b/doc/Theming.md index 7fb8d927..a21899c2 100644 --- a/doc/Theming.md +++ b/doc/Theming.md @@ -16,20 +16,21 @@ _WARNING - This feature is currently being worked on and will be improved in the - Find it's git clone URL or download the zip archive for the template. - In your Shaarli `tpl/` directory, run `git clone https://url/of/my-template/` or unpack the zip archive. - There should now be a `my-template/` directory under the `tpl/` dir, containing directly all the template files. -- Edit `data/config.php` to have Shaarli use this template, e.g. -```php -$GLOBALS['config'['RAINTPL_TPL'] = 'tpl/my-template/';]('RAINTPL_TPL']-=-'tpl/my-template/';.html) +- Edit `data/config.json.php` to have Shaarli use this template, in `"resource"` e.g. +```json +"raintpl_tpl": "tpl\/my-template\/", ``` ## Community themes & templates - [AkibaTech/Shaarli Superhero Theme](https://github.com/AkibaTech/Shaarli---SuperHero-Theme) - A template/theme for Shaarli[](.html) - [alexisju/albinomouse-template](https://github.com/alexisju/albinomouse-template) - A full template for Shaarli[](.html) +- [ArthurHoaro/shaarli-launch](https://github.com/ArthurHoaro/shaarli-launch) - Customizable Shaarli theme.[](.html) - [dhoko/ShaarliTemplate](https://github.com/dhoko/ShaarliTemplate) - A template/theme for Shaarli[](.html) - [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli[](.html) - [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.[](.html) +- [ManufacturaInd/shaarli-2004licious-theme](https://github.com/ManufacturaInd/shaarli-2004licious-theme) - A template/theme as a humble homage to the early looks of the del.icio.us site.[](.html) - [misterair/Limonade](https://github.com/misterair/limonade) - A fork of (legacy) Shaarli with a new template[](.html) - [mrjovanovic/serious-theme-shaarli](https://github.com/mrjovanovic/serious-theme-shaarli) - A serious theme for SHaarli.[](.html) -- [Vinm/Blue-theme-for Shaarli](https://github.com/Vinm/Blue-theme-for-Shaarli) - A template/theme for Shaarli ([unmaintained](https://github.com/Vinm/Blue-theme-for-Shaarli/issues/2), compatibility unknown)[](.html) - [vivienhaese/shaarlitheme](https://github.com/vivienhaese/shaarlitheme) - A Shaarli fork meant to be run in an openshift instance[](.html) ### Example installation: AlbinoMouse template diff --git a/index.php b/index.php index fdbdfaa2..cc448352 100644 --- a/index.php +++ b/index.php @@ -1,6 +1,6 @@ /shaarli/ define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0))); diff --git a/shaarli_version.php b/shaarli_version.php index eaab95c6..431387bb 100644 --- a/shaarli_version.php +++ b/shaarli_version.php @@ -1 +1 @@ - + -- cgit v1.2.3 From 00be9941f37e1281dacfd395ce410f9a69460abc Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 15 Dec 2016 11:18:56 +0100 Subject: Fix a regression: permalinks change when old links are edited fixes #713 --- index.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.php b/index.php index cc448352..a0a3a8c7 100644 --- a/index.php +++ b/index.php @@ -1249,10 +1249,12 @@ function renderPage($conf, $pluginManager) // Edit $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); $updated = new DateTime(); + $shortUrl = $LINKSDB[$id]['shorturl']; } else { // New link $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); $updated = null; + $shortUrl = link_small_hash($created, $id); } // Remove multiple spaces. @@ -1279,7 +1281,7 @@ function renderPage($conf, $pluginManager) 'created' => $created, 'updated' => $updated, 'tags' => str_replace(',', ' ', $tags), - 'shorturl' => link_small_hash($created, $id), + 'shorturl' => $shortUrl, ); // If title is empty, use the URL as title. -- cgit v1.2.3 From 5036cffadee0acc3c87474139361e6e359380bcd Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 15 Dec 2016 11:49:41 +0100 Subject: v0.8.2 Changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d5436c..d3157d5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +## [v0.8.2](https://github.com/shaarli/Shaarli/releases/tag/v0.8.2) - 2016-12-15 + +### Fixed + +- Editing a link created before the new ID system would change its permalink. ## [v0.8.1](https://github.com/shaarli/Shaarli/releases/tag/v0.8.1) - 2016-12-12 -- cgit v1.2.3 From 455f776a3d0f9132e5a94160b5634111d44cade4 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 15 Dec 2016 11:52:31 +0100 Subject: Bump version to v0.8.2 --- index.php | 4 ++-- shaarli_version.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.php b/index.php index a0a3a8c7..62bdffd5 100644 --- a/index.php +++ b/index.php @@ -1,6 +1,6 @@ /shaarli/ define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0))); diff --git a/shaarli_version.php b/shaarli_version.php index 431387bb..e93b0e7f 100644 --- a/shaarli_version.php +++ b/shaarli_version.php @@ -1 +1 @@ - + -- cgit v1.2.3 From 848939b7ba6e34789baa32e467042f481754e2e5 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Thu, 15 Dec 2016 10:57:11 +0100 Subject: Fixes can login function call in loginform.html Fixes #711 --- .travis.yml | 1 + tpl/loginform.html | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9ffb3d00..6ff1b20f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ cache: directories: - $HOME/.composer/cache php: + - 7.1 - 7.0 - 5.6 - 5.5 diff --git a/tpl/loginform.html b/tpl/loginform.html index a49b42d3..84176385 100644 --- a/tpl/loginform.html +++ b/tpl/loginform.html @@ -2,7 +2,7 @@ {include="includes"} - {if="!ban_canLogin()"} + {if="!ban_canLogin($conf)"} You have been banned from login after too many failed attempts. Try later. {else} -- cgit v1.2.3 From faf8bdda50ed8ed393e8840e341239aa41a139f8 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Fri, 20 Jan 2017 16:44:52 +0100 Subject: Changelog v0.8.3 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3157d5b..502fdad6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +## [v0.8.3](https://github.com/shaarli/Shaarli/releases/tag/v0.8.3) - 2017-01-20 + +### Fixed + +- PHP 7.1 compatibility: add ConfigManager parameter to anti-bruteforce function call in login template. + ## [v0.8.2](https://github.com/shaarli/Shaarli/releases/tag/v0.8.2) - 2016-12-15 ### Fixed -- cgit v1.2.3 From 63bddaad4b6578d5d9a5728cba9f2f0d552805e5 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Fri, 20 Jan 2017 16:47:36 +0100 Subject: Bump version to v0.8.3 Signed-off-by: ArthurHoaro --- index.php | 2 +- shaarli_version.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.php b/index.php index 62bdffd5..17fb4a2f 100644 --- a/index.php +++ b/index.php @@ -1,6 +1,6 @@ + -- cgit v1.2.3 From 9ff17ae20effa5d54fd8481c19518123590e3bd0 Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Mon, 27 Feb 2017 19:45:55 +0100 Subject: Add markdown_escape setting This setting allows to escape HTML in markdown rendering or not. The goal behind it is to avoid XSS issue in shared instances. More info: * the setting is set to true by default * it is set to false for anyone who already have the plugin enabled (avoid breaking existing entries) * improve the HTML sanitization when the setting is set to false - but don't consider it XSS proof * mention the setting in the plugin README --- application/Updater.php | 22 ++++++++++++ plugins/markdown/README.md | 27 +++++++++++---- plugins/markdown/markdown.php | 29 ++++++++++------ tests/Updater/UpdaterTest.php | 65 +++++++++++++++++++++++++++++++++++ tests/plugins/PluginMarkdownTest.php | 57 ++++++++++++++++++++++++++---- tests/plugins/resources/markdown.html | 6 ++-- 6 files changed, 179 insertions(+), 27 deletions(-) diff --git a/application/Updater.php b/application/Updater.php index f0d02814..555d4c25 100644 --- a/application/Updater.php +++ b/application/Updater.php @@ -256,6 +256,28 @@ class Updater return true; } + + /** + * * `markdown_escape` is a new setting, set to true as default. + * + * If the markdown plugin was already enabled, escaping is disabled to avoid + * breaking existing entries. + */ + public function updateMethodEscapeMarkdown() + { + if ($this->conf->exists('security.markdown_escape')) { + return true; + } + + if (in_array('markdown', $this->conf->get('general.enabled_plugins'))) { + $this->conf->set('security.markdown_escape', false); + } else { + $this->conf->set('security.markdown_escape', true); + } + $this->conf->write($this->isLoggedIn); + + return true; + } } /** diff --git a/plugins/markdown/README.md b/plugins/markdown/README.md index aafcf066..bc9427e2 100644 --- a/plugins/markdown/README.md +++ b/plugins/markdown/README.md @@ -50,9 +50,20 @@ If the tag `nomarkdown` is set for a shaare, it won't be converted to Markdown s > Note: this is a special tag, so it won't be displayed in link list. -### HTML rendering +### HTML escape -Markdown support HTML tags. For example: +By default, HTML tags are escaped. You can enable HTML tags rendering +by setting `security.markdwon_escape` to `false` in `data/config.json.php`: + +```json +{ + "security": { + "markdown_escape": false + } +} +``` + +With this setting, Markdown support HTML tags. For example: > strongstrike @@ -60,12 +71,14 @@ Will render as: > strongstrike -If you want to shaare HTML code, it is necessary to use inline code or code blocks. - -**If your shaared descriptions containing HTML tags before enabling the markdown plugin, -enabling it might break your page.** -> Note: HTML tags such as script, iframe, etc. are disabled for security reasons. +**Warning:** + + * This setting might present **security risks** (XSS) on shared instances, even though tags + such as script, iframe, etc should be disabled. + * If you want to shaare HTML code, it is necessary to use inline code or code blocks. + * If your shaared descriptions contained HTML tags before enabling the markdown plugin, +enabling it might break your page. ### Known issue diff --git a/plugins/markdown/markdown.php b/plugins/markdown/markdown.php index 0cf6e6e2..de7c823d 100644 --- a/plugins/markdown/markdown.php +++ b/plugins/markdown/markdown.php @@ -14,18 +14,19 @@ define('NO_MD_TAG', 'nomarkdown'); /** * Parse linklist descriptions. * - * @param array $data linklist data. + * @param array $data linklist data. + * @param ConfigManager $conf instance. * * @return mixed linklist data parsed in markdown (and converted to HTML). */ -function hook_markdown_render_linklist($data) +function hook_markdown_render_linklist($data, $conf) { foreach ($data['links'] as &$value) { if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { $value = stripNoMarkdownTag($value); continue; } - $value['description'] = process_markdown($value['description']); + $value['description'] = process_markdown($value['description'], $conf->get('security.markdown_escape', true)); } return $data; } @@ -34,17 +35,18 @@ function hook_markdown_render_linklist($data) * Parse feed linklist descriptions. * * @param array $data linklist data. + * @param ConfigManager $conf instance. * * @return mixed linklist data parsed in markdown (and converted to HTML). */ -function hook_markdown_render_feed($data) +function hook_markdown_render_feed($data, $conf) { foreach ($data['links'] as &$value) { if (!empty($value['tags']) && noMarkdownTag($value['tags'])) { $value = stripNoMarkdownTag($value); continue; } - $value['description'] = process_markdown($value['description']); + $value['description'] = process_markdown($value['description'], $conf->get('security.markdown_escape', true)); } return $data; @@ -53,11 +55,12 @@ function hook_markdown_render_feed($data) /** * Parse daily descriptions. * - * @param array $data daily data. + * @param array $data daily data. + * @param ConfigManager $conf instance. * * @return mixed daily data parsed in markdown (and converted to HTML). */ -function hook_markdown_render_daily($data) +function hook_markdown_render_daily($data, $conf) { // Manipulate columns data foreach ($data['cols'] as &$value) { @@ -66,7 +69,10 @@ function hook_markdown_render_daily($data) $value2 = stripNoMarkdownTag($value2); continue; } - $value2['formatedDescription'] = process_markdown($value2['formatedDescription']); + $value2['formatedDescription'] = process_markdown( + $value2['formatedDescription'], + $conf->get('security.markdown_escape', true) + ); } } @@ -250,7 +256,7 @@ function sanitize_html($description) $description); } $description = preg_replace( - '#(<[^>]+)on[a-z]*="[^"]*"#is', + '#(<[^>]+)on[a-z]*="?[^ "]*"?#is', '$1', $description); return $description; @@ -265,10 +271,11 @@ function sanitize_html($description) * 5. Wrap description in 'markdown' CSS class. * * @param string $description input description text. + * @param bool $escape escape HTML entities * * @return string HTML processed $description. */ -function process_markdown($description) +function process_markdown($description, $escape = true) { $parsedown = new Parsedown(); @@ -278,7 +285,7 @@ function process_markdown($description) $processedDescription = reverse_text2clickable($processedDescription); $processedDescription = unescape($processedDescription); $processedDescription = $parsedown - ->setMarkupEscaped(false) + ->setMarkupEscaped($escape) ->setBreaksEnabled(true) ->text($processedDescription); $processedDescription = sanitize_html($processedDescription); diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php index 4948fe52..17d1ba81 100644 --- a/tests/Updater/UpdaterTest.php +++ b/tests/Updater/UpdaterTest.php @@ -385,4 +385,69 @@ $GLOBALS[\'privateLinkByDefault\'] = true;'; $this->assertTrue($updater->updateMethodDatastoreIds()); $this->assertEquals($checksum, hash_file('sha1', self::$testDatastore)); } + + /** + * Test updateMethodEscapeMarkdown with markdown plugin enabled + * => setting markdown_escape set to false. + */ + public function testEscapeMarkdownSettingToFalse() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + + $this->conf->set('general.enabled_plugins', ['markdown']); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodEscapeMarkdown()); + $this->assertFalse($this->conf->get('security.markdown_escape')); + + // reload from file + $this->conf = new ConfigManager($sandboxConf); + $this->assertFalse($this->conf->get('security.markdown_escape')); + } + + /** + * Test updateMethodEscapeMarkdown with markdown plugin disabled + * => setting markdown_escape set to true. + */ + public function testEscapeMarkdownSettingToTrue() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + + $this->conf->set('general.enabled_plugins', []); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodEscapeMarkdown()); + $this->assertTrue($this->conf->get('security.markdown_escape')); + + // reload from file + $this->conf = new ConfigManager($sandboxConf); + $this->assertTrue($this->conf->get('security.markdown_escape')); + } + + /** + * Test updateMethodEscapeMarkdown with nothing to do (setting already enabled) + */ + public function testEscapeMarkdownSettingNothingToDoEnabled() + { + $sandboxConf = 'sandbox/config'; + copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); + $this->conf = new ConfigManager($sandboxConf); + $this->conf->set('security.markdown_escape', true); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodEscapeMarkdown()); + $this->assertTrue($this->conf->get('security.markdown_escape')); + } + + /** + * Test updateMethodEscapeMarkdown with nothing to do (setting already disabled) + */ + public function testEscapeMarkdownSettingNothingToDoDisabled() + { + $this->conf->set('security.markdown_escape', false); + $updater = new Updater([], [], $this->conf, true); + $this->assertTrue($updater->updateMethodEscapeMarkdown()); + $this->assertFalse($this->conf->get('security.markdown_escape')); + } } diff --git a/tests/plugins/PluginMarkdownTest.php b/tests/plugins/PluginMarkdownTest.php index 17ef2280..f1e1acf8 100644 --- a/tests/plugins/PluginMarkdownTest.php +++ b/tests/plugins/PluginMarkdownTest.php @@ -13,12 +13,18 @@ require_once 'plugins/markdown/markdown.php'; */ class PluginMarkdownTest extends PHPUnit_Framework_TestCase { + /** + * @var ConfigManager instance. + */ + protected $conf; + /** * Reset plugin path */ function setUp() { PluginManager::$PLUGINS_PATH = 'plugins'; + $this->conf = new ConfigManager('tests/utils/config/configJson'); } /** @@ -36,7 +42,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase ), ); - $data = hook_markdown_render_linklist($data); + $data = hook_markdown_render_linklist($data, $this->conf); $this->assertNotFalse(strpos($data['links'][0]['description'], '

    ')); $this->assertNotFalse(strpos($data['links'][0]['description'], '

    ')); } @@ -61,7 +67,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase ), ); - $data = hook_markdown_render_daily($data); + $data = hook_markdown_render_daily($data, $this->conf); $this->assertNotFalse(strpos($data['cols'][0][0]['formatedDescription'], '

    ')); $this->assertNotFalse(strpos($data['cols'][0][0]['formatedDescription'], '

    ')); } @@ -110,6 +116,8 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase $output = escape($input); $input .= 'link'; $output .= 'link'; + $input .= 'link'; + $output .= 'link'; $this->assertEquals($output, sanitize_html($input)); // Do not touch escaped HTML. $input = escape($input); @@ -130,10 +138,10 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase )) ); - $processed = hook_markdown_render_linklist($data); + $processed = hook_markdown_render_linklist($data, $this->conf); $this->assertEquals($str, $processed['links'][0]['description']); - $processed = hook_markdown_render_feed($data); + $processed = hook_markdown_render_feed($data, $this->conf); $this->assertEquals($str, $processed['links'][0]['description']); $data = array( @@ -151,7 +159,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase ), ); - $data = hook_markdown_render_daily($data); + $data = hook_markdown_render_daily($data, $this->conf); $this->assertEquals($str, $data['cols'][0][0]['formatedDescription']); } @@ -169,7 +177,7 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase )) ); - $data = hook_markdown_render_feed($data); + $data = hook_markdown_render_feed($data, $this->conf); $this->assertContains('', $data['links'][0]['description']); } @@ -185,4 +193,41 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase $data = process_markdown($md); $this->assertEquals($html, $data); } + + /** + * Make sure that the HTML tags are escaped. + */ + public function testMarkdownWithHtmlEscape() + { + $md = '**strong** strong'; + $html = '

    strong <strong>strong</strong>

    '; + $data = array( + 'links' => array( + 0 => array( + 'description' => $md, + ), + ), + ); + $data = hook_markdown_render_linklist($data, $this->conf); + $this->assertEquals($html, $data['links'][0]['description']); + } + + /** + * Make sure that the HTML tags aren't escaped with the setting set to false. + */ + public function testMarkdownWithHtmlNoEscape() + { + $this->conf->set('security.markdown_escape', false); + $md = '**strong** strong'; + $html = '

    strong strong

    '; + $data = array( + 'links' => array( + 0 => array( + 'description' => $md, + ), + ), + ); + $data = hook_markdown_render_linklist($data, $this->conf); + $this->assertEquals($html, $data['links'][0]['description']); + } } diff --git a/tests/plugins/resources/markdown.html b/tests/plugins/resources/markdown.html index c0fbe7f4..07a5a32e 100644 --- a/tests/plugins/resources/markdown.html +++ b/tests/plugins/resources/markdown.html @@ -12,11 +12,11 @@
  • two
  • three
  • four
  • -
  • foo #foobar
  • +
  • foo <a href="?addtag=foobar" title="Hashtag foobar">#foobar</a>
  • -

    #foobar foo lol #foo #bar

    -

    fsdfs http://link.tld #foobar http://link.tld

    +

    <a href="?addtag=foobar" title="Hashtag foobar">#foobar</a> foo lol #foo <a href="?addtag=bar" title="Hashtag bar">#bar</a>

    +

    fsdfs http://link.tld <a href="?addtag=foobar" title="Hashtag foobar">#foobar</a> http://link.tld

    http://link.tld #foobar
     next #foo

    Block:

    -- cgit v1.2.3 From 6b7ddb487126fd8f5be22e729ec8e0a2b639891b Mon Sep 17 00:00:00 2001 From: VirtualTam Date: Sat, 4 Mar 2017 09:42:26 +0100 Subject: Bump version to 0.8.4 Signed-off-by: VirtualTam --- CHANGELOG.md | 5 +++++ index.php | 2 +- shaarli_version.php | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 502fdad6..1340db56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +## [v0.8.4](https://github.com/shaarli/Shaarli/releases/tag/v0.8.4) - 2017-03-04 +### Security +- Markdown plugin: escape HTML entities by default + + ## [v0.8.3](https://github.com/shaarli/Shaarli/releases/tag/v0.8.3) - 2017-01-20 ### Fixed diff --git a/index.php b/index.php index 17fb4a2f..b4ccd1bd 100644 --- a/index.php +++ b/index.php @@ -1,6 +1,6 @@ + -- cgit v1.2.3 From 8868f3ca461011a8fb6dd9f90b60ed697ab52fc5 Mon Sep 17 00:00:00 2001 From: VirtualTam Date: Sat, 4 Mar 2017 09:52:48 +0100 Subject: UpdaterTest: ensure PHP 5.3 compatibility Signed-off-by: VirtualTam --- tests/Updater/UpdaterTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php index 17d1ba81..a3e8a4d2 100644 --- a/tests/Updater/UpdaterTest.php +++ b/tests/Updater/UpdaterTest.php @@ -396,8 +396,8 @@ $GLOBALS[\'privateLinkByDefault\'] = true;'; copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); $this->conf = new ConfigManager($sandboxConf); - $this->conf->set('general.enabled_plugins', ['markdown']); - $updater = new Updater([], [], $this->conf, true); + $this->conf->set('general.enabled_plugins', array('markdown')); + $updater = new Updater(array(), array(), $this->conf, true); $this->assertTrue($updater->updateMethodEscapeMarkdown()); $this->assertFalse($this->conf->get('security.markdown_escape')); @@ -416,8 +416,8 @@ $GLOBALS[\'privateLinkByDefault\'] = true;'; copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); $this->conf = new ConfigManager($sandboxConf); - $this->conf->set('general.enabled_plugins', []); - $updater = new Updater([], [], $this->conf, true); + $this->conf->set('general.enabled_plugins', array()); + $updater = new Updater(array(), array(), $this->conf, true); $this->assertTrue($updater->updateMethodEscapeMarkdown()); $this->assertTrue($this->conf->get('security.markdown_escape')); @@ -435,7 +435,7 @@ $GLOBALS[\'privateLinkByDefault\'] = true;'; copy(self::$configFile . '.json.php', $sandboxConf . '.json.php'); $this->conf = new ConfigManager($sandboxConf); $this->conf->set('security.markdown_escape', true); - $updater = new Updater([], [], $this->conf, true); + $updater = new Updater(array(), array(), $this->conf, true); $this->assertTrue($updater->updateMethodEscapeMarkdown()); $this->assertTrue($this->conf->get('security.markdown_escape')); } @@ -446,7 +446,7 @@ $GLOBALS[\'privateLinkByDefault\'] = true;'; public function testEscapeMarkdownSettingNothingToDoDisabled() { $this->conf->set('security.markdown_escape', false); - $updater = new Updater([], [], $this->conf, true); + $updater = new Updater(array(), array(), $this->conf, true); $this->assertTrue($updater->updateMethodEscapeMarkdown()); $this->assertFalse($this->conf->get('security.markdown_escape')); } -- cgit v1.2.3