From: ArthurHoaro Date: Sun, 18 Dec 2016 11:40:27 +0000 (+0100) Subject: merge X-Git-Url: https://git.immae.eu/?a=commitdiff_plain;h=8147ff76a49206a08999f420206d971de10db12e;hp=9eba6ff469bdea8f341af06ef23c8bb4bd3a6869;p=github%2Fshaarli%2FShaarli.git merge --- diff --git a/.gitignore b/.gitignore index 095aaded..9121905d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ composer.lock vendor/ # Release archives -*.tar +*.tar.gz *.zip # Development and test resources diff --git a/.travis.yml b/.travis.yml index 9ffb3d00..6ff1b20f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ cache: directories: - $HOME/.composer/cache php: + - 7.1 - 7.0 - 5.6 - 5.5 diff --git a/CHANGELOG.md b/CHANGELOG.md index d42d6a75..21d5436c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,19 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [v0.8.1](https://github.com/shaarli/Shaarli/releases/tag/v0.8.1) - UNPUBLISHED +## [v0.9.0](https://github.com/shaarli/Shaarli/releases/tag/v0.9.0) - UNPUBLISHED + +### Added + +### Changed + +### Fixed + + +## [v0.8.1](https://github.com/shaarli/Shaarli/releases/tag/v0.8.1) - 2016-12-12 + +> Note: this version will create an automatic backup of your database if anything goes wrong. + ### Added - Add CHANGELOG.md to track the whole project's history - Enable Composer cache for Travis builds @@ -13,20 +25,39 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Plugins: - Add an [Isso](https://posativ.org/isso/) plugin to enable user comments on permalinks - Allow defining init functions, e.g. for performing checks and error processing - -### Changed -- Cleanup `{loop}` declarations in templates + - Add a Piwik plugin for analytics. + - Markdown: add warning notice regarding HTML rendering +- Meta tag to *not* send the referrer to external resources. + +### Changed +- Link ID complete refactoring: + - Links now have a numeric ID instead of dates + - Short URLs are now created once and can't change over time (previous URL are kept) +- Templates: + - Changed placeholder behaviour for: `buttons_toolbar`, `fields_toolbar` and `action_plugin` + - Cleanup `{loop}` declarations in templates + - Tools: hide Firefox Social button when not in HTTPS + - Firefox Social: show Shaarli's title when shaaring using Firefox Social - Release archives now have the same structure as GitHub-generated archives: - archives contain a `Shaarli` directory, itself containing sources + dependencies - the tarball is now gzipped +- Plugins: + - Markdown: Parsedown library is now imported through Composer - Minor code cleanup: PHPDoc, spelling, unused variables, etc. +- Docker: explicitly set the maximum file upload size to 10 MiB ### Fixed - Fix the server `` value in Atom/RSS feeds - Plugins: - Tools: only display parameter description when it exists - archive.org: do not propose archival of private notes + - Markdown: + - render links properly in code blocks + - bug regarding the `nomarkdown` tag + - W3C compliance - Use absolute URL for hashtags in RSS and ATOM feeds +- Docker: specify the location of the favicon +- ATOM feed: remove new line between content tag and data ### Security - Allow whitelisting trusted IPs, else continue banning clients upon login failure diff --git a/COPYING b/COPYING index 5a825402..d8241e50 100644 --- a/COPYING +++ b/COPYING @@ -72,6 +72,10 @@ Files: plugins/wallabag/wallabag.png License: MIT License (http://opensource.org/licenses/MIT) Copyright: (C) 2015 Nicolas Lœuillet - https://github.com/wallabag/wallabag +Files: plugins/markdown/Parsedown.php +License: MIT License (http://opensource.org/licenses/MIT) +Copyright: (C) 2015 Emanuil Rusev - https://github.com/erusev/parsedown + Files: tpl/default/img/sad_star.png License: MIT License (http://opensource.org/licenses/MIT) Copyright: (C) 2015 kalvn - https://github.com/kalvn/Shaarli-Material diff --git a/application/.htaccess b/application/.htaccess index b584d98c..f601c1ee 100644 --- a/application/.htaccess +++ b/application/.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/application/FeedBuilder.php b/application/FeedBuilder.php index 4036a7cc..b0aa5764 100644 --- a/application/FeedBuilder.php +++ b/application/FeedBuilder.php @@ -156,12 +156,12 @@ class FeedBuilder $link['description'] = format_description($link['description'], '', $pageaddr); $link['description'] .= PHP_EOL .'
— '. $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..php. + * + * LinkDB also adds the field 'shorturl' with the precedent format (linkdate smallhash), + * which will be saved by this method. + * + * @return bool true if the update is successful, false otherwise. + */ + public function updateMethodDatastoreIds() + { + // up to date database + if (isset($this->linkDB[0])) { + return true; + } + + $save = $this->conf->get('resource.data_dir') .'/datastore.'. date('YmdHis') .'.php'; + copy($this->conf->get('resource.datastore'), $save); + + $links = array(); + foreach ($this->linkDB as $offset => $value) { + $links[] = $value; + unset($this->linkDB[$offset]); + } + $links = array_reverse($links); + $cpt = 0; + foreach ($links as $l) { + unset($l['linkdate']); + $l['id'] = $cpt; + $this->linkDB[$cpt++] = $l; + } + + $this->linkDB->save($this->conf->get('resource.page_cache')); + $this->linkDB->reorder(); + + return true; + } } /** diff --git a/application/Utils.php b/application/Utils.php index 0166ee2a..0a5b476e 100644 --- a/application/Utils.php +++ b/application/Utils.php @@ -31,7 +31,11 @@ function logm($logFile, $clientIp, $message) * - are NOT cryptographically secure (they CAN be forged) * * In Shaarli, they are used as a tinyurl-like link to individual entries, - * e.g. smallHash('20111006_131924') --> yZH23w + * built once with the combination of the date and item ID. + * e.g. smallHash('20111006_131924' . 142) --> eaWxtQ + * + * @warning before v0.8.1, smallhashes were built only with the date, + * and their value has been preserved. * * @param string $text Create a hash from this text. * diff --git a/cache/.htaccess b/cache/.htaccess index b584d98c..f601c1ee 100644 --- a/cache/.htaccess +++ b/cache/.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/data/.htaccess b/data/.htaccess index b584d98c..f601c1ee 100644 --- a/data/.htaccess +++ b/data/.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/doc/Community-&-Related-software.html b/doc/Community-&-Related-software.html index accbacdc..cbc73d54 100644 --- a/doc/Community-&-Related-software.html +++ b/doc/Community-&-Related-software.html @@ -81,10 +81,11 @@

