*/
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'];
$link['description'] = format_description($link['description'], '', $pageaddr);
$link['description'] .= PHP_EOL .'<br>— '. $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);;
*
* 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
{
// - 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)
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;
}
/**
*/
public function offsetExists($offset)
{
- return array_key_exists($offset, $this->links);
+ return array_key_exists($this->getLinkOffset($offset), $this->links);
}
/**
// 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]);
}
/**
*/
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;
}
/**
*/
public function current()
{
- return $this->links[$this->keys[$this->position]];
+ return $this[$this->keys[$this->position]];
}
/**
*/
public function rewind()
{
- $this->keys = array_keys($this->links);
- rsort($this->keys);
+ $this->keys = array_keys($this->ids);
$this->position = 0;
}
// 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.
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();
*/
private function read()
{
-
// Public links are hidden and user not logged in => nothing to show
if ($this->hidePublicLinks && !$this->loggedIn) {
$this->links = array();
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);
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();
}
/**
$request = '';
}
- $linkFilter = new LinkFilter($this->links);
+ $linkFilter = new LinkFilter($this);
return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
}
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;
+ }
}
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)
{
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;
}
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;
}
}
$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) {
}
if ($found) {
- $filtered[$link['linkdate']] = $link;
+ $filtered[$id] = $link;
}
}
- krsort($filtered);
return $filtered;
}
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;
}
if ($found) {
- $filtered[$link['linkdate']] = $link;
+ $filtered[$key] = $link;
}
}
- krsort($filtered);
return $filtered;
}
}
$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);
}
/**
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);
+}
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']);
'url' => $bkm['uri'],
'description' => $bkm['note'],
'private' => $private,
- 'linkdate'=> '',
'tags' => $bkm['tags']
);
}
// 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++;
}
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;
}
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;
+ }
}
/**
* - 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.
*
);
/* 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) {
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:
$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.
// 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.
{
// -------- 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.
}
$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'];
$pluginManager->executeHooks('save_link', $link);
- $LINKSDB[$linkdate] = $link;
+ $LINKSDB[$id] = $link;
$LINKSDB->save($conf->get('resource.page_cache'));
pubsubhub($conf);
$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;
{
// 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;
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:
// -------- 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,
$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]
}
if ($url == '') {
- $url = '?' . smallHash($linkdate);
+ $url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
$title = 'Note: ';
}
$url = escape($url);
'tags' => $tags,
'private' => $private
);
+ } else {
+ $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
}
$data = array(
$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) {
// 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.
$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']);
$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']);
}
/**
$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']);
}
/**
$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']);
}
/**
$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']);
}
/**
$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']);
$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);
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()
);
}
'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()
);
'tag2' => 1,
'tag3' => 1,
'tag4' => 1,
+ 'ut' => 1,
),
self::$privateLinkDB->allTags()
);
1,
count(self::$publicLinkDB->filterHash($request))
);
+ $request = smallHash('20150310_114633' . 8);
+ $this->assertEquals(
+ 1,
+ count(self::$publicLinkDB->filterHash($request))
+ );
}
/**
{
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);
+ }
+ }
}
$this->assertEquals(
'MediaGoblin',
- $links['20130614_184135']['title']
+ $links[7]['title']
);
}
);
$this->assertEquals(
- 6,
+ 7,
count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution'))
);
}
);
$this->assertEquals(
- 6,
+ 7,
count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free'))
);
}
$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']
$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']
$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']
*/
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
*/
$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
*/
$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
*/
$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')
);
.' 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')
);
$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')
);
$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']
);
}
$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']
);
}
$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',
$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']
);
}
$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
$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']
);
}
$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']
);
}
$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']
);
}
/**
- * Ensure each imported bookmark has a unique linkdate
+ * Ensure each imported bookmark has a unique id
*
* See https://github.com/shaarli/Shaarli/issues/351
*/
$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']
);
}
}
$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();
$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));
+ }
}
$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),
)
)
);
// 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'));
}
$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),
),
)
);
$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
*/
class ReferenceLinkDB
{
- public static $NB_LINKS_TOTAL = 7;
+ public static $NB_LINKS_TOTAL = 8;
private $_links = array();
private $_publicCount = 0;
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 '
);
}
/**
* 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++;
{
return $this->_links;
}
+
+ /**
+ * Setter to override link creation.
+ *
+ * @param array $links List of links.
+ */
+ public function setLinks($links)
+ {
+ $this->_links = $links;
+ }
}
{$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"}
<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>
{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}