Merge pull request #697 from ArthurHoaro/feature/ids-bis

Link ID refactoring
This commit is contained in:
Arthur 2016-12-12 03:15:32 +01:00 committed by GitHub
commit 9cf93bcfc5
20 changed files with 618 additions and 234 deletions

View file

@ -143,7 +143,7 @@ public function buildData()
*/ */
protected function buildItem($link, $pageaddr) 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. // Check for both signs of a note: starting with ? and 7 chars long.
if ($link['url'][0] === '?' && strlen($link['url']) === 7) { if ($link['url'][0] === '?' && strlen($link['url']) === 7) {
$link['url'] = $pageaddr . $link['url']; $link['url'] = $pageaddr . $link['url'];
@ -156,12 +156,12 @@ protected function buildItem($link, $pageaddr)
$link['description'] = format_description($link['description'], '', $pageaddr); $link['description'] = format_description($link['description'], '', $pageaddr);
$link['description'] .= PHP_EOL .'<br>&#8212; '. $permalink; $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); $link['pub_iso_date'] = $this->getIsoDate($pubDate);
// atom:entry elements MUST contain exactly one atom:updated element. // atom:entry elements MUST contain exactly one atom:updated element.
if (!empty($link['updated'])) { 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); $link['up_iso_date'] = $this->getIsoDate($upDate, DateTime::ATOM);
} else { } else {
$link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);; $link['up_iso_date'] = $this->getIsoDate($pubDate, DateTime::ATOM);;

View file

