]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
merge
authorArthurHoaro <arthur@hoa.ro>
Sun, 18 Dec 2016 11:40:27 +0000 (12:40 +0100)
committerArthurHoaro <arthur@hoa.ro>
Sun, 18 Dec 2016 11:40:27 +0000 (12:40 +0100)
68 files changed:
.gitignore
.travis.yml
CHANGELOG.md
COPYING
application/.htaccess
application/FeedBuilder.php
application/LinkDB.php
application/LinkFilter.php
application/LinkUtils.php
application/NetscapeBookmarkUtils.php
application/Updater.php
application/Utils.php
cache/.htaccess
data/.htaccess
doc/Community-&-Related-software.html
doc/Community-&-Related-software.md
doc/Download-and-Installation.html
doc/Download-and-Installation.md
doc/Release-Shaarli.html
doc/Release-Shaarli.md
doc/Server-configuration.html
doc/Server-configuration.md
doc/Theming.html
doc/Theming.md
docker/.htaccess
docker/development/Dockerfile
docker/development/nginx.conf
docker/production/Dockerfile
docker/production/nginx.conf
docker/production/stable/Dockerfile
docker/production/stable/nginx.conf
inc/shaarli.css
index.php
pagecache/.htaccess
plugins/addlink_toolbar/addlink_toolbar.css [deleted file]
plugins/addlink_toolbar/addlink_toolbar.php
plugins/archiveorg/archiveorg.html
plugins/demo_plugin/demo_plugin.php
plugins/isso/isso.php
plugins/markdown/README.md
plugins/markdown/markdown.meta
plugins/markdown/markdown.php
plugins/playvideos/playvideos.php
plugins/qrcode/qrcode.html
plugins/readityourself/readityourself.html
plugins/wallabag/wallabag.html
shaarli_version.php
tests/.htaccess
tests/FeedBuilderTest.php
tests/LinkDBTest.php
tests/LinkFilterTest.php
tests/NetscapeBookmarkUtils/BookmarkExportTest.php
tests/NetscapeBookmarkUtils/BookmarkImportTest.php
tests/Updater/UpdaterTest.php
tests/plugins/PluginIssoTest.php
tests/plugins/PluginMarkdownTest.php
tests/utils/ReferenceLinkDB.php
tmp/.htaccess
tpl/daily.html
tpl/editlink.html
tpl/feed.atom.html
tpl/includes.html
tpl/linklist.html
tpl/linklist.paging.html
tpl/loginform.html
tpl/page.header.html
tpl/pluginsadmin.html
tpl/tools.html

index 095aaded26909ee9583bfe12130b72993625379a..9121905d84603c1943753a4e7bd8d981af6f9aec 100644 (file)
@@ -17,7 +17,7 @@ composer.lock
 vendor/
 
 # Release archives
-*.tar
+*.tar.gz
 *.zip
 
 # Development and test resources
index 9ffb3d007b971db6e450c9744ffd3e778be3b7b7..6ff1b20f564e130e345821eaefe75a0fb829d55f 100644 (file)
@@ -4,6 +4,7 @@ cache:
   directories:
     - $HOME/.composer/cache
 php:
+  - 7.1
   - 7.0
   - 5.6
   - 5.5
index d42d6a75e2fa42c02d9be9e27b763079a8795a94..21d5436ccdefeae86fe4dc72385b732816e8c324 100644 (file)
@@ -5,7 +5,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
 
-## [v0.8.1](https://github.com/shaarli/Shaarli/releases/tag/v0.8.1) - UNPUBLISHED
+## [v0.9.0](https://github.com/shaarli/Shaarli/releases/tag/v0.9.0) - UNPUBLISHED
+
+### Added
+
+### Changed
+
+### Fixed
+
+
+## [v0.8.1](https://github.com/shaarli/Shaarli/releases/tag/v0.8.1) - 2016-12-12
+
+> Note: this version will create an automatic backup of your database if anything goes wrong. 
+
 ### Added
 - Add CHANGELOG.md to track the whole project's history
 - Enable Composer cache for Travis builds
