Skip to content

Commit

Permalink
Add a persistent 'shorturl' key to all links
Browse files Browse the repository at this point in the history
All existing link will keep their permalinks.
New links will have smallhash generated with date+id.

The purpose of this is to avoid collision between links due to their creation date.
  • Loading branch information
ArthurHoaro committed Dec 12, 2016
1 parent 8e7c729 commit cd484e7
Show file tree
Hide file tree
Showing 15 changed files with 115 additions and 91 deletions.
2 changes: 1 addition & 1 deletion application/FeedBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public function buildData()
*/
protected function buildItem($link, $pageaddr)
{
$link['guid'] = $pageaddr .'?'. smallHash($link['created']->format('Ymd_His'));
$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'];
Expand Down
12 changes: 8 additions & 4 deletions application/LinkDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* 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;
Expand Down Expand Up @@ -264,6 +265,7 @@ private function check()
'created'=> new DateTime(),
'tags'=>'opensource software'
);
$link['shorturl'] = link_small_hash($link['created'], $link['id']);
$this->links[1] = $link;

$link = array(
Expand All @@ -273,8 +275,9 @@ private function check()
'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.',
'private'=>1,
'created'=> new DateTime('1 minute ago'),
'tags'=>'secretstuff'
'tags'=>'secretstuff',
);
$link['shorturl'] = link_small_hash($link['created'], $link['id']);
$this->links[0] = $link;

// Write database to disk
Expand Down Expand Up @@ -335,10 +338,11 @@ private function read()
// 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('Ymd_His', $link['linkdate']);
$link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']);
if (! empty($link['updated'])) {
$link['updated'] = DateTime::createFromFormat('Ymd_His', $link['updated']);
$link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']);
}
$link['shorturl'] = smallHash($link['linkdate']);
}
}

