// Link date storage format
const LINK_DATE_FORMAT = 'Ymd_His';
- // Datastore PHP prefix
- protected static $phpPrefix = '<?php /* ';
-
- // Datastore PHP suffix
- protected static $phpSuffix = ' */ ?>';
-
// List of links (associative array)
// - key: link date (e.g. "20110823_124546"),
// - value: associative array (keys: title, description...)
if (!isset($value['id']) || empty($value['url'])) {
die('Internal Error: A link should always have an id and URL.');
}
- if ((! empty($offset) && ! is_int($offset)) || ! is_int($value['id'])) {
+ if (($offset !== null && ! is_int($offset)) || ! is_int($value['id'])) {
die('You must specify an integer as a key.');
}
- if (! empty($offset) && $offset !== $value['id']) {
+ if ($offset !== null && $offset !== $value['id']) {
die('Array offset and link ID must be equal.');
}
return;
}
- // Read data
- // Note that gzinflate is faster than gzuncompress.
- // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
- $this->links = array();
-
- if (file_exists($this->datastore)) {
- $this->links = unserialize(gzinflate(base64_decode(
- substr(file_get_contents($this->datastore),
- strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
- }
+ $this->links = FileUtils::readFlatDB($this->datastore, []);
$toremove = array();
foreach ($this->links as $key => &$link) {
*/
private function write()
{
- if (is_file($this->datastore) && !is_writeable($this->datastore)) {
- // The datastore exists but is not writeable
- throw new IOException($this->datastore);
- } else if (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) {
- // The datastore does not exist and its parent directory is not writeable
- throw new IOException(dirname($this->datastore));
- }
-
- file_put_contents(
- $this->datastore,
- self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix
- );
-
+ FileUtils::writeFlatDB($this->datastore, $this->links);
}
/**
public function filterSearch($filterRequest = array(), $casesensitive = false, $visibility = 'all')
{
// Filter link database according to parameters.
- $searchtags = !empty($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
- $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
+ $searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
+ $searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
- // Search tags + fullsearch.
- if (! empty($searchtags) && ! empty($searchterm)) {
- $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT;
- $request = array($searchtags, $searchterm);
- }
- // Search by tags.
- elseif (! empty($searchtags)) {
- $type = LinkFilter::$FILTER_TAG;
- $request = $searchtags;
- }
- // Fulltext search.
- elseif (! empty($searchterm)) {
- $type = LinkFilter::$FILTER_TEXT;
- $request = $searchterm;
- }
- // Otherwise, display without filtering.
- else {
- $type = '';
- $request = '';
- }
+ // Search tags + fullsearch - blank string parameter will return all links.
+ $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT;
+ $request = [$searchtags, $searchterm];
$linkFilter = new LinkFilter($this);
return $linkFilter->filter($type, $request, $casesensitive, $visibility);
}
/**
- * Returns the list of all tags
- * Output: associative array key=tags, value=0
+ * Returns the list tags appearing in the links with the given tags
+ * @param $filteringTags: tags selecting the links to consider
+ * @param $visibility: process only all/private/public links
+ * @return: a tag=>linksCount array
*/
- public function allTags()
+ public function linksCountPerTag($filteringTags = [], $visibility = 'all')
{
+ $links = empty($filteringTags) ? $this->links : $this->filterSearch(['searchtags' => $filteringTags], false, $visibility);
$tags = array();
$caseMapping = array();
- foreach ($this->links as $link) {
+ foreach ($links as $link) {
foreach (preg_split('/\s+/', $link['tags'], 0, PREG_SPLIT_NO_EMPTY) as $tag) {
if (empty($tag)) {
continue;
*/
function escape($input)
{
+ if (is_bool($input)) {
+ return $input;
+ }
+
if (is_array($input)) {
$out = array();
foreach($input as $key => $value) {
* otherwise default format '%c' will be returned.
*
* @param DateTime $date to format.
+ * @param bool $time Displays time if true.
* @param bool $intl Use international format if true.
*
* @return bool|string Formatted date, or false if the input is invalid.
*/
-function format_date($date, $intl = true)
+function format_date($date, $time = true, $intl = true)
{
if (! $date instanceof DateTime) {
return false;
}
if (! $intl || ! class_exists('IntlDateFormatter')) {
- return strftime('%c', $date->getTimestamp());
+ $format = $time ? '%c' : '%x';
+ return strftime($format, $date->getTimestamp());
}
$formatter = new IntlDateFormatter(
setlocale(LC_TIME, 0),
IntlDateFormatter::LONG,
- IntlDateFormatter::LONG
+ $time ? IntlDateFormatter::LONG : IntlDateFormatter::NONE
);
return $formatter->format($date);
}
+
+/**
+ * Check if the input is an integer, no matter its real type.
+ *
+ * PHP is a bit messy regarding this:
+ * - is_int returns false if the input is a string
+ * - ctype_digit returns false if the input is an integer or negative
+ *
+ * @param mixed $input value
+ *
+ * @return bool true if the input is an integer, false otherwise
+ */
+function is_integer_mixed($input)
+{
+ if (is_array($input) || is_bool($input) || is_object($input)) {
+ return false;
+ }
+ $input = strval($input);
+ return ctype_digit($input) || (startsWith($input, '-') && ctype_digit(substr($input, 1)));
+}
+
+/**
+ * Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes.
+ *
+ * @param string $val Size expressed in string.
+ *
+ * @return int Size expressed in bytes.
+ */
+function return_bytes($val)
+{
+ if (is_integer_mixed($val) || $val === '0' || empty($val)) {
+ return $val;
+ }
+ $val = trim($val);
+ $last = strtolower($val[strlen($val)-1]);
+ $val = intval(substr($val, 0, -1));
+ switch($last) {
+ case 'g': $val *= 1024;
+ case 'm': $val *= 1024;
+ case 'k': $val *= 1024;
+ }
+ return $val;
+}
+
+/**
+ * Return a human readable size from bytes.
+ *
+ * @param int $bytes value
+ *
+ * @return string Human readable size
+ */
+function human_bytes($bytes)
+{
+ if ($bytes === '') {
+ return t('Setting not set');
+ }
+ if (! is_integer_mixed($bytes)) {
+ return $bytes;
+ }
+ $bytes = intval($bytes);
+ if ($bytes === 0) {
+ return t('Unlimited');
+ }
+
+ $units = [t('B'), t('kiB'), t('MiB'), t('GiB')];
+ for ($i = 0; $i < count($units) && $bytes >= 1024; ++$i) {
+ $bytes /= 1024;
+ }
+
+ return round($bytes) . $units[$i];
+}
+
+/**
+ * Try to determine max file size for uploads (POST).
+ * Returns an integer (in bytes) or formatted depending on $format.
+ *
+ * @param mixed $limitPost post_max_size PHP setting
+ * @param mixed $limitUpload upload_max_filesize PHP setting
+ * @param bool $format Format max upload size to human readable size
+ *
+ * @return int|string max upload file size
+ */
+function get_max_upload_size($limitPost, $limitUpload, $format = true)
+{
+ $size1 = return_bytes($limitPost);
+ $size2 = return_bytes($limitUpload);
+ // Return the smaller of two:
+ $maxsize = min($size1, $size2);
+ return $format ? human_bytes($maxsize) : $maxsize;
+}
+
+/**
+ * Sort the given array alphabetically using php-intl if available.
+ * Case sensitive.
+ *
+ * Note: doesn't support multidimensional arrays
+ *
+ * @param array $data Input array, passed by reference
+ * @param bool $reverse Reverse sort if set to true
+ * @param bool $byKeys Sort the array by keys if set to true, by value otherwise.
+ */
+function alphabetical_sort(&$data, $reverse = false, $byKeys = false)
+{
+ $callback = function($a, $b) use ($reverse) {
+ // Collator is part of PHP intl.
+ if (class_exists('Collator')) {
+ $collator = new Collator(setlocale(LC_COLLATE, 0));
+ if (!intl_is_failure(intl_get_error_code())) {
+ return $collator->compare($a, $b) * ($reverse ? -1 : 1);
+ }
+ }
+
+ return strcasecmp($a, $b) * ($reverse ? -1 : 1);
+ };
+
+ if ($byKeys) {
+ uksort($data, $callback);
+ } else {
+ usort($data, $callback);
+ }
+}
require_once 'application/config/ConfigPlugin.php';
require_once 'application/FeedBuilder.php';
require_once 'application/FileUtils.php';
+require_once 'application/History.php';
require_once 'application/HttpUtils.php';
require_once 'application/Languages.php';
require_once 'application/LinkDB.php';
}
$userIsLoggedIn = setup_login_state($conf);
-/**
- * PubSubHubbub protocol support (if enabled) [UNTESTED]
- * (Source: http://aldarone.fr/les-flux-rss-shaarli-et-pubsubhubbub/ )
- *
- * @param ConfigManager $conf Configuration Manager instance.
- */
-function pubsubhub($conf)
-{
- $pshUrl = $conf->get('config.PUBSUBHUB_URL');
- if (!empty($pshUrl))
- {
- include_once './publisher.php';
- $p = new Publisher($pshUrl);
- $topic_url = array (
- index_url($_SERVER).'?do=atom',
- index_url($_SERVER).'?do=rss'
- );
- $p->publish_update($topic_url);
- }
-}
-
// ------------------------------------------------------------------------------------------
// Session management
}
}
-// ------------------------------------------------------------------------------------------
-// Misc utility functions:
-
-// Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes.
-function return_bytes($val)
-{
- $val = trim($val); $last=strtolower($val[strlen($val)-1]);
- switch($last)
- {
- case 'g': $val *= 1024;
- case 'm': $val *= 1024;
- case 'k': $val *= 1024;
- }
- return $val;
-}
-
-// Try to determine max file size for uploads (POST).
-// Returns an integer (in bytes)
-function getMaxFileSize()
-{
- $size1 = return_bytes(ini_get('post_max_size'));
- $size2 = return_bytes(ini_get('upload_max_filesize'));
- // Return the smaller of two:
- $maxsize = min($size1,$size2);
- // FIXME: Then convert back to readable notations ? (e.g. 2M instead of 2000000)
- return $maxsize;
-}
-
// ------------------------------------------------------------------------------------------
// Token management for XSRF protection
// Token should be used in any form which acts on data (create,update,delete,import...).
$dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000');
$data = array(
+ 'pagetitle' => $conf->get('general.title') .' - '. format_date($dayDate, false),
'linksToDisplay' => $linksToDisplay,
'cols' => $columns,
'day' => $dayDate->getTimestamp(),
+ 'dayDate' => $dayDate,
'previousday' => $previousday,
'nextday' => $nextday,
);
* @param PluginManager $pluginManager Plugin Manager instance,
* @param LinkDB $LINKSDB
*/
-function renderPage($conf, $pluginManager, $LINKSDB)
+function renderPage($conf, $pluginManager, $LINKSDB, $history)
{
$updater = new Updater(
read_updates_file($conf->get('resource.updates')),
die($e->getMessage());
}
- $PAGE = new PageBuilder($conf);
+ $PAGE = new PageBuilder($conf, $LINKSDB);
$PAGE->assign('linkcount', count($LINKSDB));
$PAGE->assign('privateLinkcount', count_private($LINKSDB));
$PAGE->assign('plugin_errors', $pluginManager->getErrors());
// -------- Tag cloud
if ($targetPage == Router::$PAGE_TAGCLOUD)
{
- $tags= $LINKSDB->allTags();
+ $visibility = ! empty($_SESSION['privateonly']) ? 'private' : 'all';
+ $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : [];
+ $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility);
// We sort tags alphabetically, then choose a font size according to count.
// First, find max value.
$maxcount = max($maxcount, $value);
}
- // Sort tags alphabetically: case insensitive, support locale if available.
- uksort($tags, function($a, $b) {
- // Collator is part of PHP intl.
- if (class_exists('Collator')) {
- $c = new Collator(setlocale(LC_COLLATE, 0));
- if (!intl_is_failure(intl_get_error_code())) {
- return $c->compare($a, $b);
- }
- }
- return strcasecmp($a, $b);
- });
+ alphabetical_sort($tags, true, true);
$tagList = array();
foreach($tags as $key => $value) {
}
$data = array(
+ 'search_tags' => implode(' ', $filteringTags),
'tags' => $tagList,
);
$pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn()));
$PAGE->assign($key, $value);
}
- $PAGE->renderPage('tagcloud');
+ $PAGE->renderPage('tag.cloud');
+ exit;
+ }
+
+ // -------- Tag cloud
+ if ($targetPage == Router::$PAGE_TAGLIST)
+ {
+ $visibility = ! empty($_SESSION['privateonly']) ? 'private' : 'all';
+ $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : [];
+ $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility);
+
+ if (! empty($_GET['sort']) && $_GET['sort'] === 'alpha') {
+ alphabetical_sort($tags, false, true);
+ }
+
+ $data = [
+ 'search_tags' => implode(' ', $filteringTags),
+ 'tags' => $tags,
+ ];
+ $pluginManager->executeHooks('render_taglist', $data, ['loggedin' => isLoggedIn()]);
+
+ foreach ($data as $key => $value) {
+ $PAGE->assign($key, $value);
+ }
+
+ $PAGE->renderPage('tag.list');
exit;
}
$conf->set('api.secret', escape($_POST['apiSecret']));
try {
$conf->write(isLoggedIn());
+ $history->updateSettings();
invalidateCaches($conf->get('resource.page_cache'));
}
catch(Exception $e) {
$PAGE->assign('theme', $conf->get('resource.theme'));
$PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl')));
$PAGE->assign('redirector', $conf->get('redirector.url'));
- list($timezone_form, $timezone_js) = generateTimeZoneForm($conf->get('general.timezone'));
- $PAGE->assign('timezone_form', $timezone_form);
- $PAGE->assign('timezone_js',$timezone_js);
+ list($continents, $cities) = generateTimeZoneData(
+ timezone_identifiers_list(),
+ $conf->get('general.timezone')
+ );
+ $PAGE->assign('continents', $continents);
+ $PAGE->assign('cities', $cities);
$PAGE->assign('private_links_default', $conf->get('privacy.default_private_links', false));
$PAGE->assign('session_protection_disabled', $conf->get('security.session_protection_disabled', false));
$PAGE->assign('enable_rss_permalinks', $conf->get('feed.rss_permalinks', false));
if ($targetPage == Router::$PAGE_CHANGETAG)
{
if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) {
- $PAGE->assign('tags', $LINKSDB->allTags());
+ $PAGE->assign('fromtag', ! empty($_GET['fromtag']) ? escape($_GET['fromtag']) : '');
$PAGE->renderPage('changetag');
exit;
}
unset($tags[array_search($needle,$tags)]); // Remove tag.
$value['tags']=trim(implode(' ',$tags));
$LINKSDB[$key]=$value;
+ $history->updateLink($LINKSDB[$key]);
}
$LINKSDB->save($conf->get('resource.page_cache'));
echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?do=changetag\';</script>';
$tags[array_search($needle, $tags)] = trim($_POST['totag']);
$value['tags'] = implode(' ', array_unique($tags));
$LINKSDB[$key] = $value;
+ $history->updateLink($LINKSDB[$key]);
}
$LINKSDB->save($conf->get('resource.page_cache')); // Save to disk.
echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode(escape($_POST['totag'])).'\';</script>';
$created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
$updated = new DateTime();
$shortUrl = $LINKSDB[$id]['shorturl'];
+ $new = false;
} else {
// New link
$created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
$updated = null;
$shortUrl = link_small_hash($created, $id);
+ $new = true;
}
// Remove multiple spaces.
$LINKSDB[$id] = $link;
$LINKSDB->save($conf->get('resource.page_cache'));
+ if ($new) {
+ $history->addLink($link);
+ } else {
+ $history->updateLink($link);
+ }
// If we are called from the bookmarklet, we must close the popup:
if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
// -------- User clicked the "Delete" button when editing a link: Delete link from database.
if ($targetPage == Router::$PAGE_DELETELINK)
{
- // We do not need to ask for confirmation:
- // - confirmation is handled by JavaScript
- // - we are protected from XSRF by the token.
-
if (! tokenOk($_GET['token'])) {
die('Wrong token.');
}
- $id = intval(escape($_GET['lf_linkdate']));
- $link = $LINKSDB[$id];
- $pluginManager->executeHooks('delete_link', $link);
- unset($LINKSDB[$id]);
+ if (strpos($_GET['lf_linkdate'], ' ') !== false) {
+ $ids = array_values(array_filter(preg_split('/\s+/', escape($_GET['lf_linkdate']))));
+ } else {
+ $ids = [$_GET['lf_linkdate']];
+ }
+ foreach ($ids as $id) {
+ $id = (int) escape($id);
+ $link = $LINKSDB[$id];
+ $pluginManager->executeHooks('delete_link', $link);
+ unset($LINKSDB[$id]);
+ }
$LINKSDB->save($conf->get('resource.page_cache')); // save to disk
+ $history->deleteLink($link);
// If we are called from the bookmarklet, we must close the popup:
if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
'link' => $link,
'link_is_new' => false,
'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
- 'tags' => $LINKSDB->allTags(),
+ 'tags' => $LINKSDB->linksCountPerTag(),
);
$pluginManager->executeHooks('render_editlink', $data);
'link_is_new' => $link_is_new,
'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
'source' => (isset($_GET['source']) ? $_GET['source'] : ''),
- 'tags' => $LINKSDB->allTags(),
+ 'tags' => $LINKSDB->linksCountPerTag(),
'default_private_links' => $conf->get('privacy.default_private_links', false),
);
$pluginManager->executeHooks('render_editlink', $data);
if (! isset($_POST['token']) || ! isset($_FILES['filetoupload'])) {
// Show import dialog
- $PAGE->assign('maxfilesize', getMaxFileSize());
+ $PAGE->assign(
+ 'maxfilesize',
+ get_max_upload_size(
+ ini_get('post_max_size'),
+ ini_get('upload_max_filesize'),
+ false
+ )
+ );
+ $PAGE->assign(
+ 'maxfilesizeHuman',
+ get_max_upload_size(
+ ini_get('post_max_size'),
+ ini_get('upload_max_filesize'),
+ true
+ )
+ );
$PAGE->renderPage('import');
exit;
}
// The file is too big or some form field may be missing.
echo '<script>alert("The file you are trying to upload is probably'
.' bigger than what this webserver can accept ('
- .getMaxFileSize().' bytes).'
+ .get_max_upload_size(ini_get('post_max_size'), ini_get('upload_max_filesize')).').'
.' Please upload in smaller chunks.");document.location=\'?do='
.Router::$PAGE_IMPORT .'\';</script>';
exit;
$_POST,
$_FILES,
$LINKSDB,
- $conf
+ $conf,
+ $history
);
echo '<script>alert("'.$status.'");document.location=\'?do='
.Router::$PAGE_IMPORT .'\';</script>';
$conf->set('general.enabled_plugins', save_plugin_config($_POST));
}
$conf->write(isLoggedIn());
+ $history->updateSettings();
}
catch (Exception $e) {
error_log(
exit;
}
+ // Get a fresh token
+ if ($targetPage == Router::$GET_TOKEN) {
+ header('Content-Type:text/plain');
+ echo getToken($conf);
+ exit;
+ }
+
// -------- Otherwise, simply display search form and links:
showLinkList($PAGE, $LINKSDB, $conf, $pluginManager);
exit;
function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
{
// Used in templates
- $searchtags = !empty($_GET['searchtags']) ? escape(normalize_spaces($_GET['searchtags'])) : '';
+ if (isset($_GET['searchtags'])) {
+ if (! empty($_GET['searchtags'])) {
+ $searchtags = escape(normalize_spaces($_GET['searchtags']));
+ } else {
+ $searchtags = false;
+ }
+ } else {
+ $searchtags = '';
+ }
$searchterm = !empty($_GET['searchterm']) ? escape(normalize_spaces($_GET['searchterm'])) : '';
// Smallhash filter
} else {
// Filter links according search parameters.
$visibility = ! empty($_SESSION['privateonly']) ? 'private' : 'all';
- $linksToDisplay = $LINKSDB->filterSearch($_GET, false, $visibility);
+ $request = [
+ 'searchtags' => $searchtags,
+ 'searchterm' => $searchterm,
+ ];
+ $linksToDisplay = $LINKSDB->filterSearch($request, false, $visibility);
}
// ---- Handle paging.
}
// Compute paging navigation
- $searchtagsUrl = empty($searchtags) ? '' : '&searchtags=' . urlencode($searchtags);
+ $searchtagsUrl = $searchtags === '' ? '' : '&searchtags=' . urlencode($searchtags);
$searchtermUrl = empty($searchterm) ? '' : '&searchterm=' . urlencode($searchterm);
$previous_page_url = '';
if ($i != count($keys)) {
'visibility' => ! empty($_SESSION['privateonly']) ? 'private' : '',
'redirector' => $conf->get('redirector.url'), // Optional redirector URL.
'links' => $linkDisp,
- 'tags' => $LINKSDB->allTags(),
);
// If there is only a single link, we change on-the-fly the title of the page.
exit;
}
- // Display config form:
- list($timezone_form, $timezone_js) = generateTimeZoneForm();
- $timezone_html = '';
- if ($timezone_form != '') {
- $timezone_html = '<tr><td><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>';
- }
-
$PAGE = new PageBuilder($conf);
- $PAGE->assign('timezone_html',$timezone_html);
- $PAGE->assign('timezone_js',$timezone_js);
+ list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get());
+ $PAGE->assign('continents', $continents);
+ $PAGE->assign('cities', $cities);
$PAGE->renderPage('install');
exit;
}
$conf->get('redirector.encode_url')
);
+try {
+ $history = new History($conf->get('resource.history'));
+} catch(Exception $e) {
+ die($e->getMessage());
+}
+
$container = new \Slim\Container();
$container['conf'] = $conf;
$container['plugins'] = $pluginManager;
+$container['history'] = $history;
$app = new \Slim\App($container);
// REST API routes
$app->group('/api/v1', function() {
- $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo');
- $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks');
- $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink');
+ $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo');
+ $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks')->setName('getLinks');
+ $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink')->setName('getLink');
+ $this->post('/links', '\Shaarli\Api\Controllers\Links:postLink')->setName('postLink');
+ $this->put('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:putLink')->setName('putLink');
+ $this->delete('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:deleteLink')->setName('deleteLink');
+ $this->get('/history', '\Shaarli\Api\Controllers\History:getHistory')->setName('getHistory');
})->add('\Shaarli\Api\ApiMiddleware');
$response = $app->run(true);
if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) {
// We use UTF-8 for proper international characters handling.
header('Content-Type: text/html; charset=utf-8');
- renderPage($conf, $pluginManager, $linkDb);
+ renderPage($conf, $pluginManager, $linkDb, $history);
} else {
$app->respond($response);
}