@@ -13,20 +25,39 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Plugins:
     - Add an [Isso](https://posativ.org/isso/) plugin to enable user comments on permalinks
     - Allow defining init functions, e.g. for performing checks and error processing
-
-### Changed
-- Cleanup `{loop}` declarations in templates
+    - Add a Piwik plugin for analytics.
+    - Markdown: add warning notice regarding HTML rendering
+- Meta tag to *not* send the referrer to external resources.
+
+### Changed
+- Link ID complete refactoring:
+    - Links now have a numeric ID instead of dates
+    - Short URLs are now created once and can't change over time (previous URL are kept)
+- Templates: 
+    - Changed placeholder behaviour for: `buttons_toolbar`, `fields_toolbar` and `action_plugin`
+    - Cleanup `{loop}` declarations in templates
+    - Tools: hide Firefox Social button when not in HTTPS
+    - Firefox Social: show Shaarli's title when shaaring using Firefox Social
 - Release archives now have the same structure as GitHub-generated archives:
     - archives contain a `Shaarli` directory, itself containing sources + dependencies
     - the tarball is now gzipped
+- Plugins:
+    - Markdown: Parsedown library is now imported through Composer
 - Minor code cleanup: PHPDoc, spelling, unused variables, etc.
+- Docker: explicitly set the maximum file upload size to 10 MiB
 
 ### Fixed
 - Fix the server `<self>` value in Atom/RSS feeds
 - Plugins:
     - Tools: only display parameter description when it exists
     - archive.org: do not propose archival of private notes
+    - Markdown: 
+        - render links properly in code blocks
+        - bug regarding the `nomarkdown` tag
+    - W3C compliance
 - Use absolute URL for hashtags in RSS and ATOM feeds
+- Docker: specify the location of the favicon
+- ATOM feed: remove new line between content tag and data
 
 ### Security
 - Allow whitelisting trusted IPs, else continue banning clients upon login failure
diff --git a/COPYING b/COPYING
index 5a825402b16133fef72e047ed37a71589a7f8b7f..d8241e5092e95eb44d0b239eaaa9a9d797cd0668 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -72,6 +72,10 @@ Files: plugins/wallabag/wallabag.png
 License: MIT License (http://opensource.org/licenses/MIT)
 Copyright: (C) 2015 Nicolas Lœuillet - https://github.com/wallabag/wallabag
 
+Files: plugins/markdown/Parsedown.php
+License: MIT License (http://opensource.org/licenses/MIT)
+Copyright: (C) 2015 Emanuil Rusev - https://github.com/erusev/parsedown
+
 Files: tpl/default/img/sad_star.png
 License: MIT License (http://opensource.org/licenses/MIT)
 Copyright: (C) 2015 kalvn - https://github.com/kalvn/Shaarli-Material
index b584d98c56a27ebb09c963313876fd8d1a3e9987..f601c1eeee5ff6e42f4eccdd43f76169fdf0ba0a 100644 (file)
@@ -1,2 +1,13 @@
-Allow from none
-Deny from all
+<IfModule version_module>
+  <IfVersion >= 2.4>
+     Require all denied
+  </IfVersion>
+  <IfVersion < 2.4>
+     Allow from none
+     Deny from all
+  </IfVersion>
+</IfModule>
+
+<IfModule !version_module>
+    Require all denied
+</IfModule>
index 4036a7cccdf3e77fc1a026adc5ddff93c9845fde..b0aa5764e9329070f6399797ae1a3d24b846206c 100644 (file)
@@ -156,12 +156,12 @@ class FeedBuilder
         $link['description']  = format_description($link['description'], '', $pageaddr);
         $link['description'] .= PHP_EOL .'<br>&#8212; '. $permalink;
 
-        $pubDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
+        $pubDate = $link['created'];
         $link['pub_iso_date'] = $this->getIsoDate($pubDate);
 
         // atom:entry elements MUST contain exactly one atom:updated element.
         if (!empty($link['updated'])) {
-            $upDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['updated']);
+            $upDate = $link['updated'];
             $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM);
         } else {
             $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);;
index c8b162b6b4931678fde97767cfc3775483e07efb..1e13286ad31808a778c41eab2bd4b6533e7775bb 100644 (file)
@@ -6,15 +6,15 @@
  *
  * Example:
  *    $myLinks = new LinkDB();
- *    echo $myLinks['20110826_161819']['title'];
+ *    echo $myLinks[350]['title'];
  *    foreach ($myLinks as $link)
  *       echo $link['title'].' at url '.$link['url'].'; description:'.$link['description'];
  *
  * Available keys:
+ *  - id:       primary key, incremental integer identifier (persistent)
  *  - description: description of the entry
- *  - linkdate: creation date of this entry, format: YYYYMMDD_HHMMSS
- *              (e.g.'20110914_192317')
- *  - updated:  last modification date of this entry, format: YYYYMMDD_HHMMSS
+ *  - created:  creation date of this entry, DateTime object.
+ *  - updated:  last modification date of this entry, DateTime object.
  *  - private:  Is this link private? 0=no, other value=yes
  *  - tags:     tags attached to this entry (separated by spaces)
  *  - title     Title of the link
  *              Can be absolute or relative.
  *              Relative URLs are permalinks (e.g.'?m-ukcw')
  *  - real_url  Absolute processed URL.
+ *  - shorturl  Permalink smallhash
  *
  * Implements 3 interfaces:
  *  - ArrayAccess: behaves like an associative array;
  *  - Countable:   there is a count() method;
  *  - Iterator:    usable in foreach () loops.
+ *
+ * ID mechanism:
+ *   ArrayAccess is implemented in a way that will allow to access a link
+ *   with the unique identifier ID directly with $link[ID].
+ *   Note that it's not the real key of the link array attribute.
+ *   This mechanism is in place to have persistent link IDs,
+ *   even though the internal array is reordered by date.
+ *   Example:
+ *     - DB: link #1 (2010-01-01) link #2 (2016-01-01)
+ *     - Order: #2 #1
+ *     - Import links containing: link #3 (2013-01-01)
+ *     - New DB: link #1 (2010-01-01) link #2 (2016-01-01) link #3 (2013-01-01)
+ *     - Real order: #2 #3 #1
  */
 class LinkDB implements Iterator, Countable, ArrayAccess
 {
@@ -47,11 +61,17 @@ class LinkDB implements Iterator, Countable, ArrayAccess
     //  - value: associative array (keys: title, description...)
     private $links;
 
-    // List of all recorded URLs (key=url, value=linkdate)
-    // for fast reserve search (url-->linkdate)
+    // List of all recorded URLs (key=url, value=link offset)
+    // for fast reserve search (url-->link offset)
     private $urls;
 
-    // List of linkdate keys (for the Iterator interface implementation)
+    /**
+     * @var array List of all links IDS mapped with their array offset.
+     *            Map: id->offset.
+     */
+    protected $ids;
+
+    // List of offset keys (for the Iterator interface implementation)
     private $keys;
 
     // Position in the $this->keys array (for the Iterator interface)
@@ -121,14 +141,26 @@ class LinkDB implements Iterator, Countable, ArrayAccess
         if (!$this->loggedIn) {
             die('You are not authorized to add a link.');
         }
-        if (empty($value['linkdate']) || empty($value['url'])) {
-            die('Internal Error: A link should always have a linkdate and URL.');
+        if (!isset($value['id']) || empty($value['url'])) {
+            die('Internal Error: A link should always have an id and URL.');
         }
-        if (empty($offset)) {
-            die('You must specify a key.');
+        if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) {
+            die('You must specify an integer as a key.');
+        }
+        if (! empty($offset) && $offset !== $value['id']) {
+            die('Array offset and link ID must be equal.');
+        }
+
+        // If the link exists, we reuse the real offset, otherwise new entry
+        $existing = $this->getLinkOffset($offset);
+        if ($existing !== null) {
+            $offset = $existing;
+        } else {
+            $offset = count($this->links);
         }
         $this->links[$offset] = $value;
-        $this->urls[$value['url']]=$offset;
+        $this->urls[$value['url']] = $offset;
+        $this->ids[$value['id']] = $offset;
     }
 
     /**
@@ -136,7 +168,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
      */
     public function offsetExists($offset)
     {
-        return array_key_exists($offset, $this->links);
+        return array_key_exists($this->getLinkOffset($offset), $this->links);
     }
 
     /**
@@ -148,9 +180,11 @@ class LinkDB implements Iterator, Countable, ArrayAccess
             // TODO: raise an exception
             die('You are not authorized to delete a link.');
         }
-        $url = $this->links[$offset]['url'];
+        $realOffset = $this->getLinkOffset($offset);
+        $url = $this->links[$realOffset]['url'];
         unset($this->urls[$url]);
-        unset($this->links[$offset]);
+        unset($this->ids[$realOffset]);
+        unset($this->links[$realOffset]);
     }
 
     /**
@@ -158,7 +192,8 @@ class LinkDB implements Iterator, Countable, ArrayAccess
      */
     public function offsetGet($offset)
     {
-        return isset($this->links[$offset]) ? $this->links[$offset] : null;
+        $realOffset = $this->getLinkOffset($offset);
+        return isset($this->links[$realOffset]) ? $this->links[$realOffset] : null;
     }
 
     /**
@@ -166,7 +201,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
      */
     public function current()
     {
-        return $this->links[$this->keys[$this->position]];
+        return $this[$this->keys[$this->position]];
     }
 
     /**
@@ -192,8 +227,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
      */
     public function rewind()
     {
-        $this->keys = array_keys($this->links);
-        rsort($this->keys);
+        $this->keys = array_keys($this->ids);
         $this->position = 0;
     }
 
@@ -219,6 +253,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
         // Create a dummy database for example
         $this->links = array();
         $link = array(
+            'id' => 1,
             'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone',
             'url'=>'https://github.com/shaarli/Shaarli/wiki',
             'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login.
@@ -227,20 +262,23 @@ To learn how to use Shaarli, consult the link "Help/documentation" at the bottom
 
 You use the community supported version of the original Shaarli project, by Sebastien Sauvage.',
             'private'=>0,
-            'linkdate'=> date('Ymd_His'),
+            'created'=> new DateTime(),
             'tags'=>'opensource software'
         );
-        $this->links[$link['linkdate']] = $link;
+        $link['shorturl'] = link_small_hash($link['created'], $link['id']);
+        $this->links[1] = $link;
 
         $link = array(
+            'id' => 0,
             'title'=>'My secret stuff... - Pastebin.com',
             'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
             'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.',
             'private'=>1,
-            'linkdate'=> date('Ymd_His', strtotime('-1 minute')),
-            'tags'=>'secretstuff'
+            'created'=> new DateTime('1 minute ago'),
+            'tags'=>'secretstuff',
         );
-        $this->links[$link['linkdate']] = $link;
+        $link['shorturl'] = link_small_hash($link['created'], $link['id']);
+        $this->links[0] = $link;
 
         // Write database to disk
         $this->write();
@@ -251,7 +289,6 @@ You use the community supported version of the original Shaarli project, by Seba
      */
     private function read()
     {
-
         // Public links are hidden and user not logged in => nothing to show
         if ($this->hidePublicLinks && !$this->loggedIn) {
             $this->links = array();
@@ -269,23 +306,13 @@ You use the community supported version of the original Shaarli project, by Seba
                        strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
         }
 
-        // If user is not logged in, filter private links.
-        if (!$this->loggedIn) {
-            $toremove = array();
-            foreach ($this->links as $link) {
-                if ($link['private'] != 0) {
-                    $toremove[] = $link['linkdate'];
-                }
-            }
-            foreach ($toremove as $linkdate) {
-                unset($this->links[$linkdate]);
+        $toremove = array();
+        foreach ($this->links as $key => &$link) {
+            if (! $this->loggedIn && $link['private'] != 0) {
+                // Transition for not upgraded databases.
+                $toremove[] = $key;
+                continue;
             }
-        }
-
-        $this->urls = array();
-        foreach ($this->links as &$link) {
-            // Keep the list of the mapping URLs-->linkdate up-to-date.
-            $this->urls[$link['url']] = $link['linkdate'];
 
             // Sanitize data fields.
             sanitizeLink($link);
@@ -307,7 +334,24 @@ You use the community supported version of the original Shaarli project, by Seba
             else {
                 $link['real_url'] = $link['url'];
             }
+
+            // To be able to load links before running the update, and prepare the update
+            if (! isset($link['created'])) {
+                $link['id'] = $link['linkdate'];
+                $link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']);
+                if (! empty($link['updated'])) {
+                    $link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']);
+                }
+                $link['shorturl'] = smallHash($link['linkdate']);
+            }
+        }
+
+        // If user is not logged in, filter private links.
+        foreach ($toremove as $offset) {
+            unset($this->links[$offset]);
         }
+
+        $this->reorder();
     }
 
     /**
@@ -430,7 +474,7 @@ You use the community supported version of the original Shaarli project, by Seba
             $request = '';
         }
 
-        $linkFilter = new LinkFilter($this->links);
+        $linkFilter = new LinkFilter($this);
         return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
     }
 
@@ -467,12 +511,64 @@ You use the community supported version of the original Shaarli project, by Seba
     public function days()
     {
         $linkDays = array();
-        foreach (array_keys($this->links) as $day) {
-            $linkDays[substr($day, 0, 8)] = 0;
+        foreach ($this->links as $link) {
+            $linkDays[$link['created']->format('Ymd')] = 0;
         }
         $linkDays = array_keys($linkDays);
         sort($linkDays);
 
         return $linkDays;
     }
+
+    /**
+     * Reorder links by creation date (newest first).
+     *
+     * Also update the urls and ids mapping arrays.
+     *
+     * @param string $order ASC|DESC
+     */
+    public function reorder($order = 'DESC')
+    {
+        $order = $order === 'ASC' ? -1 : 1;
+        // Reorder array by dates.
+        usort($this->links, function($a, $b) use ($order) {
+            return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
+        });
+
+        $this->urls = array();
+        $this->ids = array();
+        foreach ($this->links as $key => $link) {
+            $this->urls[$link['url']] = $key;
+            $this->ids[$link['id']] = $key;
+        }
+    }
+
+    /**
+     * Return the next key for link creation.
+     * E.g. If the last ID is 597, the next will be 598.
+     *
+     * @return int next ID.
+     */
+    public function getNextId()
+    {
+        if (!empty($this->ids)) {
+            return max(array_keys($this->ids)) + 1;
+        }
+        return 0;
+    }
+
+    /**
+     * Returns a link offset in links array from its unique ID.
+     *
+     * @param int $id Persistent ID of a link.
+     *
+     * @return int Real offset in local array, or null if doesn't exist.
+     */
+    protected function getLinkOffset($id)
+    {
+        if (isset($this->ids[$id])) {
+            return $this->ids[$id];
+        }
+        return null;
+    }
 }
index d4fe28df6d4968af4ac673110022ab35d2be9488..daa6d9cc26a8ed139f34581ff619d98969415073 100644 (file)
@@ -33,12 +33,12 @@ class LinkFilter
     public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}';
 
     /**
-     * @var array all available links.
+     * @var LinkDB all available links.
      */
     private $links;
 
     /**
-     * @param array $links initialization.
+     * @param LinkDB $links initialization.
      */
     public function __construct($links)
     {
@@ -94,18 +94,16 @@ class LinkFilter
     private function noFilter($privateonly = false)
     {
         if (! $privateonly) {
-            krsort($this->links);
             return $this->links;
         }
 
         $out = array();
-        foreach ($this->links as $value) {
+        foreach ($this->links as $key => $value) {
             if ($value['private']) {
-                $out[$value['linkdate']] = $value;
+                $out[$key] = $value;
             }
         }
 
-        krsort($out);
         return $out;
     }
 
@@ -121,10 +119,10 @@ class LinkFilter
     private function filterSmallHash($smallHash)
     {
         $filtered = array();
-        foreach ($this->links as $l) {
-            if ($smallHash == smallHash($l['linkdate'])) {
+        foreach ($this->links as $key => $l) {
+            if ($smallHash == $l['shorturl']) {
                 // Yes, this is ugly and slow
-                $filtered[$l['linkdate']] = $l;
+                $filtered[$key] = $l;
                 return $filtered;
             }
         }
@@ -188,7 +186,7 @@ class LinkFilter
         $keys = array('title', 'description', 'url', 'tags');
 
         // Iterate over every stored link.
-        foreach ($this->links as $link) {
+        foreach ($this->links as $id => $link) {
 
             // ignore non private links when 'privatonly' is on.
             if (! $link['private'] && $privateonly === true) {
@@ -222,11 +220,10 @@ class LinkFilter
             }
 
             if ($found) {
-                $filtered[$link['linkdate']] = $link;
+                $filtered[$id] = $link;
             }
         }
 
-        krsort($filtered);
         return $filtered;
     }
 
@@ -256,7 +253,7 @@ class LinkFilter
             return $filtered;
         }
 
-        foreach ($this->links as $link) {
+        foreach ($this->links as $key => $link) {
             // ignore non private links when 'privatonly' is on.
             if (! $link['private'] && $privateonly === true) {
                 continue;
@@ -278,10 +275,9 @@ class LinkFilter
             }
 
             if ($found) {
-                $filtered[$link['linkdate']] = $link;
+                $filtered[$key] = $link;
             }
         }
-        krsort($filtered);
         return $filtered;
     }
 
@@ -304,13 +300,14 @@ class LinkFilter
         }
 
         $filtered = array();
-        foreach ($this->links as $l) {
-            if (startsWith($l['linkdate'], $day)) {
-                $filtered[$l['linkdate']] = $l;
+        foreach ($this->links as $key => $l) {
+            if ($l['created']->format('Ymd') == $day) {
+                $filtered[$key] = $l;
             }
         }
-        ksort($filtered);
-        return $filtered;
+
+        // sort by date ASC
+        return array_reverse($filtered, true);
     }
 
     /**
index 9d9ae3cb29f603f6a82a8125bf3cc4b8860c1183..cf58f8083355af37e0a9271cf1168823252708dc 100644 (file)
@@ -169,3 +169,16 @@ function space2nbsp($text)
 function format_description($description, $redirector = '', $indexUrl = '') {
     return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $indexUrl)));
 }
+
+/**
+ * Generate a small hash for a link.
+ *
+ * @param DateTime $date Link creation date.
+ * @param int      $id   Link ID.
+ *
+ * @return string the small hash generated from link data.
+ */
+function link_small_hash($date, $id)
+{
+    return smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id);
+}
index dd21f05b1d9a66f4df53af4576003bc23ae33a35..f21ee359b2b1f8975346dbdf32c9419c42306f06 100644 (file)
@@ -38,7 +38,7 @@ class NetscapeBookmarkUtils
             if ($link['private'] == 0 && $selection == 'private') {
                 continue;
             }
-            $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
+            $date = $link['created'];
             $link['timestamp'] = $date->getTimestamp();
             $link['taglist'] = str_replace(' ', ',', $link['tags']);
 
@@ -82,6 +82,143 @@ class NetscapeBookmarkUtils
         return $status;
     }
 
+    /**
+     * Imports Web bookmarks from an uploaded Netscape bookmark dump
+     *
+     * @param array  $post      Server $_POST parameters
+     * @param array  $files     Server $_FILES parameters
+     * @param LinkDB $linkDb    Loaded LinkDB instance
+     * @param string $pagecache Page cache
+     *
+     * @return string Summary of the bookmark import status
+     */
+    public static function import($post, $files, $linkDb, $pagecache)
+    {
+        $filename = $files['filetoupload']['name'];
+        $filesize = $files['filetoupload']['size'];
+        $data = file_get_contents($files['filetoupload']['tmp_name']);
+
+        if (strpos($data, '<!DOCTYPE NETSCAPE-Bookmark-file-1>') === false) {
+            return self::importStatus($filename, $filesize);
+        }
+
+        // Overwrite existing links?
+        $overwrite = ! empty($post['overwrite']);
+
+        // Add tags to all imported links?
+        if (empty($post['default_tags'])) {
+            $defaultTags = array();
+        } else {
+            $defaultTags = preg_split(
+                '/[\s,]+/',
+                escape($post['default_tags'])
+            );
+        }
+
+        // links are imported as public by default
+        $defaultPrivacy = 0;
+
+        $parser = new NetscapeBookmarkParser(
+            true,                       // nested tag support
+            $defaultTags,               // additional user-specified tags
+            strval(1 - $defaultPrivacy) // defaultPub = 1 - defaultPrivacy
+        );
+        $bookmarks = $parser->parseString($data);
+
+        $importCount = 0;
+        $overwriteCount = 0;
+        $skipCount = 0;
+
+        foreach ($bookmarks as $bkm) {
+            $private = $defaultPrivacy;
+            if (empty($post['privacy']) || $post['privacy'] == 'default') {
+                // use value from the imported file
+                $private = $bkm['pub'] == '1' ? 0 : 1;
+            } else if ($post['privacy'] == 'private') {
+                // all imported links are private
+                $private = 1;
+            } else if ($post['privacy'] == 'public') {
+                // all imported links are public
+                $private = 0;
+            }                
+
+            $newLink = array(
+                'title' => $bkm['title'],
+                'url' => $bkm['uri'],
+                'description' => $bkm['note'],
+                'private' => $private,
+                'tags' => $bkm['tags']
+            );
+
+            $existingLink = $linkDb->getLinkFromUrl($bkm['uri']);
+
+            if ($existingLink !== false) {
+                if ($overwrite === false) {
+                    // Do not overwrite an existing link
+                    $skipCount++;
+                    continue;
+                }
+
+                // Overwrite an existing link, keep its date
+                $newLink['id'] = $existingLink['id'];
+                $newLink['created'] = $existingLink['created'];
+                $newLink['updated'] = new DateTime();
+                $linkDb[$existingLink['id']] = $newLink;
+                $importCount++;
+                $overwriteCount++;
+                continue;
+            }
+
+            // Add a new link - @ used for UNIX timestamps
+            $newLinkDate = new DateTime('@'.strval($bkm['time']));
+            $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
+            $newLink['created'] = $newLinkDate;
+            $newLink['id'] = $linkDb->getNextId();
+            $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
+            $linkDb[$newLink['id']] = $newLink;
+            $importCount++;
+        }
+
+        $linkDb->save($pagecache);
+        return self::importStatus(
+            $filename,
+            $filesize,
+            $importCount,
+            $overwriteCount,
+            $skipCount
+        );
+    }
+
+    /**
+     * Generates an import status summary
+     *
+     * @param string $filename       name of the file to import
+     * @param int    $filesize       size of the file to import
+     * @param int    $importCount    how many links were imported
+     * @param int    $overwriteCount how many links were overwritten
+     * @param int    $skipCount      how many links were skipped
+     *
+     * @return string Summary of the bookmark import status
+     */
+    private static function importStatus(
+        $filename,
+        $filesize,
+        $importCount=0,
+        $overwriteCount=0,
+        $skipCount=0
+    )
+    {
+        $status = 'File '.$filename.' ('.$filesize.' bytes) ';
+        if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
+            $status .= 'has an unknown file format. Nothing was imported.';
+        } else {
+            $status .= 'was successfully processed: '.$importCount.' links imported, ';
+            $status .= $overwriteCount.' links overwritten, ';
+            $status .= $skipCount.' links skipped.';
+        }
+        return $status;
+    }
+
     /**
      * Imports Web bookmarks from an uploaded Netscape bookmark dump
      *
index 36eddd4f12e100f86b1cc97b66f0844fd9ed643f..f0d02814b5599654b0c6638e40e02e86068a5ab7 100644 (file)
@@ -138,10 +138,10 @@ class Updater
     public function updateMethodRenameDashTags()
     {
         $linklist = $this->linkDB->filterSearch();
-        foreach ($linklist as $link) {
+        foreach ($linklist as $key => $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[$key] = $link;
         }
         $this->linkDB->save($this->conf->get('resource.page_cache'));
         return true;
@@ -215,6 +215,47 @@ class Updater
         }
         return true;
     }
+
+    /**
+     * Update the database to use the new ID system, which replaces linkdate primary keys.
+     * Also, creation and update dates are now DateTime objects (done by LinkDB).
+     *
+     * Since this update is very sensitve (changing the whole database), the datastore will be
+     * automatically backed up into the file datastore.<datetime>.php.
+     *
+     * LinkDB also adds the field 'shorturl' with the precedent format (linkdate smallhash),
+     * which will be saved by this method.
+     *
+     * @return bool true if the update is successful, false otherwise.
+     */
+    public function updateMethodDatastoreIds()
+    {
+        // up to date database
+        if (isset($this->linkDB[0])) {
+            return true;
+        }
+
+        $save = $this->conf->get('resource.data_dir') .'/datastore.'. date('YmdHis') .'.php';
+        copy($this->conf->get('resource.datastore'), $save);
+
+        $links = array();
+        foreach ($this->linkDB as $offset => $value) {
+            $links[] = $value;
+            unset($this->linkDB[$offset]);
+        }
+        $links = array_reverse($links);
+        $cpt = 0;
+        foreach ($links as $l) {
+            unset($l['linkdate']);
+            $l['id'] = $cpt;
+            $this->linkDB[$cpt++] = $l;
+        }
+
+        $this->linkDB->save($this->conf->get('resource.page_cache'));
+        $this->linkDB->reorder();
+
+        return true;
+    }
 }
 
 /**
index 0166ee2ac0e132035af72e132a50ceb4368933e7..0a5b476ebf9779bbe47f8ea7f28ef0de21b28481 100644 (file)
@@ -31,7 +31,11 @@ function logm($logFile, $clientIp, $message)
  *   - are NOT cryptographically secure (they CAN be forged)
  *
  *  In Shaarli, they are used as a tinyurl-like link to individual entries,
- *  e.g. smallHash('20111006_131924') --> yZH23w
+ *  built once with the combination of the date and item ID.
+ *  e.g. smallHash('20111006_131924' . 142) --> eaWxtQ
+ *
+ * @warning before v0.8.1, smallhashes were built only with the date,
+ *          and their value has been preserved.
  *
  * @param string $text Create a hash from this text.
  *
index b584d98c56a27ebb09c963313876fd8d1a3e9987..f601c1eeee5ff6e42f4eccdd43f76169fdf0ba0a 100644 (file)
@@ -1,2 +1,13 @@
-Allow from none
-Deny from all
+<IfModule version_module>
+  <IfVersion >= 2.4>
+     Require all denied
+  </IfVersion>
+  <IfVersion < 2.4>
+     Allow from none
+     Deny from all
+  </IfVersion>
+</IfModule>
+
+<IfModule !version_module>
+    Require all denied
+</IfModule>
index b584d98c56a27ebb09c963313876fd8d1a3e9987..f601c1eeee5ff6e42f4eccdd43f76169fdf0ba0a 100644 (file)
@@ -1,2 +1,13 @@
-Allow from none
-Deny from all
+<IfModule version_module>
+  <IfVersion >= 2.4>
+     Require all denied
+  </IfVersion>
+  <IfVersion < 2.4>
+     Allow from none
+     Deny from all
+  </IfVersion>
+</IfModule>
+
+<IfModule !version_module>
+    Require all denied
+</IfModule>
index accbacdcba853f45d3e7861b9d35f45c6810eb0a..cbc73d54e55366647fb69ddff57d092059803b8e 100644 (file)
 <ul>
 <li><a href="https://github.com/kalvn/shaarli-plugin-autosave">autosave</a> by <a href="https://github.com/kalvn">@kalvn</a>: Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.<a href=".html"></a></li>
 <li><a href="https://github.com/ArthurHoaro/code-coloration">Code Coloration</a> by <a href="https://github.com/ArthurHoaro">@ArthurHoaro</a>: client side code syntax highlighter.<a href=".html"></a></li>
-<li><a href="https://github.com/alexisju/social">social</a> by <a href="https://github.com/alexisju">@alexisju</a>: share links to social networks.<a href=".html"></a></li>
+<li><a href="https://github.com/kalvn/shaarli-plugin-disqus">Disqus</a> by <a href="https://github.com/kalvn">@kalvn</a>: Adds Disqus comment system to your Shaarli.<a href=".html"></a></li>
 <li><a href="https://github.com/NerosTie/emojione">emojione</a> by <a href="https://github.com/NerosTie">@NerosTie</a>: Add colorful emojis to your Shaarli.<a href=".html"></a></li>
 <li><a href="https://github.com/ArthurHoaro/launch-plugin">launch</a> - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.<a href=".html"></a></li>
-<li><a href="https://github.com/kalvn/shaarli-plugin-disqus">Disqus</a> by <a href="https://github.com/kalvn">@kalvn</a>: Adds Disqus comment system to your Shaarli.<a href=".html"></a></li>
+<li><a href="https://github.com/alexisju/social">social</a> by <a href="https://github.com/alexisju">@alexisju</a>: share links to social networks.<a href=".html"></a></li>
+<li><a href="https://github.com/ArthurHoaro/shaarli2twitter">shaarli2twitter</a> by <a href="https://github.com/ArthurHoaro">@ArthurHoaro</a> - Automatically tweet your shared links from Shaarli<a href=".html"></a></li>
 </ul>
 <h3 id="themes">Themes</h3>
 <p>See <a href="Theming.html">Theming</a> for the list of community-contributed themes, and an installation guide.</p>
@@ -95,7 +96,7 @@
 <li><a href="https://github.com/DMeloni/shaarlo">Shaarlo</a> - An aggregator for shaarlis with many features (a very popular running instance among french shaarliers: <a href="http://shaarli.fr/">shaarli.fr</a>)<a href=".html"></a></li>
 <li><a href="https://github.com/BoboTiG/shaarlimages">Shaarlimages</a> - An image-oriented aggregator for Shaarlis<a href=".html"></a></li>
 <li><a href="https://github.com/mknexen/shaarli-api">mknexen/shaarli-api</a> - A REST API for Shaarli<a href=".html"></a></li>
-<li><a href="https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php">Self dead link</a> - Detect dead links on shaarli. This version use the database of shaarli. An <a href="https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php">another version</a>, can be used for others shaarli (but use most ressources).<a href=".html"></a></li>
+<li><a href="https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php">Self dead link</a> - Detect dead links on shaarli. This version use the database of shaarli. <a href="https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php">Another version</a>, can be used for other shaarli instances (but is more resource consuming).<a href=".html"></a></li>
 </ul>
 <h3 id="mobile-apps">Mobile Apps</h3>
 <ul>
 <ul>
 <li><a href="https://github.com/jcsaaddupuy/tt-rss-shaarli">tt-rss-shaarli</a> - <a href="http://tt-rss.org/">TinyTiny RSS</a> plugin that adds support for sharing articles with Shaarli<a href=".html"></a></li>
 <li><a href="https://github.com/ahmet2mir/octopress-shaarli">octopress-shaarli</a> - Octopress plugin to retrieve Shaarli links on the sidebar<a href=".html"></a></li>
+<li><a href="https://github.com/q2apro/scuttle-to-shaarli">Scuttle to Shaarli</a> - Import bookmarks from Scuttle<a href=".html"></a></li>
 </ul>
 <h2 id="alternatives-to-shaarli">Alternatives to Shaarli</h2>
 <ul>
index 3945d0058d04690c19d1aa43167d6096c4df0e19..291bf643bed8defc00faaa8f019b8977e2bb41a7 100644 (file)
@@ -20,10 +20,11 @@ _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)
+  * [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system 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)
+  * [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.[](.html)
+  * [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli[](.html)
 
 
 ### Themes
@@ -35,7 +36,7 @@ See [Theming](Theming.html) for the list of community-contributed themes, and an
 - [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among french shaarliers: [shaarli.fr](http://shaarli.fr/))[](.html)
 - [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis[](.html)
 - [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli[](.html)
-- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. An [another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for others shaarli (but use most ressources).[](.html)
+- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. [Another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming).[](.html)
 
 ### Mobile Apps
 - [Shaarli💫](http://app.mro.name/Shaarli💫) iOS share extension - see [#308](https://github.com/shaarli/Shaarli/issues/308#issuecomment-184592070) for some promo codes,[](.html)
@@ -45,6 +46,7 @@ See [Theming](Theming.html) for the list of community-contributed themes, and an
 ## Integration with other platforms 
 - [tt-rss-shaarli](https://github.com/jcsaaddupuy/tt-rss-shaarli) - [TinyTiny RSS](http://tt-rss.org/) plugin that adds support for sharing articles with Shaarli[](.html)
 - [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar[](.html)
+- [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle[](.html)
 
 ## Alternatives to Shaarli
 - [Shaarli alternatives](http://alternativeto.net/software/shaarli/) (alternativeto.net)[](.html)
index 17c7b69e68480fb311a7eb685a956e2cf7b31266..b9cac3605fa289504873226760545fec5cd789c6 100644 (file)
@@ -105,13 +105,14 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
 <p>Several releases are available:</p>
 <hr />
 <h2 id="latest-release-recommended">Latest release (recommended)</h2>
-<p>Get the latest released version from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page.<a href=".html"></a></p>
-<p>The current latest released version is <code>v0.7.0</code>.</p>
 <h3 id="download-as-an-archive">Download as an archive</h3>
-<p>As a .zip archive:</p>
-<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">wget</span> https://github.com/shaarli/Shaarli/archive/v0.7.0.zip
-$ <span class="fu">unzip</span> Shaarli-v0.7.0.zip
-$ <span class="fu">mv</span> Shaarli-v0.7.0 /path/to/shaarli/</code></pre></div>
+<p>Get the latest released version from the <a href="https://github.com/shaarli/Shaarli/releases">releases</a> page.<a href=".html"></a></p>
+<p><strong>Download our <em>shaarli-full</em> archive</strong> to include dependencies.</p>
+<p>The current latest released version is <code>v0.8.0</code></p>
+<p>Or in command lines:</p>
+<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="fu">wget</span> https://github.com/shaarli/Shaarli/releases/download/v0.8.0/shaarli-v0.8.0-full.zip
+$ <span class="fu">unzip</span> shaarli-v0.8.0-full.zip
+$ <span class="fu">mv</span> Shaarli /path/to/shaarli/</code></pre></div>
 <table style="width:46%;">
 <colgroup>
 <col style="width: 8%" />
@@ -126,6 +127,10 @@ $ <span class="fu">mv</span> Shaarli-v0.7.0 /path/to/shaarli/</code></pre></div>
 <tbody>
 </tbody>
 </table>
+<h3 id="using-git">Using git</h3>
+<pre><code>mkdir -p /path/to/shaarli &amp;&amp; cd /path/to/shaarli/
+git clone -b v0.8.0 https://github.com/shaarli/Shaarli.git .
+composer update --no-dev</code></pre>
 <hr />
 <h2 id="stable-version">Stable version</h2>
 <p>The stable version has been experienced by Shaarli users, and will receive security updates.</p>
index 77af25eb0ae7a951700c4d2c936c8e2f9d4dadba..32df898462732f780f64fa0b1db56d95ef8aecd9 100644 (file)
@@ -8,26 +8,31 @@ Several releases are available:
 --------------------------------------------------------
 
 ## Latest release (recommended)
-
+### Download as an archive
 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 our *shaarli-full* archive** to include dependencies.
 
-### Download as an archive
+The current latest released version is `v0.8.0`
 
-As a .zip archive:
+Or in command lines:
 
 ```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/
+$ wget https://github.com/shaarli/Shaarli/releases/download/v0.8.0/shaarli-v0.8.0-full.zip
+$ unzip shaarli-v0.8.0-full.zip
+$ mv Shaarli /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)
 |-----|--------------------------|
 
+### Using git
 
+```
+mkdir -p /path/to/shaarli && cd /path/to/shaarli/
+git clone -b v0.8.0 https://github.com/shaarli/Shaarli.git .
+composer update --no-dev
+```
 
 --------------------------------------------------------
 
index cdefd3d640862061f3541fdbe7435a15aa85d400..0d9fa3e1c91bb366c0c6a9823e975c0577a45f7d 100644 (file)
@@ -115,9 +115,35 @@ releases</a>.</p>
 <li><code>origin</code> pointing to your GitHub fork</li>
 <li><code>upstream</code> pointing to the main Shaarli repository</li>
 </ul></li>
-<li>maintainer permissions on the main Shaarli repository (to push the signed tag)</li>
+<li>maintainer permissions on the main Shaarli repository, to:
+<ul>
+<li>push the signed tag</li>
+<li>create a new release</li>
+</ul></li>
 <li><a href="https://getcomposer.org/">Composer</a> and <a href="http://pandoc.org/">Pandoc</a> need to be installed<a href=".html"></a></li>
 </ul>
+<h2 id="github-release-draft-and-changelog.md">GitHub release draft and <code>CHANGELOG.md</code></h2>
+<p>See <a href="http://keepachangelog.com/en/0.3.0/" class="uri">http://keepachangelog.com/en/0.3.0/</a> for changelog formatting.</p>
+<h3 id="github-release-draft">GitHub release draft</h3>
+<p>GitHub allows drafting the release note for the upcoming release, from the <a href="https://github.com/shaarli/Shaarli/releases">Releases</a> page. This way, the release note can be drafted while contributions are merged to <code>master</code>.<a href=".html"></a></p>
+<h3 id="changelog.md"><code>CHANGELOG.md</code></h3>
+<p>This file should contain the same information as the release note draft for the upcoming version.</p>
+<p>Update it to:</p>
+<ul>
+<li>add new entries (additions, fixes, etc.)</li>
+<li>mark the current version as released by setting its date and link</li>
+<li>add a new section for the future unreleased version</li>
+</ul>
+<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="bu">cd</span> /path/to/shaarli
+
+$ <span class="fu">nano</span> CHANGELOG.md
+
+[<span class="ex">...</span>][](.html)
+<span class="co">## vA.B.C - UNRELEASED</span>
+<span class="ex">TBA</span>
+
+<span class="co">## [vX.Y.Z](https://github.com/shaarli/Shaarli/releases/tag/vX.Y.Z) - YYYY-MM-DD[](.html)</span>
+[<span class="ex">...</span>][](.html)</code></pre></div>
 <h2 id="increment-the-version-code-create-and-push-a-signed-tag">Increment the version code, create and push a signed tag</h2>
 <h3 id="bump-shaarlis-version">Bump Shaarli's version</h3>
 <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash">$ <span class="bu">cd</span> /path/to/shaarli
@@ -165,7 +191,16 @@ $ <span class="fu">git</span> show-ref tags/v0.5.0
 $ <span class="fu">git</span> verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1
 <span class="ex">gpg</span>: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F
 <span class="ex">gpg</span>: Good signature from <span class="st">&quot;VirtualTam &lt;virtualtam@flibidi.net&gt;&quot;</span> [ultimate][](.html)</code></pre></div>
-<h2 id="generate-and-upload-all-in-one-release-archives">Generate and upload all-in-one release archives</h2>
+<h2 id="publish-the-github-release">Publish the GitHub release</h2>
+<h3 id="create-a-github-release-from-a-git-tag">Create a GitHub release from a Git tag</h3>
+<p>From the previously drafted release:</p>
+<ul>
+<li>edit the release notes (if needed)</li>
+<li>specify the appropriate Git tag</li>
+<li>publish the release</li>
+<li>profit!</li>
+</ul>
+<h3 id="generate-and-upload-all-in-one-release-archives">Generate and upload all-in-one release archives</h3>
 <p>Users with a shared hosting may have:</p>
 <ul>
 <li>no SSH access</li>
index 5cbcd79ae702f437bdfe4b64db8fd72020cd2e63..556a96ee587dadb66a1a49b918a1760d7563fde3 100644 (file)
@@ -10,9 +10,39 @@ This guide assumes that you have:
 - a local clone of your Shaarli fork, with the following remotes:
     - `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)
+- maintainer permissions on the main Shaarli repository, to:
+    - push the signed tag
+    - create a new release
 - [Composer](https://getcomposer.org/) and [Pandoc](http://pandoc.org/) need to be installed[](.html)
 
+## GitHub release draft and `CHANGELOG.md`
+See http://keepachangelog.com/en/0.3.0/ for changelog formatting.
+
+### GitHub release draft
+GitHub allows drafting the release note for the upcoming release, from the [Releases](https://github.com/shaarli/Shaarli/releases) page. This way, the release note can be drafted while contributions are merged to `master`.[](.html)
+
+### `CHANGELOG.md`
+This file should contain the same information as the release note draft for the upcoming version.
+
+Update it to:
+- add new entries (additions, fixes, etc.)
+- mark the current version as released by setting its date and link
+- add a new section for the future unreleased version
+
+```bash
+$ cd /path/to/shaarli
+
+$ nano CHANGELOG.md
+
+[...][](.html)
+## vA.B.C - UNRELEASED
+TBA
+
+## [vX.Y.Z](https://github.com/shaarli/Shaarli/releases/tag/vX.Y.Z) - YYYY-MM-DD[](.html)
+[...][](.html)
+```
+
+
 ## Increment the version code, create and push a signed tag
 ### Bump Shaarli's version
 ```bash
@@ -72,7 +102,15 @@ 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
+## Publish the GitHub release
+### Create a GitHub release from a Git tag
+From the previously drafted release:
+- edit the release notes (if needed)
+- specify the appropriate Git tag
+- publish the release
+- profit!
+
+### 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
index 068900b88ee35484b05adf831c30e55881f07712..2f1c25b50f0ff78c6953cea7d87f0f5d4b790238 100644 (file)
@@ -193,6 +193,9 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
     ErrorLog<span class="st">  /var/log/apache2/shaarli-error.log</span>
     CustomLog<span class="st"> /var/log/apache2/shaarli-access.log combined</span>
 <span class="fu">&lt;/VirtualHost&gt;</span></code></pre></div>
+<h3 id="htaccess">.htaccess</h3>
+<p>Shaarli use <code>.htaccess</code> Apache files to deny access to files that shouldn't be directly accessed (datastore, config, etc.). You need the directive <code>AllowOverride All</code> in your virtual host configuration for them to work.</p>
+<p><strong>Warning</strong>: If you use Apache 2.2 or lower, you need <a href="https://httpd.apache.org/docs/current/mod/mod_version.html">mod_version</a> to be installed and enabled.<a href=".html"></a></p>
 <h2 id="lighthttpd">LightHttpd</h2>
 <h2 id="nginx">Nginx</h2>
 <h3 id="foreword">Foreword</h3>
@@ -233,7 +236,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
 <li>files may be located in a user's home directory</li>
 <li>in this case, make sure both Nginx and PHP-FPM are running as the local user/group!</li>
 </ul>
-<p>For all following examples, a development configuration will be used:</p>
+<p>For all following configuration examples, this user/group pair will be used:</p>
 <ul>
 <li><code>user:group = john:users</code>,</li>
 </ul>
@@ -251,6 +254,24 @@ user john users;
 http {
     [...][](.html)
 }</code></pre>
+<h3 id="optional-increase-the-maximum-file-upload-size">(Optional) Increase the maximum file upload size</h3>
+<p>Some bookmark dumps generated by web browsers can be <em>huge</em> due to the presence of Base64-encoded images and favicons, as well as extra verbosity when nesting links in (sub-)folders.</p>
+<p>To increase upload size, you will need to modify both nginx and PHP configuration:</p>
+<pre class="nginx"><code># /etc/nginx/nginx.conf
+
+http {
+    [...][](.html)
+
+    client_max_body_size 10m;
+
+    [...][](.html)
+}</code></pre>
+<div class="sourceCode"><pre class="sourceCode ini"><code class="sourceCode ini"><span class="co"># /etc/php5/fpm/php.ini</span>
+
+<span class="kw">[...][]</span><span class="dt">(.html)</span>
+<span class="dt">post_max_size </span><span class="ot">=</span><span class="st"> 10M</span>
+<span class="kw">[...][]</span><span class="dt">(.html)</span>
+<span class="dt">upload_max_filesize </span><span class="ot">=</span><span class="st"> 10M</span></code></pre></div>
 <h3 id="minimal-1">Minimal</h3>
 <p><em>WARNING: Use for development only!</em></p>
 <pre class="nginx"><code>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;
index 1ab57a0a34a977f8cdeb18d7ed8882c882c4dc2b..df10feb255b4321ac8312a5b3c04d186cd188af8 100644 (file)
@@ -102,6 +102,12 @@ See [Server-side TLS](https://wiki.mozilla.org/Security/Server_Side_TLS#Apache)
 </VirtualHost>
 ```
 
+### .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;
index 13e6acf0b6fed9394e53f07b96fc3022ab1dd2d2..7cbf7aef1f2d4839338479c56efd2b6d1cabf2ae 100644 (file)
@@ -119,19 +119,20 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
 <ul>
 <li>There should now be a <code>my-template/</code> directory under the <code>tpl/</code> dir, containing directly all the template files.</li>
 </ul></li>
-<li><p>Edit <code>data/config.php</code> to have Shaarli use this template, e.g.</p>
-<div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php"><span class="kw">$GLOBALS</span><span class="ot">[</span><span class="st">&#39;config&#39;</span><span class="ot">[</span><span class="st">&#39;RAINTPL_TPL&#39;</span><span class="ot">]</span> = <span class="st">&#39;tpl/my-template/&#39;</span><span class="ot">;](</span><span class="st">&#39;RAINTPL_TPL&#39;</span><span class="ot">]</span>-=-<span class="st">&#39;tpl/my-template/&#39;</span><span class="ot">;</span>.html<span class="ot">)</span></code></pre></div></li>
+<li><p>Edit <code>data/config.json.php</code> to have Shaarli use this template, in <code>&quot;resource&quot;</code> e.g.</p>
+<div class="sourceCode"><pre class="sourceCode json"><code class="sourceCode json"><span class="er">&quot;raintpl_tpl&quot;:</span> <span class="er">&quot;tpl\/my-template\/&quot;,</span></code></pre></div></li>
 </ul>
 <h2 id="community-themes-templates">Community themes &amp; templates</h2>
 <ul>
 <li><a href="https://github.com/AkibaTech/Shaarli---SuperHero-Theme">AkibaTech/Shaarli Superhero Theme</a> - A template/theme for Shaarli<a href=".html"></a></li>
 <li><a href="https://github.com/alexisju/albinomouse-template">alexisju/albinomouse-template</a> - A full template for Shaarli<a href=".html"></a></li>
+<li><a href="https://github.com/ArthurHoaro/shaarli-launch">ArthurHoaro/shaarli-launch</a> - Customizable Shaarli theme.<a href=".html"></a></li>
 <li><a href="https://github.com/dhoko/ShaarliTemplate">dhoko/ShaarliTemplate</a> - A template/theme for Shaarli<a href=".html"></a></li>
 <li><a href="https://github.com/kalvn/shaarli-blocks">kalvn/shaarli-blocks</a> - A template/theme for Shaarli<a href=".html"></a></li>
 <li><a href="https://github.com/kalvn/Shaarli-Material">kalvn/Shaarli-Material</a> - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.<a href=".html"></a></li>
+<li><a href="https://github.com/ManufacturaInd/shaarli-2004licious-theme">ManufacturaInd/shaarli-2004licious-theme</a> - A template/theme as a humble homage to the early looks of the del.icio.us site.<a href=".html"></a></li>
 <li><a href="https://github.com/misterair/limonade">misterair/Limonade</a> - A fork of (legacy) Shaarli with a new template<a href=".html"></a></li>
 <li><a href="https://github.com/mrjovanovic/serious-theme-shaarli">mrjovanovic/serious-theme-shaarli</a> - A serious theme for SHaarli.<a href=".html"></a></li>
-<li><a href="https://github.com/Vinm/Blue-theme-for-Shaarli">Vinm/Blue-theme-for Shaarli</a> - A template/theme for Shaarli (<a href="https://github.com/Vinm/Blue-theme-for-Shaarli/issues/2">unmaintained</a>, compatibility unknown)<a href=".html"></a></li>
 <li><a href="https://github.com/vivienhaese/shaarlitheme">vivienhaese/shaarlitheme</a> - A Shaarli fork meant to be run in an openshift instance<a href=".html"></a></li>
 </ul>
 <h3 id="example-installation-albinomouse-template">Example installation: AlbinoMouse template</h3>
index 7fb8d927f7ba5a56193b5497cf6010ca2957dd3c..a21899c27ec0379e6ee2cc8ef2f442d736a96763 100644 (file)
@@ -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
index b584d98c56a27ebb09c963313876fd8d1a3e9987..f601c1eeee5ff6e42f4eccdd43f76169fdf0ba0a 100644 (file)
@@ -1,2 +1,13 @@
-Allow from none
-Deny from all
+<IfModule version_module>
+  <IfVersion >= 2.4>
+     Require all denied
+  </IfVersion>
+  <IfVersion < 2.4>
+     Allow from none
+     Deny from all
+  </IfVersion>
+</IfModule>
+
+<IfModule !version_module>
+    Require all denied
+</IfModule>
index 0c19b0855d0b9d282e04e906cce7d254bab2af3f..d9ef8da7db2eada974c7d6ce6cd6f684583755f6 100644 (file)
@@ -15,6 +15,8 @@ RUN apt-get update \
        nano \
     && apt-get clean
 
+RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
+RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
 COPY nginx.conf /etc/nginx/nginx.conf
 COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
 
index cda09b565028b0ad39400b3d715d03789e989250..ac0c6c61ce165976b7572bb96eb8ba90b5f4d2a8 100644 (file)
@@ -11,6 +11,8 @@ http {
     default_type       application/octet-stream;
     keepalive_timeout  20;
 
+    client_max_body_size 10m;
+
     index index.html index.php;
 
     server {
@@ -49,6 +51,11 @@ http {
             add_header Cache-Control "public, must-revalidate, proxy-revalidate";
         }
 
+        location = /favicon.ico {
+            # serve the Shaarli favicon from its custom location
+            alias /var/www/shaarli/images/favicon.ico;
+        }
+
         location ~ (index)\.php$ {
             # filter and proxy PHP requests to PHP-FPM
             fastcgi_pass   unix:/var/run/php5-fpm.sock;
index d93ed262b79acd97ce114256aa940a927bf794a4..d0509115c5907e158c52292919b7a87506ae070e 100644 (file)
@@ -14,6 +14,8 @@ RUN apt-get update \
        supervisor \
     && apt-get clean
 
+RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
+RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
 COPY nginx.conf /etc/nginx/nginx.conf
 COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
 
index e23c4587da122c790f26c6893838280c39b03908..5ffa02d0a9d570136d4ab7ea8f8bef4107f03cb7 100644 (file)
@@ -11,6 +11,8 @@ http {
     default_type       application/octet-stream;
     keepalive_timeout  20;
 
+    client_max_body_size 10m;
+
     index index.html index.php;
 
     server {
@@ -41,6 +43,11 @@ http {
             add_header Cache-Control "public, must-revalidate, proxy-revalidate";
         }
 
+        location = /favicon.ico {
+            # serve the Shaarli favicon from its custom location
+            alias /var/www/shaarli/images/favicon.ico;
+        }
+
         location ~ (index)\.php$ {
             # filter and proxy PHP requests to PHP-FPM
             fastcgi_pass   unix:/var/run/php5-fpm.sock;
index a509fda65cfc00fee884127fda3e2433f6adfe9b..fc9588b0d604dd4023a232ef17c4a8d76f26d98b 100644 (file)
@@ -14,6 +14,8 @@ RUN apt-get update \
        supervisor \
     && apt-get clean
 
+RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
+RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
 COPY nginx.conf /etc/nginx/nginx.conf
 COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
 
index e23c4587da122c790f26c6893838280c39b03908..5ffa02d0a9d570136d4ab7ea8f8bef4107f03cb7 100644 (file)
@@ -11,6 +11,8 @@ http {
     default_type       application/octet-stream;
     keepalive_timeout  20;
 
+    client_max_body_size 10m;
+
     index index.html index.php;
 
     server {
@@ -41,6 +43,11 @@ http {
             add_header Cache-Control "public, must-revalidate, proxy-revalidate";
         }
 
+        location = /favicon.ico {
+            # serve the Shaarli favicon from its custom location
+            alias /var/www/shaarli/images/favicon.ico;
+        }
+
         location ~ (index)\.php$ {
             # filter and proxy PHP requests to PHP-FPM
             fastcgi_pass   unix:/var/run/php5-fpm.sock;
index 5808320cb485fb38b61f734157921d6d0a91f577..a24d4b7c1e384cfc72a03000e462263063c77fb0 100644 (file)
@@ -37,6 +37,10 @@ em {
     font-style: italic;
 }
 
+strong {
+    font-weight: bold;
+}
+
 /* Buttons */
 .bigbutton {
     background-color: #c0c0c0;
@@ -1168,8 +1172,13 @@ ul.errors {
 }
 
 #pluginsadmin a {
+    color: #486D08;
+}
+
+#pluginsadmin a.arrow {
     color: black;
 }
+
 /* 404 page */
 .error-container {
 
index c4c0d15ada79b7420dca028f149d50b043c9178b..a0a3a8c70648f26098675a21d45387325edda3f3 100644 (file)
--- a/index.php
+++ b/index.php
@@ -1,6 +1,6 @@
 <?php
 /**
- * Shaarli v0.8.0 - Shaare your links...
+ * Shaarli v0.8.1 - Shaare your links...
  *
  * The personal, minimalist, super-fast, database free, bookmarking service.
  *
@@ -25,7 +25,7 @@ if (date_default_timezone_get() == '') {
 /*
  * PHP configuration
  */
-define('shaarli_version', '0.8.0');
+define('shaarli_version', '0.8.1');
 
 // http://server.com/x/shaarli --> /shaarli/
 define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0)));