Themes

See Theming for the list of community-contributed themes, and an installation guide.

@@ -95,7 +96,7 @@
  • Shaarlo - An aggregator for shaarlis with many features (a very popular running instance among french shaarliers: shaarli.fr)
  • Shaarlimages - An image-oriented aggregator for Shaarlis
  • mknexen/shaarli-api - A REST API for Shaarli
  • -
  • Self dead link - Detect dead links on shaarli. This version use the database of shaarli. An another version, can be used for others shaarli (but use most ressources).
  • +
  • Self dead link - Detect dead links on shaarli. This version use the database of shaarli. Another version, can be used for other shaarli instances (but is more resource consuming).
  • 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

        Several releases are available:


        -

        Get the latest released version from the releases page.

        -

        The current latest released version is v0.7.0.

        Download as an archive

        -

        As a .zip 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/
        +

        Get the latest released version from the releases page.

        +

        Download our shaarli-full archive to include dependencies.

        +

        The current latest released version is v0.8.0

        +

        Or in command lines:

        +
        $ 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/
        @@ -126,6 +127,10 @@ $ mv Shaarli-v0.7.0 /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

        The stable version has been experienced by Shaarli users, and will receive security updates.

        diff --git a/doc/Download-and-Installation.md b/doc/Download-and-Installation.md index 77af25eb..32df8984 100644 --- a/doc/Download-and-Installation.md +++ b/doc/Download-and-Installation.md @@ -8,26 +8,31 @@ Several releases are available: -------------------------------------------------------- ## Latest release (recommended) - +### Download as an archive Get the latest released version from the [releases](https://github.com/shaarli/Shaarli/releases) page.[](.html) -The current latest released version is `v0.7.0`. +**Download our *shaarli-full* archive** to include dependencies. -### Download as an archive +The current latest released version is `v0.8.0` -As a .zip archive: +Or in command lines: ```bash -$ 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/ +$ 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/ ``` - | ! |In most cases, download Shaarli from the [releases](https://github.com/shaarli/Shaarli/releases) page. Cloning using `git` or downloading Github branches as zip files requires additional steps (see below).|[](.html) |-----|--------------------------| +### 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 +``` -------------------------------------------------------- diff --git a/doc/Release-Shaarli.html b/doc/Release-Shaarli.html index cdefd3d6..0d9fa3e1 100644 --- a/doc/Release-Shaarli.html +++ b/doc/Release-Shaarli.html @@ -115,9 +115,35 @@ releases.

      • origin pointing to your GitHub fork
      • upstream pointing to the main Shaarli repository
      -
    • maintainer permissions on the main Shaarli repository (to push the signed tag)
    • +
    • maintainer permissions on the main Shaarli repository, to: +
        +
      • push the signed tag
      • +
      • create a new release
      • +
    • Composer and Pandoc need to be installed
    +

    GitHub release draft and CHANGELOG.md

    +

    See http://keepachangelog.com/en/0.3.0/ for changelog formatting.

    +

    GitHub release draft

    +

    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:

    +
      +
    • add new entries (additions, fixes, etc.)
    • +
    • mark the current version as released by setting its date and link
    • +
    • add a new section for the future unreleased version
    • +
    +
    $ 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)

    Increment the version code, create and push a signed tag

    Bump Shaarli's version

    $ 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)
    -

    Generate and upload all-in-one release archives

    +

    Publish the GitHub release

    +

    Create a GitHub release from a Git tag

    +

    From the previously drafted release:

    +
      +
    • edit the release notes (if needed)
    • +
    • specify the appropriate Git tag
    • +
    • publish the release
    • +
    • profit!
    • +
    +

    Generate and upload all-in-one release archives

    Users with a shared hosting may have:

    • no SSH access
    • diff --git a/doc/Release-Shaarli.md b/doc/Release-Shaarli.md index 5cbcd79a..556a96ee 100644 --- a/doc/Release-Shaarli.md +++ b/doc/Release-Shaarli.md @@ -10,9 +10,39 @@ This guide assumes that you have: - a local clone of your Shaarli fork, with the following remotes: - `origin` pointing to your GitHub fork - `upstream` pointing to the main Shaarli repository -- maintainer permissions on the main Shaarli repository (to push the signed tag) +- maintainer permissions on the main Shaarli repository, to: + - push the signed tag + - create a new release - [Composer](https://getcomposer.org/) and [Pandoc](http://pandoc.org/) need to be installed[](.html) +## GitHub release draft and `CHANGELOG.md` +See http://keepachangelog.com/en/0.3.0/ for changelog formatting. + +### GitHub release draft +GitHub allows drafting the release note for the upcoming release, from the [Releases](https://github.com/shaarli/Shaarli/releases) page. This way, the release note can be drafted while contributions are merged to `master`.[](.html) + +### `CHANGELOG.md` +This file should contain the same information as the release note draft for the upcoming version. + +Update it to: +- add new entries (additions, fixes, etc.) +- mark the current version as released by setting its date and link +- add a new section for the future unreleased version + +```bash +$ 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) +``` + + ## Increment the version code, create and push a signed tag ### Bump Shaarli's version ```bash @@ -72,7 +102,15 @@ gpg: Signature made Thu 30 Jul 2015 11:46:34 CEST using RSA key ID 4100DF6F gpg: Good signature from "VirtualTam " [ultimate][](.html) ``` -## Generate and upload all-in-one release archives +## Publish the GitHub release +### Create a GitHub release from a Git tag +From the previously drafted release: +- edit the release notes (if needed) +- specify the appropriate Git tag +- publish the release +- profit! + +### Generate and upload all-in-one release archives Users with a shared hosting may have: - no SSH access - no possibility to install PHP packages or server extensions diff --git a/doc/Server-configuration.html b/doc/Server-configuration.html index 068900b8..2f1c25b5 100644 --- a/doc/Server-configuration.html +++ b/doc/Server-configuration.html @@ -193,6 +193,9 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf ErrorLog /var/log/apache2/shaarli-error.log CustomLog /var/log/apache2/shaarli-access.log combined </VirtualHost> +

      .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 to be installed and enabled.

      LightHttpd

      Nginx

      Foreword

      @@ -233,7 +236,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
    • 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,
    @@ -251,6 +254,24 @@ user john users; http { [...][](.html) } +

    (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:

    +
    # /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

    Minimal

    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

    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 @@ - +archive.org 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 @@ - +readityourself 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 @@ - +wallabag 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.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"}