@ -6,15 +6,15 @@
* *
* Example: * Example:
* $myLinks = new LinkDB(); * $myLinks = new LinkDB();
* echo $myLinks['20110826_161819']['title']; * echo $myLinks[350]['title'];
* foreach ($myLinks as $link) * foreach ($myLinks as $link)
* echo $link['title'].' at url '.$link['url'].'; description:'.$link['description']; * echo $link['title'].' at url '.$link['url'].'; description:'.$link['description'];
* *
* Available keys: * Available keys:
* - id: primary key, incremental integer identifier (persistent)
* - description: description of the entry * - description: description of the entry
* - linkdate: creation date of this entry, format: YYYYMMDD_HHMMSS * - created: creation date of this entry, DateTime object.
* (e.g.'20110914_192317') * - updated: last modification date of this entry, DateTime object.
* - updated: last modification date of this entry, format: YYYYMMDD_HHMMSS
* - private: Is this link private? 0=no, other value=yes * - private: Is this link private? 0=no, other value=yes
* - tags: tags attached to this entry (separated by spaces) * - tags: tags attached to this entry (separated by spaces)
* - title Title of the link * - title Title of the link
@ -22,11 +22,25 @@
* Can be absolute or relative. * Can be absolute or relative.
* Relative URLs are permalinks (e.g.'?m-ukcw') * Relative URLs are permalinks (e.g.'?m-ukcw')
* - real_url Absolute processed URL. * - real_url Absolute processed URL.
* - shorturl Permalink smallhash
* *
* Implements 3 interfaces: * Implements 3 interfaces:
* - ArrayAccess: behaves like an associative array; * - ArrayAccess: behaves like an associative array;
* - Countable: there is a count() method; * - Countable: there is a count() method;
* - Iterator: usable in foreach () loops. * - 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 class LinkDB implements Iterator, Countable, ArrayAccess
{ {
@ -47,11 +61,17 @@ class LinkDB implements Iterator, Countable, ArrayAccess
// - value: associative array (keys: title, description...) // - value: associative array (keys: title, description...)
private $links; private $links;
// List of all recorded URLs (key=url, value=linkdate) // List of all recorded URLs (key=url, value=link offset)
// for fast reserve search (url-->linkdate) // for fast reserve search (url-->link offset)
private $urls; 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; private $keys;
// Position in the $this->keys array (for the Iterator interface) // Position in the $this->keys array (for the Iterator interface)
@ -121,14 +141,26 @@ public function offsetSet($offset, $value)
if (!$this->loggedIn) { if (!$this->loggedIn) {
die('You are not authorized to add a link.'); die('You are not authorized to add a link.');
} }
if (empty($value['linkdate']) || empty($value['url'])) { if (!isset($value['id']) || empty($value['url'])) {
die('Internal Error: A link should always have a linkdate and URL.'); die('Internal Error: A link should always have an id and URL.');
} }
if (empty($offset)) { if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) {
die('You must specify a key.'); 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->links[$offset] = $value;
$this->urls[$value['url']] = $offset; $this->urls[$value['url']] = $offset;
$this->ids[$value['id']] = $offset;
} }
/** /**
@ -136,7 +168,7 @@ public function offsetSet($offset, $value)
*/ */
public function offsetExists($offset) public function offsetExists($offset)
{ {
return array_key_exists($offset, $this->links); return array_key_exists($this->getLinkOffset($offset), $this->links);
} }
/** /**
@ -148,9 +180,11 @@ public function offsetUnset($offset)
// TODO: raise an exception // TODO: raise an exception
die('You are not authorized to delete a link.'); 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->urls[$url]);
unset($this->links[$offset]); unset($this->ids[$realOffset]);
unset($this->links[$realOffset]);
} }
/** /**
@ -158,7 +192,8 @@ public function offsetUnset($offset)
*/ */
public function offsetGet($offset) 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 @@ public function offsetGet($offset)
*/ */
public function current() public function current()
{ {
return $this->links[$this->keys[$this->position]]; return $this[$this->keys[$this->position]];
} }
/** /**
@ -192,8 +227,7 @@ public function next()
*/ */
public function rewind() public function rewind()
{ {
$this->keys = array_keys($this->links); $this->keys = array_keys($this->ids);
rsort($this->keys);
$this->position = 0; $this->position = 0;
} }
@ -219,6 +253,7 @@ private function check()
// Create a dummy database for example // Create a dummy database for example
$this->links = array(); $this->links = array();
$link = array( $link = array(
'id' => 1,
'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone',
'url'=>'https://github.com/shaarli/Shaarli/wiki', '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. 'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login.
@ -227,20 +262,23 @@ private function check()
You use the community supported version of the original Shaarli project, by Sebastien Sauvage.', You use the community supported version of the original Shaarli project, by Sebastien Sauvage.',
'private'=>0, 'private'=>0,
'linkdate'=> date('Ymd_His'), 'created'=> new DateTime(),
'tags'=>'opensource software' 'tags'=>'opensource software'
); );
$this->links[$link['linkdate']] = $link; $link['shorturl'] = link_small_hash($link['created'], $link['id']);
$this->links[1] = $link;
$link = array( $link = array(
'id' => 0,
'title'=>'My secret stuff... - Pastebin.com', 'title'=>'My secret stuff... - Pastebin.com',
'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=', 'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.', 'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.',
'private'=>1, 'private'=>1,
'linkdate'=> date('Ymd_His', strtotime('-1 minute')), 'created'=> new DateTime('1 minute ago'),
'tags'=>'secretstuff' 'tags'=>'secretstuff',
); );
$this->links[$link['linkdate']] = $link; $link['shorturl'] = link_small_hash($link['created'], $link['id']);
$this->links[0] = $link;
// Write database to disk // Write database to disk
$this->write(); $this->write();
@ -251,7 +289,6 @@ private function check()
*/ */
private function read() private function read()
{ {
// Public links are hidden and user not logged in => nothing to show // Public links are hidden and user not logged in => nothing to show
if ($this->hidePublicLinks && !$this->loggedIn) { if ($this->hidePublicLinks && !$this->loggedIn) {
$this->links = array(); $this->links = array();
@ -269,23 +306,13 @@ private function read()
strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
} }
// If user is not logged in, filter private links.
if (!$this->loggedIn) {
$toremove = array(); $toremove = array();
foreach ($this->links as $link) { foreach ($this->links as $key => &$link) {
if ($link['private'] != 0) { if (! $this->loggedIn && $link['private'] != 0) {
$toremove[] = $link['linkdate']; // Transition for not upgraded databases.
$toremove[] = $key;
continue;
} }
}
foreach ($toremove as $linkdate) {
unset($this->links[$linkdate]);
}
}
$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. // Sanitize data fields.
sanitizeLink($link); sanitizeLink($link);
@ -307,7 +334,24 @@ private function read()
else { else {
$link['real_url'] = $link['url']; $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 @@ public function filterSearch($filterRequest = array(), $casesensitive = false, $
$request = ''; $request = '';
} }
$linkFilter = new LinkFilter($this->links); $linkFilter = new LinkFilter($this);
return $linkFilter->filter($type, $request, $casesensitive, $privateonly); return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
} }
@ -467,12 +511,64 @@ public function allTags()
public function days() public function days()
{ {
$linkDays = array(); $linkDays = array();
foreach (array_keys($this->links) as $day) { foreach ($this->links as $link) {
$linkDays[substr($day, 0, 8)] = 0; $linkDays[$link['created']->format('Ymd')] = 0;
} }
$linkDays = array_keys($linkDays); $linkDays = array_keys($linkDays);
sort($linkDays); sort($linkDays);
return $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;
}
} }

View file

@ -33,12 +33,12 @@ class LinkFilter
public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}'; public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}';
/** /**
* @var array all available links. * @var LinkDB all available links.
*/ */
private $links; private $links;
/** /**
* @param array $links initialization. * @param LinkDB $links initialization.
*/ */
public function __construct($links) public function __construct($links)
{ {
@ -94,18 +94,16 @@ public function filter($type, $request, $casesensitive = false, $privateonly = f
private function noFilter($privateonly = false) private function noFilter($privateonly = false)
{ {
if (! $privateonly) { if (! $privateonly) {
krsort($this->links);
return $this->links; return $this->links;
} }
$out = array(); $out = array();
foreach ($this->links as $value) { foreach ($this->links as $key => $value) {
if ($value['private']) { if ($value['private']) {
$out[$value['linkdate']] = $value; $out[$key] = $value;
} }
} }
krsort($out);
return $out; return $out;
} }
@ -121,10 +119,10 @@ private function noFilter($privateonly = false)
private function filterSmallHash($smallHash) private function filterSmallHash($smallHash)
{ {
$filtered = array(); $filtered = array();
foreach ($this->links as $l) { foreach ($this->links as $key => $l) {
if ($smallHash == smallHash($l['linkdate'])) { if ($smallHash == $l['shorturl']) {
// Yes, this is ugly and slow // Yes, this is ugly and slow
$filtered[$l['linkdate']] = $l; $filtered[$key] = $l;
return $filtered; return $filtered;
} }
} }
@ -188,7 +186,7 @@ private function filterFulltext($searchterms, $privateonly = false)
$keys = array('title', 'description', 'url', 'tags'); $keys = array('title', 'description', 'url', 'tags');
// Iterate over every stored link. // Iterate over every stored link.
foreach ($this->links as $link) { foreach ($this->links as $id => $link) {
// ignore non private links when 'privatonly' is on. // ignore non private links when 'privatonly' is on.
if (! $link['private'] && $privateonly === true) { if (! $link['private'] && $privateonly === true) {
@ -222,11 +220,10 @@ private function filterFulltext($searchterms, $privateonly = false)
} }
if ($found) { if ($found) {
$filtered[$link['linkdate']] = $link; $filtered[$id] = $link;
} }
} }
krsort($filtered);
return $filtered; return $filtered;
} }
@ -256,7 +253,7 @@ public function filterTags($tags, $casesensitive = false, $privateonly = false)
return $filtered; return $filtered;
} }
foreach ($this->links as $link) { foreach ($this->links as $key => $link) {
// ignore non private links when 'privatonly' is on. // ignore non private links when 'privatonly' is on.
if (! $link['private'] && $privateonly === true) { if (! $link['private'] && $privateonly === true) {
continue; continue;
@ -278,10 +275,9 @@ public function filterTags($tags, $casesensitive = false, $privateonly = false)
} }
if ($found) { if ($found) {
$filtered[$link['linkdate']] = $link; $filtered[$key] = $link;
} }
} }
krsort($filtered);
return $filtered; return $filtered;
} }
@ -304,13 +300,14 @@ public function filterDay($day)
} }
$filtered = array(); $filtered = array();
foreach ($this->links as $l) { foreach ($this->links as $key => $l) {
if (startsWith($l['linkdate'], $day)) { if ($l['created']->format('Ymd') == $day) {
$filtered[$l['linkdate']] = $l; $filtered[$key] = $l;
} }
} }
ksort($filtered);
return $filtered; // sort by date ASC
return array_reverse($filtered, true);
} }
/** /**

View file

@ -169,3 +169,16 @@ function space2nbsp($text)
function format_description($description, $redirector = '', $indexUrl = '') { function format_description($description, $redirector = '', $indexUrl = '') {
return nl2br(space2nbsp(hashtag_autolink(text2clickable($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);
}

View file

@ -38,7 +38,7 @@ public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $in
if ($link['private'] == 0 && $selection == 'private') { if ($link['private'] == 0 && $selection == 'private') {
continue; continue;
} }
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); $date = $link['created'];
$link['timestamp'] = $date->getTimestamp(); $link['timestamp'] = $date->getTimestamp();
$link['taglist'] = str_replace(' ', ',', $link['tags']); $link['taglist'] = str_replace(' ', ',', $link['tags']);
@ -147,7 +147,6 @@ public static function import($post, $files, $linkDb, $pagecache)
'url' => $bkm['uri'], 'url' => $bkm['uri'],
'description' => $bkm['note'], 'description' => $bkm['note'],
'private' => $private, 'private' => $private,
'linkdate'=> '',
'tags' => $bkm['tags'] 'tags' => $bkm['tags']
); );
@ -161,25 +160,22 @@ public static function import($post, $files, $linkDb, $pagecache)
} }
// Overwrite an existing link, keep its date // Overwrite an existing link, keep its date
$newLink['linkdate'] = $existingLink['linkdate']; $newLink['id'] = $existingLink['id'];
$linkDb[$existingLink['linkdate']] = $newLink; $newLink['created'] = $existingLink['created'];
$newLink['updated'] = new DateTime();
$linkDb[$existingLink['id']] = $newLink;
$importCount++; $importCount++;
$overwriteCount++; $overwriteCount++;
continue; continue;
} }
// Add a new link // Add a new link - @ used for UNIX timestamps
$newLinkDate = new DateTime('@'.strval($bkm['time'])); $newLinkDate = new DateTime('@'.strval($bkm['time']));
while (!empty($linkDb[$newLinkDate->format(LinkDB::LINK_DATE_FORMAT)])) { $newLinkDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
// Ensure the date/time is not already used $newLink['created'] = $newLinkDate;
// - this hack is necessary as the date/time acts as a primary key $newLink['id'] = $linkDb->getNextId();
// - apply 1 second increments until an unused index is found $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
// See https://github.com/shaarli/Shaarli/issues/351 $linkDb[$newLink['id']] = $newLink;
$newLinkDate->add(new DateInterval('PT1S'));
}
$linkDbDate = $newLinkDate->format(LinkDB::LINK_DATE_FORMAT);
$newLink['linkdate'] = $linkDbDate;
$linkDb[$linkDbDate] = $newLink;
$importCount++; $importCount++;
} }

View file

@ -138,10 +138,10 @@ public function updateMethodMergeDeprecatedConfigFile()
public function updateMethodRenameDashTags() public function updateMethodRenameDashTags()
{ {
$linklist = $this->linkDB->filterSearch(); $linklist = $this->linkDB->filterSearch();
foreach ($linklist as $link) { foreach ($linklist as $key => $link) {
$link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']); $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
$link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true))); $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')); $this->linkDB->save($this->conf->get('resource.page_cache'));
return true; return true;
@ -215,6 +215,47 @@ public function updateMethodEscapeUnescapedConfig()
} }
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;
}
} }
/** /**

View file

@ -31,7 +31,11 @@ function logm($logFile, $clientIp, $message)
* - are NOT cryptographically secure (they CAN be forged) * - are NOT cryptographically secure (they CAN be forged)
* *
* In Shaarli, they are used as a tinyurl-like link to individual entries, * 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. * @param string $text Create a hash from this text.
* *

100
index.php
View file

@ -564,24 +564,19 @@ function showDailyRSS($conf) {
); );
/* Some Shaarlies may have very few links, so we need to look /* 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. $nb_of_days = 7; // We take 7 days.
$today = date('Ymd'); $today = date('Ymd');
$days = array(); $days = array();
foreach ($linkdates as $linkdate) { foreach ($LINKSDB as $link) {
$day = substr($linkdate, 0, 8); // Extract day (without time) $day = $link['created']->format('Ymd'); // Extract day (without time)
if (strcmp($day, $today) < 0) { if (strcmp($day, $today) < 0) {
if (empty($days[$day])) { if (empty($days[$day])) {
$days[$day] = array(); $days[$day] = array();
} }
$days[$day][] = $linkdate; $days[$day][] = $link;
} }
if (count($days) > $nb_of_days) { if (count($days) > $nb_of_days) {
@ -601,24 +596,18 @@ function showDailyRSS($conf) {
echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL; echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL;
// For each day. // For each day.
foreach ($days as $day => $linkdates) { foreach ($days as $day => $links) {
$dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); $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. $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. // We pre-format some fields for proper output.
foreach ($linkdates as $linkdate) { foreach ($links as &$link) {
$l = $LINKSDB[$linkdate]; $link['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
$l['formatedDescription'] = format_description($l['description'], $conf->get('redirector.url')); $link['thumbnail'] = thumbnail($conf, $link['url']);
$l['thumbnail'] = thumbnail($conf, $l['url']); $link['timestamp'] = $link['created']->getTimestamp();
$l_date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $l['linkdate']); if (startsWith($link['url'], '?')) {
$l['timestamp'] = $l_date->getTimestamp(); $link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute
if (startsWith($l['url'], '?')) {
$l['url'] = index_url($_SERVER) . $l['url']; // make permalink URL absolute
} }
$links[$linkdate] = $l;
} }
// Then build the HTML for this day: // Then build the HTML for this day:
@ -680,8 +669,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
$linksToDisplay[$key]['taglist']=$taglist; $linksToDisplay[$key]['taglist']=$taglist;
$linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url')); $linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
$linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']); $linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']);
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp();
$linksToDisplay[$key]['timestamp'] = $date->getTimestamp();
} }
/* We need to spread the articles on 3 columns. /* We need to spread the articles on 3 columns.
@ -831,7 +819,7 @@ function renderPage($conf, $pluginManager)
// Get only links which have a thumbnail. // Get only links which have a thumbnail.
foreach($links as $link) foreach($links as $link)
{ {
$permalink='?'.escape(smallHash($link['linkdate'])); $permalink='?'.$link['shorturl'];
$thumb=lazyThumbnail($conf, $link['url'],$permalink); $thumb=lazyThumbnail($conf, $link['url'],$permalink);
if ($thumb!='') // Only output links which have a thumbnail. 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. // -------- User clicked the "Save" button when editing a link: Save link to database.
if (isset($_POST['save_edit'])) if (isset($_POST['save_edit']))
{ {
$linkdate = $_POST['lf_linkdate'];
$updated = isset($LINKSDB[$linkdate]) ? strval(date('Ymd_His')) : false;
// Go away! // Go away!
if (! tokenOk($_POST['token'])) { if (! tokenOk($_POST['token'])) {
die('Wrong 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. // Remove multiple spaces.
$tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags'])); $tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags']));
// Remove first '-' char in tags. // Remove first '-' char in tags.
@ -1268,14 +1271,17 @@ function renderPage($conf, $pluginManager)
} }
$link = array( $link = array(
'id' => $id,
'title' => trim($_POST['lf_title']), 'title' => trim($_POST['lf_title']),
'url' => $url, 'url' => $url,
'description' => $_POST['lf_description'], 'description' => $_POST['lf_description'],
'private' => (isset($_POST['lf_private']) ? 1 : 0), 'private' => (isset($_POST['lf_private']) ? 1 : 0),
'linkdate' => $linkdate, 'created' => $created,
'updated' => $updated, '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 title is empty, use the URL as title.
if ($link['title'] == '') { if ($link['title'] == '') {
$link['title'] = $link['url']; $link['title'] = $link['url'];
@ -1283,7 +1289,7 @@ function renderPage($conf, $pluginManager)
$pluginManager->executeHooks('save_link', $link); $pluginManager->executeHooks('save_link', $link);
$LINKSDB[$linkdate] = $link; $LINKSDB[$id] = $link;
$LINKSDB->save($conf->get('resource.page_cache')); $LINKSDB->save($conf->get('resource.page_cache'));
pubsubhub($conf); pubsubhub($conf);
@ -1296,7 +1302,7 @@ function renderPage($conf, $pluginManager)
$returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?'; $returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?';
$location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link')); $location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
// Scroll to the link which has been edited. // 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. // After saving the link, redirect to the page the user was on.
header('Location: '. $location); header('Location: '. $location);
exit; exit;
@ -1307,8 +1313,10 @@ function renderPage($conf, $pluginManager)
{ {
// If we are called from the bookmarklet, we must close the popup: // 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; } 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 = ( 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')); $returnurl = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
header('Location: '.$returnurl); // After canceling, redirect to the page the user was on. header('Location: '.$returnurl); // After canceling, redirect to the page the user was on.
exit; exit;
@ -1318,14 +1326,17 @@ function renderPage($conf, $pluginManager)
if (isset($_POST['delete_link'])) if (isset($_POST['delete_link']))
{ {
if (!tokenOk($_POST['token'])) die('Wrong token.'); if (!tokenOk($_POST['token'])) die('Wrong token.');
// We do not need to ask for confirmation: // We do not need to ask for confirmation:
// - confirmation is handled by JavaScript // - confirmation is handled by JavaScript
// - we are protected from XSRF by the token. // - 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']));
unset($LINKSDB[$linkdate]); $pluginManager->executeHooks('delete_link', $LINKSDB[$id]);
unset($LINKSDB[$id]);
$LINKSDB->save('resource.page_cache'); // save to disk $LINKSDB->save('resource.page_cache'); // save to disk
// If we are called from the bookmarklet, we must close the popup: // 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. // -------- User clicked the "EDIT" button on a link: Display link edit form.
if (isset($_GET['edit_link'])) 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. if (!$link) { header('Location: ?'); exit; } // Link not found in database.
$link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
$data = array( $data = array(
'link' => $link, 'link' => $link,
'link_is_new' => false, 'link_is_new' => false,
@ -1392,7 +1405,7 @@ function renderPage($conf, $pluginManager)
if (! $link) if (! $link)
{ {
$link_is_new = true; $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). // Get title if it was provided in URL (by the bookmarklet).
$title = empty($_GET['title']) ? '' : escape($_GET['title']); $title = empty($_GET['title']) ? '' : escape($_GET['title']);
// Get description if it was provided in URL (by the bookmarklet). [Bronco added that] // Get description if it was provided in URL (by the bookmarklet). [Bronco added that]
@ -1416,7 +1429,7 @@ function renderPage($conf, $pluginManager)
} }
if ($url == '') { if ($url == '') {
$url = '?' . smallHash($linkdate); $url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
$title = 'Note: '; $title = 'Note: ';
} }
$url = escape($url); $url = escape($url);
@ -1430,6 +1443,8 @@ function renderPage($conf, $pluginManager)
'tags' => $tags, 'tags' => $tags,
'private' => $private 'private' => $private
); );
} else {
$link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
} }
$data = array( $data = array(
@ -1635,18 +1650,15 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
$link['description'] = format_description($link['description'], $conf->get('redirector.url')); $link['description'] = format_description($link['description'], $conf->get('redirector.url'));
$classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight'; $classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight';
$link['class'] = $link['private'] == 0 ? $classLi : 'private'; $link['class'] = $link['private'] == 0 ? $classLi : 'private';
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); $link['timestamp'] = $link['created']->getTimestamp();
$link['timestamp'] = $date->getTimestamp();
if (! empty($link['updated'])) { if (! empty($link['updated'])) {
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['updated']); $link['updated_timestamp'] = $link['updated']->getTimestamp();
$link['updated_timestamp'] = $date->getTimestamp();
} else { } else {
$link['updated_timestamp'] = ''; $link['updated_timestamp'] = '';
} }
$taglist = explode(' ', $link['tags']); $taglist = explode(' ', $link['tags']);
uasort($taglist, 'strcasecmp'); uasort($taglist, 'strcasecmp');
$link['taglist'] = $taglist; $link['taglist'] = $taglist;
$link['shorturl'] = smallHash($link['linkdate']);
// Check for both signs of a note: starting with ? and 7 chars long. // Check for both signs of a note: starting with ? and 7 chars long.
if ($link['url'][0] === '?' && if ($link['url'][0] === '?' &&
strlen($link['url']) === 7) { strlen($link['url']) === 7) {

View file

@ -41,9 +41,9 @@ function hook_isso_render_linklist($data, $conf)
// Only display comments for permalinks. // Only display comments for permalinks.
if (count($data['links']) == 1 && empty($data['search_tags']) && empty($data['search_term'])) { if (count($data['links']) == 1 && empty($data['search_tags']) && empty($data['search_term'])) {
$link = reset($data['links']); $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; $data['plugin_end_zone'][] = $isso;
// Hackish way to include this CSS file only when necessary. // Hackish way to include this CSS file only when necessary.

View file

@ -84,8 +84,9 @@ public function testRSSBuildData()
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
// Test first link (note link) // Test first link (note link)
$link = array_shift($data['links']); $link = reset($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['guid']);
$this->assertEquals('http://host.tld/?WDWyig', $link['url']); $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
$this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']); $this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']);
@ -99,14 +100,14 @@ public function testRSSBuildData()
$this->assertEquals('sTuff', $link['taglist'][0]); $this->assertEquals('sTuff', $link['taglist'][0]);
// Test URL with external link. // 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. // Test multitags.
$this->assertEquals(5, count($data['links']['20141125_084734']['taglist'])); $this->assertEquals(5, count($data['links'][6]['taglist']));
$this->assertEquals('css', $data['links']['20141125_084734']['taglist'][0]); $this->assertEquals('css', $data['links'][6]['taglist'][0]);
// Test update date // 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 @@ public function testAtomBuildData()
$data = $feedBuilder->buildData(); $data = $feedBuilder->buildData();
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links'])); $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
$this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']); $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('/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 @@ public function testBuildDataFiltered()
$data = $feedBuilder->buildData(); $data = $feedBuilder->buildData();
$this->assertEquals(1, count($data['links'])); $this->assertEquals(1, count($data['links']));
$link = array_shift($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 @@ public function testBuildDataCount()
$data = $feedBuilder->buildData(); $data = $feedBuilder->buildData();
$this->assertEquals(1, count($data['links'])); $this->assertEquals(1, count($data['links']));
$link = array_shift($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 @@ public function testBuildDataPermalinks()
$this->assertTrue($data['usepermalinks']); $this->assertTrue($data['usepermalinks']);
// First link is a permalink // First link is a permalink
$link = array_shift($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->assertEquals('http://host.tld/?WDWyig', $link['guid']); $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
$this->assertEquals('http://host.tld/?WDWyig', $link['url']); $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
$this->assertContains('Direct link', $link['description']); $this->assertContains('Direct link', $link['description']);
$this->assertContains('http://host.tld/?WDWyig', $link['description']); $this->assertContains('http://host.tld/?WDWyig', $link['description']);
// Second link is a direct link // Second link is a direct link
$link = array_shift($data['links']); $link = array_shift($data['links']);
$this->assertEquals('20150310_114633', $link['linkdate']); $this->assertEquals(8, $link['id']);
$this->assertEquals('http://host.tld/?kLHmZg', $link['guid']); $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->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']);
$this->assertContains('Direct link', $link['description']); $this->assertContains('Direct link', $link['description']);
$this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']); $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']);

View file

@ -186,14 +186,15 @@ public function testSave()
$dbSize = sizeof($testDB); $dbSize = sizeof($testDB);
$link = array( $link = array(
'id' => 42,
'title'=>'an additional link', 'title'=>'an additional link',
'url'=>'http://dum.my', 'url'=>'http://dum.my',
'description'=>'One more', 'description'=>'One more',
'private'=>0, 'private'=>0,
'linkdate'=>'20150518_190000', 'created'=> DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150518_190000'),
'tags'=>'unit test' 'tags'=>'unit test'
); );
$testDB[$link['linkdate']] = $link; $testDB[$link['id']] = $link;
$testDB->save('tests'); $testDB->save('tests');
$testDB = new LinkDB(self::$testDatastore, true, false); $testDB = new LinkDB(self::$testDatastore, true, false);
@ -238,12 +239,12 @@ public function testCountHiddenPublic()
public function testDays() public function testDays()
{ {
$this->assertEquals( $this->assertEquals(
array('20121206', '20130614', '20150310'), array('20100310', '20121206', '20130614', '20150310'),
self::$publicLinkDB->days() self::$publicLinkDB->days()
); );
$this->assertEquals( $this->assertEquals(
array('20121206', '20130614', '20141125', '20150310'), array('20100310', '20121206', '20130614', '20141125', '20150310'),
self::$privateLinkDB->days() self::$privateLinkDB->days()
); );
} }
@ -290,10 +291,11 @@ public function testAllTags()
'stallman' => 1, 'stallman' => 1,
'free' => 1, 'free' => 1,
'-exclude' => 1, '-exclude' => 1,
// The DB contains a link with `sTuff` and another one with `stuff` tag.
// They need to be grouped with the first case found (`sTuff`).
'sTuff' => 2,
'hashtag' => 2, '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 - order by date DESC: `sTuff`.
'sTuff' => 2,
'ut' => 1,
), ),
self::$publicLinkDB->allTags() self::$publicLinkDB->allTags()
); );
@ -321,6 +323,7 @@ public function testAllTags()
'tag2' => 1, 'tag2' => 1,
'tag3' => 1, 'tag3' => 1,
'tag4' => 1, 'tag4' => 1,
'ut' => 1,
), ),
self::$privateLinkDB->allTags() self::$privateLinkDB->allTags()
); );
@ -411,6 +414,11 @@ public function testFilterHashValid()
1, 1,
count(self::$publicLinkDB->filterHash($request)) count(self::$publicLinkDB->filterHash($request))
); );
$request = smallHash('20150310_114633' . 8);
$this->assertEquals(
1,
count(self::$publicLinkDB->filterHash($request))
);
} }
/** /**
@ -433,4 +441,23 @@ public function testFilterHashInValid()
{ {
self::$publicLinkDB->filterHash(''); 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);
}
}
} }

View file

@ -159,7 +159,7 @@ public function testFilterSmallHash()
$this->assertEquals( $this->assertEquals(
'MediaGoblin', 'MediaGoblin',
$links['20130614_184135']['title'] $links[7]['title']
); );
} }
@ -286,7 +286,7 @@ public function testExcludeSearch()
); );
$this->assertEquals( $this->assertEquals(
6, 7,
count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution')) count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution'))
); );
} }
@ -346,7 +346,7 @@ public function testTagFilterWithExclusion()
); );
$this->assertEquals( $this->assertEquals(
6, 7,
count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free')) count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free'))
); );
} }

View file

@ -50,7 +50,7 @@ public function testFilterAndFormatAll()
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all', false, ''); $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all', false, '');
$this->assertEquals(self::$refDb->countLinks(), sizeof($links)); $this->assertEquals(self::$refDb->countLinks(), sizeof($links));
foreach ($links as $link) { foreach ($links as $link) {
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); $date = $link['created'];
$this->assertEquals( $this->assertEquals(
$date->getTimestamp(), $date->getTimestamp(),
$link['timestamp'] $link['timestamp']
@ -70,7 +70,7 @@ public function testFilterAndFormatPrivate()
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private', false, ''); $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private', false, '');
$this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links)); $this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links));
foreach ($links as $link) { foreach ($links as $link) {
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); $date = $link['created'];
$this->assertEquals( $this->assertEquals(
$date->getTimestamp(), $date->getTimestamp(),
$link['timestamp'] $link['timestamp']
@ -90,7 +90,7 @@ public function testFilterAndFormatPublic()
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, ''); $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, '');
$this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links)); $this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links));
foreach ($links as $link) { foreach ($links as $link) {
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); $date = $link['created'];
$this->assertEquals( $this->assertEquals(
$date->getTimestamp(), $date->getTimestamp(),
$link['timestamp'] $link['timestamp']

View file

@ -42,6 +42,18 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
*/ */
protected $pagecache = 'tests'; 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 * Resets test data before each test
*/ */
@ -55,6 +67,11 @@ protected function setUp()
$this->linkDb = new LinkDB(self::$testDatastore, true, false); $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 * Attempt to import bookmarks from an empty file
*/ */
@ -98,18 +115,19 @@ public function testImportInternetExplorerEncoding()
$this->assertEquals( $this->assertEquals(
array( array(
'linkdate' => '20160618_173944', 'id' => 0,
'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160618_203944'),
'title' => 'Hg Init a Mercurial tutorial by Joel Spolsky', 'title' => 'Hg Init a Mercurial tutorial by Joel Spolsky',
'url' => 'http://hginit.com/', 'url' => 'http://hginit.com/',
'description' => '', 'description' => '',
'private' => 0, 'private' => 0,
'tags' => '' 'tags' => '',
'shorturl' => 'La37cg',
), ),
$this->linkDb->getLinkFromUrl('http://hginit.com/') $this->linkDb->getLinkFromUrl('http://hginit.com/')
); );
} }
/** /**
* Import bookmarks nested in a folder hierarchy * Import bookmarks nested in a folder hierarchy
*/ */
@ -126,89 +144,105 @@ public function testImportNested()
$this->assertEquals( $this->assertEquals(
array( array(
'linkdate' => '20160225_205541', 'id' => 0,
'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235541'),
'title' => 'Nested 1', 'title' => 'Nested 1',
'url' => 'http://nest.ed/1', 'url' => 'http://nest.ed/1',
'description' => '', 'description' => '',
'private' => 0, 'private' => 0,
'tags' => 'tag1 tag2' 'tags' => 'tag1 tag2',
'shorturl' => 'KyDNKA',
), ),
$this->linkDb->getLinkFromUrl('http://nest.ed/1') $this->linkDb->getLinkFromUrl('http://nest.ed/1')
); );
$this->assertEquals( $this->assertEquals(
array( array(
'linkdate' => '20160225_205542', 'id' => 1,
'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235542'),
'title' => 'Nested 1-1', 'title' => 'Nested 1-1',
'url' => 'http://nest.ed/1-1', 'url' => 'http://nest.ed/1-1',
'description' => '', 'description' => '',
'private' => 0, 'private' => 0,
'tags' => 'folder1 tag1 tag2' 'tags' => 'folder1 tag1 tag2',
'shorturl' => 'T2LnXg',
), ),
$this->linkDb->getLinkFromUrl('http://nest.ed/1-1') $this->linkDb->getLinkFromUrl('http://nest.ed/1-1')
); );
$this->assertEquals( $this->assertEquals(
array( array(
'linkdate' => '20160225_205547', 'id' => 2,
'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235547'),
'title' => 'Nested 1-2', 'title' => 'Nested 1-2',
'url' => 'http://nest.ed/1-2', 'url' => 'http://nest.ed/1-2',
'description' => '', 'description' => '',
'private' => 0, 'private' => 0,
'tags' => 'folder1 tag3 tag4' 'tags' => 'folder1 tag3 tag4',
'shorturl' => '46SZxA',
), ),
$this->linkDb->getLinkFromUrl('http://nest.ed/1-2') $this->linkDb->getLinkFromUrl('http://nest.ed/1-2')
); );
$this->assertEquals( $this->assertEquals(
array( array(
'linkdate' => '20160202_172222', 'id' => 3,
'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'),
'title' => 'Nested 2-1', 'title' => 'Nested 2-1',
'url' => 'http://nest.ed/2-1', 'url' => 'http://nest.ed/2-1',
'description' => 'First link of the second section', 'description' => 'First link of the second section',
'private' => 1, 'private' => 1,
'tags' => 'folder2' 'tags' => 'folder2',
'shorturl' => '4UHOSw',
), ),
$this->linkDb->getLinkFromUrl('http://nest.ed/2-1') $this->linkDb->getLinkFromUrl('http://nest.ed/2-1')
); );
$this->assertEquals( $this->assertEquals(
array( array(
'linkdate' => '20160119_200227', 'id' => 4,
'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'),
'title' => 'Nested 2-2', 'title' => 'Nested 2-2',
'url' => 'http://nest.ed/2-2', 'url' => 'http://nest.ed/2-2',
'description' => 'Second link of the second section', 'description' => 'Second link of the second section',
'private' => 1, 'private' => 1,
'tags' => 'folder2' 'tags' => 'folder2',
'shorturl' => 'yfzwbw',
), ),
$this->linkDb->getLinkFromUrl('http://nest.ed/2-2') $this->linkDb->getLinkFromUrl('http://nest.ed/2-2')
); );
$this->assertEquals( $this->assertEquals(
array( array(
'linkdate' => '20160202_172223', 'id' => 5,
'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'),
'title' => 'Nested 3-1', 'title' => 'Nested 3-1',
'url' => 'http://nest.ed/3-1', 'url' => 'http://nest.ed/3-1',
'description' => '', 'description' => '',
'private' => 0, 'private' => 0,
'tags' => 'folder3 folder3-1 tag3' 'tags' => 'folder3 folder3-1 tag3',
'shorturl' => 'UwxIUQ',
), ),
$this->linkDb->getLinkFromUrl('http://nest.ed/3-1') $this->linkDb->getLinkFromUrl('http://nest.ed/3-1')
); );
$this->assertEquals( $this->assertEquals(
array( array(
'linkdate' => '20160119_200228', 'id' => 6,
'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'),
'title' => 'Nested 3-2', 'title' => 'Nested 3-2',
'url' => 'http://nest.ed/3-2', 'url' => 'http://nest.ed/3-2',
'description' => '', 'description' => '',
'private' => 0, 'private' => 0,
'tags' => 'folder3 folder3-1' 'tags' => 'folder3 folder3-1',
'shorturl' => 'p8dyZg',
), ),
$this->linkDb->getLinkFromUrl('http://nest.ed/3-2') $this->linkDb->getLinkFromUrl('http://nest.ed/3-2')
); );
$this->assertEquals( $this->assertEquals(
array( array(
'linkdate' => '20160229_081541', 'id' => 7,
'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160229_111541'),
'title' => 'Nested 2', 'title' => 'Nested 2',
'url' => 'http://nest.ed/2', 'url' => 'http://nest.ed/2',
'description' => '', 'description' => '',
'private' => 0, 'private' => 0,
'tags' => 'tag4' 'tags' => 'tag4',
'shorturl' => 'Gt3Uug',
), ),
$this->linkDb->getLinkFromUrl('http://nest.ed/2') $this->linkDb->getLinkFromUrl('http://nest.ed/2')
); );
@ -227,28 +261,34 @@ public function testImportDefaultPrivacyNoPost()
.' 2 links imported, 0 links overwritten, 0 links skipped.', .' 2 links imported, 0 links overwritten, 0 links skipped.',
NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->pagecache) NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->pagecache)
); );
$this->assertEquals(2, count($this->linkDb)); $this->assertEquals(2, count($this->linkDb));
$this->assertEquals(1, count_private($this->linkDb)); $this->assertEquals(1, count_private($this->linkDb));
$this->assertEquals( $this->assertEquals(
array( 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', 'title' => 'Secret stuff',
'url' => 'https://private.tld', 'url' => 'https://private.tld',
'description' => "Super-secret stuff you're not supposed to know about", 'description' => "Super-secret stuff you're not supposed to know about",
'private' => 1, 'private' => 1,
'tags' => 'private secret' 'tags' => 'private secret',
'shorturl' => 'EokDtA',
), ),
$this->linkDb->getLinkFromUrl('https://private.tld') $this->linkDb->getLinkFromUrl('https://private.tld')
); );
$this->assertEquals( $this->assertEquals(
array( array(
'linkdate' => '20160225_205548', 'id' => 1,
'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'),
'title' => 'Public stuff', 'title' => 'Public stuff',
'url' => 'http://public.tld', 'url' => 'http://public.tld',
'description' => '', 'description' => '',
'private' => 0, 'private' => 0,
'tags' => 'public hello world' 'tags' => 'public hello world',
'shorturl' => 'Er9ddA',
), ),
$this->linkDb->getLinkFromUrl('http://public.tld') $this->linkDb->getLinkFromUrl('http://public.tld')
); );
@ -271,23 +311,28 @@ public function testImportKeepPrivacy()
$this->assertEquals( $this->assertEquals(
array( 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', 'title' => 'Secret stuff',
'url' => 'https://private.tld', 'url' => 'https://private.tld',
'description' => "Super-secret stuff you're not supposed to know about", 'description' => "Super-secret stuff you're not supposed to know about",
'private' => 1, 'private' => 1,
'tags' => 'private secret' 'tags' => 'private secret',
'shorturl' => 'EokDtA',
), ),
$this->linkDb->getLinkFromUrl('https://private.tld') $this->linkDb->getLinkFromUrl('https://private.tld')
); );
$this->assertEquals( $this->assertEquals(
array( array(
'linkdate' => '20160225_205548', 'id' => 1,
'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'),
'title' => 'Public stuff', 'title' => 'Public stuff',
'url' => 'http://public.tld', 'url' => 'http://public.tld',
'description' => '', 'description' => '',
'private' => 0, 'private' => 0,
'tags' => 'public hello world' 'tags' => 'public hello world',
'shorturl' => 'Er9ddA',
), ),
$this->linkDb->getLinkFromUrl('http://public.tld') $this->linkDb->getLinkFromUrl('http://public.tld')
); );
@ -309,11 +354,11 @@ public function testImportAsPublic()
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
$this->assertEquals( $this->assertEquals(
0, 0,
$this->linkDb['20001010_105536']['private'] $this->linkDb[0]['private']
); );
$this->assertEquals( $this->assertEquals(
0, 0,
$this->linkDb['20160225_205548']['private'] $this->linkDb[1]['private']
); );
} }
@ -333,11 +378,11 @@ public function testImportAsPrivate()
$this->assertEquals(2, count_private($this->linkDb)); $this->assertEquals(2, count_private($this->linkDb));
$this->assertEquals( $this->assertEquals(
1, 1,
$this->linkDb['20001010_105536']['private'] $this->linkDb['0']['private']
); );
$this->assertEquals( $this->assertEquals(
1, 1,
$this->linkDb['20160225_205548']['private'] $this->linkDb['1']['private']
); );
} }
@ -359,13 +404,12 @@ public function testOverwriteAsPublic()
$this->assertEquals(2, count_private($this->linkDb)); $this->assertEquals(2, count_private($this->linkDb));
$this->assertEquals( $this->assertEquals(
1, 1,
$this->linkDb['20001010_105536']['private'] $this->linkDb[0]['private']
); );
$this->assertEquals( $this->assertEquals(
1, 1,
$this->linkDb['20160225_205548']['private'] $this->linkDb[1]['private']
); );
// re-import as public, enable overwriting // re-import as public, enable overwriting
$post = array( $post = array(
'privacy' => 'public', 'privacy' => 'public',
@ -380,11 +424,11 @@ public function testOverwriteAsPublic()
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
$this->assertEquals( $this->assertEquals(
0, 0,
$this->linkDb['20001010_105536']['private'] $this->linkDb[0]['private']
); );
$this->assertEquals( $this->assertEquals(
0, 0,
$this->linkDb['20160225_205548']['private'] $this->linkDb[1]['private']
); );
} }
@ -406,11 +450,11 @@ public function testOverwriteAsPrivate()
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
$this->assertEquals( $this->assertEquals(
0, 0,
$this->linkDb['20001010_105536']['private'] $this->linkDb['0']['private']
); );
$this->assertEquals( $this->assertEquals(
0, 0,
$this->linkDb['20160225_205548']['private'] $this->linkDb['1']['private']
); );
// re-import as private, enable overwriting // re-import as private, enable overwriting
@ -427,11 +471,11 @@ public function testOverwriteAsPrivate()
$this->assertEquals(2, count_private($this->linkDb)); $this->assertEquals(2, count_private($this->linkDb));
$this->assertEquals( $this->assertEquals(
1, 1,
$this->linkDb['20001010_105536']['private'] $this->linkDb['0']['private']
); );
$this->assertEquals( $this->assertEquals(
1, 1,
$this->linkDb['20160225_205548']['private'] $this->linkDb['1']['private']
); );
} }
@ -480,11 +524,11 @@ public function testSetDefaultTags()
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
$this->assertEquals( $this->assertEquals(
'tag1 tag2 tag3 private secret', 'tag1 tag2 tag3 private secret',
$this->linkDb['20001010_105536']['tags'] $this->linkDb['0']['tags']
); );
$this->assertEquals( $this->assertEquals(
'tag1 tag2 tag3 public hello world', 'tag1 tag2 tag3 public hello world',
$this->linkDb['20160225_205548']['tags'] $this->linkDb['1']['tags']
); );
} }
@ -507,16 +551,16 @@ public function testSanitizeDefaultTags()
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
$this->assertEquals( $this->assertEquals(
'tag1&amp; tag2 &quot;tag3&quot; private secret', 'tag1&amp; tag2 &quot;tag3&quot; private secret',
$this->linkDb['20001010_105536']['tags'] $this->linkDb['0']['tags']
); );
$this->assertEquals( $this->assertEquals(
'tag1&amp; tag2 &quot;tag3&quot; public hello world', '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 * See https://github.com/shaarli/Shaarli/issues/351
*/ */
@ -531,16 +575,16 @@ public function testImportSameDate()
$this->assertEquals(3, count($this->linkDb)); $this->assertEquals(3, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
$this->assertEquals( $this->assertEquals(
'20160225_205548', 0,
$this->linkDb['20160225_205548']['linkdate'] $this->linkDb[0]['id']
); );
$this->assertEquals( $this->assertEquals(
'20160225_205549', 1,
$this->linkDb['20160225_205549']['linkdate'] $this->linkDb[1]['id']
); );
$this->assertEquals( $this->assertEquals(
'20160225_205550', 2,
$this->linkDb['20160225_205550']['linkdate'] $this->linkDb[2]['id']
); );
} }
} }