@@ -564,24 +564,19 @@ function showDailyRSS($conf) {
     );
 
     /* Some Shaarlies may have very few links, so we need to look
-       back in time (rsort()) until we have enough days ($nb_of_days).
+       back in time until we have enough days ($nb_of_days).
     */
-    $linkdates = array();
-    foreach ($LINKSDB as $linkdate => $value) {
-        $linkdates[] = $linkdate;
-    }
-    rsort($linkdates);
     $nb_of_days = 7; // We take 7 days.
     $today = date('Ymd');
     $days = array();
 
-    foreach ($linkdates as $linkdate) {
-        $day = substr($linkdate, 0, 8); // Extract day (without time)
-        if (strcmp($day,$today) < 0) {
+    foreach ($LINKSDB as $link) {
+        $day = $link['created']->format('Ymd'); // Extract day (without time)
+        if (strcmp($day, $today) < 0) {
             if (empty($days[$day])) {
                 $days[$day] = array();
             }
-            $days[$day][] = $linkdate;
+            $days[$day][] = $link;
         }
 
         if (count($days) > $nb_of_days) {
@@ -601,24 +596,18 @@ function showDailyRSS($conf) {
     echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL;
 
     // For each day.
-    foreach ($days as $day => $linkdates) {
+    foreach ($days as $day => $links) {
         $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000');
         $absurl = escape(index_url($_SERVER).'?do=daily&day='.$day);  // Absolute URL of the corresponding "Daily" page.
 
-        // Build the HTML body of this RSS entry.
-        $links = array();
-
         // We pre-format some fields for proper output.
-        foreach ($linkdates as $linkdate) {
-            $l = $LINKSDB[$linkdate];
-            $l['formatedDescription'] = format_description($l['description'], $conf->get('redirector.url'));
-            $l['thumbnail'] = thumbnail($conf, $l['url']);
-            $l_date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $l['linkdate']);
-            $l['timestamp'] = $l_date->getTimestamp();
-            if (startsWith($l['url'], '?')) {
-                $l['url'] = index_url($_SERVER) . $l['url'];  // make permalink URL absolute
+        foreach ($links as &$link) {
+            $link['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
+            $link['thumbnail'] = thumbnail($conf, $link['url']);
+            $link['timestamp'] = $link['created']->getTimestamp();
+            if (startsWith($link['url'], '?')) {
+                $link['url'] = index_url($_SERVER) . $link['url'];  // make permalink URL absolute
             }
-            $links[$linkdate] = $l;
         }
 
         // Then build the HTML for this day:
@@ -680,8 +669,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
         $linksToDisplay[$key]['taglist']=$taglist;
         $linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
         $linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']);
-        $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
-        $linksToDisplay[$key]['timestamp'] = $date->getTimestamp();
+        $linksToDisplay[$key]['timestamp'] =  $link['created']->getTimestamp();
     }
 
     /* We need to spread the articles on 3 columns.
@@ -831,7 +819,7 @@ function renderPage($conf, $pluginManager)
         // Get only links which have a thumbnail.
         foreach($links as $link)
         {
-            $permalink='?'.escape(smallHash($link['linkdate']));
+            $permalink='?'.$link['shorturl'];
             $thumb=lazyThumbnail($conf, $link['url'],$permalink);
             if ($thumb!='') // Only output links which have a thumbnail.
             {
@@ -1078,6 +1066,7 @@ function renderPage($conf, $pluginManager)
     {
         $data = array(
             'pageabsaddr' => index_url($_SERVER),
+            'sslenabled' => !empty($_SERVER['HTTPS'])
         );
         $pluginManager->executeHooks('render_tools', $data);
 
@@ -1244,13 +1233,30 @@ function renderPage($conf, $pluginManager)
     // -------- User clicked the "Save" button when editing a link: Save link to database.
     if (isset($_POST['save_edit']))
     {
-        $linkdate = $_POST['lf_linkdate'];
-        $updated = isset($LINKSDB[$linkdate]) ? strval(date('Ymd_His')) : false;
-
         // Go away!
         if (! tokenOk($_POST['token'])) {
             die('Wrong token.');
         }
+
+        // lf_id should only be present if the link exists.
+        $id = !empty($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId();
+        // Linkdate is kept here to:
+        //   - use the same permalink for notes as they're displayed when creating them
+        //   - let users hack creation date of their posts
+        //     See: https://github.com/shaarli/Shaarli/wiki/Datastore-hacks#changing-the-timestamp-for-a-link
+        $linkdate = escape($_POST['lf_linkdate']);
+        if (isset($LINKSDB[$id])) {
+            // 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.
         $tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags']));
         // Remove first '-' char in tags.
@@ -1267,14 +1273,17 @@ function renderPage($conf, $pluginManager)
         }
 
         $link = array(
+            'id' => $id,
             'title' => trim($_POST['lf_title']),
             'url' => $url,
             'description' => $_POST['lf_description'],
             'private' => (isset($_POST['lf_private']) ? 1 : 0),
-            'linkdate' => $linkdate,
+            'created' => $created,
             'updated' => $updated,
-            'tags' => str_replace(',', ' ', $tags)
+            'tags' => str_replace(',', ' ', $tags),
+            'shorturl' => $shortUrl,
         );
+
         // If title is empty, use the URL as title.
         if ($link['title'] == '') {
             $link['title'] = $link['url'];
@@ -1282,7 +1291,7 @@ function renderPage($conf, $pluginManager)
 
         $pluginManager->executeHooks('save_link', $link);
 
-        $LINKSDB[$linkdate] = $link;
+        $LINKSDB[$id] = $link;
         $LINKSDB->save($conf->get('resource.page_cache'));
         pubsubhub($conf);
 
@@ -1295,7 +1304,7 @@ function renderPage($conf, $pluginManager)
         $returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?';
         $location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
         // Scroll to the link which has been edited.
-        $location .= '#' . smallHash($_POST['lf_linkdate']);
+        $location .= '#' . $link['shorturl'];
         // After saving the link, redirect to the page the user was on.
         header('Location: '. $location);
         exit;
@@ -1306,27 +1315,31 @@ function renderPage($conf, $pluginManager)
     {
         // If we are called from the bookmarklet, we must close the popup:
         if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
+        $link = $LINKSDB[(int) escape($_POST['lf_id'])];
         $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
-        $returnurl .= '#'.smallHash($_POST['lf_linkdate']);  // Scroll to the link which has been edited.
+        // Scroll to the link which has been edited.
+        $returnurl .= '#'. $link['shorturl'];
         $returnurl = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
         header('Location: '.$returnurl); // After canceling, redirect to the page the user was on.
         exit;
     }
 
     // -------- User clicked the "Delete" button when editing a link: Delete link from database.
-    if ($targetPage == Router::$PAGE_DELETELINK)
+    if (isset($_POST['delete_link']))
     {
-        if (!tokenOk($_GET['token'])) die('Wrong token.');
+        if (!tokenOk($_POST['token'])) die('Wrong token.');
+
         // We do not need to ask for confirmation:
         // - confirmation is handled by JavaScript
         // - we are protected from XSRF by the token.
-        $linkdate = $_GET['delete_link'];
-        $link = $LINKSDB[$linkdate];
-        
-        $pluginManager->executeHooks('delete_link', $link);
 
-        unset($LINKSDB[$linkdate]);
-        $LINKSDB->save($conf->get('resource.page_cache')); // save to disk
+        // FIXME! We keep `lf_linkdate` for consistency before a proper API. To be removed.
+        $id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : intval(escape($_POST['lf_linkdate']));
+
+        $pluginManager->executeHooks('delete_link', $LINKSDB[$id]);
+
+        unset($LINKSDB[$id]);
+        $LINKSDB->save('resource.page_cache'); // save to disk
 
         // If we are called from the bookmarklet, we must close the popup:
         if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
@@ -1364,8 +1377,10 @@ function renderPage($conf, $pluginManager)
     // -------- User clicked the "EDIT" button on a link: Display link edit form.
     if (isset($_GET['edit_link']))
     {
-        $link = $LINKSDB[$_GET['edit_link']];  // Read database
+        $id = (int) escape($_GET['edit_link']);
+        $link = $LINKSDB[$id];  // Read database
         if (!$link) { header('Location: ?'); exit; } // Link not found in database.
+        $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
         $data = array(
             'link' => $link,
             'link_is_new' => false,
@@ -1389,10 +1404,10 @@ function renderPage($conf, $pluginManager)
         $link_is_new = false;
         // Check if URL is not already in database (in this case, we will edit the existing link)
         $link = $LINKSDB->getLinkFromUrl($url);
-        if (!$link)
+        if (! $link)
         {
             $link_is_new = true;
-            $linkdate = strval(date('Ymd_His'));
+            $linkdate = strval(date(LinkDB::LINK_DATE_FORMAT));
             // Get title if it was provided in URL (by the bookmarklet).
             $title = empty($_GET['title']) ? '' : escape($_GET['title']);
             // Get description if it was provided in URL (by the bookmarklet). [Bronco added that]
@@ -1416,7 +1431,7 @@ function renderPage($conf, $pluginManager)
             }
 
             if ($url == '') {
-                $url = '?' . smallHash($linkdate);
+                $url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
                 $title = 'Note: ';
             }
             $url = escape($url);
@@ -1430,6 +1445,8 @@ function renderPage($conf, $pluginManager)
                 'tags' => $tags,
                 'private' => $private
             );
+        } else {
+            $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
         }
 
         $data = array(
@@ -1635,18 +1652,15 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
         $link['description'] = format_description($link['description'], $conf->get('redirector.url'));
         $classLi =  ($i % 2) != 0 ? '' : 'publicLinkHightLight';
         $link['class'] = $link['private'] == 0 ? $classLi : 'private';
-        $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
-        $link['timestamp'] = $date->getTimestamp();
+        $link['timestamp'] = $link['created']->getTimestamp();
         if (! empty($link['updated'])) {
-            $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['updated']);
-            $link['updated_timestamp'] = $date->getTimestamp();
+            $link['updated_timestamp'] = $link['updated']->getTimestamp();
         } else {
             $link['updated_timestamp'] = '';
         }
         $taglist = explode(' ', $link['tags']);
         uasort($taglist, 'strcasecmp');
         $link['taglist'] = $taglist;
-        $link['shorturl'] = smallHash($link['linkdate']);
         // Check for both signs of a note: starting with ? and 7 chars long.
         if ($link['url'][0] === '?' &&
             strlen($link['url']) === 7) {
index b584d98c56a27ebb09c963313876fd8d1a3e9987..f601c1eeee5ff6e42f4eccdd43f76169fdf0ba0a 100644 (file)
@@ -1,2 +1,13 @@
-Allow from none
-Deny from all
+<IfModule version_module>
+  <IfVersion >= 2.4>
+     Require all denied
+  </IfVersion>
+  <IfVersion < 2.4>
+     Allow from none
+     Deny from all
+  </IfVersion>
+</IfModule>
+
+<IfModule !version_module>
+    Require all denied
+</IfModule>
diff --git a/plugins/addlink_toolbar/addlink_toolbar.css b/plugins/addlink_toolbar/addlink_toolbar.css
deleted file mode 100644 (file)
index b6a612f..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-#addlink_toolbar {
-    display: inline;
-    margin: 0 0 0 25px;
-}
\ No newline at end of file
index c96a891ef72dd185d188c0b850119067d8a77d10..bf8a198a4ed41a44c86320c71117a525f085df7e 100644 (file)
@@ -16,10 +16,12 @@ function hook_addlink_toolbar_render_header($data)
 {
     if ($data['_PAGE_'] == Router::$PAGE_LINKLIST && $data['_LOGGEDIN_'] === true) {
         $form = array(
-            'method' => 'GET',
-            'action' => '',
-            'name'   => 'addform',
-            'class'  => 'addform',
+            'attr' => array(
+                'method' => 'GET',
+                'action' => '',
+                'name'   => 'addform',
+                'class'  => 'addform',
+            ),
             'inputs' => array(
                 array(
                     'type' => 'text',
index 576bd46eee866686b3ca40411c41b46a96d0196f..0781fe35d045631971f304533496f1bec82ddcc2 100644 (file)
@@ -1 +1 @@
-<span><a href="https://web.archive.org/web/%s"><img class="linklist-plugin-icon" src="plugins/archiveorg/internetarchive.png" title="View on archive.org" /></a></span>
+<span><a href="https://web.archive.org/web/%s"><img class="linklist-plugin-icon" src="plugins/archiveorg/internetarchive.png" title="View on archive.org" alt="archive.org" /></a></span>
index 15482fe0fb867c8bfe05674de05333041f024deb..8fdbf66383c144226770cc03334701801e7f5da8 100644 (file)
@@ -56,9 +56,11 @@ function hook_demo_plugin_render_header($data)
              * and a mandatory `html` key, which contains its value.
              */
             $button = array(
-                'href' => '#',
-                'class' => 'mybutton',
-                'title' => 'hover me',
+                'attr' => array (
+                    'href' => '#',
+                    'class' => 'mybutton',
+                    'title' => 'hover me',
+                ),
                 'html' => 'DEMO buttons toolbar',
             );
             $data['buttons_toolbar'][] = $button;
@@ -87,9 +89,11 @@ function hook_demo_plugin_render_header($data)
          *      </form>
          */
         $form = array(
-            'method' => 'GET',
-            'action' => '?',
-            'class' => 'addform',
+            'attr' => array(
+                'method' => 'GET',
+                'action' => '?',
+                'class' => 'addform',
+            ),
             'inputs' => array(
                 array(
                     'type' => 'text',
@@ -102,7 +106,9 @@ function hook_demo_plugin_render_header($data)
     }
     // Another button always displayed
     $button = array(
-        'href' => '#',
+        'attr' => array(
+            'href' => '#',
+        ),
         'html' => 'Demo',
     );
     $data['buttons_toolbar'][] = $button;
@@ -197,8 +203,10 @@ function hook_demo_plugin_render_linklist($data)
      * It's also recommended to add key 'on' or 'off' for theme rendering.
      */
     $action = array(
-        'href' => '?up',
-        'title' => 'Uppercase!',
+        'attr' => array(
+            'href' => '?up',
+            'title' => 'Uppercase!',
+        ),
         'html' => '←',
     );
 
index ffb7cfacd5dff8cea437ec925f2b683cc452693f..ce16645f9ad72f15121c511e7a2077aa65c3983f 100644 (file)
@@ -41,9 +41,9 @@ function hook_isso_render_linklist($data, $conf)
     // Only display comments for permalinks.
     if (count($data['links']) == 1 && empty($data['search_tags']) && empty($data['search_term'])) {
         $link = reset($data['links']);
-        $isso_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/isso/isso.html');
+        $issoHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/isso/isso.html');
 
-        $isso = sprintf($isso_html, $issoUrl, $issoUrl, $link['linkdate'], $link['linkdate']);
+        $isso = sprintf($issoHtml, $issoUrl, $issoUrl, $link['id'], $link['id']);
         $data['plugin_end_zone'][] = $isso;
 
         // Hackish way to include this CSS file only when necessary.
index 4f021871e47d6827118b223cdffd2902e819f3b5..aafcf0662ecf778da3051a44f891f5add70d3479 100644 (file)
@@ -20,26 +20,52 @@ The directory structure should look like:
      |--- markdown.css
      |--- markdown.meta
      |--- markdown.php
-     |--- Parsedown.php
      |--- README.md
 ```
 
 To enable the plugin, just check it in the plugin administration page.
 
-You can also add `markdown` to your list of enabled plugins in `data/config.php`
-(`ENABLED_PLUGINS` array).
+You can also add `markdown` to your list of enabled plugins in `data/config.json.php`
+(`general.enabled_plugins` list).
 
 This should look like:
 
 ```
-$GLOBALS['config']['ENABLED_PLUGINS'] = array('qrcode', 'any_other_plugin', 'markdown')
+"general": {
+  "enabled_plugins": [
+    "markdown",
+    [...]
+  ],
+}
 ```
 
+Parsedown parsing library is imported using Composer. If you installed Shaarli using `git`,
+or the `master` branch, run
+
+    composer update --no-dev --prefer-dist
+
 ### No Markdown tag
 
-If the tag `.nomarkdown` is set for a shaare, it won't be converted to Markdown syntax.
+If the tag `nomarkdown` is set for a shaare, it won't be converted to Markdown syntax.
  
-> Note: it's a private tag (leading dot), so it won't be displayed to visitors.
+> Note: this is a special tag, so it won't be displayed in link list.
+
+### HTML rendering
+
+Markdown support HTML tags. For example:
+
+    > <strong>strong</strong><strike>strike</strike>
+   
+Will render as:
+
+> <strong>strong</strong><strike>strike</strike>
+
+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.
 
 ### Known issue
 
index e3904ed83289d9dd294eb8afaa26b8447b3cbbc0..8df2ed0bc3b6af6694166c3c613db97d8fdf924a 100644 (file)
@@ -1 +1,4 @@
-description="Render shaare description with Markdown syntax."
+description="Render shaare description with Markdown syntax.<br><strong>Warning</strong>:
+If your shaared descriptions containing HTML tags before enabling the markdown plugin,
+enabling it might break your page.
+See the <a href=\"https://github.com/shaarli/Shaarli/tree/master/plugins/markdown#html-rendering\">README</a>."
index a764b6fa88520711ec6b46183894040055e6fe3a..0cf6e6e2d283e32de9ec978389cad40ad5c309c0 100644 (file)
@@ -22,7 +22,7 @@ function hook_markdown_render_linklist($data)
 {
     foreach ($data['links'] as &$value) {
         if (!empty($value['tags']) && noMarkdownTag($value['tags'])) {
-            $value['taglist'] = stripNoMarkdownTag($value['taglist']);
+            $value = stripNoMarkdownTag($value);
             continue;
         }
         $value['description'] = process_markdown($value['description']);
@@ -41,7 +41,7 @@ function hook_markdown_render_feed($data)
 {
     foreach ($data['links'] as &$value) {
         if (!empty($value['tags']) && noMarkdownTag($value['tags'])) {
-            $value['tags'] = stripNoMarkdownTag($value['tags']);
+            $value = stripNoMarkdownTag($value);
             continue;
         }
         $value['description'] = process_markdown($value['description']);
@@ -63,6 +63,7 @@ function hook_markdown_render_daily($data)
     foreach ($data['cols'] as &$value) {
         foreach ($value as &$value2) {
             if (!empty($value2['tags']) && noMarkdownTag($value2['tags'])) {
+                $value2 = stripNoMarkdownTag($value2);
                 continue;
             }
             $value2['formatedDescription'] = process_markdown($value2['formatedDescription']);
@@ -81,20 +82,30 @@ function hook_markdown_render_daily($data)
  */
 function noMarkdownTag($tags)
 {
-    return strpos($tags, NO_MD_TAG) !== false;
+    return preg_match('/(^|\s)'. NO_MD_TAG .'(\s|$)/', $tags);
 }
 
 /**
  * Remove the no-markdown meta tag so it won't be displayed.
  *
- * @param string $tags Tag list.
+ * @param array $link Link data.
  *
- * @return string tag list without no markdown tag.
+ * @return array Updated link without no markdown tag.
  */
-function stripNoMarkdownTag($tags)
+function stripNoMarkdownTag($link)
 {
-    unset($tags[array_search(NO_MD_TAG, $tags)]);
-    return array_values($tags);
+    if (! empty($link['taglist'])) {
+        $offset = array_search(NO_MD_TAG, $link['taglist']);
+        if ($offset !== false) {
+            unset($link['taglist'][$offset]);
+        }
+    }
+
+    if (!empty($link['tags'])) {
+        str_replace(NO_MD_TAG, '', $link['tags']);
+    }
+
+    return $link;
 }
 
 /**
index 2f4b0a3167c2c3510ec149c58337bf0af1bdd360..644845049ff6599407fc43f9c5d99a9c0d4efd81 100644 (file)
@@ -17,9 +17,11 @@ function hook_playvideos_render_header($data)
 {
     if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) {
         $playvideo = array(
-            'href' => '#',
-            'title' => 'Video player',
-            'id' => 'playvideos',
+            'attr' => array(
+                'href' => '#',
+                'title' => 'Video player',
+                'id' => 'playvideos',
+            ),
             'html' => '► Play Videos'
         );
         $data['buttons_toolbar'][] = $playvideo;
index cebc56443b2e6b04b16bf4f48dfda49b8d44dabc..dc214ed159ad35904c4842ba52b158a61ecd6a1f 100644 (file)
@@ -1,5 +1,5 @@
 <div class="linkqrcode">
     <a href="http://qrfree.kaywa.com/?l=1&amp;s=8&amp;d=%s" onclick="showQrCode(this); return false;" class="qrcode" data-permalink="%s">
-        <img src="%s/qrcode/qrcode.png" class="linklist-plugin-icon" title="QR-Code">
+        <img src="%s/qrcode/qrcode.png" class="linklist-plugin-icon" title="QR-Code" alt="QRCode">
     </a>
 </div>
index e8c5f7844806f4dca09efec332ca7cea9512fa36..5e200715a866f21be1b62243c6e1f2fb404d0283 100644 (file)
@@ -1 +1 @@
-<span><a href="%s?url=%s"><img class="linklist-plugin-icon" src="%s/readityourself/book-open.png" title="Read with Readityourself" /></a></span>
+<span><a href="%s?url=%s"><img class="linklist-plugin-icon" src="%s/readityourself/book-open.png" title="Read with Readityourself" alt="readityourself" /></a></span>
index c7b1d044bb22201904bddc1c120d27ead1b9831e..e861536d53866fe4fe7f2a47182f3442043a5b84 100644 (file)
@@ -1 +1 @@
-<span><a href="%s%s" target="_blank"><img class="linklist-plugin-icon" src="%s/wallabag/wallabag.png" title="Save to wallabag" /></a></span>
+<span><a href="%s%s" target="_blank"><img class="linklist-plugin-icon" src="%s/wallabag/wallabag.png" title="Save to wallabag" alt="wallabag" /></a></span>
index eaab95c6f6295374c8f2699858e22b0ae043ab01..431387bb75bf236a7696629476a1bf25088b7371 100644 (file)
@@ -1 +1 @@
-<?php /* 0.8.0 */ ?>
+<?php /* 0.8.1 */ ?>
index b584d98c56a27ebb09c963313876fd8d1a3e9987..f601c1eeee5ff6e42f4eccdd43f76169fdf0ba0a 100644 (file)
@@ -1,2 +1,13 @@
-Allow from none
-Deny from all
+<IfModule version_module>
+  <IfVersion >= 2.4>
+     Require all denied
+  </IfVersion>
+  <IfVersion < 2.4>
+     Allow from none
+     Deny from all
+  </IfVersion>
+</IfModule>
+
+<IfModule !version_module>
+    Require all denied
+</IfModule>
index d783940273c83f07e4e79a23f3f63d4d5fc5d9e4..06a445064db3aab7c46fceda27ba08b205d1a143 100644 (file)
@@ -84,8 +84,9 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
 
         // Test first link (note link)
-        $link = array_shift($data['links']);
-        $this->assertEquals('20150310_114651', $link['linkdate']);
+        $link = reset($data['links']);
+        $this->assertEquals(41, $link['id']);
+        $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
         $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
         $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
         $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']);
@@ -99,14 +100,14 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
         $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']);
+        $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links'][8]['url']);
 
         // Test multitags.
-        $this->assertEquals(5, count($data['links']['20141125_084734']['taglist']));
-        $this->assertEquals('css', $data['links']['20141125_084734']['taglist'][0]);
+        $this->assertEquals(5, count($data['links'][6]['taglist']));
+        $this->assertEquals('css', $data['links'][6]['taglist'][0]);
 
         // Test update date
-        $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links']['20150310_114633']['up_iso_date']);
+        $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
     }
 
     /**
@@ -119,9 +120,9 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
         $data = $feedBuilder->buildData();
         $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
         $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']);
-        $link = array_shift($data['links']);
+        $link = reset($data['links']);
         $this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:\d{2}/', $link['pub_iso_date']);
-        $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links']['20150310_114633']['up_iso_date']);
+        $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
     }
 
     /**
@@ -138,7 +139,8 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
         $data = $feedBuilder->buildData();
         $this->assertEquals(1, count($data['links']));
         $link = array_shift($data['links']);
-        $this->assertEquals('20150310_114651', $link['linkdate']);
+        $this->assertEquals(41, $link['id']);
+        $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
     }
 
     /**
@@ -154,7 +156,8 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
         $data = $feedBuilder->buildData();
         $this->assertEquals(1, count($data['links']));
         $link = array_shift($data['links']);
-        $this->assertEquals('20150310_114651', $link['linkdate']);
+        $this->assertEquals(41, $link['id']);
+        $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
     }
 
     /**
@@ -170,15 +173,17 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
         $this->assertTrue($data['usepermalinks']);
         // First link is a permalink
         $link = array_shift($data['links']);
-        $this->assertEquals('20150310_114651', $link['linkdate']);
+        $this->assertEquals(41, $link['id']);
+        $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
         $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
         $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
         $this->assertContains('Direct link', $link['description']);
         $this->assertContains('http://host.tld/?WDWyig', $link['description']);
         // Second link is a direct link
         $link = array_shift($data['links']);
-        $this->assertEquals('20150310_114633', $link['linkdate']);
-        $this->assertEquals('http://host.tld/?kLHmZg', $link['guid']);
+        $this->assertEquals(8, $link['id']);
+        $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'), $link['created']);
+        $this->assertEquals('http://host.tld/?RttfEw', $link['guid']);
         $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']);
         $this->assertContains('Direct link', $link['description']);
         $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']);
index 9d79386c0b0b5a76aecdfe8825434722278290d4..1f62a34a160ea5e17c31bbaa0117ef7785ff4202 100644 (file)
@@ -186,14 +186,15 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
         $dbSize = sizeof($testDB);
 
         $link = array(
+            'id' => 42,
             'title'=>'an additional link',
             'url'=>'http://dum.my',
             'description'=>'One more',
             'private'=>0,
-            'linkdate'=>'20150518_190000',
+            'created'=> DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150518_190000'),
             'tags'=>'unit test'
         );
-        $testDB[$link['linkdate']] = $link;
+        $testDB[$link['id']] = $link;
         $testDB->save('tests');
 
         $testDB = new LinkDB(self::$testDatastore, true, false);
@@ -238,12 +239,12 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
     public function testDays()
     {
         $this->assertEquals(
-            array('20121206', '20130614', '20150310'),
+            array('20100310', '20121206', '20130614', '20150310'),
             self::$publicLinkDB->days()
         );
 
         $this->assertEquals(
-            array('20121206', '20130614', '20141125', '20150310'),
+            array('20100310', '20121206', '20130614', '20141125', '20150310'),
             self::$privateLinkDB->days()
         );
     }
@@ -290,10 +291,11 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
                 'stallman' => 1,
                 'free' => 1,
                 '-exclude' => 1,
+                'hashtag' => 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`).
+                // They need to be grouped with the first case found - order by date DESC: `sTuff`.
                 'sTuff' => 2,
-                'hashtag' => 2,
+                'ut' => 1,
             ),
             self::$publicLinkDB->allTags()
         );
@@ -321,6 +323,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
                 'tag2' => 1,
                 'tag3' => 1,
                 'tag4' => 1,
+                'ut' => 1,
             ),
             self::$privateLinkDB->allTags()
         );
@@ -411,6 +414,11 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
             1,
             count(self::$publicLinkDB->filterHash($request))
         );
+        $request = smallHash('20150310_114633' . 8);
+        $this->assertEquals(
+            1,
+            count(self::$publicLinkDB->filterHash($request))
+        );
     }
 
     /**
@@ -433,4 +441,23 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
     {
         self::$publicLinkDB->filterHash('');
     }
+
+    /**
+     * Test reorder with asc/desc parameter.
+     */
+    public function testReorderLinksDesc()
+    {
+        self::$privateLinkDB->reorder('ASC');
+        $linkIds = array(42, 4, 1, 0, 7, 6, 8, 41);
+        $cpt = 0;
+        foreach (self::$privateLinkDB as $key => $value) {
+            $this->assertEquals($linkIds[$cpt++], $key);
+        }
+        self::$privateLinkDB->reorder('DESC');
+        $linkIds = array_reverse($linkIds);
+        $cpt = 0;
+        foreach (self::$privateLinkDB as $key => $value) {
+            $this->assertEquals($linkIds[$cpt++], $key);
+        }
+    }
 }
index 7d45fc59c985af654365646c2767bf4e08936a5f..21d680a5ae59f55532e85e45f67e7abc4e1b31f1 100644 (file)
@@ -159,7 +159,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             'MediaGoblin',
-            $links['20130614_184135']['title']
+            $links[7]['title']
         );
     }
 
@@ -286,7 +286,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
         );
 
         $this->assertEquals(
-            6,
+            7,
             count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution'))
         );
     }