Expand Down Expand Up @@ -558,7 +562,7 @@ public function getNextId()
*
* @param int $id Persistent ID of a link.
*
* @return int Real offset in local array, or null if doesn't exists.
* @return int Real offset in local array, or null if doesn't exist.
*/
protected function getLinkOffset($id)
{
Expand Down
2 changes: 1 addition & 1 deletion application/LinkFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ private function filterSmallHash($smallHash)
{
$filtered = array();
foreach ($this->links as $key => $l) {
if ($smallHash == smallHash($l['created']->format('Ymd_His'))) {
if ($smallHash == $l['shorturl']) {
// Yes, this is ugly and slow
$filtered[$key] = $l;
return $filtered;
Expand Down
13 changes: 13 additions & 0 deletions application/LinkUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions application/NetscapeBookmarkUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ public static function import($post, $files, $linkDb, $pagecache)
$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++;
}
Expand Down
3 changes: 3 additions & 0 deletions application/Updater.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ public function updateMethodEscapeUnescapedConfig()
* 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()
Expand Down
6 changes: 5 additions & 1 deletion application/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
56 changes: 23 additions & 33 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -566,21 +566,17 @@ function showDailyRSS($conf) {
/* Some Shaarlies may have very few links, so we need to look
back in time until we have enough days ($nb_of_days).
*/
$ids = array();
foreach ($LINKSDB as $id => $value) {
$ids[] = $id;
}
$nb_of_days = 7; // We take 7 days.
$today = date('Ymd');
$days = array();

foreach ($ids as $id) {
$day = $LINKSDB[$id]['created']->format('Ymd'); // Extract day (without time)
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][] = $id;
$days[$day][] = $link;
}

if (count($days) > $nb_of_days) {
Expand All @@ -600,23 +596,18 @@ function showDailyRSS($conf) {
echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL;

// For each day.
foreach ($days as $day => $ids) {
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 ($ids as $id) {
$l = $LINKSDB[$id];
$l['formatedDescription'] = format_description($l['description'], $conf->get('redirector.url'));
$l['thumbnail'] = thumbnail($conf, $l['url']);
$l['timestamp'] = $l['created']->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[$id] = $l;
}

// Then build the HTML for this day:
Expand Down Expand Up @@ -675,7 +666,6 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager)

$taglist = explode(' ',$link['tags']);
uasort($taglist, 'strcasecmp');
$linksToDisplay[$key]['shorturl'] = smallHash($link['created']->format('Ymd_His'));
$linksToDisplay[$key]['taglist']=$taglist;
$linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $conf->get('redirector.url'));
$linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']);
Expand Down Expand Up @@ -829,7 +819,7 @@ function renderPage($conf, $pluginManager)
// Get only links which have a thumbnail.
foreach($links as $link)
{
$permalink='?'.escape(smallHash($link['created']->format('Ymd_His')));
$permalink='?'.$link['shorturl'];
$thumb=lazyThumbnail($conf, $link['url'],$permalink);
if ($thumb!='') // Only output links which have a thumbnail.
{
Expand Down Expand Up @@ -1248,19 +1238,19 @@ function renderPage($conf, $pluginManager)
}

// lf_id should only be present if the link exists.
$id = !empty($_POST['lf_id']) ? (int) escape($_POST['lf_id']) : $LINKSDB->getNextId();
$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('Ymd_His', $linkdate);
$created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
$updated = new DateTime();
} else {
// New link
$created = DateTime::createFromFormat('Ymd_His', $linkdate);
$created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
$updated = null;
}

Expand All @@ -1287,7 +1277,8 @@ function renderPage($conf, $pluginManager)
'private' => (isset($_POST['lf_private']) ? 1 : 0),
'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.
Expand All @@ -1310,7 +1301,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($created->format('Ymd_His'));
$location .= '#' . $link['shorturl'];
// After saving the link, redirect to the page the user was on.
header('Location: '. $location);
exit;
Expand All @@ -1324,7 +1315,7 @@ function renderPage($conf, $pluginManager)
$link = $LINKSDB[(int) escape($_POST['lf_id'])];
$returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
// Scroll to the link which has been edited.
$returnurl .= '#'.smallHash($link['created']->format('Ymd_His'));
$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;
Expand All @@ -1340,7 +1331,7 @@ function renderPage($conf, $pluginManager)
// - we are protected from XSRF by the token.

// FIXME! We keep `lf_linkdate` for consistency before a proper API. To be removed.
$id = isset($_POST['lf_id']) ? (int) escape($_POST['lf_id']) : (int) escape($_POST['lf_linkdate']);
$id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : intval(escape($_POST['lf_linkdate']));

$pluginManager->executeHooks('delete_link', $LINKSDB[$id]);

Expand Down Expand Up @@ -1386,7 +1377,7 @@ function renderPage($conf, $pluginManager)
$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('Ymd_His');
$link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
$data = array(
'link' => $link,
'link_is_new' => false,
Expand All @@ -1413,7 +1404,7 @@ function renderPage($conf, $pluginManager)
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]
Expand All @@ -1437,7 +1428,7 @@ function renderPage($conf, $pluginManager)
}

if ($url == '') {
$url = '?' . smallHash($linkdate);
$url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
$title = 'Note: ';
}
$url = escape($url);
Expand All @@ -1452,7 +1443,7 @@ function renderPage($conf, $pluginManager)
'private' => $private
);
} else {
$link['linkdate'] = $link['created']->format('Ymd_His');
$link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
}

$data = array(
Expand Down Expand Up @@ -1667,7 +1658,6 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
$taglist = explode(' ', $link['tags']);
uasort($taglist, 'strcasecmp');
$link['taglist'] = $taglist;
$link['shorturl'] = smallHash($link['created']->format('Ymd_His'));
// Check for both signs of a note: starting with ? and 7 chars long.
if ($link['url'][0] === '?' &&
strlen($link['url']) === 7) {
Expand Down
4 changes: 1 addition & 3 deletions plugins/isso/isso.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ function hook_isso_render_linklist($data, $conf)
$link = reset($data['links']);
$issoHtml = file_get_contents(PluginManager::$PLUGINS_PATH . '/isso/isso.html');

// FIXME! KO thread unique si même date
$linkDate = $link['created']->format('Ymd_His');
$isso = sprintf($issoHtml, $issoUrl, $issoUrl, $linkDate, $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.
Expand Down
10 changes: 5 additions & 5 deletions tests/FeedBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function testRSSBuildData()
// Test first link (note link)
$link = reset($data['links']);
$this->assertEquals(41, $link['id']);
$this->assertEquals(DateTime::createFromFormat('Ymd_His', '20150310_114651'), $link['created']);
$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']);
Expand Down Expand Up @@ -140,7 +140,7 @@ public function testBuildDataFiltered()
$this->assertEquals(1, count($data['links']));
$link = array_shift($data['links']);
$this->assertEquals(41, $link['id']);
$this->assertEquals(DateTime::createFromFormat('Ymd_His', '20150310_114651'), $link['created']);
$this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
}

/**
Expand All @@ -157,7 +157,7 @@ public function testBuildDataCount()
$this->assertEquals(1, count($data['links']));
$link = array_shift($data['links']);
$this->assertEquals(41, $link['id']);
$this->assertEquals(DateTime::createFromFormat('Ymd_His', '20150310_114651'), $link['created']);
$this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
}

/**
Expand All @@ -174,15 +174,15 @@ public function testBuildDataPermalinks()
// First link is a permalink
$link = array_shift($data['links']);
$this->assertEquals(41, $link['id']);
$this->assertEquals(DateTime::createFromFormat('Ymd_His', '20150310_114651'), $link['created']);
$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(8, $link['id']);
$this->assertEquals(DateTime::createFromFormat('Ymd_His', '20150310_114633'), $link['created']);
$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']);
Expand Down
22 changes: 11 additions & 11 deletions tests/LinkDBTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public function testSave()
'url'=>'http://dum.my',
'description'=>'One more',
'private'=>0,
'created'=> DateTime::createFromFormat('Ymd_His', '20150518_190000'),
'created'=> DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150518_190000'),
'tags'=>'unit test'
);
$testDB[$link['id']] = $link;
Expand Down Expand Up @@ -447,17 +447,17 @@ public function testFilterHashInValid()
*/
public function testReorderLinksDesc()
{
self::$publicLinkDB->reorder('ASC');
$linkIdToTest = 42;
foreach (self::$publicLinkDB as $key => $value) {
$this->assertEquals($linkIdToTest, $key);
break;
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::$publicLinkDB->reorder('DESC');
$linkIdToTest = 41;
foreach (self::$publicLinkDB as $key => $value) {
$this->assertEquals($linkIdToTest, $key);
break;
self::$privateLinkDB->reorder('DESC');
$linkIds = array_reverse($linkIds);
$cpt = 0;
foreach (self::$privateLinkDB as $key => $value) {
$this->assertEquals($linkIds[$cpt++], $key);
}
}
}
Loading

0 comments on commit cd484e7

Please sign in to comment.