]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Merge pull request #697 from ArthurHoaro/feature/ids-bis
authorArthur <arthur@hoa.ro>
Mon, 12 Dec 2016 02:15:32 +0000 (03:15 +0100)
committerGitHub <noreply@github.com>
Mon, 12 Dec 2016 02:15:32 +0000 (03:15 +0100)
Link ID refactoring

20 files changed:
application/FeedBuilder.php
application/LinkDB.php
application/LinkFilter.php
application/LinkUtils.php
application/NetscapeBookmarkUtils.php
application/Updater.php
application/Utils.php
index.php
plugins/isso/isso.php
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/utils/ReferenceLinkDB.php
tpl/daily.html
tpl/editlink.html
tpl/linklist.html

index 4036a7cccdf3e77fc1a026adc5ddff93c9845fde..fedd90e661faefc4d6cec4a3152eaae2996bcbf9 100644 (file)
@@ -143,7 +143,7 @@ class FeedBuilder
      */
     protected function buildItem($link, $pageaddr)
     {
-        $link['guid'] = $pageaddr .'?'. smallHash($link['linkdate']);
+        $link['guid'] = $pageaddr .'?'. $link['shorturl'];
         // Check for both signs of a note: starting with ? and 7 chars long.
         if ($link['url'][0] === '?' && strlen($link['url']) === 7) {
             $link['url'] = $pageaddr . $link['url'];
@@ -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..e7148d005776c7c41df62e72e5cba237a6a6f753 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']);
 
@@ -147,7 +147,6 @@ class NetscapeBookmarkUtils
                 'url' => $bkm['uri'],
                 'description' => $bkm['note'],
                 'private' => $private,
-                'linkdate'=> '',
                 'tags' => $bkm['tags']
             );
 
@@ -161,25 +160,22 @@ class NetscapeBookmarkUtils
                 }
 
                 // Overwrite an existing link, keep its date
-                $newLink['linkdate'] = $existingLink['linkdate'];
-                $linkDb[$existingLink['linkdate']] = $newLink;
+                $newLink['id'] = $existingLink['id'];
+                $newLink['created'] = $existingLink['created'];
+                $newLink['updated'] = new DateTime();
+                $linkDb[$existingLink['id']] = $newLink;
                 $importCount++;
                 $overwriteCount++;
                 continue;
             }
 
-            // Add a new link
+            // Add a new link - @ used for UNIX timestamps
             $newLinkDate = new DateTime('@'.strval($bkm['time']));
-            while (!empty($linkDb[$newLinkDate->format(LinkDB::LINK_DATE_FORMAT)])) {
-                // Ensure the date/time is not already used
-                // - this hack is necessary as the date/time acts as a primary key
-                // - apply 1 second increments until an unused index is found
-                // See https://github.com/shaarli/Shaarli/issues/351
-                $newLinkDate->add(new DateInterval('PT1S'));
-            }
-            $linkDbDate = $newLinkDate->format(LinkDB::LINK_DATE_FORMAT);
-            $newLink['linkdate'] = $linkDbDate;
-            $linkDb[$linkDbDate] = $newLink;
+            $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++;
         }
 
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 5366cb0e22fbdafe221e0b0a2c1910b67f5122f0..fdbdfaa2aa93ada0960e067aba955844eddf81fe 100644 (file)
--- a/index.php
+++ b/index.php
@@ -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.
             {
@@ -1245,13 +1233,28 @@ 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();
+        } else {
+            // New link
+            $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
+            $updated = null;
+        }
+
         // Remove multiple spaces.
         $tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags']));
         // Remove first '-' char in tags.
@@ -1268,14 +1271,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' => link_small_hash($created, $id),
         );
+
         // If title is empty, use the URL as title.
         if ($link['title'] == '') {
             $link['title'] = $link['url'];
@@ -1283,7 +1289,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);
 
@@ -1296,7 +1302,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;
@@ -1307,8 +1313,10 @@ 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;
@@ -1318,14 +1326,17 @@ function renderPage($conf, $pluginManager)
     if (isset($_POST['delete_link']))
     {
         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=$_POST['lf_linkdate'];
 
-        $pluginManager->executeHooks('delete_link', $LINKSDB[$linkdate]);
+        // 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[$linkdate]);
+        unset($LINKSDB[$id]);
         $LINKSDB->save('resource.page_cache'); // save to disk
 
         // If we are called from the bookmarklet, we must close the popup:
@@ -1364,8 +1375,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 +1402,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 +1429,7 @@ function renderPage($conf, $pluginManager)
             }
 
             if ($url == '') {
-                $url = '?' . smallHash($linkdate);
+                $url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
                 $title = 'Note: ';
             }
             $url = escape($url);
@@ -1430,6 +1443,8 @@ function renderPage($conf, $pluginManager)
                 'tags' => $tags,
                 'private' => $private
             );
+        } else {
+            $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
         }
 
         $data = array(
@@ -1635,18 +1650,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 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 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 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 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 9e7621dbea26b7452a167974b25fc1ca16d48a52..870cc1688ac4d3ffb29f4b17bbb65b658a910b02 100644 (file)
@@ -16,6 +16,9 @@
     <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>
index 3fe86deba1af17b85e378f513bddb45200090907..0f1a5e8ca9911ed164ee147ef3227bc9c0adf443 100644 (file)
                 {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="POST" class="buttoneditform">
-                            <input type="hidden" name="lf_linkdate" value="{$value.linkdate}">
+                            <input type="hidden" name="lf_linkdate" value="{$value.id}">
                             <input type="hidden" name="token" value="{$token}">
                             <input type="hidden" name="delete_link">
                             <input type="image" alt="Delete" src="images/delete_icon.png#" title="Delete"
                 {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}