@@ -346,7 +346,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
         );
 
         $this->assertEquals(
-            6,
+            7,
             count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free'))
         );
     }
index cc54ab9fcd2cdce9b3ac9c604e87236f4537e50d..6a47bbb97acac26996766c7cf9c30f8738fc069c 100644 (file)
@@ -50,7 +50,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
         $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all', false, '');
         $this->assertEquals(self::$refDb->countLinks(), sizeof($links));
         foreach ($links as $link) {
-            $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
+            $date = $link['created'];
             $this->assertEquals(
                 $date->getTimestamp(),
                 $link['timestamp']
@@ -70,7 +70,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
         $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private', false, '');
         $this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links));
         foreach ($links as $link) {
-            $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
+            $date = $link['created'];
             $this->assertEquals(
                 $date->getTimestamp(),
                 $link['timestamp']
@@ -90,7 +90,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
         $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, '');
         $this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links));
         foreach ($links as $link) {
-            $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
+            $date = $link['created'];
             $this->assertEquals(
                 $date->getTimestamp(),
                 $link['timestamp']
index f0ad500f09a1879803d11b23bb4ed4a9a6924745..0ca07eacb7741e83aebb126e19502da62cbe5fee 100644 (file)
@@ -42,6 +42,18 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
      */
     protected $pagecache = 'tests';
 
+    /**
+     * @var string Save the current timezone.
+     */
+    protected static $defaultTimeZone;
+
+    public static function setUpBeforeClass()
+    {
+        self::$defaultTimeZone = date_default_timezone_get();
+        // Timezone without DST for test consistency
+        date_default_timezone_set('Africa/Nairobi');
+    }
+
     /**
      * Resets test data before each test
      */
@@ -55,6 +67,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
         $this->linkDb = new LinkDB(self::$testDatastore, true, false);
     }
 
+    public static function tearDownAfterClass()
+    {
+        date_default_timezone_set(self::$defaultTimeZone);
+    }
+
     /**
      * Attempt to import bookmarks from an empty file
      */
@@ -98,18 +115,19 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             array(
-                'linkdate' => '20160618_173944',
+                'id' => 0,
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160618_203944'),
                 'title' => 'Hg Init a Mercurial tutorial by Joel Spolsky',
                 'url' => 'http://hginit.com/',
                 'description' => '',
                 'private' => 0,
-                'tags' => ''
+                'tags' => '',
+                'shorturl' => 'La37cg',
             ),
             $this->linkDb->getLinkFromUrl('http://hginit.com/')
         );
     }
 