View file

@ -214,6 +214,7 @@ public function testRenameDashTags()
$refDB = new ReferenceLinkDB(); $refDB = new ReferenceLinkDB();
$refDB->write(self::$testDatastore); $refDB->write(self::$testDatastore);
$linkDB = new LinkDB(self::$testDatastore, true, false); $linkDB = new LinkDB(self::$testDatastore, true, false);
$this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude'))); $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
$updater = new Updater(array(), $linkDB, $this->conf, true); $updater = new Updater(array(), $linkDB, $this->conf, true);
$updater->updateMethodRenameDashTags(); $updater->updateMethodRenameDashTags();
@ -287,4 +288,101 @@ public function testEscapeConfig()
$this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url')); $this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url'));
unlink($sandbox .'.json.php'); 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));
}
} }

View file

@ -47,12 +47,14 @@ function testIssoDisplayed()
$conf->set('plugins.ISSO_SERVER', 'value'); $conf->set('plugins.ISSO_SERVER', 'value');
$str = 'http://randomstr.com/test'; $str = 'http://randomstr.com/test';
$date = '20161118_100001';
$data = array( $data = array(
'title' => $str, 'title' => $str,
'links' => array( 'links' => array(
array( array(
'id' => 12,
'url' => $str, 'url' => $str,
'linkdate' => 'abc', 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
) )
) )
); );
@ -65,7 +67,14 @@ function testIssoDisplayed()
// plugin data // plugin data
$this->assertEquals(1, count($data['plugin_end_zone'])); $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')); $this->assertNotFalse(strpos($data['plugin_end_zone'][0], 'embed.min.js'));
} }
@ -78,16 +87,20 @@ function testIssoMultipleLinks()
$conf->set('plugins.ISSO_SERVER', 'value'); $conf->set('plugins.ISSO_SERVER', 'value');
$str = 'http://randomstr.com/test'; $str = 'http://randomstr.com/test';
$date1 = '20161118_100001';
$date2 = '20161118_100002';
$data = array( $data = array(
'title' => $str, 'title' => $str,
'links' => array( 'links' => array(
array( array(
'id' => 12,
'url' => $str, 'url' => $str,
'linkdate' => 'abc', 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date1),
), ),
array( array(
'id' => 13,
'url' => $str . '2', 'url' => $str . '2',
'linkdate' => 'abc2', 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date2),
), ),
) )
); );
@ -106,12 +119,14 @@ function testIssoNotDisplayedWhenSearch()
$conf->set('plugins.ISSO_SERVER', 'value'); $conf->set('plugins.ISSO_SERVER', 'value');
$str = 'http://randomstr.com/test'; $str = 'http://randomstr.com/test';
$date = '20161118_100001';
$data = array( $data = array(
'title' => $str, 'title' => $str,
'links' => array( 'links' => array(
array( array(
'id' => 12,
'url' => $str, 'url' => $str,
'linkdate' => 'abc', 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
) )
), ),
'search_term' => $str 'search_term' => $str

View file

@ -4,7 +4,7 @@
*/ */
class ReferenceLinkDB class ReferenceLinkDB
{ {
public static $NB_LINKS_TOTAL = 7; public static $NB_LINKS_TOTAL = 8;
private $_links = array(); private $_links = array();
private $_publicCount = 0; private $_publicCount = 0;
@ -16,66 +16,87 @@ class ReferenceLinkDB
public function __construct() public function __construct()
{ {
$this->addLink( $this->addLink(
41,
'Link title: @website', 'Link title: @website',
'?WDWyig', '?WDWyig',
'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag', 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag',
0, 0,
'20150310_114651', DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'),
'sTuff' 'sTuff',
null,
'WDWyig'
); );
$this->addLink( $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', 'Free as in Freedom 2.0 @website',
'https://static.fsf.org/nosvn/faif-2.0.pdf', 'https://static.fsf.org/nosvn/faif-2.0.pdf',
'Richard Stallman and the Free Software Revolution. Read this. #hashtag', 'Richard Stallman and the Free Software Revolution. Read this. #hashtag',
0, 0,
'20150310_114633', DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'),
'free gnu software stallman -exclude stuff hashtag', 'free gnu software stallman -exclude stuff hashtag',
'20160803_093033' DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160803_093033')
); );
$this->addLink( $this->addLink(
7,
'MediaGoblin', 'MediaGoblin',
'http://mediagoblin.org/', 'http://mediagoblin.org/',
'A free software media publishing platform #hashtagOther', 'A free software media publishing platform #hashtagOther',
0, 0,
'20130614_184135', DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
'gnu media web .hidden hashtag' 'gnu media web .hidden hashtag',
null,
'IuWvgA'
); );
$this->addLink( $this->addLink(
6,
'w3c-markup-validator', 'w3c-markup-validator',
'https://dvcs.w3.org/hg/markup-validator/summary', 'https://dvcs.w3.org/hg/markup-validator/summary',
'Mercurial repository for the W3C Validator #private', 'Mercurial repository for the W3C Validator #private',
1, 1,
'20141125_084734', DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20141125_084734'),
'css html w3c web Mercurial' 'css html w3c web Mercurial'
); );
$this->addLink( $this->addLink(
4,
'UserFriendly - Web Designer', 'UserFriendly - Web Designer',
'http://ars.userfriendly.org/cartoons/?id=20121206', 'http://ars.userfriendly.org/cartoons/?id=20121206',
'Naming conventions... #private', 'Naming conventions... #private',
0, 0,
'20121206_142300', DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'),
'dev cartoon web' 'dev cartoon web'
); );
$this->addLink( $this->addLink(
1,
'UserFriendly - Samba', 'UserFriendly - Samba',
'http://ars.userfriendly.org/cartoons/?id=20010306', 'http://ars.userfriendly.org/cartoons/?id=20010306',
'Tropical printing', 'Tropical printing',
0, 0,
'20121206_172539', DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'),
'samba cartoon web' 'samba cartoon web'
); );
$this->addLink( $this->addLink(
0,
'Geek and Poke', 'Geek and Poke',
'http://geek-and-poke.com/', 'http://geek-and-poke.com/',
'', '',
1, 1,
'20121206_182539', DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'),
'dev cartoon tag1 tag2 tag3 tag4 ' 'dev cartoon tag1 tag2 tag3 tag4 '
); );
} }
@ -83,18 +104,20 @@ public function __construct()
/** /**
* Adds a new link * 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( $link = array(
'id' => $id,
'title' => $title, 'title' => $title,
'url' => $url, 'url' => $url,
'description' => $description, 'description' => $description,
'private' => $private, 'private' => $private,
'linkdate' => $date,
'tags' => $tags, 'tags' => $tags,
'created' => $date,
'updated' => $updated, 'updated' => $updated,
'shorturl' => $shorturl ? $shorturl : smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id),
); );
$this->_links[$date] = $link; $this->_links[$id] = $link;
if ($private) { if ($private) {
$this->_privateCount++; $this->_privateCount++;
@ -142,4 +165,14 @@ public function getLinks()
{ {
return $this->_links; return $this->_links;
} }
/**
* Setter to override link creation.
*
* @param array $links List of links.
*/
public function setLinks($links)
{
$this->_links = $links;
}
} }

View file

@ -49,13 +49,13 @@
{$link=$value} {$link=$value}
<div class="dailyEntry"> <div class="dailyEntry">
<div class="dailyEntryPermalink"> <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"> <img src="../images/squiggle2.png" width="25" height="26" title="permalink" alt="permalink">
</a> </a>
</div> </div>
{if="!$hide_timestamps || isLoggedIn()"} {if="!$hide_timestamps || isLoggedIn()"}
<div class="dailyEntryLinkdate"> <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> </div>
{/if} {/if}
{if="$link.tags"} {if="$link.tags"}

View file

@ -16,6 +16,9 @@
<div id="editlinkform"> <div id="editlinkform">
<form method="post" name="linkform"> <form method="post" name="linkform">
<input type="hidden" name="lf_linkdate" value="{$link.linkdate}"> <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_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_title"><i>Title</i></label><br><input type="text" name="lf_title" id="lf_title" value="{$link.title}" class="lf_input"><br>
<label for="lf_description"><i>Description</i></label><br><textarea name="lf_description" id="lf_description" rows="4" cols="25">{$link.description}</textarea><br> <label for="lf_description"><i>Description</i></label><br><textarea name="lf_description" id="lf_description" rows="4" cols="25">{$link.description}</textarea><br>

View file

@ -81,11 +81,11 @@
{if="isLoggedIn()"} {if="isLoggedIn()"}
<div class="linkeditbuttons"> <div class="linkeditbuttons">
<form method="GET" class="buttoneditform"> <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"> <input type="image" alt="Edit" src="images/edit_icon.png#" title="Edit" class="button_edit">
</form><br> </form><br>
<form method="POST" class="buttoneditform"> <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="token" value="{$token}">
<input type="hidden" name="delete_link"> <input type="hidden" name="delete_link">
<input type="image" alt="Delete" src="images/delete_icon.png#" title="Delete" <input type="image" alt="Delete" src="images/delete_icon.png#" title="Delete"
@ -101,7 +101,7 @@
{if="!$hide_timestamps || isLoggedIn()"} {if="!$hide_timestamps || isLoggedIn()"}
{$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'} {$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'}
<span class="linkdate" title="Permalink"> <span class="linkdate" title="Permalink">
<a href="?{$value.linkdate|smallHash}"> <a href="?{$value.shorturl}">
<span title="{$updated}"> <span title="{$updated}">
{function="strftime('%c', $value.timestamp)"} {function="strftime('%c', $value.timestamp)"}
{if="$value.updated_timestamp"}*{/if} {if="$value.updated_timestamp"}*{/if}