Merge pull request #697 from ArthurHoaro/feature/ids-bis
Link ID refactoring
This commit is contained in:
commit
9cf93bcfc5
20 changed files with 618 additions and 234 deletions
|
@ -143,7 +143,7 @@ public function buildData()
|
|||
*/
|
||||
protected function buildItem($link, $pageaddr)
|
||||
{
|
||||
$link['guid'] = $pageaddr .'?'. smallHash($link['linkdate']);
|
||||
$link['guid'] = $pageaddr .'?'. $link['shorturl'];
|
||||
// Check for both signs of a note: starting with ? and 7 chars long.
|
||||
if ($link['url'][0] === '?' && strlen($link['url']) === 7) {
|
||||
$link['url'] = $pageaddr . $link['url'];
|
||||
|
@ -156,12 +156,12 @@ protected function buildItem($link, $pageaddr)
|
|||
$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);;
|
||||
|
|
|
@ -6,15 +6,15 @@
|
|||
*
|
||||
* Example:
|
||||
* $myLinks = new LinkDB();
|
||||
* echo $myLinks['20110826_161819']['title'];
|
||||
* echo $myLinks[350]['title'];
|
||||
* foreach ($myLinks as $link)
|
||||
* echo $link['title'].' at url '.$link['url'].'; description:'.$link['description'];
|
||||
*
|
||||
* Available keys:
|
||||
* - id: primary key, incremental integer identifier (persistent)
|
||||
* - description: description of the entry
|
||||
* - linkdate: creation date of this entry, format: YYYYMMDD_HHMMSS
|
||||
* (e.g.'20110914_192317')
|
||||
* - updated: last modification date of this entry, format: YYYYMMDD_HHMMSS
|
||||
* - created: creation date of this entry, DateTime object.
|
||||
* - updated: last modification date of this entry, DateTime object.
|
||||
* - private: Is this link private? 0=no, other value=yes
|
||||
* - tags: tags attached to this entry (separated by spaces)
|
||||
* - title Title of the link
|
||||
|
@ -22,11 +22,25 @@
|
|||
* Can be absolute or relative.
|
||||
* Relative URLs are permalinks (e.g.'?m-ukcw')
|
||||
* - real_url Absolute processed URL.
|
||||
* - shorturl Permalink smallhash
|
||||
*
|
||||
* Implements 3 interfaces:
|
||||
* - ArrayAccess: behaves like an associative array;
|
||||
* - Countable: there is a count() method;
|
||||
* - Iterator: usable in foreach () loops.
|
||||
*
|
||||
* ID mechanism:
|
||||
* ArrayAccess is implemented in a way that will allow to access a link
|
||||
* with the unique identifier ID directly with $link[ID].
|
||||
* Note that it's not the real key of the link array attribute.
|
||||
* This mechanism is in place to have persistent link IDs,
|
||||
* even though the internal array is reordered by date.
|
||||
* Example:
|
||||
* - DB: link #1 (2010-01-01) link #2 (2016-01-01)
|
||||
* - Order: #2 #1
|
||||
* - Import links containing: link #3 (2013-01-01)
|
||||
* - New DB: link #1 (2010-01-01) link #2 (2016-01-01) link #3 (2013-01-01)
|
||||
* - Real order: #2 #3 #1
|
||||
*/
|
||||
class LinkDB implements Iterator, Countable, ArrayAccess
|
||||
{
|
||||
|
@ -47,11 +61,17 @@ class LinkDB implements Iterator, Countable, ArrayAccess
|
|||
// - value: associative array (keys: title, description...)
|
||||
private $links;
|
||||
|
||||
// List of all recorded URLs (key=url, value=linkdate)
|
||||
// for fast reserve search (url-->linkdate)
|
||||
// List of all recorded URLs (key=url, value=link offset)
|
||||
// for fast reserve search (url-->link offset)
|
||||
private $urls;
|
||||
|
||||
// List of linkdate keys (for the Iterator interface implementation)
|
||||
/**
|
||||
* @var array List of all links IDS mapped with their array offset.
|
||||
* Map: id->offset.
|
||||
*/
|
||||
protected $ids;
|
||||
|
||||
// List of offset keys (for the Iterator interface implementation)
|
||||
private $keys;
|
||||
|
||||
// Position in the $this->keys array (for the Iterator interface)
|
||||
|
@ -121,14 +141,26 @@ public function offsetSet($offset, $value)
|
|||
if (!$this->loggedIn) {
|
||||
die('You are not authorized to add a link.');
|
||||
}
|
||||
if (empty($value['linkdate']) || empty($value['url'])) {
|
||||
die('Internal Error: A link should always have a linkdate and URL.');
|
||||
if (!isset($value['id']) || empty($value['url'])) {
|
||||
die('Internal Error: A link should always have an id and URL.');
|
||||
}
|
||||
if (empty($offset)) {
|
||||
die('You must specify a key.');
|
||||
if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) {
|
||||
die('You must specify an integer as a key.');
|
||||
}
|
||||
if (! empty($offset) && $offset !== $value['id']) {
|
||||
die('Array offset and link ID must be equal.');
|
||||
}
|
||||
|
||||
// If the link exists, we reuse the real offset, otherwise new entry
|
||||
$existing = $this->getLinkOffset($offset);
|
||||
if ($existing !== null) {
|
||||
$offset = $existing;
|
||||
} else {
|
||||
$offset = count($this->links);
|
||||
}
|
||||
$this->links[$offset] = $value;
|
||||
$this->urls[$value['url']]=$offset;
|
||||
$this->urls[$value['url']] = $offset;
|
||||
$this->ids[$value['id']] = $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,7 +168,7 @@ public function offsetSet($offset, $value)
|
|||
*/
|
||||
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
|
||||
die('You are not authorized to delete a link.');
|
||||
}
|
||||
$url = $this->links[$offset]['url'];
|
||||
$realOffset = $this->getLinkOffset($offset);
|
||||
$url = $this->links[$realOffset]['url'];
|
||||
unset($this->urls[$url]);
|
||||
unset($this->links[$offset]);
|
||||
unset($this->ids[$realOffset]);
|
||||
unset($this->links[$realOffset]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -158,7 +192,8 @@ public function offsetUnset($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()
|
||||
{
|
||||
return $this->links[$this->keys[$this->position]];
|
||||
return $this[$this->keys[$this->position]];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -192,8 +227,7 @@ public function next()
|
|||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->keys = array_keys($this->links);
|
||||
rsort($this->keys);
|
||||
$this->keys = array_keys($this->ids);
|
||||
$this->position = 0;
|
||||
}
|
||||
|
||||
|
@ -219,6 +253,7 @@ private function check()
|
|||
// Create a dummy database for example
|
||||
$this->links = array();
|
||||
$link = array(
|
||||
'id' => 1,
|
||||
'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone',
|
||||
'url'=>'https://github.com/shaarli/Shaarli/wiki',
|
||||
'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login.
|
||||
|
@ -227,20 +262,23 @@ private function check()
|
|||
|
||||
You use the community supported version of the original Shaarli project, by Sebastien Sauvage.',
|
||||
'private'=>0,
|
||||
'linkdate'=> date('Ymd_His'),
|
||||
'created'=> new DateTime(),
|
||||
'tags'=>'opensource software'
|
||||
);
|
||||
$this->links[$link['linkdate']] = $link;
|
||||
$link['shorturl'] = link_small_hash($link['created'], $link['id']);
|
||||
$this->links[1] = $link;
|
||||
|
||||
$link = array(
|
||||
'id' => 0,
|
||||
'title'=>'My secret stuff... - Pastebin.com',
|
||||
'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
|
||||
'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.',
|
||||
'private'=>1,
|
||||
'linkdate'=> date('Ymd_His', strtotime('-1 minute')),
|
||||
'tags'=>'secretstuff'
|
||||
'created'=> new DateTime('1 minute ago'),
|
||||
'tags'=>'secretstuff',
|
||||
);
|
||||
$this->links[$link['linkdate']] = $link;
|
||||
$link['shorturl'] = link_small_hash($link['created'], $link['id']);
|
||||
$this->links[0] = $link;
|
||||
|
||||
// Write database to disk
|
||||
$this->write();
|
||||
|
@ -251,7 +289,6 @@ private function check()
|
|||
*/
|
||||
private function read()
|
||||
{
|
||||
|
||||
// Public links are hidden and user not logged in => nothing to show
|
||||
if ($this->hidePublicLinks && !$this->loggedIn) {
|
||||
$this->links = array();
|
||||
|
@ -269,23 +306,13 @@ private function read()
|
|||
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'];
|
||||
}
|
||||
$toremove = array();
|
||||
foreach ($this->links as $key => &$link) {
|
||||
if (! $this->loggedIn && $link['private'] != 0) {
|
||||
// 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.
|
||||
sanitizeLink($link);
|
||||
|
@ -307,7 +334,24 @@ private function read()
|
|||
else {
|
||||
$link['real_url'] = $link['url'];
|
||||
}
|
||||
|
||||
// To be able to load links before running the update, and prepare the update
|
||||
if (! isset($link['created'])) {
|
||||
$link['id'] = $link['linkdate'];
|
||||
$link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
if (! empty($link['updated'])) {
|
||||
$link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']);
|
||||
}
|
||||
$link['shorturl'] = smallHash($link['linkdate']);
|
||||
}
|
||||
}
|
||||
|
||||
// If user is not logged in, filter private links.
|
||||
foreach ($toremove as $offset) {
|
||||
unset($this->links[$offset]);
|
||||
}
|
||||
|
||||
$this->reorder();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -430,7 +474,7 @@ public function filterSearch($filterRequest = array(), $casesensitive = false, $
|
|||
$request = '';
|
||||
}
|
||||
|
||||
$linkFilter = new LinkFilter($this->links);
|
||||
$linkFilter = new LinkFilter($this);
|
||||
return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
|
||||
}
|
||||
|
||||
|
@ -467,12 +511,64 @@ public function allTags()
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,12 +33,12 @@ class LinkFilter
|
|||
public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}';
|
||||
|
||||
/**
|
||||
* @var array all available links.
|
||||
* @var LinkDB all available links.
|
||||
*/
|
||||
private $links;
|
||||
|
||||
/**
|
||||
* @param array $links initialization.
|
||||
* @param LinkDB $links initialization.
|
||||
*/
|
||||
public function __construct($links)
|
||||
{
|
||||
|
@ -94,18 +94,16 @@ public function filter($type, $request, $casesensitive = false, $privateonly = f
|
|||
private function noFilter($privateonly = false)
|
||||
{
|
||||
if (! $privateonly) {
|
||||
krsort($this->links);
|
||||
return $this->links;
|
||||
}
|
||||
|
||||
$out = array();
|
||||
foreach ($this->links as $value) {
|
||||
foreach ($this->links as $key => $value) {
|
||||
if ($value['private']) {
|
||||
$out[$value['linkdate']] = $value;
|
||||
$out[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
krsort($out);
|
||||
return $out;
|
||||
}
|
||||
|
||||
|
@ -121,10 +119,10 @@ private function noFilter($privateonly = false)
|
|||
private function filterSmallHash($smallHash)
|
||||
{
|
||||
$filtered = array();
|
||||
foreach ($this->links as $l) {
|
||||
if ($smallHash == smallHash($l['linkdate'])) {
|
||||
foreach ($this->links as $key => $l) {
|
||||
if ($smallHash == $l['shorturl']) {
|
||||
// Yes, this is ugly and slow
|
||||
$filtered[$l['linkdate']] = $l;
|
||||
$filtered[$key] = $l;
|
||||
return $filtered;
|
||||
}
|
||||
}
|
||||
|
@ -188,7 +186,7 @@ private function filterFulltext($searchterms, $privateonly = false)
|
|||
$keys = array('title', 'description', 'url', 'tags');
|
||||
|
||||
// Iterate over every stored link.
|
||||
foreach ($this->links as $link) {
|
||||
foreach ($this->links as $id => $link) {
|
||||
|
||||
// ignore non private links when 'privatonly' is on.
|
||||
if (! $link['private'] && $privateonly === true) {
|
||||
|
@ -222,11 +220,10 @@ private function filterFulltext($searchterms, $privateonly = false)
|
|||
}
|
||||
|
||||
if ($found) {
|
||||
$filtered[$link['linkdate']] = $link;
|
||||
$filtered[$id] = $link;
|
||||
}
|
||||
}
|
||||
|
||||
krsort($filtered);
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
|
@ -256,7 +253,7 @@ public function filterTags($tags, $casesensitive = false, $privateonly = false)
|
|||
return $filtered;
|
||||
}
|
||||
|
||||
foreach ($this->links as $link) {
|
||||
foreach ($this->links as $key => $link) {
|
||||
// ignore non private links when 'privatonly' is on.
|
||||
if (! $link['private'] && $privateonly === true) {
|
||||
continue;
|
||||
|
@ -278,10 +275,9 @@ public function filterTags($tags, $casesensitive = false, $privateonly = false)
|
|||
}
|
||||
|
||||
if ($found) {
|
||||
$filtered[$link['linkdate']] = $link;
|
||||
$filtered[$key] = $link;
|
||||
}
|
||||
}
|
||||
krsort($filtered);
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
|
@ -304,13 +300,14 @@ public function filterDay($day)
|
|||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -169,3 +169,16 @@ function space2nbsp($text)
|
|||
function format_description($description, $redirector = '', $indexUrl = '') {
|
||||
return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $indexUrl)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a small hash for a link.
|
||||
*
|
||||
* @param DateTime $date Link creation date.
|
||||
* @param int $id Link ID.
|
||||
*
|
||||
* @return string the small hash generated from link data.
|
||||
*/
|
||||
function link_small_hash($date, $id)
|
||||
{
|
||||
return smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public static function filterAndFormat($linkDb, $selection, $prependNoteUrl, $in
|
|||
if ($link['private'] == 0 && $selection == 'private') {
|
||||
continue;
|
||||
}
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
$date = $link['created'];
|
||||
$link['timestamp'] = $date->getTimestamp();
|
||||
$link['taglist'] = str_replace(' ', ',', $link['tags']);
|
||||
|
||||
|
@ -147,7 +147,6 @@ public static function import($post, $files, $linkDb, $pagecache)
|
|||
'url' => $bkm['uri'],
|
||||
'description' => $bkm['note'],
|
||||
'private' => $private,
|
||||
'linkdate'=> '',
|
||||
'tags' => $bkm['tags']
|
||||
);
|
||||
|
||||
|
@ -161,25 +160,22 @@ public static function import($post, $files, $linkDb, $pagecache)
|
|||
}
|
||||
|
||||
// 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++;
|
||||
}
|
||||
|
||||
|
|
|
@ -138,10 +138,10 @@ public function updateMethodMergeDeprecatedConfigFile()
|
|||
public function updateMethodRenameDashTags()
|
||||
{
|
||||
$linklist = $this->linkDB->filterSearch();
|
||||
foreach ($linklist as $link) {
|
||||
foreach ($linklist as $key => $link) {
|
||||
$link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
|
||||
$link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
|
||||
$this->linkDB[$link['linkdate']] = $link;
|
||||
$this->linkDB[$key] = $link;
|
||||
}
|
||||
$this->linkDB->save($this->conf->get('resource.page_cache'));
|
||||
return true;
|
||||
|
@ -215,6 +215,47 @@ public function updateMethodEscapeUnescapedConfig()
|
|||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -31,7 +31,11 @@ function logm($logFile, $clientIp, $message)
|
|||
* - are NOT cryptographically secure (they CAN be forged)
|
||||
*
|
||||
* In Shaarli, they are used as a tinyurl-like link to individual entries,
|
||||
* e.g. smallHash('20111006_131924') --> yZH23w
|
||||
* built once with the combination of the date and item ID.
|
||||
* e.g. smallHash('20111006_131924' . 142) --> eaWxtQ
|
||||
*
|
||||
* @warning before v0.8.1, smallhashes were built only with the date,
|
||||
* and their value has been preserved.
|
||||
*
|
||||
* @param string $text Create a hash from this text.
|
||||
*
|
||||
|
|
104
index.php
104
index.php
|
@ -564,24 +564,19 @@ function showDailyRSS($conf) {
|
|||
);
|
||||
|
||||
/* Some Shaarlies may have very few links, so we need to look
|
||||
back in time (rsort()) until we have enough days ($nb_of_days).
|
||||
back in time until we have enough days ($nb_of_days).
|
||||
*/
|
||||
$linkdates = array();
|
||||
foreach ($LINKSDB as $linkdate => $value) {
|
||||
$linkdates[] = $linkdate;
|
||||
}
|
||||
rsort($linkdates);
|
||||
$nb_of_days = 7; // We take 7 days.
|
||||
$today = date('Ymd');
|
||||
$days = array();
|
||||
|
||||
foreach ($linkdates as $linkdate) {
|
||||
$day = substr($linkdate, 0, 8); // Extract day (without time)
|
||||
if (strcmp($day,$today) < 0) {
|
||||
foreach ($LINKSDB as $link) {
|
||||
$day = $link['created']->format('Ymd'); // Extract day (without time)
|
||||
if (strcmp($day, $today) < 0) {
|
||||
if (empty($days[$day])) {
|
||||
$days[$day] = array();
|
||||
}
|
||||
$days[$day][] = $linkdate;
|
||||
$days[$day][] = $link;
|
||||
}
|
||||
|
||||
if (count($days) > $nb_of_days) {
|
||||
|
@ -601,24 +596,18 @@ function showDailyRSS($conf) {
|
|||
echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL;
|
||||
|
||||
// For each day.
|
||||
foreach ($days as $day => $linkdates) {
|
||||
foreach ($days as $day => $links) {
|
||||
$dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000');
|
||||
$absurl = escape(index_url($_SERVER).'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page.
|
||||
|
||||
// Build the HTML body of this RSS entry.
|
||||
$links = array();
|
||||
|
||||
// We pre-format some fields for proper output.
|
||||
foreach ($linkdates as $linkdate) {
|
||||
$l = $LINKSDB[$linkdate];
|
||||
$l['formatedDescription'] = format_description($l['description'], $conf->get('redirector.url'));
|
||||
$l['thumbnail'] = thumbnail($conf, $l['url']);
|
||||
$l_date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $l['linkdate']);
|
||||
$l['timestamp'] = $l_date->getTimestamp();
|
||||
if (startsWith($l['url'], '?')) {
|
||||
$l['url'] = index_url($_SERVER) . $l['url']; // make permalink URL absolute
|
||||
foreach ($links as &$link) {
|
||||
$link['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
|
||||
$link['thumbnail'] = thumbnail($conf, $link['url']);
|
||||
$link['timestamp'] = $link['created']->getTimestamp();
|
||||
if (startsWith($link['url'], '?')) {
|
||||
$link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute
|
||||
}
|
||||
$links[$linkdate] = $l;
|
||||
}
|
||||
|
||||
// Then build the HTML for this day:
|
||||
|
@ -680,8 +669,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)
|
|||
$linksToDisplay[$key]['taglist']=$taglist;
|
||||
$linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
|
||||
$linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']);
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
$linksToDisplay[$key]['timestamp'] = $date->getTimestamp();
|
||||
$linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp();
|
||||
}
|
||||
|
||||
/* We need to spread the articles on 3 columns.
|
||||
|
@ -831,7 +819,7 @@ function renderPage($conf, $pluginManager)
|
|||
// Get only links which have a thumbnail.
|
||||
foreach($links as $link)
|
||||
{
|
||||
$permalink='?'.escape(smallHash($link['linkdate']));
|
||||
$permalink='?'.$link['shorturl'];
|
||||
$thumb=lazyThumbnail($conf, $link['url'],$permalink);
|
||||
if ($thumb!='') // Only output links which have a thumbnail.
|
||||
{
|
||||
|
@ -1245,13 +1233,28 @@ function renderPage($conf, $pluginManager)
|
|||
// -------- User clicked the "Save" button when editing a link: Save link to database.
|
||||
if (isset($_POST['save_edit']))
|
||||
{
|
||||
$linkdate = $_POST['lf_linkdate'];
|
||||
$updated = isset($LINKSDB[$linkdate]) ? strval(date('Ymd_His')) : false;
|
||||
|
||||
// Go away!
|
||||
if (! tokenOk($_POST['token'])) {
|
||||
die('Wrong token.');
|
||||
}
|
||||
|
||||
// lf_id should only be present if the link exists.
|
||||
$id = !empty($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : $LINKSDB->getNextId();
|
||||
// Linkdate is kept here to:
|
||||
// - use the same permalink for notes as they're displayed when creating them
|
||||
// - let users hack creation date of their posts
|
||||
// See: https://github.com/shaarli/Shaarli/wiki/Datastore-hacks#changing-the-timestamp-for-a-link
|
||||
$linkdate = escape($_POST['lf_linkdate']);
|
||||
if (isset($LINKSDB[$id])) {
|
||||
// Edit
|
||||
$created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
|
||||
$updated = new DateTime();
|
||||
} else {
|
||||
// New link
|
||||
$created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
|
||||
$updated = null;
|
||||
}
|
||||
|
||||
// Remove multiple spaces.
|
||||
$tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags']));
|
||||
// Remove first '-' char in tags.
|
||||
|
@ -1268,14 +1271,17 @@ function renderPage($conf, $pluginManager)
|
|||
}
|
||||
|
||||
$link = array(
|
||||
'id' => $id,
|
||||
'title' => trim($_POST['lf_title']),
|
||||
'url' => $url,
|
||||
'description' => $_POST['lf_description'],
|
||||
'private' => (isset($_POST['lf_private']) ? 1 : 0),
|
||||
'linkdate' => $linkdate,
|
||||
'created' => $created,
|
||||
'updated' => $updated,
|
||||
'tags' => str_replace(',', ' ', $tags)
|
||||
'tags' => str_replace(',', ' ', $tags),
|
||||
'shorturl' => link_small_hash($created, $id),
|
||||
);
|
||||
|
||||
// If title is empty, use the URL as title.
|
||||
if ($link['title'] == '') {
|
||||
$link['title'] = $link['url'];
|
||||
|
@ -1283,7 +1289,7 @@ function renderPage($conf, $pluginManager)
|
|||
|
||||
$pluginManager->executeHooks('save_link', $link);
|
||||
|
||||
$LINKSDB[$linkdate] = $link;
|
||||
$LINKSDB[$id] = $link;
|
||||
$LINKSDB->save($conf->get('resource.page_cache'));
|
||||
pubsubhub($conf);
|
||||
|
||||
|
@ -1296,7 +1302,7 @@ function renderPage($conf, $pluginManager)
|
|||
$returnurl = !empty($_POST['returnurl']) ? $_POST['returnurl'] : '?';
|
||||
$location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
|
||||
// Scroll to the link which has been edited.
|
||||
$location .= '#' . smallHash($_POST['lf_linkdate']);
|
||||
$location .= '#' . $link['shorturl'];
|
||||
// After saving the link, redirect to the page the user was on.
|
||||
header('Location: '. $location);
|
||||
exit;
|
||||
|
@ -1307,8 +1313,10 @@ function renderPage($conf, $pluginManager)
|
|||
{
|
||||
// If we are called from the bookmarklet, we must close the popup:
|
||||
if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
|
||||
$link = $LINKSDB[(int) escape($_POST['lf_id'])];
|
||||
$returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
|
||||
$returnurl .= '#'.smallHash($_POST['lf_linkdate']); // Scroll to the link which has been edited.
|
||||
// Scroll to the link which has been edited.
|
||||
$returnurl .= '#'. $link['shorturl'];
|
||||
$returnurl = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
|
||||
header('Location: '.$returnurl); // After canceling, redirect to the page the user was on.
|
||||
exit;
|
||||
|
@ -1318,14 +1326,17 @@ function renderPage($conf, $pluginManager)
|
|||
if (isset($_POST['delete_link']))
|
||||
{
|
||||
if (!tokenOk($_POST['token'])) die('Wrong token.');
|
||||
|
||||
// We do not need to ask for confirmation:
|
||||
// - confirmation is handled by JavaScript
|
||||
// - we are protected from XSRF by the token.
|
||||
$linkdate=$_POST['lf_linkdate'];
|
||||
|
||||
$pluginManager->executeHooks('delete_link', $LINKSDB[$linkdate]);
|
||||
// FIXME! We keep `lf_linkdate` for consistency before a proper API. To be removed.
|
||||
$id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : intval(escape($_POST['lf_linkdate']));
|
||||
|
||||
unset($LINKSDB[$linkdate]);
|
||||
$pluginManager->executeHooks('delete_link', $LINKSDB[$id]);
|
||||
|
||||
unset($LINKSDB[$id]);
|
||||
$LINKSDB->save('resource.page_cache'); // save to disk
|
||||
|
||||
// If we are called from the bookmarklet, we must close the popup:
|
||||
|
@ -1364,8 +1375,10 @@ function renderPage($conf, $pluginManager)
|
|||
// -------- User clicked the "EDIT" button on a link: Display link edit form.
|
||||
if (isset($_GET['edit_link']))
|
||||
{
|
||||
$link = $LINKSDB[$_GET['edit_link']]; // Read database
|
||||
$id = (int) escape($_GET['edit_link']);
|
||||
$link = $LINKSDB[$id]; // Read database
|
||||
if (!$link) { header('Location: ?'); exit; } // Link not found in database.
|
||||
$link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
|
||||
$data = array(
|
||||
'link' => $link,
|
||||
'link_is_new' => false,
|
||||
|
@ -1389,10 +1402,10 @@ function renderPage($conf, $pluginManager)
|
|||
$link_is_new = false;
|
||||
// Check if URL is not already in database (in this case, we will edit the existing link)
|
||||
$link = $LINKSDB->getLinkFromUrl($url);
|
||||
if (!$link)
|
||||
if (! $link)
|
||||
{
|
||||
$link_is_new = true;
|
||||
$linkdate = strval(date('Ymd_His'));
|
||||
$linkdate = strval(date(LinkDB::LINK_DATE_FORMAT));
|
||||
// Get title if it was provided in URL (by the bookmarklet).
|
||||
$title = empty($_GET['title']) ? '' : escape($_GET['title']);
|
||||
// Get description if it was provided in URL (by the bookmarklet). [Bronco added that]
|
||||
|
@ -1416,7 +1429,7 @@ function renderPage($conf, $pluginManager)
|
|||
}
|
||||
|
||||
if ($url == '') {
|
||||
$url = '?' . smallHash($linkdate);
|
||||
$url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
|
||||
$title = 'Note: ';
|
||||
}
|
||||
$url = escape($url);
|
||||
|
@ -1430,6 +1443,8 @@ function renderPage($conf, $pluginManager)
|
|||
'tags' => $tags,
|
||||
'private' => $private
|
||||
);
|
||||
} else {
|
||||
$link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
|
||||
}
|
||||
|
||||
$data = array(
|
||||
|
@ -1635,18 +1650,15 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
|
|||
$link['description'] = format_description($link['description'], $conf->get('redirector.url'));
|
||||
$classLi = ($i % 2) != 0 ? '' : 'publicLinkHightLight';
|
||||
$link['class'] = $link['private'] == 0 ? $classLi : 'private';
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
$link['timestamp'] = $date->getTimestamp();
|
||||
$link['timestamp'] = $link['created']->getTimestamp();
|
||||
if (! empty($link['updated'])) {
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['updated']);
|
||||
$link['updated_timestamp'] = $date->getTimestamp();
|
||||
$link['updated_timestamp'] = $link['updated']->getTimestamp();
|
||||
} else {
|
||||
$link['updated_timestamp'] = '';
|
||||
}
|
||||
$taglist = explode(' ', $link['tags']);
|
||||
uasort($taglist, 'strcasecmp');
|
||||
$link['taglist'] = $taglist;
|
||||
$link['shorturl'] = smallHash($link['linkdate']);
|
||||
// Check for both signs of a note: starting with ? and 7 chars long.
|
||||
if ($link['url'][0] === '?' &&
|
||||
strlen($link['url']) === 7) {
|
||||
|
|
|
@ -41,9 +41,9 @@ function hook_isso_render_linklist($data, $conf)
|
|||
// Only display comments for permalinks.
|
||||
if (count($data['links']) == 1 && empty($data['search_tags']) && empty($data['search_term'])) {
|
||||
$link = reset($data['links']);
|
||||
$isso_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/isso/isso.html');
|
||||
$issoHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/isso/isso.html');
|
||||
|
||||
$isso = sprintf($isso_html, $issoUrl, $issoUrl, $link['linkdate'], $link['linkdate']);
|
||||
$isso = sprintf($issoHtml, $issoUrl, $issoUrl, $link['id'], $link['id']);
|
||||
$data['plugin_end_zone'][] = $isso;
|
||||
|
||||
// Hackish way to include this CSS file only when necessary.
|
||||
|
|
|
@ -84,8 +84,9 @@ public function testRSSBuildData()
|
|||
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
|
||||
|
||||
// Test first link (note link)
|
||||
$link = array_shift($data['links']);
|
||||
$this->assertEquals('20150310_114651', $link['linkdate']);
|
||||
$link = reset($data['links']);
|
||||
$this->assertEquals(41, $link['id']);
|
||||
$this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
|
||||
$this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
|
||||
$this->assertEquals('http://host.tld/?WDWyig', $link['url']);
|
||||
$this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']);
|
||||
|
@ -99,14 +100,14 @@ public function testRSSBuildData()
|
|||
$this->assertEquals('sTuff', $link['taglist'][0]);
|
||||
|
||||
// Test URL with external link.
|
||||
$this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links']['20150310_114633']['url']);
|
||||
$this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links'][8]['url']);
|
||||
|
||||
// Test multitags.
|
||||
$this->assertEquals(5, count($data['links']['20141125_084734']['taglist']));
|
||||
$this->assertEquals('css', $data['links']['20141125_084734']['taglist'][0]);
|
||||
$this->assertEquals(5, count($data['links'][6]['taglist']));
|
||||
$this->assertEquals('css', $data['links'][6]['taglist'][0]);
|
||||
|
||||
// Test update date
|
||||
$this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links']['20150310_114633']['up_iso_date']);
|
||||
$this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,9 +120,9 @@ public function testAtomBuildData()
|
|||
$data = $feedBuilder->buildData();
|
||||
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
|
||||
$this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']);
|
||||
$link = array_shift($data['links']);
|
||||
$link = reset($data['links']);
|
||||
$this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:\d{2}/', $link['pub_iso_date']);
|
||||
$this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links']['20150310_114633']['up_iso_date']);
|
||||
$this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,7 +139,8 @@ public function testBuildDataFiltered()
|
|||
$data = $feedBuilder->buildData();
|
||||
$this->assertEquals(1, count($data['links']));
|
||||
$link = array_shift($data['links']);
|
||||
$this->assertEquals('20150310_114651', $link['linkdate']);
|
||||
$this->assertEquals(41, $link['id']);
|
||||
$this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,7 +156,8 @@ public function testBuildDataCount()
|
|||
$data = $feedBuilder->buildData();
|
||||
$this->assertEquals(1, count($data['links']));
|
||||
$link = array_shift($data['links']);
|
||||
$this->assertEquals('20150310_114651', $link['linkdate']);
|
||||
$this->assertEquals(41, $link['id']);
|
||||
$this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -170,15 +173,17 @@ public function testBuildDataPermalinks()
|
|||
$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']);
|
||||
|
|
|
@ -186,14 +186,15 @@ public function testSave()
|
|||
$dbSize = sizeof($testDB);
|
||||
|
||||
$link = array(
|
||||
'id' => 42,
|
||||
'title'=>'an additional link',
|
||||
'url'=>'http://dum.my',
|
||||
'description'=>'One more',
|
||||
'private'=>0,
|
||||
'linkdate'=>'20150518_190000',
|
||||
'created'=> DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150518_190000'),
|
||||
'tags'=>'unit test'
|
||||
);
|
||||
$testDB[$link['linkdate']] = $link;
|
||||
$testDB[$link['id']] = $link;
|
||||
$testDB->save('tests');
|
||||
|
||||
$testDB = new LinkDB(self::$testDatastore, true, false);
|
||||
|
@ -238,12 +239,12 @@ public function testCountHiddenPublic()
|
|||
public function testDays()
|
||||
{
|
||||
$this->assertEquals(
|
||||
array('20121206', '20130614', '20150310'),
|
||||
array('20100310', '20121206', '20130614', '20150310'),
|
||||
self::$publicLinkDB->days()
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('20121206', '20130614', '20141125', '20150310'),
|
||||
array('20100310', '20121206', '20130614', '20141125', '20150310'),
|
||||
self::$privateLinkDB->days()
|
||||
);
|
||||
}
|
||||
|
@ -290,10 +291,11 @@ public function testAllTags()
|
|||
'stallman' => 1,
|
||||
'free' => 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,
|
||||
// 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()
|
||||
);
|
||||
|
@ -321,6 +323,7 @@ public function testAllTags()
|
|||
'tag2' => 1,
|
||||
'tag3' => 1,
|
||||
'tag4' => 1,
|
||||
'ut' => 1,
|
||||
),
|
||||
self::$privateLinkDB->allTags()
|
||||
);
|
||||
|
@ -411,6 +414,11 @@ public function testFilterHashValid()
|
|||
1,
|
||||
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('');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@ public function testFilterSmallHash()
|
|||
|
||||
$this->assertEquals(
|
||||
'MediaGoblin',
|
||||
$links['20130614_184135']['title']
|
||||
$links[7]['title']
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -286,7 +286,7 @@ public function testExcludeSearch()
|
|||
);
|
||||
|
||||
$this->assertEquals(
|
||||
6,
|
||||
7,
|
||||
count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution'))
|
||||
);
|
||||
}
|
||||
|
@ -346,7 +346,7 @@ public function testTagFilterWithExclusion()
|
|||
);
|
||||
|
||||
$this->assertEquals(
|
||||
6,
|
||||
7,
|
||||
count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free'))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ public function testFilterAndFormatAll()
|
|||
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all', false, '');
|
||||
$this->assertEquals(self::$refDb->countLinks(), sizeof($links));
|
||||
foreach ($links as $link) {
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
$date = $link['created'];
|
||||
$this->assertEquals(
|
||||
$date->getTimestamp(),
|
||||
$link['timestamp']
|
||||
|
@ -70,7 +70,7 @@ public function testFilterAndFormatPrivate()
|
|||
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private', false, '');
|
||||
$this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links));
|
||||
foreach ($links as $link) {
|
||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||
$date = $link['created'];
|
||||
$this->assertEquals(
|
||||
$date->getTimestamp(),
|
||||
$link['timestamp']
|
||||
|
@ -90,7 +90,7 @@ public function testFilterAndFormatPublic()
|
|||
$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']
|
||||
|
|
|
@ -42,6 +42,18 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
|
|||
*/
|
||||
protected $pagecache = 'tests';
|
||||
|
||||
/**
|
||||
* @var string Save the current timezone.
|
||||
*/
|
||||
protected static $defaultTimeZone;
|
||||
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
self::$defaultTimeZone = date_default_timezone_get();
|
||||
// Timezone without DST for test consistency
|
||||
date_default_timezone_set('Africa/Nairobi');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets test data before each test
|
||||
*/
|
||||
|
@ -55,6 +67,11 @@ protected function setUp()
|
|||
$this->linkDb = new LinkDB(self::$testDatastore, true, false);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass()
|
||||
{
|
||||
date_default_timezone_set(self::$defaultTimeZone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to import bookmarks from an empty file
|
||||
*/
|
||||
|
@ -98,18 +115,19 @@ public function testImportInternetExplorerEncoding()
|
|||
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'linkdate' => '20160618_173944',
|
||||
'id' => 0,
|
||||
'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160618_203944'),
|
||||
'title' => 'Hg Init a Mercurial tutorial by Joel Spolsky',
|
||||
'url' => 'http://hginit.com/',
|
||||
'description' => '',
|
||||
'private' => 0,
|
||||
'tags' => ''
|
||||
'tags' => '',
|
||||
'shorturl' => 'La37cg',
|
||||
),
|
||||
$this->linkDb->getLinkFromUrl('http://hginit.com/')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Import bookmarks nested in a folder hierarchy
|
||||
*/
|
||||
|
@ -126,89 +144,105 @@ public function testImportNested()
|
|||
|
||||
$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',
|
||||