-
     /**
      * Import bookmarks nested in a folder hierarchy
      */
@@ -126,89 +144,105 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             array(
-                'linkdate' => '20160225_205541',
+                'id' => 0,
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235541'),
                 'title' => 'Nested 1',
                 'url' => 'http://nest.ed/1',
                 'description' => '',
                 'private' => 0,
-                'tags' => 'tag1 tag2'
+                'tags' => 'tag1 tag2',
+                'shorturl' => 'KyDNKA',
             ),
             $this->linkDb->getLinkFromUrl('http://nest.ed/1')
         );
         $this->assertEquals(
             array(
-                'linkdate' => '20160225_205542',
+                'id' => 1,
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235542'),
                 'title' => 'Nested 1-1',
                 'url' => 'http://nest.ed/1-1',
                 'description' => '',
                 'private' => 0,
-                'tags' => 'folder1 tag1 tag2'
+                'tags' => 'folder1 tag1 tag2',
+                'shorturl' => 'T2LnXg',
             ),
             $this->linkDb->getLinkFromUrl('http://nest.ed/1-1')
         );
         $this->assertEquals(
             array(
-                'linkdate' => '20160225_205547',
+                'id' => 2,
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235547'),
                 'title' => 'Nested 1-2',
                 'url' => 'http://nest.ed/1-2',
                 'description' => '',
                 'private' => 0,
-                'tags' => 'folder1 tag3 tag4'
+                'tags' => 'folder1 tag3 tag4',
+                'shorturl' => '46SZxA',
             ),
             $this->linkDb->getLinkFromUrl('http://nest.ed/1-2')
         );
         $this->assertEquals(
             array(
-                'linkdate' => '20160202_172222',
+                'id' => 3,
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'),
                 'title' => 'Nested 2-1',
                 'url' => 'http://nest.ed/2-1',
                 'description' => 'First link of the second section',
                 'private' => 1,
-                'tags' => 'folder2'
+                'tags' => 'folder2',
+                'shorturl' => '4UHOSw',
             ),
             $this->linkDb->getLinkFromUrl('http://nest.ed/2-1')
         );
         $this->assertEquals(
             array(
-                'linkdate' => '20160119_200227',
+                'id' => 4,
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'),
                 'title' => 'Nested 2-2',
                 'url' => 'http://nest.ed/2-2',
                 'description' => 'Second link of the second section',
                 'private' => 1,
-                'tags' => 'folder2'
+                'tags' => 'folder2',
+                'shorturl' => 'yfzwbw',
             ),
             $this->linkDb->getLinkFromUrl('http://nest.ed/2-2')
         );
         $this->assertEquals(
             array(
-                'linkdate' => '20160202_172223',
+                'id' => 5,
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'),
                 'title' => 'Nested 3-1',
                 'url' => 'http://nest.ed/3-1',
                 'description' => '',
                 'private' => 0,
-                'tags' => 'folder3 folder3-1 tag3'
+                'tags' => 'folder3 folder3-1 tag3',
+                'shorturl' => 'UwxIUQ',
             ),
             $this->linkDb->getLinkFromUrl('http://nest.ed/3-1')
         );
         $this->assertEquals(
             array(
-                'linkdate' => '20160119_200228',
+                'id' => 6,
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'),
                 'title' => 'Nested 3-2',
                 'url' => 'http://nest.ed/3-2',
                 'description' => '',
                 'private' => 0,
-                'tags' => 'folder3 folder3-1'
+                'tags' => 'folder3 folder3-1',
+                'shorturl' => 'p8dyZg',
             ),
             $this->linkDb->getLinkFromUrl('http://nest.ed/3-2')
         );
         $this->assertEquals(
             array(
-                'linkdate' => '20160229_081541',
+                'id' => 7,
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160229_111541'),
                 'title' => 'Nested 2',
                 'url' => 'http://nest.ed/2',
                 'description' => '',
                 'private' => 0,
-                'tags' => 'tag4'
+                'tags' => 'tag4',
+                'shorturl' => 'Gt3Uug',
             ),
             $this->linkDb->getLinkFromUrl('http://nest.ed/2')
         );
