From: ArthurHoaro See Theming for the list of community-contributed themes, and an installation guide. Several releases are available: Get the latest released version from the releases page. The current latest released version is As a .zip archive: Get the latest released version from the releases page. Download our shaarli-full archive to include dependencies. The current latest released version is Or in command lines: The stable version has been experienced by Shaarli users, and will receive security updates.
— '. $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);;
diff --git a/application/LinkDB.php b/application/LinkDB.php
index c8b162b6..1e13286a 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -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 @@ class LinkDB implements Iterator, Countable, ArrayAccess
if (!$this->loggedIn) {
die('You are not authorized to add a link.');
}
- if (empty($value['linkdate']) || empty($value['url'])) {
- die('Internal Error: A link should always have a linkdate and URL.');
+ if (!isset($value['id']) || empty($value['url'])) {
+ die('Internal Error: A link should always have an id and URL.');
}
- if (empty($offset)) {
- die('You must specify a key.');
+ if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) {
+ die('You must specify an integer as a key.');
+ }
+ if (! empty($offset) && $offset !== $value['id']) {
+ die('Array offset and link ID must be equal.');
+ }
+
+ // If the link exists, we reuse the real offset, otherwise new entry
+ $existing = $this->getLinkOffset($offset);
+ if ($existing !== null) {
+ $offset = $existing;
+ } else {
+ $offset = count($this->links);
}
$this->links[$offset] = $value;
- $this->urls[$value['url']]=$offset;
+ $this->urls[$value['url']] = $offset;
+ $this->ids[$value['id']] = $offset;
}
/**
@@ -136,7 +168,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
*/
public function offsetExists($offset)
{
- return array_key_exists($offset, $this->links);
+ return array_key_exists($this->getLinkOffset($offset), $this->links);
}
/**
@@ -148,9 +180,11 @@ class LinkDB implements Iterator, Countable, ArrayAccess
// TODO: raise an exception
die('You are not authorized to delete a link.');
}
- $url = $this->links[$offset]['url'];
+ $realOffset = $this->getLinkOffset($offset);
+ $url = $this->links[$realOffset]['url'];
unset($this->urls[$url]);
- unset($this->links[$offset]);
+ unset($this->ids[$realOffset]);
+ unset($this->links[$realOffset]);
}
/**
@@ -158,7 +192,8 @@ class LinkDB implements Iterator, Countable, ArrayAccess
*/
public function offsetGet($offset)
{
- return isset($this->links[$offset]) ? $this->links[$offset] : null;
+ $realOffset = $this->getLinkOffset($offset);
+ return isset($this->links[$realOffset]) ? $this->links[$realOffset] : null;
}
/**
@@ -166,7 +201,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
*/
public function current()
{
- return $this->links[$this->keys[$this->position]];
+ return $this[$this->keys[$this->position]];
}
/**
@@ -192,8 +227,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
*/
public function rewind()
{
- $this->keys = array_keys($this->links);
- rsort($this->keys);
+ $this->keys = array_keys($this->ids);
$this->position = 0;
}
@@ -219,6 +253,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
// Create a dummy database for example
$this->links = array();
$link = array(
+ 'id' => 1,
'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone',
'url'=>'https://github.com/shaarli/Shaarli/wiki',
'description'=>'Welcome to Shaarli! This is your first public bookmark. To edit or delete me, you must first login.
@@ -227,20 +262,23 @@ To learn how to use Shaarli, consult the link "Help/documentation" at the bottom
You use the community supported version of the original Shaarli project, by Sebastien Sauvage.',
'private'=>0,
- 'linkdate'=> date('Ymd_His'),
+ 'created'=> new DateTime(),
'tags'=>'opensource software'
);
- $this->links[$link['linkdate']] = $link;
+ $link['shorturl'] = link_small_hash($link['created'], $link['id']);
+ $this->links[1] = $link;
$link = array(
+ 'id' => 0,
'title'=>'My secret stuff... - Pastebin.com',
'url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=',
'description'=>'Shhhh! I\'m a private link only YOU can see. You can delete me too.',
'private'=>1,
- 'linkdate'=> date('Ymd_His', strtotime('-1 minute')),
- 'tags'=>'secretstuff'
+ 'created'=> new DateTime('1 minute ago'),
+ 'tags'=>'secretstuff',
);
- $this->links[$link['linkdate']] = $link;
+ $link['shorturl'] = link_small_hash($link['created'], $link['id']);
+ $this->links[0] = $link;
// Write database to disk
$this->write();
@@ -251,7 +289,6 @@ You use the community supported version of the original Shaarli project, by Seba
*/
private function read()
{
-
// Public links are hidden and user not logged in => nothing to show
if ($this->hidePublicLinks && !$this->loggedIn) {
$this->links = array();
@@ -269,23 +306,13 @@ You use the community supported version of the original Shaarli project, by Seba
strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
}
- // If user is not logged in, filter private links.
- if (!$this->loggedIn) {
- $toremove = array();
- foreach ($this->links as $link) {
- if ($link['private'] != 0) {
- $toremove[] = $link['linkdate'];
- }
- }
- foreach ($toremove as $linkdate) {
- unset($this->links[$linkdate]);
+ $toremove = array();
+ foreach ($this->links as $key => &$link) {
+ if (! $this->loggedIn && $link['private'] != 0) {
+ // Transition for not upgraded databases.
+ $toremove[] = $key;
+ continue;
}
- }
-
- $this->urls = array();
- foreach ($this->links as &$link) {
- // Keep the list of the mapping URLs-->linkdate up-to-date.
- $this->urls[$link['url']] = $link['linkdate'];
// Sanitize data fields.
sanitizeLink($link);
@@ -307,7 +334,24 @@ You use the community supported version of the original Shaarli project, by Seba
else {
$link['real_url'] = $link['url'];
}
+
+ // To be able to load links before running the update, and prepare the update
+ if (! isset($link['created'])) {
+ $link['id'] = $link['linkdate'];
+ $link['created'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['linkdate']);
+ if (! empty($link['updated'])) {
+ $link['updated'] = DateTime::createFromFormat(self::LINK_DATE_FORMAT, $link['updated']);
+ }
+ $link['shorturl'] = smallHash($link['linkdate']);
+ }
+ }
+
+ // If user is not logged in, filter private links.
+ foreach ($toremove as $offset) {
+ unset($this->links[$offset]);
}
+
+ $this->reorder();
}
/**
@@ -430,7 +474,7 @@ You use the community supported version of the original Shaarli project, by Seba
$request = '';
}
- $linkFilter = new LinkFilter($this->links);
+ $linkFilter = new LinkFilter($this);
return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
}
@@ -467,12 +511,64 @@ You use the community supported version of the original Shaarli project, by Seba
public function days()
{
$linkDays = array();
- foreach (array_keys($this->links) as $day) {
- $linkDays[substr($day, 0, 8)] = 0;
+ foreach ($this->links as $link) {
+ $linkDays[$link['created']->format('Ymd')] = 0;
}
$linkDays = array_keys($linkDays);
sort($linkDays);
return $linkDays;
}
+
+ /**
+ * Reorder links by creation date (newest first).
+ *
+ * Also update the urls and ids mapping arrays.
+ *
+ * @param string $order ASC|DESC
+ */
+ public function reorder($order = 'DESC')
+ {
+ $order = $order === 'ASC' ? -1 : 1;
+ // Reorder array by dates.
+ usort($this->links, function($a, $b) use ($order) {
+ return $a['created'] < $b['created'] ? 1 * $order : -1 * $order;
+ });
+
+ $this->urls = array();
+ $this->ids = array();
+ foreach ($this->links as $key => $link) {
+ $this->urls[$link['url']] = $key;
+ $this->ids[$link['id']] = $key;
+ }
+ }
+
+ /**
+ * Return the next key for link creation.
+ * E.g. If the last ID is 597, the next will be 598.
+ *
+ * @return int next ID.
+ */
+ public function getNextId()
+ {
+ if (!empty($this->ids)) {
+ return max(array_keys($this->ids)) + 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns a link offset in links array from its unique ID.
+ *
+ * @param int $id Persistent ID of a link.
+ *
+ * @return int Real offset in local array, or null if doesn't exist.
+ */
+ protected function getLinkOffset($id)
+ {
+ if (isset($this->ids[$id])) {
+ return $this->ids[$id];
+ }
+ return null;
+ }
}
diff --git a/application/LinkFilter.php b/application/LinkFilter.php
index d4fe28df..daa6d9cc 100644
--- a/application/LinkFilter.php
+++ b/application/LinkFilter.php
@@ -33,12 +33,12 @@ class LinkFilter
public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}';
/**
- * @var array all available links.
+ * @var LinkDB all available links.
*/
private $links;
/**
- * @param array $links initialization.
+ * @param LinkDB $links initialization.
*/
public function __construct($links)
{
@@ -94,18 +94,16 @@ class LinkFilter
private function noFilter($privateonly = false)
{
if (! $privateonly) {
- krsort($this->links);
return $this->links;
}
$out = array();
- foreach ($this->links as $value) {
+ foreach ($this->links as $key => $value) {
if ($value['private']) {
- $out[$value['linkdate']] = $value;
+ $out[$key] = $value;
}
}
- krsort($out);
return $out;
}
@@ -121,10 +119,10 @@ class LinkFilter
private function filterSmallHash($smallHash)
{
$filtered = array();
- foreach ($this->links as $l) {
- if ($smallHash == smallHash($l['linkdate'])) {
+ foreach ($this->links as $key => $l) {
+ if ($smallHash == $l['shorturl']) {
// Yes, this is ugly and slow
- $filtered[$l['linkdate']] = $l;
+ $filtered[$key] = $l;
return $filtered;
}
}
@@ -188,7 +186,7 @@ class LinkFilter
$keys = array('title', 'description', 'url', 'tags');
// Iterate over every stored link.
- foreach ($this->links as $link) {
+ foreach ($this->links as $id => $link) {
// ignore non private links when 'privatonly' is on.
if (! $link['private'] && $privateonly === true) {
@@ -222,11 +220,10 @@ class LinkFilter
}
if ($found) {
- $filtered[$link['linkdate']] = $link;
+ $filtered[$id] = $link;
}
}
- krsort($filtered);
return $filtered;
}
@@ -256,7 +253,7 @@ class LinkFilter
return $filtered;
}
- foreach ($this->links as $link) {
+ foreach ($this->links as $key => $link) {
// ignore non private links when 'privatonly' is on.
if (! $link['private'] && $privateonly === true) {
continue;
@@ -278,10 +275,9 @@ class LinkFilter
}
if ($found) {
- $filtered[$link['linkdate']] = $link;
+ $filtered[$key] = $link;
}
}
- krsort($filtered);
return $filtered;
}
@@ -304,13 +300,14 @@ class LinkFilter
}
$filtered = array();
- foreach ($this->links as $l) {
- if (startsWith($l['linkdate'], $day)) {
- $filtered[$l['linkdate']] = $l;
+ foreach ($this->links as $key => $l) {
+ if ($l['created']->format('Ymd') == $day) {
+ $filtered[$key] = $l;
}
}
- ksort($filtered);
- return $filtered;
+
+ // sort by date ASC
+ return array_reverse($filtered, true);
}
/**
diff --git a/application/LinkUtils.php b/application/LinkUtils.php
index 9d9ae3cb..cf58f808 100644
--- a/application/LinkUtils.php
+++ b/application/LinkUtils.php
@@ -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);
+}
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php
index dd21f05b..f21ee359 100644
--- a/application/NetscapeBookmarkUtils.php
+++ b/application/NetscapeBookmarkUtils.php
@@ -38,7 +38,7 @@ class NetscapeBookmarkUtils
if ($link['private'] == 0 && $selection == 'private') {
continue;
}
- $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
+ $date = $link['created'];
$link['timestamp'] = $date->getTimestamp();
$link['taglist'] = str_replace(' ', ',', $link['tags']);
@@ -82,6 +82,143 @@ class NetscapeBookmarkUtils
return $status;
}
+ /**
+ * Imports Web bookmarks from an uploaded Netscape bookmark dump
+ *
+ * @param array $post Server $_POST parameters
+ * @param array $files Server $_FILES parameters
+ * @param LinkDB $linkDb Loaded LinkDB instance
+ * @param string $pagecache Page cache
+ *
+ * @return string Summary of the bookmark import status
+ */
+ public static function import($post, $files, $linkDb, $pagecache)
+ {
+ $filename = $files['filetoupload']['name'];
+ $filesize = $files['filetoupload']['size'];
+ $data = file_get_contents($files['filetoupload']['tmp_name']);
+
+ if (strpos($data, '') === false) {
+ return self::importStatus($filename, $filesize);
+ }
+
+ // Overwrite existing links?
+ $overwrite = ! empty($post['overwrite']);
+
+ // Add tags to all imported links?
+ if (empty($post['default_tags'])) {
+ $defaultTags = array();
+ } else {
+ $defaultTags = preg_split(
+ '/[\s,]+/',
+ escape($post['default_tags'])
+ );
+ }
+
+ // links are imported as public by default
+ $defaultPrivacy = 0;
+
+ $parser = new NetscapeBookmarkParser(
+ true, // nested tag support
+ $defaultTags, // additional user-specified tags
+ strval(1 - $defaultPrivacy) // defaultPub = 1 - defaultPrivacy
+ );
+ $bookmarks = $parser->parseString($data);
+
+ $importCount = 0;
+ $overwriteCount = 0;
+ $skipCount = 0;
+
+ foreach ($bookmarks as $bkm) {
+ $private = $defaultPrivacy;
+ if (empty($post['privacy']) || $post['privacy'] == 'default') {
+ // use value from the imported file
+ $private = $bkm['pub'] == '1' ? 0 : 1;
+ } else if ($post['privacy'] == 'private') {
+ // all imported links are private
+ $private = 1;
+ } else if ($post['privacy'] == 'public') {
+ // all imported links are public
+ $private = 0;
+ }
+
+ $newLink = array(
+ 'title' => $bkm['title'],
+ 'url' => $bkm['uri'],
+ 'description' => $bkm['note'],
+ 'private' => $private,
+ 'tags' => $bkm['tags']
+ );
+
+ $existingLink = $linkDb->getLinkFromUrl($bkm['uri']);
+
+ if ($existingLink !== false) {
+ if ($overwrite === false) {
+ // Do not overwrite an existing link
+ $skipCount++;
+ continue;
+ }
+
+ // Overwrite an existing link, keep its date
+ $newLink['id'] = $existingLink['id'];
+ $newLink['created'] = $existingLink['created'];
+ $newLink['updated'] = new DateTime();
+ $linkDb[$existingLink['id']] = $newLink;
+ $importCount++;
+ $overwriteCount++;
+ continue;
+ }
+
+ // Add a new link - @ used for UNIX timestamps
+ $newLinkDate = new DateTime('@'.strval($bkm['time']));
+ $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++;
+ }
+
+ $linkDb->save($pagecache);
+ return self::importStatus(
+ $filename,
+ $filesize,
+ $importCount,
+ $overwriteCount,
+ $skipCount
+ );
+ }
+
+ /**
+ * Generates an import status summary
+ *
+ * @param string $filename name of the file to import
+ * @param int $filesize size of the file to import
+ * @param int $importCount how many links were imported
+ * @param int $overwriteCount how many links were overwritten
+ * @param int $skipCount how many links were skipped
+ *
+ * @return string Summary of the bookmark import status
+ */
+ private static function importStatus(
+ $filename,
+ $filesize,
+ $importCount=0,
+ $overwriteCount=0,
+ $skipCount=0
+ )
+ {
+ $status = 'File '.$filename.' ('.$filesize.' bytes) ';
+ if ($importCount == 0 && $overwriteCount == 0 && $skipCount == 0) {
+ $status .= 'has an unknown file format. Nothing was imported.';
+ } else {
+ $status .= 'was successfully processed: '.$importCount.' links imported, ';
+ $status .= $overwriteCount.' links overwritten, ';
+ $status .= $skipCount.' links skipped.';
+ }
+ return $status;
+ }
+
/**
* Imports Web bookmarks from an uploaded Netscape bookmark dump
*
diff --git a/application/Updater.php b/application/Updater.php
index 36eddd4f..f0d02814 100644
--- a/application/Updater.php
+++ b/application/Updater.php
@@ -138,10 +138,10 @@ class Updater
public function updateMethodRenameDashTags()
{
$linklist = $this->linkDB->filterSearch();
- foreach ($linklist as $link) {
+ foreach ($linklist as $key => $link) {
$link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
$link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
- $this->linkDB[$link['linkdate']] = $link;
+ $this->linkDB[$key] = $link;
}
$this->linkDB->save($this->conf->get('resource.page_cache'));
return true;
@@ -215,6 +215,47 @@ class Updater
}
return true;
}
+
+ /**
+ * Update the database to use the new ID system, which replaces linkdate primary keys.
+ * Also, creation and update dates are now DateTime objects (done by LinkDB).
+ *
+ * Since this update is very sensitve (changing the whole database), the datastore will be
+ * automatically backed up into the file datastore.
Themes
Mobile Apps
@@ -107,6 +108,7 @@
Alternatives to Shaarli
diff --git a/doc/Community-&-Related-software.md b/doc/Community-&-Related-software.md
index 3945d005..291bf643 100644
--- a/doc/Community-&-Related-software.md
+++ b/doc/Community-&-Related-software.md
@@ -20,10 +20,11 @@ _TODO: contact repos owners to see if they'd like to standardize their work with
* [autosave](https://github.com/kalvn/shaarli-plugin-autosave) by [@kalvn](https://github.com/kalvn): Automatically saves data when editing a link to avoid any loss in case of crash or unexpected shutdown.[](.html)
* [Code Coloration](https://github.com/ArthurHoaro/code-coloration) by [@ArthurHoaro](https://github.com/ArthurHoaro): client side code syntax highlighter.[](.html)
- * [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.[](.html)
+ * [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.[](.html)
* [emojione](https://github.com/NerosTie/emojione) by [@NerosTie](https://github.com/NerosTie): Add colorful emojis to your Shaarli.[](.html)
* [launch](https://github.com/ArthurHoaro/launch-plugin) - Launch Plugin is a plugin designed to enhance and customize Launch Theme for Shaarli.[](.html)
- * [Disqus](https://github.com/kalvn/shaarli-plugin-disqus) by [@kalvn](https://github.com/kalvn): Adds Disqus comment system to your Shaarli.[](.html)
+ * [social](https://github.com/alexisju/social) by [@alexisju](https://github.com/alexisju): share links to social networks.[](.html)
+ * [shaarli2twitter](https://github.com/ArthurHoaro/shaarli2twitter) by [@ArthurHoaro](https://github.com/ArthurHoaro) - Automatically tweet your shared links from Shaarli[](.html)
### Themes
@@ -35,7 +36,7 @@ See [Theming](Theming.html) for the list of community-contributed themes, and an
- [Shaarlo](https://github.com/DMeloni/shaarlo) - An aggregator for shaarlis with many features (a very popular running instance among french shaarliers: [shaarli.fr](http://shaarli.fr/))[](.html)
- [Shaarlimages](https://github.com/BoboTiG/shaarlimages) - An image-oriented aggregator for Shaarlis[](.html)
- [mknexen/shaarli-api](https://github.com/mknexen/shaarli-api) - A REST API for Shaarli[](.html)
-- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. An [another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for others shaarli (but use most ressources).[](.html)
+- [Self dead link](https://github.com/qwertygc/shaarli-dev-code/blob/master/self-dead-link.php) - Detect dead links on shaarli. This version use the database of shaarli. [Another version](https://github.com/qwertygc/shaarli-dev-code/blob/master/dead-link.php), can be used for other shaarli instances (but is more resource consuming).[](.html)
### Mobile Apps
- [Shaarlið«](http://app.mro.name/Shaarlið«) iOS share extension - see [#308](https://github.com/shaarli/Shaarli/issues/308#issuecomment-184592070) for some promo codes,[](.html)
@@ -45,6 +46,7 @@ See [Theming](Theming.html) for the list of community-contributed themes, and an
## Integration with other platforms
- [tt-rss-shaarli](https://github.com/jcsaaddupuy/tt-rss-shaarli) - [TinyTiny RSS](http://tt-rss.org/) plugin that adds support for sharing articles with Shaarli[](.html)
- [octopress-shaarli](https://github.com/ahmet2mir/octopress-shaarli) - Octopress plugin to retrieve Shaarli links on the sidebar[](.html)
+- [Scuttle to Shaarli](https://github.com/q2apro/scuttle-to-shaarli) - Import bookmarks from Scuttle[](.html)
## Alternatives to Shaarli
- [Shaarli alternatives](http://alternativeto.net/software/shaarli/) (alternativeto.net)[](.html)
diff --git a/doc/Download-and-Installation.html b/doc/Download-and-Installation.html
index 17c7b69e..b9cac360 100644
--- a/doc/Download-and-Installation.html
+++ b/doc/Download-and-Installation.html
@@ -105,13 +105,14 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
Latest release (recommended)
-v0.7.0
.Download as an archive
-$ wget https://github.com/shaarli/Shaarli/archive/v0.7.0.zip
-$ unzip Shaarli-v0.7.0.zip
-$ mv Shaarli-v0.7.0 /path/to/shaarli/
v0.8.0
$ wget https://github.com/shaarli/Shaarli/releases/download/v0.8.0/shaarli-v0.8.0-full.zip
+$ unzip shaarli-v0.8.0-full.zip
+$ mv Shaarli /path/to/shaarli/
+Using git
+mkdir -p /path/to/shaarli && cd /path/to/shaarli/
+git clone -b v0.8.0 https://github.com/shaarli/Shaarli.git .
+composer update --no-dev
Stable version
origin
pointing to your GitHub forkupstream
pointing to the main Shaarli repositoryCHANGELOG.md
See http://keepachangelog.com/en/0.3.0/ for changelog formatting.
+GitHub allows drafting the release note for the upcoming release, from the Releases page. This way, the release note can be drafted while contributions are merged to master
.
CHANGELOG.md
This file should contain the same information as the release note draft for the upcoming version.
+Update it to:
+$ cd /path/to/shaarli
+
+$ nano CHANGELOG.md
+
+[...][](.html)
+## vA.B.C - UNRELEASED
+TBA
+
+## [vX.Y.Z](https://github.com/shaarli/Shaarli/releases/tag/vX.Y.Z) - YYYY-MM-DD[](.html)
+[...][](.html)
$ cd /path/to/shaarli
@@ -165,7 +191,16 @@ $ git show-ref tags/v0.5.0
$ git verify-tag f7762cf803f03f5caf4b8078359a63783d0090c1
gpg: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F
gpg: Good signature from "VirtualTam <virtualtam@flibidi.net>" [ultimate][](.html)
From the previously drafted release:
+Users with a shared hosting may have:
Shaarli use .htaccess
Apache files to deny access to files that shouldn't be directly accessed (datastore, config, etc.). You need the directive AllowOverride All
in your virtual host configuration for them to work.
Warning: If you use Apache 2.2 or lower, you need mod_version to be installed and enabled.
For all following examples, a development configuration will be used:
+For all following configuration examples, this user/group pair will be used:
user:group = john:users
,Some bookmark dumps generated by web browsers can be huge due to the presence of Base64-encoded images and favicons, as well as extra verbosity when nesting links in (sub-)folders.
+To increase upload size, you will need to modify both nginx and PHP configuration:
+# /etc/nginx/nginx.conf
+
+http {
+ [...][](.html)
+
+ client_max_body_size 10m;
+
+ [...][](.html)
+}
+# /etc/php5/fpm/php.ini
+
+[...][](.html)
+post_max_size = 10M
+[...][](.html)
+upload_max_filesize = 10M
WARNING: Use for development only!
user john users;
@@ -350,6 +371,11 @@ http {
error_log /var/log/nginx/shaarli.error.log;
}
+ location = /shaarli/favicon.ico {
+ # serve the Shaarli favicon from its custom location
+ alias /var/www/shaarli/images/favicon.ico;
+ }
+
include deny.conf;
include static_assets.conf;
include php.conf;
@@ -403,6 +429,11 @@ http {
error_log /var/log/nginx/shaarli.error.log;
}
+ location = /shaarli/favicon.ico {
+ # serve the Shaarli favicon from its custom location
+ alias /var/www/shaarli/images/favicon.ico;
+ }
+
include deny.conf;
include static_assets.conf;
include php.conf;
diff --git a/doc/Server-configuration.md b/doc/Server-configuration.md
index 1ab57a0a..df10feb2 100644
--- a/doc/Server-configuration.md
+++ b/doc/Server-configuration.md
@@ -102,6 +102,12 @@ See [Server-side TLS](https://wiki.mozilla.org/Security/Server_Side_TLS#Apache)
```
+### .htaccess
+
+Shaarli use `.htaccess` Apache files to deny access to files that shouldn't be directly accessed (datastore, config, etc.). You need the directive `AllowOverride All` in your virtual host configuration for them to work.
+
+**Warning**: If you use Apache 2.2 or lower, you need [mod_version](https://httpd.apache.org/docs/current/mod/mod_version.html) to be installed and enabled.[](.html)
+
## LightHttpd
## Nginx
@@ -136,7 +142,7 @@ On a development server:
- files may be located in a user's home directory
- in this case, make sure both Nginx and PHP-FPM are running as the local user/group!
-For all following examples, a development configuration will be used:
+For all following configuration examples, this user/group pair will be used:
- `user:group = john:users`,
which corresponds to the following service configuration:
@@ -160,6 +166,32 @@ http {
}
```
+### (Optional) Increase the maximum file upload size
+Some bookmark dumps generated by web browsers can be _huge_ due to the presence of Base64-encoded images and favicons, as well as extra verbosity when nesting links in (sub-)folders.
+
+To increase upload size, you will need to modify both nginx and PHP configuration:
+
+```nginx
+# /etc/nginx/nginx.conf
+
+http {
+ [...][](.html)
+
+ client_max_body_size 10m;
+
+ [...][](.html)
+}
+```
+
+```ini
+# /etc/php5/fpm/php.ini
+
+[...][](.html)
+post_max_size = 10M
+[...][](.html)
+upload_max_filesize = 10M
+```
+
### Minimal
_WARNING: Use for development only!_
@@ -271,6 +303,11 @@ http {
error_log /var/log/nginx/shaarli.error.log;
}
+ location = /shaarli/favicon.ico {
+ # serve the Shaarli favicon from its custom location
+ alias /var/www/shaarli/images/favicon.ico;
+ }
+
include deny.conf;
include static_assets.conf;
include php.conf;
@@ -328,6 +365,11 @@ http {
error_log /var/log/nginx/shaarli.error.log;
}
+ location = /shaarli/favicon.ico {
+ # serve the Shaarli favicon from its custom location
+ alias /var/www/shaarli/images/favicon.ico;
+ }
+
include deny.conf;
include static_assets.conf;
include php.conf;
diff --git a/doc/Theming.html b/doc/Theming.html
index 13e6acf0..7cbf7aef 100644
--- a/doc/Theming.html
+++ b/doc/Theming.html
@@ -119,19 +119,20 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
- There should now be a
my-template/
directory under the tpl/
dir, containing directly all the template files.
-Edit data/config.php
to have Shaarli use this template, e.g.
-$GLOBALS['config'['RAINTPL_TPL'] = 'tpl/my-template/';]('RAINTPL_TPL']-=-'tpl/my-template/';.html)
+Edit data/config.json.php
to have Shaarli use this template, in "resource"
e.g.
+"raintpl_tpl": "tpl\/my-template\/",
Community themes & templates
- AkibaTech/Shaarli Superhero Theme - A template/theme for Shaarli
- alexisju/albinomouse-template - A full template for Shaarli
+- ArthurHoaro/shaarli-launch - Customizable Shaarli theme.
- dhoko/ShaarliTemplate - A template/theme for Shaarli
- kalvn/shaarli-blocks - A template/theme for Shaarli
- kalvn/Shaarli-Material - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.
+- ManufacturaInd/shaarli-2004licious-theme - A template/theme as a humble homage to the early looks of the del.icio.us site.
- misterair/Limonade - A fork of (legacy) Shaarli with a new template
- mrjovanovic/serious-theme-shaarli - A serious theme for SHaarli.
-- Vinm/Blue-theme-for Shaarli - A template/theme for Shaarli (unmaintained, compatibility unknown)
- vivienhaese/shaarlitheme - A Shaarli fork meant to be run in an openshift instance
Example installation: AlbinoMouse template
diff --git a/doc/Theming.md b/doc/Theming.md
index 7fb8d927..a21899c2 100644
--- a/doc/Theming.md
+++ b/doc/Theming.md
@@ -16,20 +16,21 @@ _WARNING - This feature is currently being worked on and will be improved in the
- Find it's git clone URL or download the zip archive for the template.
- In your Shaarli `tpl/` directory, run `git clone https://url/of/my-template/` or unpack the zip archive.
- There should now be a `my-template/` directory under the `tpl/` dir, containing directly all the template files.
-- Edit `data/config.php` to have Shaarli use this template, e.g.
-```php
-$GLOBALS['config'['RAINTPL_TPL'] = 'tpl/my-template/';]('RAINTPL_TPL']-=-'tpl/my-template/';.html)
+- Edit `data/config.json.php` to have Shaarli use this template, in `"resource"` e.g.
+```json
+"raintpl_tpl": "tpl\/my-template\/",
```
## Community themes & templates
- [AkibaTech/Shaarli Superhero Theme](https://github.com/AkibaTech/Shaarli---SuperHero-Theme) - A template/theme for Shaarli[](.html)
- [alexisju/albinomouse-template](https://github.com/alexisju/albinomouse-template) - A full template for Shaarli[](.html)
+- [ArthurHoaro/shaarli-launch](https://github.com/ArthurHoaro/shaarli-launch) - Customizable Shaarli theme.[](.html)
- [dhoko/ShaarliTemplate](https://github.com/dhoko/ShaarliTemplate) - A template/theme for Shaarli[](.html)
- [kalvn/shaarli-blocks](https://github.com/kalvn/shaarli-blocks) - A template/theme for Shaarli[](.html)
- [kalvn/Shaarli-Material](https://github.com/kalvn/Shaarli-Material) - A theme (template) based on Google's Material Design for Shaarli, the superfast delicious clone.[](.html)
+- [ManufacturaInd/shaarli-2004licious-theme](https://github.com/ManufacturaInd/shaarli-2004licious-theme) - A template/theme as a humble homage to the early looks of the del.icio.us site.[](.html)
- [misterair/Limonade](https://github.com/misterair/limonade) - A fork of (legacy) Shaarli with a new template[](.html)
- [mrjovanovic/serious-theme-shaarli](https://github.com/mrjovanovic/serious-theme-shaarli) - A serious theme for SHaarli.[](.html)
-- [Vinm/Blue-theme-for Shaarli](https://github.com/Vinm/Blue-theme-for-Shaarli) - A template/theme for Shaarli ([unmaintained](https://github.com/Vinm/Blue-theme-for-Shaarli/issues/2), compatibility unknown)[](.html)
- [vivienhaese/shaarlitheme](https://github.com/vivienhaese/shaarlitheme) - A Shaarli fork meant to be run in an openshift instance[](.html)
### Example installation: AlbinoMouse template
diff --git a/docker/.htaccess b/docker/.htaccess
index b584d98c..f601c1ee 100644
--- a/docker/.htaccess
+++ b/docker/.htaccess
@@ -1,2 +1,13 @@
-Allow from none
-Deny from all
+
+ = 2.4>
+ Require all denied
+
+
+ Allow from none
+ Deny from all
+
+
+
+
+ Require all denied
+
diff --git a/docker/development/Dockerfile b/docker/development/Dockerfile
index 0c19b085..d9ef8da7 100644
--- a/docker/development/Dockerfile
+++ b/docker/development/Dockerfile
@@ -15,6 +15,8 @@ RUN apt-get update \
nano \
&& apt-get clean
+RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
+RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
COPY nginx.conf /etc/nginx/nginx.conf
COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
diff --git a/docker/development/nginx.conf b/docker/development/nginx.conf
index cda09b56..ac0c6c61 100644
--- a/docker/development/nginx.conf
+++ b/docker/development/nginx.conf
@@ -11,6 +11,8 @@ http {
default_type application/octet-stream;
keepalive_timeout 20;
+ client_max_body_size 10m;
+
index index.html index.php;
server {
@@ -49,6 +51,11 @@ http {
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
+ location = /favicon.ico {
+ # serve the Shaarli favicon from its custom location
+ alias /var/www/shaarli/images/favicon.ico;
+ }
+
location ~ (index)\.php$ {
# filter and proxy PHP requests to PHP-FPM
fastcgi_pass unix:/var/run/php5-fpm.sock;
diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile
index d93ed262..d0509115 100644
--- a/docker/production/Dockerfile
+++ b/docker/production/Dockerfile
@@ -14,6 +14,8 @@ RUN apt-get update \
supervisor \
&& apt-get clean
+RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
+RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
COPY nginx.conf /etc/nginx/nginx.conf
COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
diff --git a/docker/production/nginx.conf b/docker/production/nginx.conf
index e23c4587..5ffa02d0 100644
--- a/docker/production/nginx.conf
+++ b/docker/production/nginx.conf
@@ -11,6 +11,8 @@ http {
default_type application/octet-stream;
keepalive_timeout 20;
+ client_max_body_size 10m;
+
index index.html index.php;
server {
@@ -41,6 +43,11 @@ http {
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
+ location = /favicon.ico {
+ # serve the Shaarli favicon from its custom location
+ alias /var/www/shaarli/images/favicon.ico;
+ }
+
location ~ (index)\.php$ {
# filter and proxy PHP requests to PHP-FPM
fastcgi_pass unix:/var/run/php5-fpm.sock;
diff --git a/docker/production/stable/Dockerfile b/docker/production/stable/Dockerfile
index a509fda6..fc9588b0 100644
--- a/docker/production/stable/Dockerfile
+++ b/docker/production/stable/Dockerfile
@@ -14,6 +14,8 @@ RUN apt-get update \
supervisor \
&& apt-get clean
+RUN sed -i 's/post_max_size.*/post_max_size = 10M/' /etc/php5/fpm/php.ini
+RUN sed -i 's/upload_max_filesize.*/upload_max_filesize = 10M/' /etc/php5/fpm/php.ini
COPY nginx.conf /etc/nginx/nginx.conf
COPY supervised.conf /etc/supervisor/conf.d/supervised.conf
diff --git a/docker/production/stable/nginx.conf b/docker/production/stable/nginx.conf
index e23c4587..5ffa02d0 100644
--- a/docker/production/stable/nginx.conf
+++ b/docker/production/stable/nginx.conf
@@ -11,6 +11,8 @@ http {
default_type application/octet-stream;
keepalive_timeout 20;
+ client_max_body_size 10m;
+
index index.html index.php;
server {
@@ -41,6 +43,11 @@ http {
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
+ location = /favicon.ico {
+ # serve the Shaarli favicon from its custom location
+ alias /var/www/shaarli/images/favicon.ico;
+ }
+
location ~ (index)\.php$ {
# filter and proxy PHP requests to PHP-FPM
fastcgi_pass unix:/var/run/php5-fpm.sock;
diff --git a/inc/shaarli.css b/inc/shaarli.css
index 5808320c..a24d4b7c 100644
--- a/inc/shaarli.css
+++ b/inc/shaarli.css
@@ -37,6 +37,10 @@ em {
font-style: italic;
}
+strong {
+ font-weight: bold;
+}
+
/* Buttons */
.bigbutton {
background-color: #c0c0c0;
@@ -1168,8 +1172,13 @@ ul.errors {
}
#pluginsadmin a {
+ color: #486D08;
+}
+
+#pluginsadmin a.arrow {
color: black;
}
+
/* 404 page */
.error-container {
diff --git a/index.php b/index.php
index c4c0d15a..a0a3a8c7 100644
--- a/index.php
+++ b/index.php
@@ -1,6 +1,6 @@
/shaarli/
define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0)));
@@ -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 ''. $pageaddr .' '. 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.
{
@@ -1078,6 +1066,7 @@ function renderPage($conf, $pluginManager)
{
$data = array(
'pageabsaddr' => index_url($_SERVER),
+ 'sslenabled' => !empty($_SERVER['HTTPS'])
);
$pluginManager->executeHooks('render_tools', $data);
@@ -1244,13 +1233,30 @@ 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();
+ $shortUrl = $LINKSDB[$id]['shorturl'];
+ } else {
+ // New link
+ $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
+ $updated = null;
+ $shortUrl = link_small_hash($created, $id);
+ }
+
// Remove multiple spaces.
$tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags']));
// Remove first '-' char in tags.
@@ -1267,14 +1273,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' => $shortUrl,
);
+
// If title is empty, use the URL as title.
if ($link['title'] == '') {
$link['title'] = $link['url'];
@@ -1282,7 +1291,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);
@@ -1295,7 +1304,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;
@@ -1306,27 +1315,31 @@ 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 ''; 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;
}
// -------- User clicked the "Delete" button when editing a link: Delete link from database.
- if ($targetPage == Router::$PAGE_DELETELINK)
+ if (isset($_POST['delete_link']))
{
- if (!tokenOk($_GET['token'])) die('Wrong token.');
+ 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 = $_GET['delete_link'];
- $link = $LINKSDB[$linkdate];
-
- $pluginManager->executeHooks('delete_link', $link);
- unset($LINKSDB[$linkdate]);
- $LINKSDB->save($conf->get('resource.page_cache')); // save to disk
+ // FIXME! We keep `lf_linkdate` for consistency before a proper API. To be removed.
+ $id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : intval(escape($_POST['lf_linkdate']));
+
+ $pluginManager->executeHooks('delete_link', $LINKSDB[$id]);
+
+ unset($LINKSDB[$id]);
+ $LINKSDB->save('resource.page_cache'); // save to disk
// If we are called from the bookmarklet, we must close the popup:
if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo ''; exit; }
@@ -1364,8 +1377,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 +1404,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 +1431,7 @@ function renderPage($conf, $pluginManager)
}
if ($url == '') {
- $url = '?' . smallHash($linkdate);
+ $url = '?' . smallHash($linkdate . $LINKSDB->getNextId());
$title = 'Note: ';
}
$url = escape($url);
@@ -1430,6 +1445,8 @@ function renderPage($conf, $pluginManager)
'tags' => $tags,
'private' => $private
);
+ } else {
+ $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
}
$data = array(
@@ -1635,18 +1652,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) {
diff --git a/pagecache/.htaccess b/pagecache/.htaccess
index b584d98c..f601c1ee 100644
--- a/pagecache/.htaccess
+++ b/pagecache/.htaccess
@@ -1,2 +1,13 @@
-Allow from none
-Deny from all
+
+ = 2.4>
+ Require all denied
+
+
+ Allow from none
+ Deny from all
+
+
+
+
+ Require all denied
+
diff --git a/plugins/addlink_toolbar/addlink_toolbar.css b/plugins/addlink_toolbar/addlink_toolbar.css
deleted file mode 100644
index b6a612f0..00000000
--- a/plugins/addlink_toolbar/addlink_toolbar.css
+++ /dev/null
@@ -1,4 +0,0 @@
-#addlink_toolbar {
- display: inline;
- margin: 0 0 0 25px;
-}
\ No newline at end of file
diff --git a/plugins/addlink_toolbar/addlink_toolbar.php b/plugins/addlink_toolbar/addlink_toolbar.php
index c96a891e..bf8a198a 100644
--- a/plugins/addlink_toolbar/addlink_toolbar.php
+++ b/plugins/addlink_toolbar/addlink_toolbar.php
@@ -16,10 +16,12 @@ function hook_addlink_toolbar_render_header($data)
{
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST && $data['_LOGGEDIN_'] === true) {
$form = array(
- 'method' => 'GET',
- 'action' => '',
- 'name' => 'addform',
- 'class' => 'addform',
+ 'attr' => array(
+ 'method' => 'GET',
+ 'action' => '',
+ 'name' => 'addform',
+ 'class' => 'addform',
+ ),
'inputs' => array(
array(
'type' => 'text',
diff --git a/plugins/archiveorg/archiveorg.html b/plugins/archiveorg/archiveorg.html
index 576bd46e..0781fe35 100644
--- a/plugins/archiveorg/archiveorg.html
+++ b/plugins/archiveorg/archiveorg.html
@@ -1 +1 @@
-
+
diff --git a/plugins/demo_plugin/demo_plugin.php b/plugins/demo_plugin/demo_plugin.php
index 15482fe0..8fdbf663 100644
--- a/plugins/demo_plugin/demo_plugin.php
+++ b/plugins/demo_plugin/demo_plugin.php
@@ -56,9 +56,11 @@ function hook_demo_plugin_render_header($data)
* and a mandatory `html` key, which contains its value.
*/
$button = array(
- 'href' => '#',
- 'class' => 'mybutton',
- 'title' => 'hover me',
+ 'attr' => array (
+ 'href' => '#',
+ 'class' => 'mybutton',
+ 'title' => 'hover me',
+ ),
'html' => 'DEMO buttons toolbar',
);
$data['buttons_toolbar'][] = $button;
@@ -87,9 +89,11 @@ function hook_demo_plugin_render_header($data)
*
*/
$form = array(
- 'method' => 'GET',
- 'action' => '?',
- 'class' => 'addform',
+ 'attr' => array(
+ 'method' => 'GET',
+ 'action' => '?',
+ 'class' => 'addform',
+ ),
'inputs' => array(
array(
'type' => 'text',
@@ -102,7 +106,9 @@ function hook_demo_plugin_render_header($data)
}
// Another button always displayed
$button = array(
- 'href' => '#',
+ 'attr' => array(
+ 'href' => '#',
+ ),
'html' => 'Demo',
);
$data['buttons_toolbar'][] = $button;
@@ -197,8 +203,10 @@ function hook_demo_plugin_render_linklist($data)
* It's also recommended to add key 'on' or 'off' for theme rendering.
*/
$action = array(
- 'href' => '?up',
- 'title' => 'Uppercase!',
+ 'attr' => array(
+ 'href' => '?up',
+ 'title' => 'Uppercase!',
+ ),
'html' => 'â',
);
diff --git a/plugins/isso/isso.php b/plugins/isso/isso.php
index ffb7cfac..ce16645f 100644
--- a/plugins/isso/isso.php
+++ b/plugins/isso/isso.php
@@ -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.
diff --git a/plugins/markdown/README.md b/plugins/markdown/README.md
index 4f021871..aafcf066 100644
--- a/plugins/markdown/README.md
+++ b/plugins/markdown/README.md
@@ -20,26 +20,52 @@ The directory structure should look like:
|--- markdown.css
|--- markdown.meta
|--- markdown.php
- |--- Parsedown.php
|--- README.md
```
To enable the plugin, just check it in the plugin administration page.
-You can also add `markdown` to your list of enabled plugins in `data/config.php`
-(`ENABLED_PLUGINS` array).
+You can also add `markdown` to your list of enabled plugins in `data/config.json.php`
+(`general.enabled_plugins` list).
This should look like:
```
-$GLOBALS['config']['ENABLED_PLUGINS'] = array('qrcode', 'any_other_plugin', 'markdown')
+"general": {
+ "enabled_plugins": [
+ "markdown",
+ [...]
+ ],
+}
```
+Parsedown parsing library is imported using Composer. If you installed Shaarli using `git`,
+or the `master` branch, run
+
+ composer update --no-dev --prefer-dist
+
### No Markdown tag
-If the tag `.nomarkdown` is set for a shaare, it won't be converted to Markdown syntax.
+If the tag `nomarkdown` is set for a shaare, it won't be converted to Markdown syntax.
-> Note: it's a private tag (leading dot), so it won't be displayed to visitors.
+> Note: this is a special tag, so it won't be displayed in link list.
+
+### HTML rendering
+
+Markdown support HTML tags. For example:
+
+ > strongstrike
+
+Will render as:
+
+> strongstrike
+
+If you want to shaare HTML code, it is necessary to use inline code or code blocks.
+
+**If your shaared descriptions containing HTML tags before enabling the markdown plugin,
+enabling it might break your page.**
+
+> Note: HTML tags such as script, iframe, etc. are disabled for security reasons.
### Known issue
diff --git a/plugins/markdown/markdown.meta b/plugins/markdown/markdown.meta
index e3904ed8..8df2ed0b 100644
--- a/plugins/markdown/markdown.meta
+++ b/plugins/markdown/markdown.meta
@@ -1 +1,4 @@
-description="Render shaare description with Markdown syntax."
+description="Render shaare description with Markdown syntax.
Warning:
+If your shaared descriptions containing HTML tags before enabling the markdown plugin,
+enabling it might break your page.
+See the README."
diff --git a/plugins/markdown/markdown.php b/plugins/markdown/markdown.php
index a764b6fa..0cf6e6e2 100644
--- a/plugins/markdown/markdown.php
+++ b/plugins/markdown/markdown.php
@@ -22,7 +22,7 @@ function hook_markdown_render_linklist($data)
{
foreach ($data['links'] as &$value) {
if (!empty($value['tags']) && noMarkdownTag($value['tags'])) {
- $value['taglist'] = stripNoMarkdownTag($value['taglist']);
+ $value = stripNoMarkdownTag($value);
continue;
}
$value['description'] = process_markdown($value['description']);
@@ -41,7 +41,7 @@ function hook_markdown_render_feed($data)
{
foreach ($data['links'] as &$value) {
if (!empty($value['tags']) && noMarkdownTag($value['tags'])) {
- $value['tags'] = stripNoMarkdownTag($value['tags']);
+ $value = stripNoMarkdownTag($value);
continue;
}
$value['description'] = process_markdown($value['description']);
@@ -63,6 +63,7 @@ function hook_markdown_render_daily($data)
foreach ($data['cols'] as &$value) {
foreach ($value as &$value2) {
if (!empty($value2['tags']) && noMarkdownTag($value2['tags'])) {
+ $value2 = stripNoMarkdownTag($value2);
continue;
}
$value2['formatedDescription'] = process_markdown($value2['formatedDescription']);
@@ -81,20 +82,30 @@ function hook_markdown_render_daily($data)
*/
function noMarkdownTag($tags)
{
- return strpos($tags, NO_MD_TAG) !== false;
+ return preg_match('/(^|\s)'. NO_MD_TAG .'(\s|$)/', $tags);
}
/**
* Remove the no-markdown meta tag so it won't be displayed.
*
- * @param string $tags Tag list.
+ * @param array $link Link data.
*
- * @return string tag list without no markdown tag.
+ * @return array Updated link without no markdown tag.
*/
-function stripNoMarkdownTag($tags)
+function stripNoMarkdownTag($link)
{
- unset($tags[array_search(NO_MD_TAG, $tags)]);
- return array_values($tags);
+ if (! empty($link['taglist'])) {
+ $offset = array_search(NO_MD_TAG, $link['taglist']);
+ if ($offset !== false) {
+ unset($link['taglist'][$offset]);
+ }
+ }
+
+ if (!empty($link['tags'])) {
+ str_replace(NO_MD_TAG, '', $link['tags']);
+ }
+
+ return $link;
}
/**
diff --git a/plugins/playvideos/playvideos.php b/plugins/playvideos/playvideos.php
index 2f4b0a31..64484504 100644
--- a/plugins/playvideos/playvideos.php
+++ b/plugins/playvideos/playvideos.php
@@ -17,9 +17,11 @@ function hook_playvideos_render_header($data)
{
if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) {
$playvideo = array(
- 'href' => '#',
- 'title' => 'Video player',
- 'id' => 'playvideos',
+ 'attr' => array(
+ 'href' => '#',
+ 'title' => 'Video player',
+ 'id' => 'playvideos',
+ ),
'html' => '⺠Play Videos'
);
$data['buttons_toolbar'][] = $playvideo;
diff --git a/plugins/qrcode/qrcode.html b/plugins/qrcode/qrcode.html
index cebc5644..dc214ed1 100644
--- a/plugins/qrcode/qrcode.html
+++ b/plugins/qrcode/qrcode.html
@@ -1,5 +1,5 @@
diff --git a/plugins/readityourself/readityourself.html b/plugins/readityourself/readityourself.html
index e8c5f784..5e200715 100644
--- a/plugins/readityourself/readityourself.html
+++ b/plugins/readityourself/readityourself.html
@@ -1 +1 @@
-
+
diff --git a/plugins/wallabag/wallabag.html b/plugins/wallabag/wallabag.html
index c7b1d044..e861536d 100644
--- a/plugins/wallabag/wallabag.html
+++ b/plugins/wallabag/wallabag.html
@@ -1 +1 @@
-
+
diff --git a/shaarli_version.php b/shaarli_version.php
index eaab95c6..431387bb 100644
--- a/shaarli_version.php
+++ b/shaarli_version.php
@@ -1 +1 @@
-
+
diff --git a/tests/.htaccess b/tests/.htaccess
index b584d98c..f601c1ee 100644
--- a/tests/.htaccess
+++ b/tests/.htaccess
@@ -1,2 +1,13 @@
-Allow from none
-Deny from all
+
+ = 2.4>
+ Require all denied
+
+
+ Allow from none
+ Deny from all
+
+
+
+
+ Require all denied
+
diff --git a/tests/FeedBuilderTest.php b/tests/FeedBuilderTest.php
index d7839402..06a44506 100644
--- a/tests/FeedBuilderTest.php
+++ b/tests/FeedBuilderTest.php
@@ -84,8 +84,9 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
// Test first link (note link)
- $link = array_shift($data['links']);
- $this->assertEquals('20150310_114651', $link['linkdate']);
+ $link = reset($data['links']);
+ $this->assertEquals(41, $link['id']);
+ $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
$this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
$this->assertEquals('http://host.tld/?WDWyig', $link['url']);
$this->assertRegExp('/Tue, 10 Mar 2015 11:46:51 \+\d{4}/', $link['pub_iso_date']);
@@ -99,14 +100,14 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
$this->assertEquals('sTuff', $link['taglist'][0]);
// Test URL with external link.
- $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links']['20150310_114633']['url']);
+ $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links'][8]['url']);
// Test multitags.
- $this->assertEquals(5, count($data['links']['20141125_084734']['taglist']));
- $this->assertEquals('css', $data['links']['20141125_084734']['taglist'][0]);
+ $this->assertEquals(5, count($data['links'][6]['taglist']));
+ $this->assertEquals('css', $data['links'][6]['taglist'][0]);
// Test update date
- $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links']['20150310_114633']['up_iso_date']);
+ $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
}
/**
@@ -119,9 +120,9 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
$data = $feedBuilder->buildData();
$this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
$this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['last_update']);
- $link = array_shift($data['links']);
+ $link = reset($data['links']);
$this->assertRegExp('/2015-03-10T11:46:51\+\d{2}:\d{2}/', $link['pub_iso_date']);
- $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links']['20150310_114633']['up_iso_date']);
+ $this->assertRegExp('/2016-08-03T09:30:33\+\d{2}:\d{2}/', $data['links'][8]['up_iso_date']);
}
/**
@@ -138,7 +139,8 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
$data = $feedBuilder->buildData();
$this->assertEquals(1, count($data['links']));
$link = array_shift($data['links']);
- $this->assertEquals('20150310_114651', $link['linkdate']);
+ $this->assertEquals(41, $link['id']);
+ $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
}
/**
@@ -154,7 +156,8 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
$data = $feedBuilder->buildData();
$this->assertEquals(1, count($data['links']));
$link = array_shift($data['links']);
- $this->assertEquals('20150310_114651', $link['linkdate']);
+ $this->assertEquals(41, $link['id']);
+ $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
}
/**
@@ -170,15 +173,17 @@ class FeedBuilderTest extends PHPUnit_Framework_TestCase
$this->assertTrue($data['usepermalinks']);
// First link is a permalink
$link = array_shift($data['links']);
- $this->assertEquals('20150310_114651', $link['linkdate']);
+ $this->assertEquals(41, $link['id']);
+ $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'), $link['created']);
$this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
$this->assertEquals('http://host.tld/?WDWyig', $link['url']);
$this->assertContains('Direct link', $link['description']);
$this->assertContains('http://host.tld/?WDWyig', $link['description']);
// Second link is a direct link
$link = array_shift($data['links']);
- $this->assertEquals('20150310_114633', $link['linkdate']);
- $this->assertEquals('http://host.tld/?kLHmZg', $link['guid']);
+ $this->assertEquals(8, $link['id']);
+ $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'), $link['created']);
+ $this->assertEquals('http://host.tld/?RttfEw', $link['guid']);
$this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']);
$this->assertContains('Direct link', $link['description']);
$this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']);
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index 9d79386c..1f62a34a 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -186,14 +186,15 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
$dbSize = sizeof($testDB);
$link = array(
+ 'id' => 42,
'title'=>'an additional link',
'url'=>'http://dum.my',
'description'=>'One more',
'private'=>0,
- 'linkdate'=>'20150518_190000',
+ 'created'=> DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150518_190000'),
'tags'=>'unit test'
);
- $testDB[$link['linkdate']] = $link;
+ $testDB[$link['id']] = $link;
$testDB->save('tests');
$testDB = new LinkDB(self::$testDatastore, true, false);
@@ -238,12 +239,12 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
public function testDays()
{
$this->assertEquals(
- array('20121206', '20130614', '20150310'),
+ array('20100310', '20121206', '20130614', '20150310'),
self::$publicLinkDB->days()
);
$this->assertEquals(
- array('20121206', '20130614', '20141125', '20150310'),
+ array('20100310', '20121206', '20130614', '20141125', '20150310'),
self::$privateLinkDB->days()
);
}
@@ -290,10 +291,11 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
'stallman' => 1,
'free' => 1,
'-exclude' => 1,
+ 'hashtag' => 2,
// The DB contains a link with `sTuff` and another one with `stuff` tag.
- // They need to be grouped with the first case found (`sTuff`).
+ // They need to be grouped with the first case found - order by date DESC: `sTuff`.
'sTuff' => 2,
- 'hashtag' => 2,
+ 'ut' => 1,
),
self::$publicLinkDB->allTags()
);
@@ -321,6 +323,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
'tag2' => 1,
'tag3' => 1,
'tag4' => 1,
+ 'ut' => 1,
),
self::$privateLinkDB->allTags()
);
@@ -411,6 +414,11 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
1,
count(self::$publicLinkDB->filterHash($request))
);
+ $request = smallHash('20150310_114633' . 8);
+ $this->assertEquals(
+ 1,
+ count(self::$publicLinkDB->filterHash($request))
+ );
}
/**
@@ -433,4 +441,23 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
{
self::$publicLinkDB->filterHash('');
}
+
+ /**
+ * Test reorder with asc/desc parameter.
+ */
+ public function testReorderLinksDesc()
+ {
+ self::$privateLinkDB->reorder('ASC');
+ $linkIds = array(42, 4, 1, 0, 7, 6, 8, 41);
+ $cpt = 0;
+ foreach (self::$privateLinkDB as $key => $value) {
+ $this->assertEquals($linkIds[$cpt++], $key);
+ }
+ self::$privateLinkDB->reorder('DESC');
+ $linkIds = array_reverse($linkIds);
+ $cpt = 0;
+ foreach (self::$privateLinkDB as $key => $value) {
+ $this->assertEquals($linkIds[$cpt++], $key);
+ }
+ }
}
diff --git a/tests/LinkFilterTest.php b/tests/LinkFilterTest.php
index 7d45fc59..21d680a5 100644
--- a/tests/LinkFilterTest.php
+++ b/tests/LinkFilterTest.php
@@ -159,7 +159,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'MediaGoblin',
- $links['20130614_184135']['title']
+ $links[7]['title']
);
}
@@ -286,7 +286,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
);
$this->assertEquals(
- 6,
+ 7,
count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution'))
);
}
@@ -346,7 +346,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
);
$this->assertEquals(
- 6,
+ 7,
count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free'))
);
}
diff --git a/tests/NetscapeBookmarkUtils/BookmarkExportTest.php b/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
index cc54ab9f..6a47bbb9 100644
--- a/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
+++ b/tests/NetscapeBookmarkUtils/BookmarkExportTest.php
@@ -50,7 +50,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all', false, '');
$this->assertEquals(self::$refDb->countLinks(), sizeof($links));
foreach ($links as $link) {
- $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
+ $date = $link['created'];
$this->assertEquals(
$date->getTimestamp(),
$link['timestamp']
@@ -70,7 +70,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private', false, '');
$this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links));
foreach ($links as $link) {
- $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
+ $date = $link['created'];
$this->assertEquals(
$date->getTimestamp(),
$link['timestamp']
@@ -90,7 +90,7 @@ class BookmarkExportTest extends PHPUnit_Framework_TestCase
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public', false, '');
$this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links));
foreach ($links as $link) {
- $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
+ $date = $link['created'];
$this->assertEquals(
$date->getTimestamp(),
$link['timestamp']
diff --git a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
index f0ad500f..0ca07eac 100644
--- a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
+++ b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
@@ -42,6 +42,18 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
*/
protected $pagecache = 'tests';
+ /**
+ * @var string Save the current timezone.
+ */
+ protected static $defaultTimeZone;
+
+ public static function setUpBeforeClass()
+ {
+ self::$defaultTimeZone = date_default_timezone_get();
+ // Timezone without DST for test consistency
+ date_default_timezone_set('Africa/Nairobi');
+ }
+
/**
* Resets test data before each test
*/
@@ -55,6 +67,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->linkDb = new LinkDB(self::$testDatastore, true, false);
}
+ public static function tearDownAfterClass()
+ {
+ date_default_timezone_set(self::$defaultTimeZone);
+ }
+
/**
* Attempt to import bookmarks from an empty file
*/
@@ -98,18 +115,19 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
array(
- 'linkdate' => '20160618_173944',
+ 'id' => 0,
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160618_203944'),
'title' => 'Hg Init a Mercurial tutorial by Joel Spolsky',
'url' => 'http://hginit.com/',
'description' => '',
'private' => 0,
- 'tags' => ''
+ 'tags' => '',
+ 'shorturl' => 'La37cg',
),
$this->linkDb->getLinkFromUrl('http://hginit.com/')
);
}
-
/**
* Import bookmarks nested in a folder hierarchy
*/
@@ -126,89 +144,105 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
array(
- 'linkdate' => '20160225_205541',
+ 'id' => 0,
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235541'),
'title' => 'Nested 1',
'url' => 'http://nest.ed/1',
'description' => '',
'private' => 0,
- 'tags' => 'tag1 tag2'
+ 'tags' => 'tag1 tag2',
+ 'shorturl' => 'KyDNKA',
),
$this->linkDb->getLinkFromUrl('http://nest.ed/1')
);
$this->assertEquals(
array(
- 'linkdate' => '20160225_205542',
+ 'id' => 1,
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235542'),
'title' => 'Nested 1-1',
'url' => 'http://nest.ed/1-1',
'description' => '',
'private' => 0,
- 'tags' => 'folder1 tag1 tag2'
+ 'tags' => 'folder1 tag1 tag2',
+ 'shorturl' => 'T2LnXg',
),
$this->linkDb->getLinkFromUrl('http://nest.ed/1-1')
);
$this->assertEquals(
array(
- 'linkdate' => '20160225_205547',
+ 'id' => 2,
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235547'),
'title' => 'Nested 1-2',
'url' => 'http://nest.ed/1-2',
'description' => '',
'private' => 0,
- 'tags' => 'folder1 tag3 tag4'
+ 'tags' => 'folder1 tag3 tag4',
+ 'shorturl' => '46SZxA',
),
$this->linkDb->getLinkFromUrl('http://nest.ed/1-2')
);
$this->assertEquals(
array(
- 'linkdate' => '20160202_172222',
+ 'id' => 3,
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'),
'title' => 'Nested 2-1',
'url' => 'http://nest.ed/2-1',
'description' => 'First link of the second section',
'private' => 1,
- 'tags' => 'folder2'
+ 'tags' => 'folder2',
+ 'shorturl' => '4UHOSw',
),
$this->linkDb->getLinkFromUrl('http://nest.ed/2-1')
);
$this->assertEquals(
array(
- 'linkdate' => '20160119_200227',
+ 'id' => 4,
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'),
'title' => 'Nested 2-2',
'url' => 'http://nest.ed/2-2',
'description' => 'Second link of the second section',
'private' => 1,
- 'tags' => 'folder2'
+ 'tags' => 'folder2',
+ 'shorturl' => 'yfzwbw',
),
$this->linkDb->getLinkFromUrl('http://nest.ed/2-2')
);
$this->assertEquals(
array(
- 'linkdate' => '20160202_172223',
+ 'id' => 5,
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160202_202222'),
'title' => 'Nested 3-1',
'url' => 'http://nest.ed/3-1',
'description' => '',
'private' => 0,
- 'tags' => 'folder3 folder3-1 tag3'
+ 'tags' => 'folder3 folder3-1 tag3',
+ 'shorturl' => 'UwxIUQ',
),
$this->linkDb->getLinkFromUrl('http://nest.ed/3-1')
);
$this->assertEquals(
array(
- 'linkdate' => '20160119_200228',
+ 'id' => 6,
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160119_230227'),
'title' => 'Nested 3-2',
'url' => 'http://nest.ed/3-2',
'description' => '',
'private' => 0,
- 'tags' => 'folder3 folder3-1'
+ 'tags' => 'folder3 folder3-1',
+ 'shorturl' => 'p8dyZg',
),
$this->linkDb->getLinkFromUrl('http://nest.ed/3-2')
);
$this->assertEquals(
array(
- 'linkdate' => '20160229_081541',
+ 'id' => 7,
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160229_111541'),
'title' => 'Nested 2',
'url' => 'http://nest.ed/2',
'description' => '',
'private' => 0,
- 'tags' => 'tag4'
+ 'tags' => 'tag4',
+ 'shorturl' => 'Gt3Uug',
),
$this->linkDb->getLinkFromUrl('http://nest.ed/2')
);
@@ -227,28 +261,34 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
.' 2 links imported, 0 links overwritten, 0 links skipped.',
NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->pagecache)
);
+
$this->assertEquals(2, count($this->linkDb));
$this->assertEquals(1, count_private($this->linkDb));
$this->assertEquals(
array(
- 'linkdate' => '20001010_105536',
+ 'id' => 0,
+ // Old link - UTC+4 (note that TZ in the import file is ignored).
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20001010_135536'),
'title' => 'Secret stuff',
'url' => 'https://private.tld',
'description' => "Super-secret stuff you're not supposed to know about",
'private' => 1,
- 'tags' => 'private secret'
+ 'tags' => 'private secret',
+ 'shorturl' => 'EokDtA',
),
$this->linkDb->getLinkFromUrl('https://private.tld')
);
$this->assertEquals(
array(
- 'linkdate' => '20160225_205548',
+ 'id' => 1,
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'),
'title' => 'Public stuff',
'url' => 'http://public.tld',
'description' => '',
'private' => 0,
- 'tags' => 'public hello world'
+ 'tags' => 'public hello world',
+ 'shorturl' => 'Er9ddA',
),
$this->linkDb->getLinkFromUrl('http://public.tld')
);
@@ -271,23 +311,28 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
array(
- 'linkdate' => '20001010_105536',
+ 'id' => 0,
+ // Note that TZ in the import file is ignored.
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20001010_135536'),
'title' => 'Secret stuff',
'url' => 'https://private.tld',
'description' => "Super-secret stuff you're not supposed to know about",
'private' => 1,
- 'tags' => 'private secret'
+ 'tags' => 'private secret',
+ 'shorturl' => 'EokDtA',
),
$this->linkDb->getLinkFromUrl('https://private.tld')
);
$this->assertEquals(
array(
- 'linkdate' => '20160225_205548',
+ 'id' => 1,
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160225_235548'),
'title' => 'Public stuff',
'url' => 'http://public.tld',
'description' => '',
'private' => 0,
- 'tags' => 'public hello world'
+ 'tags' => 'public hello world',
+ 'shorturl' => 'Er9ddA',
),
$this->linkDb->getLinkFromUrl('http://public.tld')
);
@@ -309,11 +354,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(0, count_private($this->linkDb));
$this->assertEquals(
0,
- $this->linkDb['20001010_105536']['private']
+ $this->linkDb[0]['private']
);
$this->assertEquals(
0,
- $this->linkDb['20160225_205548']['private']
+ $this->linkDb[1]['private']
);
}
@@ -333,11 +378,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(2, count_private($this->linkDb));
$this->assertEquals(
1,
- $this->linkDb['20001010_105536']['private']
+ $this->linkDb['0']['private']
);
$this->assertEquals(
1,
- $this->linkDb['20160225_205548']['private']
+ $this->linkDb['1']['private']
);
}
@@ -359,13 +404,12 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(2, count_private($this->linkDb));
$this->assertEquals(
1,
- $this->linkDb['20001010_105536']['private']
+ $this->linkDb[0]['private']
);
$this->assertEquals(
1,
- $this->linkDb['20160225_205548']['private']
+ $this->linkDb[1]['private']
);
-
// re-import as public, enable overwriting
$post = array(
'privacy' => 'public',
@@ -380,11 +424,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(0, count_private($this->linkDb));
$this->assertEquals(
0,
- $this->linkDb['20001010_105536']['private']
+ $this->linkDb[0]['private']
);
$this->assertEquals(
0,
- $this->linkDb['20160225_205548']['private']
+ $this->linkDb[1]['private']
);
}
@@ -406,11 +450,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(0, count_private($this->linkDb));
$this->assertEquals(
0,
- $this->linkDb['20001010_105536']['private']
+ $this->linkDb['0']['private']
);
$this->assertEquals(
0,
- $this->linkDb['20160225_205548']['private']
+ $this->linkDb['1']['private']
);
// re-import as private, enable overwriting
@@ -427,11 +471,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(2, count_private($this->linkDb));
$this->assertEquals(
1,
- $this->linkDb['20001010_105536']['private']
+ $this->linkDb['0']['private']
);
$this->assertEquals(
1,
- $this->linkDb['20160225_205548']['private']
+ $this->linkDb['1']['private']
);
}
@@ -480,11 +524,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(0, count_private($this->linkDb));
$this->assertEquals(
'tag1 tag2 tag3 private secret',
- $this->linkDb['20001010_105536']['tags']
+ $this->linkDb['0']['tags']
);
$this->assertEquals(
'tag1 tag2 tag3 public hello world',
- $this->linkDb['20160225_205548']['tags']
+ $this->linkDb['1']['tags']
);
}
@@ -507,16 +551,16 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(0, count_private($this->linkDb));
$this->assertEquals(
'tag1& tag2 "tag3" private secret',
- $this->linkDb['20001010_105536']['tags']
+ $this->linkDb['0']['tags']
);
$this->assertEquals(
'tag1& tag2 "tag3" public hello world',
- $this->linkDb['20160225_205548']['tags']
+ $this->linkDb['1']['tags']
);
}
/**
- * Ensure each imported bookmark has a unique linkdate
+ * Ensure each imported bookmark has a unique id
*
* See https://github.com/shaarli/Shaarli/issues/351
*/
@@ -531,16 +575,16 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(3, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb));
$this->assertEquals(
- '20160225_205548',
- $this->linkDb['20160225_205548']['linkdate']
+ 0,
+ $this->linkDb[0]['id']
);
$this->assertEquals(
- '20160225_205549',
- $this->linkDb['20160225_205549']['linkdate']
+ 1,
+ $this->linkDb[1]['id']
);
$this->assertEquals(
- '20160225_205550',
- $this->linkDb['20160225_205550']['linkdate']
+ 2,
+ $this->linkDb[2]['id']
);
}
}
diff --git a/tests/Updater/UpdaterTest.php b/tests/Updater/UpdaterTest.php
index 0d0ad922..4948fe52 100644
--- a/tests/Updater/UpdaterTest.php
+++ b/tests/Updater/UpdaterTest.php
@@ -214,6 +214,7 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
$refDB = new ReferenceLinkDB();
$refDB->write(self::$testDatastore);
$linkDB = new LinkDB(self::$testDatastore, true, false);
+
$this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
$updater = new Updater(array(), $linkDB, $this->conf, true);
$updater->updateMethodRenameDashTags();
@@ -287,4 +288,101 @@ $GLOBALS[\'privateLinkByDefault\'] = true;';
$this->assertEquals(escape($redirectorUrl), $this->conf->get('redirector.url'));
unlink($sandbox .'.json.php');
}
+
+ /**
+ * Test updateMethodDatastoreIds().
+ */
+ public function testDatastoreIds()
+ {
+ $links = array(
+ '20121206_182539' => array(
+ 'linkdate' => '20121206_182539',
+ 'title' => 'Geek and Poke',
+ 'url' => 'http://geek-and-poke.com/',
+ 'description' => 'desc',
+ 'tags' => 'dev cartoon tag1 tag2 tag3 tag4 ',
+ 'updated' => '20121206_190301',
+ 'private' => false,
+ ),
+ '20121206_172539' => array(
+ 'linkdate' => '20121206_172539',
+ 'title' => 'UserFriendly - Samba',
+ 'url' => 'http://ars.userfriendly.org/cartoons/?id=20010306',
+ 'description' => '',
+ 'tags' => 'samba cartoon web',
+ 'private' => false,
+ ),
+ '20121206_142300' => array(
+ 'linkdate' => '20121206_142300',
+ 'title' => 'UserFriendly - Web Designer',
+ 'url' => 'http://ars.userfriendly.org/cartoons/?id=20121206',
+ 'description' => 'Naming conventions... #private',
+ 'tags' => 'samba cartoon web',
+ 'private' => true,
+ ),
+ );
+ $refDB = new ReferenceLinkDB();
+ $refDB->setLinks($links);
+ $refDB->write(self::$testDatastore);
+ $linkDB = new LinkDB(self::$testDatastore, true, false);
+
+ $checksum = hash_file('sha1', self::$testDatastore);
+
+ $this->conf->set('resource.data_dir', 'sandbox');
+ $this->conf->set('resource.datastore', self::$testDatastore);
+
+ $updater = new Updater(array(), $linkDB, $this->conf, true);
+ $this->assertTrue($updater->updateMethodDatastoreIds());
+
+ $linkDB = new LinkDB(self::$testDatastore, true, false);
+
+ $backup = glob($this->conf->get('resource.data_dir') . '/datastore.'. date('YmdH') .'*.php');
+ $backup = $backup[0];
+
+ $this->assertFileExists($backup);
+ $this->assertEquals($checksum, hash_file('sha1', $backup));
+ unlink($backup);
+
+ $this->assertEquals(3, count($linkDB));
+ $this->assertTrue(isset($linkDB[0]));
+ $this->assertFalse(isset($linkDB[0]['linkdate']));
+ $this->assertEquals(0, $linkDB[0]['id']);
+ $this->assertEquals('UserFriendly - Web Designer', $linkDB[0]['title']);
+ $this->assertEquals('http://ars.userfriendly.org/cartoons/?id=20121206', $linkDB[0]['url']);
+ $this->assertEquals('Naming conventions... #private', $linkDB[0]['description']);
+ $this->assertEquals('samba cartoon web', $linkDB[0]['tags']);
+ $this->assertTrue($linkDB[0]['private']);
+ $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'), $linkDB[0]['created']);
+
+ $this->assertTrue(isset($linkDB[1]));
+ $this->assertFalse(isset($linkDB[1]['linkdate']));
+ $this->assertEquals(1, $linkDB[1]['id']);
+ $this->assertEquals('UserFriendly - Samba', $linkDB[1]['title']);
+ $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'), $linkDB[1]['created']);
+
+ $this->assertTrue(isset($linkDB[2]));
+ $this->assertFalse(isset($linkDB[2]['linkdate']));
+ $this->assertEquals(2, $linkDB[2]['id']);
+ $this->assertEquals('Geek and Poke', $linkDB[2]['title']);
+ $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'), $linkDB[2]['created']);
+ $this->assertEquals(DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_190301'), $linkDB[2]['updated']);
+ }
+
+ /**
+ * Test updateMethodDatastoreIds() with the update already applied: nothing to do.
+ */
+ public function testDatastoreIdsNothingToDo()
+ {
+ $refDB = new ReferenceLinkDB();
+ $refDB->write(self::$testDatastore);
+ $linkDB = new LinkDB(self::$testDatastore, true, false);
+
+ $this->conf->set('resource.data_dir', 'sandbox');
+ $this->conf->set('resource.datastore', self::$testDatastore);
+
+ $checksum = hash_file('sha1', self::$testDatastore);
+ $updater = new Updater(array(), $linkDB, $this->conf, true);
+ $this->assertTrue($updater->updateMethodDatastoreIds());
+ $this->assertEquals($checksum, hash_file('sha1', self::$testDatastore));
+ }
}
diff --git a/tests/plugins/PluginIssoTest.php b/tests/plugins/PluginIssoTest.php
index 1f545c7d..6b7904dd 100644
--- a/tests/plugins/PluginIssoTest.php
+++ b/tests/plugins/PluginIssoTest.php
@@ -47,12 +47,14 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
$conf->set('plugins.ISSO_SERVER', 'value');
$str = 'http://randomstr.com/test';
+ $date = '20161118_100001';
$data = array(
'title' => $str,
'links' => array(
array(
+ 'id' => 12,
'url' => $str,
- 'linkdate' => 'abc',
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
)
)
);
@@ -65,7 +67,14 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
// plugin data
$this->assertEquals(1, count($data['plugin_end_zone']));
- $this->assertNotFalse(strpos($data['plugin_end_zone'][0], 'abc'));
+ $this->assertNotFalse(strpos(
+ $data['plugin_end_zone'][0],
+ 'data-isso-id="'. $data['links'][0]['id'] .'"'
+ ));
+ $this->assertNotFalse(strpos(
+ $data['plugin_end_zone'][0],
+ 'data-title="'. $data['links'][0]['id'] .'"'
+ ));
$this->assertNotFalse(strpos($data['plugin_end_zone'][0], 'embed.min.js'));
}
@@ -78,16 +87,20 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
$conf->set('plugins.ISSO_SERVER', 'value');
$str = 'http://randomstr.com/test';
+ $date1 = '20161118_100001';
+ $date2 = '20161118_100002';
$data = array(
'title' => $str,
'links' => array(
array(
+ 'id' => 12,
'url' => $str,
- 'linkdate' => 'abc',
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date1),
),
array(
+ 'id' => 13,
'url' => $str . '2',
- 'linkdate' => 'abc2',
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date2),
),
)
);
@@ -106,12 +119,14 @@ class PluginIssoTest extends PHPUnit_Framework_TestCase
$conf->set('plugins.ISSO_SERVER', 'value');
$str = 'http://randomstr.com/test';
+ $date = '20161118_100001';
$data = array(
'title' => $str,
'links' => array(
array(
+ 'id' => 12,
'url' => $str,
- 'linkdate' => 'abc',
+ 'created' => DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $date),
)
),
'search_term' => $str
diff --git a/tests/plugins/PluginMarkdownTest.php b/tests/plugins/PluginMarkdownTest.php
index 12bdda24..4a67b2dc 100644
--- a/tests/plugins/PluginMarkdownTest.php
+++ b/tests/plugins/PluginMarkdownTest.php
@@ -8,8 +8,8 @@ require_once 'application/Utils.php';
require_once 'plugins/markdown/markdown.php';
/**
- * Class PlugQrcodeTest
- * Unit test for the QR-Code plugin
+ * Class PluginMarkdownTest
+ * Unit test for the Markdown plugin
*/
class PluginMarkdownTest extends PHPUnit_Framework_TestCase
{
@@ -130,8 +130,11 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
))
);
- $data = hook_markdown_render_linklist($data);
- $this->assertEquals($str, $data['links'][0]['description']);
+ $processed = hook_markdown_render_linklist($data);
+ $this->assertEquals($str, $processed['links'][0]['description']);
+
+ $processed = hook_markdown_render_feed($data);
+ $this->assertEquals($str, $processed['links'][0]['description']);
$data = array(
// Columns data
@@ -152,6 +155,37 @@ class PluginMarkdownTest extends PHPUnit_Framework_TestCase
$this->assertEquals($str, $data['cols'][0][0]['formatedDescription']);
}
+ /**
+ * Test that a close value to nomarkdown is not understand as nomarkdown (previous value `.nomarkdown`).
+ */
+ function testNoMarkdownNotExcactlyMatching()
+ {
+ $str = 'All _work_ and `no play` makes Jack a *dull* boy.';
+ $data = array(
+ 'links' => array(array(
+ 'description' => $str,
+ 'tags' => '.' . NO_MD_TAG,
+ 'taglist' => array('.'. NO_MD_TAG),
+ ))
+ );
+
+ $data = hook_markdown_render_feed($data);
+ $this->assertContains('', $data['links'][0]['description']);
+ }
+
+ /**
+ * Test hashtag links processed with markdown.
+ */
+ function testMarkdownHashtagLinks()
+ {
+ $md = file_get_contents('tests/plugins/resources/markdown.md');
+ $md = format_description($md);
+ $html = file_get_contents('tests/plugins/resources/markdown.html');
+
+ $data = process_markdown($md);
+ $this->assertEquals($html, $data);
+ }
+
/**
* Test hashtag links processed with markdown.
*/
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php
index abca4656..36d58c68 100644
--- a/tests/utils/ReferenceLinkDB.php
+++ b/tests/utils/ReferenceLinkDB.php
@@ -4,7 +4,7 @@
*/
class ReferenceLinkDB
{
- public static $NB_LINKS_TOTAL = 7;
+ public static $NB_LINKS_TOTAL = 8;
private $_links = array();
private $_publicCount = 0;
@@ -16,66 +16,87 @@ class ReferenceLinkDB
public function __construct()
{
$this->addLink(
+ 41,
'Link title: @website',
'?WDWyig',
'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag',
0,
- '20150310_114651',
- 'sTuff'
+ DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114651'),
+ 'sTuff',
+ null,
+ 'WDWyig'
);
$this->addLink(
+ 42,
+ 'Note: I have a big ID but an old date',
+ '?WDWyig',
+ 'Used to test links reordering.',
+ 0,
+ DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20100310_101010'),
+ 'ut'
+ );
+
+ $this->addLink(
+ 8,
'Free as in Freedom 2.0 @website',
'https://static.fsf.org/nosvn/faif-2.0.pdf',
'Richard Stallman and the Free Software Revolution. Read this. #hashtag',
0,
- '20150310_114633',
+ DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20150310_114633'),
'free gnu software stallman -exclude stuff hashtag',
- '20160803_093033'
+ DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20160803_093033')
);
$this->addLink(
+ 7,
'MediaGoblin',
'http://mediagoblin.org/',
'A free software media publishing platform #hashtagOther',
0,
- '20130614_184135',
- 'gnu media web .hidden hashtag'
+ DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
+ 'gnu media web .hidden hashtag',
+ null,
+ 'IuWvgA'
);
$this->addLink(
+ 6,
'w3c-markup-validator',
'https://dvcs.w3.org/hg/markup-validator/summary',
'Mercurial repository for the W3C Validator #private',
1,
- '20141125_084734',
+ DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20141125_084734'),
'css html w3c web Mercurial'
);
$this->addLink(
+ 4,
'UserFriendly - Web Designer',
'http://ars.userfriendly.org/cartoons/?id=20121206',
'Naming conventions... #private',
0,
- '20121206_142300',
+ DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_142300'),
'dev cartoon web'
);
$this->addLink(
+ 1,
'UserFriendly - Samba',
'http://ars.userfriendly.org/cartoons/?id=20010306',
'Tropical printing',
0,
- '20121206_172539',
+ DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_172539'),
'samba cartoon web'
);
$this->addLink(
+ 0,
'Geek and Poke',
'http://geek-and-poke.com/',
'',
1,
- '20121206_182539',
+ DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_182539'),
'dev cartoon tag1 tag2 tag3 tag4 '
);
}
@@ -83,18 +104,20 @@ class ReferenceLinkDB
/**
* Adds a new link
*/
- protected function addLink($title, $url, $description, $private, $date, $tags, $updated = '')
+ protected function addLink($id, $title, $url, $description, $private, $date, $tags, $updated = '', $shorturl = '')
{
$link = array(
+ 'id' => $id,
'title' => $title,
'url' => $url,
'description' => $description,
'private' => $private,
- 'linkdate' => $date,
'tags' => $tags,
+ 'created' => $date,
'updated' => $updated,
+ 'shorturl' => $shorturl ? $shorturl : smallHash($date->format(LinkDB::LINK_DATE_FORMAT) . $id),
);
- $this->_links[$date] = $link;
+ $this->_links[$id] = $link;
if ($private) {
$this->_privateCount++;
@@ -142,4 +165,14 @@ class ReferenceLinkDB
{
return $this->_links;
}
+
+ /**
+ * Setter to override link creation.
+ *
+ * @param array $links List of links.
+ */
+ public function setLinks($links)
+ {
+ $this->_links = $links;
+ }
}
diff --git a/tmp/.htaccess b/tmp/.htaccess
index b584d98c..f601c1ee 100644
--- a/tmp/.htaccess
+++ b/tmp/.htaccess
@@ -1,2 +1,13 @@
-Allow from none
-Deny from all
+
+ = 2.4>
+ Require all denied
+
+
+ Allow from none
+ Deny from all
+
+
+
+
+ Require all denied
+
diff --git a/tpl/daily.html b/tpl/daily.html
index b82ad483..eba0af3b 100644
--- a/tpl/daily.html
+++ b/tpl/daily.html
@@ -49,13 +49,13 @@
{$link=$value}
-
+
{if="!$hide_timestamps || isLoggedIn()"}
{/if}
{if="$link.tags"}
diff --git a/tpl/editlink.html b/tpl/editlink.html
index 441b5302..870cc168 100644
--- a/tpl/editlink.html
+++ b/tpl/editlink.html
@@ -8,13 +8,18 @@
{elseif="$link.description==''"}onload="document.linkform.lf_description.focus();"
{else}onload="document.linkform.lf_tags.focus();"{/if} >
- {if="$source !== 'firefoxsocialapi'"}
- {include="page.header"}
- {/if}
-
-
+
{if="$source !== 'firefoxsocialapi'"}
{include="page.footer"}
diff --git a/tpl/feed.atom.html b/tpl/feed.atom.html
index 40fd421a..aead0459 100644
--- a/tpl/feed.atom.html
+++ b/tpl/feed.atom.html
@@ -30,9 +30,7 @@
{$value.pub_iso_date}
{$value.up_iso_date}
{/if}
-
-
-
+
{loop="$value.taglist"}
{/loop}
diff --git a/tpl/includes.html b/tpl/includes.html
index f94ce1be..7b2997ce 100644
--- a/tpl/includes.html
+++ b/tpl/includes.html
@@ -2,6 +2,7 @@
+
@@ -11,4 +12,4 @@
{loop="$plugins_includes.css_files"}
{/loop}
-
\ No newline at end of file
+
diff --git a/tpl/linklist.html b/tpl/linklist.html
index 70c9cf79..0f1a5e8c 100644
--- a/tpl/linklist.html
+++ b/tpl/linklist.html
@@ -29,10 +29,8 @@
{loop="$plugins_header.fields_toolbar"}
{loop="$value.inputs"}
-
+
-
+
+
-
+
@@ -102,7 +101,7 @@
{if="!$hide_timestamps || isLoggedIn()"}
{$updated=$value.updated_timestamp ? 'Edited: '. strftime('%c', $value.updated_timestamp) : 'Permalink'}
-
+
{function="strftime('%c', $value.timestamp)"}
{if="$value.updated_timestamp"}*{/if}
diff --git a/tpl/linklist.paging.html b/tpl/linklist.paging.html
index 42f58d0d..86019c01 100644
--- a/tpl/linklist.paging.html
+++ b/tpl/linklist.paging.html
@@ -15,10 +15,8 @@
{loop="$action_plugin"}
{$value.html}
diff --git a/tpl/loginform.html b/tpl/loginform.html
index a49b42d3..84176385 100644
--- a/tpl/loginform.html
+++ b/tpl/loginform.html
@@ -2,7 +2,7 @@
{include="includes"}
- {if="!ban_canLogin()"}
+ {if="!ban_canLogin($conf)"}
You have been banned from login after too many failed attempts. Try later.
{else}
diff --git a/tpl/page.header.html b/tpl/page.header.html
index 89879678..cce61ec4 100644
--- a/tpl/page.header.html
+++ b/tpl/page.header.html
@@ -36,10 +36,8 @@
Daily
{loop="$plugins_header.buttons_toolbar"}
{$value.html}
diff --git a/tpl/pluginsadmin.html b/tpl/pluginsadmin.html
index 672f4993..ead1734e 100644
--- a/tpl/pluginsadmin.html
+++ b/tpl/pluginsadmin.html
@@ -38,11 +38,11 @@
-
â²
-
â¼
diff --git a/tpl/tools.html b/tpl/tools.html
index 8e285f44..e06d239d 100644
--- a/tpl/tools.html
+++ b/tpl/tools.html
@@ -50,12 +50,15 @@
Then click "âAdd Note" button anytime to start composing a private Note (text post) to your Shaarli.
+
+ {if="$sslenabled"}
âAdd to Firefox social
⇐ Click on this button to add Shaarli to the "Share this page" button in Firefox.
+ {/if}
{loop="$tools_plugin"}
{$value}
@@ -64,6 +67,7 @@