@@ -227,28 +261,34 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
             .' 2 links imported, 0 links overwritten, 0 links skipped.',
             NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->pagecache)
         );
+
         $this->assertEquals(2, count($this->linkDb));
         $this->assertEquals(1, count_private($this->linkDb));
 
         $this->assertEquals(
             array(
-                'linkdate' => '20001010_105536',
+                'id' => 0,
+                // Old link - UTC+4 (note that TZ in the import file is ignored).
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20001010_135536'),
                 'title' => 'Secret stuff',
                 'url' => 'https://private.tld',
                 'description' => "Super-secret stuff you're not supposed to know about",
                 'private' => 1,
-                'tags' => 'private secret'
+                'tags' => 'private secret',
+                'shorturl' => 'EokDtA',
             ),
             $this->linkDb->getLinkFromUrl('https://private.tld')
         );
         $this->assertEquals(
             array(
-                'linkdate' => '20160225_205548',
+                'id' => 1,
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'),
                 'title' => 'Public stuff',
                 'url' => 'http://public.tld',
                 'description' => '',
                 'private' => 0,
-                'tags' => 'public hello world'
+                'tags' => 'public hello world',
+                'shorturl' => 'Er9ddA',
             ),
             $this->linkDb->getLinkFromUrl('http://public.tld')
         );
@@ -271,23 +311,28 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             array(
-                'linkdate' => '20001010_105536',
+                'id' => 0,
+                // Note that TZ in the import file is ignored.
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20001010_135536'),
                 'title' => 'Secret stuff',
                 'url' => 'https://private.tld',
                 'description' => "Super-secret stuff you're not supposed to know about",
                 'private' => 1,
-                'tags' => 'private secret'
+                'tags' => 'private secret',
+                'shorturl' => 'EokDtA',
             ),
             $this->linkDb->getLinkFromUrl('https://private.tld')
         );
         $this->assertEquals(
             array(
-                'linkdate' => '20160225_205548',
+                'id' => 1,
+                'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'),
                 'title' => 'Public stuff',
                 'url' => 'http://public.tld',
                 'description' => '',
                 'private' => 0,
-                'tags' => 'public hello world'
+                'tags' => 'public hello world',
+                'shorturl' => 'Er9ddA',
             ),
             $this->linkDb->getLinkFromUrl('http://public.tld')
         );
@@ -309,11 +354,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(0, count_private($this->linkDb));
         $this->assertEquals(
             0,
-            $this->linkDb['20001010_105536']['private']
+            $this->linkDb[0]['private']
         );
         $this->assertEquals(
             0,
-            $this->linkDb['20160225_205548']['private']
+            $this->linkDb[1]['private']
         );
     }
 
@@ -333,11 +378,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(2, count_private($this->linkDb));
         $this->assertEquals(
             1,
-            $this->linkDb['20001010_105536']['private']
+            $this->linkDb['0']['private']
         );
         $this->assertEquals(
             1,
-            $this->linkDb['20160225_205548']['private']
+            $this->linkDb['1']['private']
         );
     }
 
@@ -359,13 +404,12 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(2, count_private($this->linkDb));
         $this->assertEquals(
             1,
-            $this->linkDb['20001010_105536']['private']
+            $this->linkDb[0]['private']
         );
         $this->assertEquals(
             1,
-            $this->linkDb['20160225_205548']['private']
+            $this->linkDb[1]['private']
         );
-
         // re-import as public, enable overwriting
         $post = array(
             'privacy' => 'public',
@@ -380,11 +424,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(0, count_private($this->linkDb));
         $this->assertEquals(
             0,
-            $this->linkDb['20001010_105536']['private']
+            $this->linkDb[0]['private']
         );
         $this->assertEquals(
             0,
-            $this->linkDb['20160225_205548']['private']
+            $this->linkDb[1]['private']
         );
     }
 
@@ -406,11 +450,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(0, count_private($this->linkDb));
         $this->assertEquals(
             0,
-            $this->linkDb['20001010_105536']['private']
+            $this->linkDb['0']['private']
         );
         $this->assertEquals(
             0,
-            $this->linkDb['20160225_205548']['private']
+            $this->linkDb['1']['private']
         );
 
         // re-import as private, enable overwriting
@@ -427,11 +471,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(2, count_private($this->linkDb));
         $this->assertEquals(
             1,
-            $this->linkDb['20001010_105536']['private']
+            $this->linkDb['0']['private']
         );
         $this->assertEquals(
             1,
-            $this->linkDb['20160225_205548']['private']
+            $this->linkDb['1']['private']
         );
     }
 
@@ -480,11 +524,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(0, count_private($this->linkDb));
         $this->assertEquals(
             'tag1 tag2 tag3 private secret',
-            $this->linkDb['20001010_105536']['tags']
+            $this->linkDb['0']['tags']
         );
         $this->assertEquals(
             'tag1 tag2 tag3 public hello world',
-            $this->linkDb['20160225_205548']['tags']
+            $this->linkDb['1']['tags']
         );
     }
 
@@ -507,16 +551,16 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(0, count_private($this->linkDb));
         $this->assertEquals(
             'tag1&amp; tag2 &quot;tag3&quot; private secret',
-            $this->linkDb['20001010_105536']['tags']
+            $this->linkDb['0']['tags']
         );
         $this->assertEquals(
             'tag1&amp; tag2 &quot;tag3&quot; public hello world',
-            $this->linkDb['20160225_205548']['tags']
+            $this->linkDb['1']['tags']
         );
     }
 
     /**
-     * Ensure each imported bookmark has a unique linkdate
+     * Ensure each imported bookmark has a unique id
      *
      * See https://github.com/shaarli/Shaarli/issues/351
      */
@@ -531,16 +575,16 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(3, count($this->linkDb));
         $this->assertEquals(0, count_private($this->linkDb));
         $this->assertEquals(
-            '20160225_205548',
-            $this->linkDb['20160225_205548']['linkdate']
+            0,
+            $this->linkDb[0]['id']
         );
         $this->assertEquals(
-            '20160225_205549',
-            $this->linkDb['20160225_205549']['linkdate']
+            1,
+            $this->linkDb[1]['id']
         );
         $this->assertEquals(
-            '20160225_205550',
-            $this->linkDb['20160225_205550']['linkdate']
+            2,
+            $this->linkDb[2]['id']
         );
     }
 }
index 0d0ad92220cac1fef8fc954e645066854c7f555a..4948fe52d20ec1e39ec30b5eb5f39633f70880d5 100644 (file)
@@ -214,6 +214,7 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
         $refDB = new ReferenceLinkDB();
         $refDB->write(self::$testDatastore);
         $linkDB = new LinkDB(self::$testDatastore, true, false);
+
         $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
         $updater = new Updater(array(), $linkDB, $this->conf, true);
         $updater->updateMethodRenameDashTags();
@@ -287,4 +288,101 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
         $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url'));
         unlink($sandbox .'.json.php');
     }
+
+    /**
+     * Test updateMethodDatastoreIds().
+     */
+    public function testDatastoreIds()
+    {
+        $links = array(
+            '20121206_182539' => array(
+                'linkdate' => '20121206_182539',
+                'title' => 'Geek and Poke',
+                'url' => 'http://geek-and-poke.com/',
+                'description' => 'desc',
+                'tags' => 'dev cartoon tag1  tag2   tag3  tag4   ',
+                'updated' => '20121206_190301',
+                'private' => false,
+            ),
+            '20121206_172539' => array(
+                'linkdate' => '20121206_172539',
+                'title' => 'UserFriendly - Samba',
+                'url' => 'http://ars.userfriendly.org/cartoons/?id=20010306',
+                'description' => '',
+                'tags' => 'samba cartoon web',
+                'private' => false,
+            ),
+            '20121206_142300' => array(
+                'linkdate' => '20121206_142300',
+                'title' => 'UserFriendly - Web Designer',
+                'url' => 'http://ars.userfriendly.org/cartoons/?id=20121206',
+                'description' => 'Naming conventions... #private',
+                'tags' => 'samba cartoon web',
+                'private' => true,
+            ),
+        );
+        $refDB = new ReferenceLinkDB();
+        $refDB->setLinks($links);
+        $refDB->write(self::$testDatastore);
+        $linkDB = new LinkDB(self::$testDatastore, true, false);
+
+        $checksum = hash_file('sha1', self::$testDatastore);
+
+        $this->conf->set('resource.data_dir', 'sandbox');
+        $this->conf->set('resource.datastore', self::$testDatastore);
+
+        $updater = new Updater(array(), $linkDB, $this->conf, true);
+        $this->assertTrue($updater->updateMethodDatastoreIds());
+
+        $linkDB = new LinkDB(self::$testDatastore, true, false);
+
+        $backup = glob($this->conf->get('resource.data_dir') . '/datastore.'. date('YmdH') .'*.php');
+        $backup = $backup[0];
+
+        $this->assertFileExists($backup);
+        $this->assertEquals($checksum, hash_file('sha1', $backup));
+        unlink($backup);
+
+        $this->assertEquals(3, count($linkDB));
+        $this->assertTrue(isset($linkDB[0]));
+        $this->assertFalse(isset($linkDB[0]['linkdate']));
+        $this->assertEquals(0, $linkDB[0]['id']);
+        $this->assertEquals('UserFriendly - Web Designer', $linkDB[0]['title']);
+        $this->assertEquals('http://ars.userfriendly.org/cartoons/?id=20121206', $linkDB[0]['url']);
+        $this->assertEquals('Naming conventions... #private', $linkDB[0]['description']);
+        $this->assertEquals('samba cartoon web', $linkDB[0]['tags']);
+        $this->assertTrue($linkDB[0]['private']);
+        $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'), $linkDB[0]['created']);
+
+        $this->assertTrue(isset($linkDB[1]));
+        $this->assertFalse(isset($linkDB[1]['linkdate']));
+        $this->assertEquals(1, $linkDB[1]['id']);
+        $this->assertEquals('UserFriendly - Samba', $linkDB[1]['title']);
+        $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'), $linkDB[1]['created']);
+
+        $this->assertTrue(isset($linkDB[2]));
+        $this->assertFalse(isset($linkDB[2]['linkdate']));
+        $this->assertEquals(2, $linkDB[2]['id']);
+        $this->assertEquals('Geek and Poke', $linkDB[2]['title']);
+        $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'), $linkDB[2]['created']);
+        $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_190301'), $linkDB[2]['updated']);
+    }
+
+    /**
+     * Test updateMethodDatastoreIds() with the update already applied: nothing to do.
+     */
+    public function testDatastoreIdsNothingToDo()
+    {
+        $refDB = new ReferenceLinkDB();
+        $refDB->write(self::$testDatastore);
+        $linkDB = new LinkDB(self::$testDatastore, true, false);
+
+        $this->conf->set('resource.data_dir', 'sandbox');
+        $this->conf->set('resource.datastore', self::$testDatastore);
+
+        $checksum = hash_file('sha1', self::$testDatastore);
+        $updater = new Updater(array(), $linkDB, $this->conf, true);
+        $this->assertTrue($updater->updateMethodDatastoreIds());
+        $this->assertEquals($checksum, hash_file('sha1', self::$testDatastore));
+    }
 }
index 1f545c7d6e4e01c69c3407471839f4334dad8237..6b7904dd595fcbc09e64c5ec5c99fe66773b7cbc 100644 (file)
@@ -47,12 +47,14 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
         $conf->set('plugins.ISSO_SERVER', 'value');
 
         $str = 'http://randomstr.com/test';
+        $date = '20161118_100001';
         $data = array(
             'title' => $str,
             'links' => array(
                 array(
+                    'id' => 12,
                     'url' => $str,
-                    'linkdate' => 'abc',
+                    'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
                 )
             )
         );
@@ -65,7 +67,14 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
 
         // plugin data
         $this->assertEquals(1, count($data['plugin_end_zone']));
-        $this->assertNotFalse(strpos($data['plugin_end_zone'][0], 'abc'));
+        $this->assertNotFalse(strpos(
+            $data['plugin_end_zone'][0],
+            'data-isso-id="'. $data['links'][0]['id'] .'"'
+        ));
+        $this->assertNotFalse(strpos(
+            $data['plugin_end_zone'][0],
+            'data-title="'. $data['links'][0]['id'] .'"'
+        ));
         $this->assertNotFalse(strpos($data['plugin_end_zone'][0], 'embed.min.js'));
     }
 
@@ -78,16 +87,20 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
         $conf->set('plugins.ISSO_SERVER', 'value');
 
         $str = 'http://randomstr.com/test';
+        $date1 = '20161118_100001';
+        $date2 = '20161118_100002';
         $data = array(
             'title' => $str,
             'links' => array(
                 array(
+                    'id' => 12,
                     'url' => $str,
-                    'linkdate' => 'abc',
+                    'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date1),
                 ),
                 array(
+                    'id' => 13,
                     'url' => $str . '2',
-                    'linkdate' => 'abc2',
+                    'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date2),
                 ),
             )
         );
@@ -106,12 +119,14 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
         $conf->set('plugins.ISSO_SERVER', 'value');
 
         $str = 'http://randomstr.com/test';
+        $date = '20161118_100001';
         $data = array(
             'title' => $str,
             'links' => array(
                 array(
+                    'id' => 12,
                     'url' => $str,
-                    'linkdate' => 'abc',
+                    'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
                 )
             ),
             'search_term' => $str
index 12bdda24231b47a9cad0454694d030d7b4884362..4a67b2dc8566b86c6becd7e4feab9bcbf84e8fed 100644 (file)
@@ -8,8 +8,8 @@ require_once 'application/Utils.php';
 require_once 'plugins/markdown/markdown.php';
 
 /**
- * Class PlugQrcodeTest
- * Unit test for the QR-Code plugin
+ * Class PluginMarkdownTest
+ * Unit test for the Markdown plugin
  */
 class PluginMarkdownTest extends PHPUnit_Framework_TestCase
 {
@@ -130,8 +130,11 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
             ))
         );
 
-        $data = hook_markdown_render_linklist($data);
-        $this->assertEquals($str, $data['links'][0]['description']);
+        $processed = hook_markdown_render_linklist($data);
+        $this->assertEquals($str, $processed['links'][0]['description']);
+
+        $processed = hook_markdown_render_feed($data);
+        $this->assertEquals($str, $processed['links'][0]['description']);
 
         $data = array(
             // Columns data
@@ -152,6 +155,37 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
         $this->assertEquals($str, $data['cols'][0][0]['formatedDescription']);
     }
 
+    /**
+     * Test that a close value to nomarkdown is not understand as nomarkdown (previous value `.nomarkdown`).
+     */
+    function testNoMarkdownNotExcactlyMatching()
+    {
+        $str = 'All _work_ and `no play` makes Jack a *dull* boy.';
+        $data = array(
+            'links' => array(array(
+                'description' => $str,
+                'tags' => '.' . NO_MD_TAG,
+                'taglist' => array('.'. NO_MD_TAG),
+            ))
+        );
+
+        $data = hook_markdown_render_feed($data);
+        $this->assertContains('<em>', $data['links'][0]['description']);
+    }
+
+    /**
+     * Test hashtag links processed with markdown.
+     */
+    function testMarkdownHashtagLinks()
+    {
+        $md = file_get_contents('tests/plugins/resources/markdown.md');
+        $md = format_description($md);
+        $html = file_get_contents('tests/plugins/resources/markdown.html');
+
+        $data = process_markdown($md);
+        $this->assertEquals($html, $data);
+    }
+
     /**
      * Test hashtag links processed with markdown.
      */
index abca465687ef8b6d020330022050b86a69ef3a4c..36d58c683e449eb11ac2388c3c4fe43bda2bd1b6 100644 (file)
@@ -4,7 +4,7 @@
  */
 class ReferenceLinkDB
 {
-    public static $NB_LINKS_TOTAL = 7;
+    public static $NB_LINKS_TOTAL = 8;
 
     private $_links = array();
     private $_publicCount = 0;
@@ -16,66 +16,87 @@ class ReferenceLinkDB
     public function __construct()
     {
         $this->addLink(
+            41,
             'Link title: @website',
             '?WDWyig',
             'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag',
             0,
-            '20150310_114651',
-            'sTuff'
+            DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'),
+            'sTuff',
+            null,
+            'WDWyig'
         );
 
         $this->addLink(
+            42,
+            'Note: I have a big ID but an old date',
+            '?WDWyig',
+            'Used to test links reordering.',
+            0,
+            DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20100310_101010'),
+            'ut'
+        );
+
+        $this->addLink(
+            8,
             '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. #hashtag',
             0,
-            '20150310_114633',
+            DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'),
             'free gnu software stallman -exclude stuff hashtag',
-            '20160803_093033'
+            DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160803_093033')
         );
 
         $this->addLink(
+            7,
             'MediaGoblin',
             'http://mediagoblin.org/',
             'A free software media publishing platform #hashtagOther',
             0,
-            '20130614_184135',
-            'gnu media web .hidden hashtag'
+            DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
+            'gnu media web .hidden hashtag',
+            null,
+            'IuWvgA'
         );
 
         $this->addLink(
+            6,
             'w3c-markup-validator',
             'https://dvcs.w3.org/hg/markup-validator/summary',
             'Mercurial repository for the W3C Validator #private',
             1,
-            '20141125_084734',
+            DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20141125_084734'),
             'css html w3c web Mercurial'
         );
 
         $this->addLink(
+            4,
             'UserFriendly - Web Designer',
             'http://ars.userfriendly.org/cartoons/?id=20121206',
             'Naming conventions... #private',
             0,
-            '20121206_142300',
+            DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'),
             'dev cartoon web'
         );
 
         $this->addLink(
+            1,
             'UserFriendly - Samba',
             'http://ars.userfriendly.org/cartoons/?id=20010306',
             'Tropical printing',
             0,
-            '20121206_172539',
+            DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'),
             'samba cartoon web'
         );
 
         $this->addLink(
+            0,
             'Geek and Poke',
             'http://geek-and-poke.com/',
             '',
             1,
-            '20121206_182539',
+            DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'),
             'dev cartoon tag1  tag2   tag3  tag4   '
         );
     }
@@ -83,18 +104,20 @@ class ReferenceLinkDB
     /**
      * Adds a new link
      */
-    protected function addLink($title, $url, $description, $private, $date, $tags, $updated = '')
+    protected function addLink($id, $title, $url, $description, $private, $date, $tags, $updated = '', $shorturl = '')
     {
         $link = array(
+            'id' => $id,
             'title' => $title,
             'url' => $url,
             'description' => $description,
             'private' => $private,
-            'linkdate' => $date,
             'tags' => $tags,
+            'created' => $date,
             'updated' => $updated,
+            'shorturl' => $shorturl ? $shorturl : smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id),
         );
-        $this->_links[$date] = $link;
+        $this->_links[$id] = $link;
 
         if ($private) {
             $this->_privateCount++;
@@ -142,4 +165,14 @@ class ReferenceLinkDB
     {
         return $this->_links;
     }
+
+    /**
+     * Setter to override link creation.
+     *
+     * @param array $links List of links.
+     */
+    public function setLinks($links)
+    {
+        $this->_links = $links;
+    }
 }
index b584d98c56a27ebb09c963313876fd8d1a3e9987..f601c1eeee5ff6e42f4eccdd43f76169fdf0ba0a 100644 (file)
@@ -1,2 +1,13 @@
-Allow from none
-Deny from all
+<IfModule version_module>
+  <IfVersion >= 2.4>
+     Require all denied
+  </IfVersion>
+  <IfVersion < 2.4>
+     Allow from none
+     Deny from all
+  </IfVersion>
+</IfModule>
+
+<IfModule !version_module>
+    Require all denied
+</IfModule>
index b82ad4831fe50baf8c92aea3a3d1750a9da81544..eba0af3bfcb290a114874ac4f7625a8260aa146d 100644 (file)
                     {$link=$value}
                     <div class="dailyEntry">
                         <div class="dailyEntryPermalink">
-                            <a href="?{$link.linkdate|smallHash}">
+                            <a href="?{$value.shorturl}">
                                 <img src="../images/squiggle2.png" width="25" height="26" title="permalink" alt="permalink">
                             </a>
                         </div>
                         {if="!$hide_timestamps || isLoggedIn()"}
                             <div class="dailyEntryLinkdate">
-                                <a href="?{$link.linkdate|smallHash}">{function="strftime('%c', $link.timestamp)"}</a>
+                                <a href="?{$value.shorturl}">{function="strftime('%c', $link.timestamp)"}</a>
                             </div>
                         {/if}
                         {if="$link.tags"}
index 441b530271fb0e5e6dcceaa5b674a6ea47de5125..870cc1688ac4d3ffb29f4b17bbb65b658a910b02 100644 (file)
@@ -8,13 +8,18 @@
 {elseif="$link.description==''"}onload="document.linkform.lf_description.focus();"
 {else}onload="document.linkform.lf_tags.focus();"{/if} >
 <div id="pageheader">
-       {if="$source !== 'firefoxsocialapi'"}
-       {include="page.header"}
-       {/if}
-       <div id="editlinkform">
-           <form method="post" name="linkform">
-               <input type="hidden" name="lf_linkdate" value="{$link.linkdate}">
-               <label for="lf_url"><i>URL</i></label><br><input type="text" name="lf_url" id="lf_url" value="{$link.url}" class="lf_input"><br>
+    {if="$source !== 'firefoxsocialapi'"}
+    {include="page.header"}
+    {else}
+    <div id="shaarli_title"><a href="{$titleLink}">{$shaarlititle}</a></div>
+    {/if}
+    <div id="editlinkform">
+        <form method="post" name="linkform">
+            <input type="hidden" name="lf_linkdate" value="{$link.linkdate}">
+          {if="isset($link.id)"}
+                 <input type="hidden" name="lf_id" value="{$link.id}">
+          {/if}
+            <label for="lf_url"><i>URL</i></label><br><input type="text" name="lf_url" id="lf_url" value="{$link.url}" class="lf_input"><br>
             <label for="lf_title"><i>Title</i></label><br><input type="text" name="lf_title" id="lf_title" value="{$link.title}" class="lf_input"><br>
             <label for="lf_description"><i>Description</i></label><br><textarea name="lf_description" id="lf_description" rows="4" cols="25">{$link.description}</textarea><br>
             <label for="lf_tags"><i>Tags</i></label><br>
                 {$value}
             {/loop}
 
-               {if="($link_is_new && $default_private_links) || $link.private == true"}
+            {if="($link_is_new && $default_private_links) || $link.private == true"}
             <input type="checkbox" checked="checked" name="lf_private" id="lf_private">
             &nbsp;<label for="lf_private"><i>Private</i></label><br>
             {else}
             <input type="checkbox"  name="lf_private" id="lf_private">
             &nbsp;<label for="lf_private"><i>Private</i></label><br>
             {/if}
-               <input type="submit" value="Save" name="save_edit" class="bigbutton">
-               <input type="submit" value="Cancel" name="cancel_edit" class="bigbutton">
-               {if="!$link_is_new"}<input type="submit" value="Delete" name="delete_link" class="bigbutton delete" onClick="return confirmDeleteLink();">{/if}
-               <input type="hidden" name="token" value="{$token}">
-               {if="$http_referer"}<input type="hidden" name="returnurl" value="{$http_referer}">{/if}
-           </form>
-       </div>
+            <input type="submit" value="Save" name="save_edit" class="bigbutton">
+            <input type="submit" value="Cancel" name="cancel_edit" class="bigbutton">
+            {if="!$link_is_new"}<input type="submit" value="Delete" name="delete_link" class="bigbutton delete" onClick="return confirmDeleteLink();">{/if}
+            <input type="hidden" name="token" value="{$token}">
+            {if="$http_referer"}<input type="hidden" name="returnurl" value="{$http_referer}">{/if}
+        </form>
+    </div>
 </div>
 {if="$source !== 'firefoxsocialapi'"}
 {include="page.footer"}
index 40fd421a4244b93f25a4a2486cf30fbe012f3b91..aead04592af4daf988d3ef9643c0b85a3203cf74 100644 (file)
@@ -30,9 +30,7 @@
         <published>{$value.pub_iso_date}</published>
         <updated>{$value.up_iso_date}</updated>
       {/if}
-      <content type="html" xml:lang="{$language}">
-        <![CDATA[{$value.description}]]>
-      </content>
+      <content type="html" xml:lang="{$language}"><![CDATA[{$value.description}]]></content>
       {loop="$value.taglist"}
         <category scheme="{$index_url}?searchtags=" term="{$value|strtolower}" label="{$value}" />
       {/loop}
index f94ce1be97ea55632c5df8fb79775f40f6d1f87f..7b2997ce45e73b8dd8ce7df72dd3ba93a08d3864 100644 (file)
@@ -2,6 +2,7 @@
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <meta name="format-detection" content="telephone=no" />
 <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+<meta name="referrer" content="same-origin">
 <link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$searchcrits}#" title="RSS Feed" />
 <link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$searchcrits}#" title="ATOM Feed" />
 <link href="images/favicon.ico#" rel="shortcut icon" type="image/x-icon" />
@@ -11,4 +12,4 @@
 {loop="$plugins_includes.css_files"}
 <link type="text/css" rel="stylesheet" href="{$value}#"/>
 {/loop}
-<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle|htmlspecialchars}"/>
\ No newline at end of file
+<link rel="search" type="application/opensearchdescription+xml" href="?do=opensearch#" title="Shaarli search - {$shaarlititle|htmlspecialchars}"/>
index 70c9cf79eecaf9716eb90078f894ec56dfd4e4b6..0f1a5e8ca9911ed164ee147ef3227bc9c0adf443 100644 (file)
         </form>
         {loop="$plugins_header.fields_toolbar"}
             <form
-                {loop="$value"}
-                    {if="$key!='inputs'"}
-                        {$key}="{$value}"
-                    {/if}
+                {loop="$value.attr"}
+                    {$key}="{$value}"
                 {/loop}>
                 {loop="$value.inputs"}
                     <input
                 {if="isLoggedIn()"}
                     <div class="linkeditbuttons">
                         <form method="GET" class="buttoneditform">
-                            <input type="hidden" name="edit_link" value="{$value.linkdate}">
+                            <input type="hidden" name="edit_link" value="{$value.id}">
                             <input type="image" alt="Edit" src="images/edit_icon.png#" title="Edit" class="button_edit">
                         </form><br>
-                        <form method="GET" class="buttoneditform">
+                        <form method="POST" class="buttoneditform">
+                            <input type="hidden" name="lf_linkdate" value="{$value.id}">
                             <input type="hidden" name="token" value="{$token}">
-                            <input type="hidden" name="delete_link" value="{$value.linkdate}">
+                            <input type="hidden" name="delete_link">
                             <input type="image" alt="Delete" src="images/delete_icon.png#" title="Delete"
                                    class="button_delete" onClick="return confirmDeleteLink();">
                         </form>
                 {if="!$hide_timestamps || isLoggedIn()"}
                     {$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'}
                     <span class="linkdate" title="Permalink">
-                        <a href="?{$value.linkdate|smallHash}">
+                        <a href="?{$value.shorturl}">
                             <span title="{$updated}">
                                 {function="strftime('%c', $value.timestamp)"}
                                 {if="$value.updated_timestamp"}*{/if}
index 42f58d0dc5ab2fc09f8717a57e2c76302a609a17..86019c01ee2a5cb4ddd2a9f8b50b59c694b00e2c 100644 (file)
     {loop="$action_plugin"}
       <div class="paging_privatelinks">
         <a
-          {loop="$value"}
-            {if="$key!='html'"}
-              {$key}="{$value}"
-            {/if}
+          {loop="$value.attr"}
+            {$key}="{$value}"
           {/loop}>
           {$value.html}
         </a>
index a49b42d3c0de71aa1d699c05dd05abe7d981c78d..84176385765837973d77be29941db622ccad1f6a 100644 (file)
@@ -2,7 +2,7 @@
 <html>
 <head>{include="includes"}</head>
 <body
-{if="ban_canLogin()"}
+{if="ban_canLogin($conf)"}
   {if="empty($username)"}
     onload="document.loginform.login.focus();"
   {else}
@@ -13,7 +13,7 @@
   {include="page.header"}
 
   <div id="headerform">
-    {if="!ban_canLogin()"}
+    {if="!ban_canLogin($conf)"}
       You have been banned from login after too many failed attempts. Try later.
     {else}
       <form method="post" name="loginform">
index 8987967858ef2aad46e148633e23885b1b2e4069..cce61ec464b8e54c8e1897a986634b5044237b7d 100644 (file)
     <li><a href="?do=daily">Daily</a></li>
     {loop="$plugins_header.buttons_toolbar"}
         <li><a
-            {loop="$value"}
-                {if="$key!='html'"}
-                    {$key}="{$value}"
-                {/if}
+            {loop="$value.attr"}
+                {$key}="{$value}"
             {/loop}>
             {$value.html}
         </a></li>
index 672f49938d68fae28750d9d55d88eef941192929..ead1734e850980096cfc59c5143ca1591bd4ad92 100644 (file)
               <tr data-line="{$key}" data-order="{$counter}">
                 <td class="center"><input type="checkbox" name="{$key}" id="{$key}" checked="checked"></td>
                 <td class="center">
-                  <a href="#"
+                  <a href="#" class="arrow"
                      onclick="return orderUp(this.parentNode.parentNode.getAttribute('data-order'));">
                     ▲
                   </a>
-                  <a href="#"
+                  <a href="#" class="arrow"
                      onclick="return orderDown(this.parentNode.parentNode.getAttribute('data-order'));">
                     ▼
                   </a>
index 8e285f445733cf5c28ded6387e1bc541521243e4..e06d239d4fe07f8beb6d2e140dde7031abee2cfc 100644 (file)
                                &nbsp;&nbsp;&nbsp;&nbsp;Then click "✚Add Note" button anytime to start composing a private Note (text post) to your Shaarli.
                        </span>
                </a><br><br>
+
+               {if="$sslenabled"}
                <a class="smallbutton" onclick="activateFirefoxSocial(this)">
                        <b>✚Add to Firefox social</b>
                </a>
                <a href="#">
                        <span>&#x21D0; Click on this button to add Shaarli to the "Share this page" button in Firefox.</span>
                </a><br><br>
+               {/if}
 
                {loop="$tools_plugin"}
             {$value}
@@ -64,6 +67,7 @@
                <div class="clear"></div>
 
                <script>
+                       {if="$sslenabled"}
                        function activateFirefoxSocial(node) {
                                var loc = location.href;
                                var baseURL = loc.substring(0, loc.lastIndexOf("/"));
@@ -87,7 +91,7 @@
                                var activate = new CustomEvent("ActivateSocialFeature");
                                node.dispatchEvent(activate);
                        }
-
+                       {/if}
                        function alertBookmarklet() {
                                alert('Drag this link to your bookmarks toolbar, or right-click it and choose Bookmark This Link...');
                                return false;