X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=index.php;h=b53b16fefb383400f90eb1b99374b24c907daf68;hb=a69cfe0dd23fbd2e25c07ec92717998585a9560d;hp=0ef3363354e54f69a958092fafaf5d7ba619855f;hpb=4adeffd7f427580bba6f7656360850d4460c1f1d;p=github%2Fshaarli%2FShaarli.git diff --git a/index.php b/index.php index 0ef33633..b53b16fe 100644 --- a/index.php +++ b/index.php @@ -10,8 +10,6 @@ * - https://github.com/sebsauvage/Shaarli * * Licence: http://www.opensource.org/licenses/zlib-license.php - * - * Requires: PHP 5.5.x */ // Set 'UTC' as the default timezone if it is not defined in php.ini @@ -28,7 +26,7 @@ if (date_default_timezone_get() == '') { define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0))); // High execution time in case of problematic imports/exports. -ini_set('max_input_time','60'); +ini_set('max_input_time', '60'); // Try to set max upload file size and read ini_set('memory_limit', '128M'); @@ -37,9 +35,6 @@ ini_set('upload_max_filesize', '16M'); // See all error except warnings error_reporting(E_ALL^E_WARNING); -// See all errors (for debugging only) -//error_reporting(-1); - // 3rd-party libraries if (! file_exists(__DIR__ . '/vendor/autoload.php')) { @@ -56,36 +51,46 @@ require_once 'inc/rain.tpl.class.php'; require_once __DIR__ . '/vendor/autoload.php'; // Shaarli library -require_once 'application/ApplicationUtils.php'; -require_once 'application/Cache.php'; -require_once 'application/CachedPage.php'; +require_once 'application/bookmark/LinkUtils.php'; require_once 'application/config/ConfigPlugin.php'; -require_once 'application/FeedBuilder.php'; +require_once 'application/feed/Cache.php'; +require_once 'application/http/HttpUtils.php'; +require_once 'application/http/UrlUtils.php'; +require_once 'application/updater/UpdaterUtils.php'; require_once 'application/FileUtils.php'; -require_once 'application/History.php'; -require_once 'application/HttpUtils.php'; -require_once 'application/LinkDB.php'; -require_once 'application/LinkFilter.php'; -require_once 'application/LinkUtils.php'; -require_once 'application/NetscapeBookmarkUtils.php'; -require_once 'application/PageBuilder.php'; require_once 'application/TimeZone.php'; -require_once 'application/Url.php'; require_once 'application/Utils.php'; -require_once 'application/PluginManager.php'; -require_once 'application/Router.php'; -require_once 'application/Updater.php'; -use \Shaarli\Config\ConfigManager; -use \Shaarli\Languages; -use \Shaarli\Security\LoginManager; -use \Shaarli\Security\SessionManager; -use \Shaarli\ThemeUtils; -use \Shaarli\Thumbnailer; + +use Shaarli\ApplicationUtils; +use Shaarli\Bookmark\Bookmark; +use Shaarli\Bookmark\BookmarkFileService; +use Shaarli\Bookmark\BookmarkFilter; +use Shaarli\Bookmark\BookmarkServiceInterface; +use Shaarli\Bookmark\Exception\BookmarkNotFoundException; +use Shaarli\Config\ConfigManager; +use Shaarli\Container\ContainerBuilder; +use Shaarli\Feed\CachedPage; +use Shaarli\Feed\FeedBuilder; +use Shaarli\Formatter\BookmarkMarkdownFormatter; +use Shaarli\Formatter\FormatterFactory; +use Shaarli\History; +use Shaarli\Languages; +use Shaarli\Netscape\NetscapeBookmarkUtils; +use Shaarli\Plugin\PluginManager; +use Shaarli\Render\PageBuilder; +use Shaarli\Render\ThemeUtils; +use Shaarli\Router; +use Shaarli\Security\LoginManager; +use Shaarli\Security\SessionManager; +use Shaarli\Thumbnailer; +use Shaarli\Updater\Updater; +use Shaarli\Updater\UpdaterUtils; +use Slim\App; // Ensure the PHP version is supported try { - ApplicationUtils::checkPHPVersion('5.5', PHP_VERSION); -} catch(Exception $exc) { + ApplicationUtils::checkPHPVersion('7.1', PHP_VERSION); +} catch (Exception $exc) { header('Content-Type: text/plain; charset=utf-8'); echo $exc->getMessage(); exit; @@ -111,7 +116,7 @@ ini_set('session.use_trans_sid', false); session_name('shaarli'); // Start session if needed (Some server auto-start sessions). -if (session_id() == '') { +if (session_status() == PHP_SESSION_NONE) { session_start(); } @@ -122,8 +127,19 @@ if (isset($_COOKIE['shaarli']) && !SessionManager::checkId($_COOKIE['shaarli'])) } $conf = new ConfigManager(); + +// In dev mode, throw exception on any warning +if ($conf->get('dev.debug', false)) { + // See all errors (for debugging only) + error_reporting(-1); + + set_error_handler(function($errno, $errstr, $errfile, $errline, array $errcontext) { + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); + }); +} + $sessionManager = new SessionManager($_SESSION, $conf); -$loginManager = new LoginManager($GLOBALS, $conf, $sessionManager); +$loginManager = new LoginManager($conf, $sessionManager); $loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']); $clientIpId = client_ip_id($_SERVER); @@ -140,7 +156,7 @@ if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { new Languages(setlocale(LC_MESSAGES, 0), $conf); $conf->setEmpty('general.timezone', date_default_timezone_get()); -$conf->setEmpty('general.title', t('Shared links on '). escape(index_url($_SERVER))); +$conf->setEmpty('general.title', t('Shared bookmarks on '). escape(index_url($_SERVER))); RainTPL::$tpl_dir = $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme').'/'; // template directory RainTPL::$cache_dir = $conf->get('resource.raintpl_tmp'); // cache directory @@ -223,19 +239,20 @@ if (isset($_POST['login'])) { $expirationTime, WEB_PATH ); - } else { // Standard session expiration (=when browser closes) $expirationTime = 0; } // Send cookie with the new expiration date to the browser + session_destroy(); session_set_cookie_params($expirationTime, $cookiedir, $_SERVER['SERVER_NAME']); + session_start(); session_regenerate_id(true); // Optional redirect after login: if (isset($_GET['post'])) { - $uri = '?post='. urlencode($_GET['post']); + $uri = './?post='. urlencode($_GET['post']); foreach (array('description', 'source', 'title', 'tags') as $param) { if (!empty($_GET[$param])) { $uri .= '&'.$param.'='.urlencode($_GET[$param]); @@ -246,21 +263,22 @@ if (isset($_POST['login'])) { } if (isset($_GET['edit_link'])) { - header('Location: ?edit_link='. escape($_GET['edit_link'])); + header('Location: ./?edit_link='. escape($_GET['edit_link'])); exit; } if (isset($_POST['returnurl'])) { // Prevent loops over login screen. - if (strpos($_POST['returnurl'], 'do=login') === false) { + if (strpos($_POST['returnurl'], '/login') === false) { header('Location: '. generateLocation($_POST['returnurl'], $_SERVER['HTTP_HOST'])); exit; } } - header('Location: ?'); exit; + header('Location: ./?'); + exit; } else { $loginManager->handleFailedLogin($_SERVER); - $redir = '&username='. urlencode($_POST['login']); + $redir = '?username='. urlencode($_POST['login']); if (isset($_GET['post'])) { $redir .= '&post=' . urlencode($_GET['post']); foreach (array('description', 'source', 'title', 'tags') as $param) { @@ -270,7 +288,7 @@ if (isset($_POST['login'])) { } } // Redirect to login screen. - echo ''; + echo ''; exit; } } @@ -278,23 +296,27 @@ if (isset($_POST['login'])) { // ------------------------------------------------------------------------------------------ // Token management for XSRF protection // Token should be used in any form which acts on data (create,update,delete,import...). -if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session. +if (!isset($_SESSION['tokens'])) { + $_SESSION['tokens']=array(); // Token are attached to the session. +} /** - * Daily RSS feed: 1 RSS entry per day giving all the links on that day. - * Gives the last 7 days (which have links). + * Daily RSS feed: 1 RSS entry per day giving all the bookmarks on that day. + * Gives the last 7 days (which have bookmarks). * This RSS feed cannot be filtered. * - * @param ConfigManager $conf Configuration Manager instance - * @param LoginManager $loginManager LoginManager instance + * @param BookmarkServiceInterface $bookmarkService + * @param ConfigManager $conf Configuration Manager instance + * @param LoginManager $loginManager LoginManager instance */ -function showDailyRSS($conf, $loginManager) { +function showDailyRSS($bookmarkService, $conf, $loginManager) +{ // Cache system $query = $_SERVER['QUERY_STRING']; $cache = new CachedPage( $conf->get('config.PAGE_CACHE'), page_url($_SERVER), - startsWith($query,'do=dailyrss') && !$loginManager->isLoggedIn() + startsWith($query, 'do=dailyrss') && !$loginManager->isLoggedIn() ); $cached = $cache->cachedVersion(); if (!empty($cached)) { @@ -302,30 +324,20 @@ function showDailyRSS($conf, $loginManager) { exit; } - // If cached was not found (or not usable), then read the database and build the response: - // Read links from database (and filter private links if used it not logged in). - $LINKSDB = new LinkDB( - $conf->get('resource.datastore'), - $loginManager->isLoggedIn(), - $conf->get('privacy.hide_public_links'), - $conf->get('redirector.url'), - $conf->get('redirector.encode_url') - ); - - /* Some Shaarlies may have very few links, so we need to look + /* Some Shaarlies may have very few bookmarks, so we need to look back in time until we have enough days ($nb_of_days). */ $nb_of_days = 7; // We take 7 days. $today = date('Ymd'); $days = array(); - foreach ($LINKSDB as $link) { - $day = $link['created']->format('Ymd'); // Extract day (without time) + foreach ($bookmarkService->search() as $bookmark) { + $day = $bookmark->getCreated()->format('Ymd'); // Extract day (without time) if (strcmp($day, $today) < 0) { if (empty($days[$day])) { $days[$day] = array(); } - $days[$day][] = $link; + $days[$day][] = $bookmark; } if (count($days) > $nb_of_days) { @@ -340,34 +352,38 @@ function showDailyRSS($conf, $loginManager) { echo ''; echo 'Daily - '. $conf->get('general.title') . ''; echo ''. $pageaddr .''; - echo 'Daily shared links'; + echo 'Daily shared bookmarks'; echo 'en-en'; echo ''. $pageaddr .''. PHP_EOL; + $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); + $formatter = $factory->getFormatter(); + $formatter->addContextData('index_url', index_url($_SERVER)); // For each day. - foreach ($days as $day => $links) { - $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); + /** @var Bookmark[] $bookmarks */ + foreach ($days as $day => $bookmarks) { + $formattedBookmarks = []; + $dayDate = DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000'); $absurl = escape(index_url($_SERVER).'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page. // We pre-format some fields for proper output. - foreach ($links as &$link) { - $link['formatedDescription'] = format_description( - $link['description'], - $conf->get('redirector.url'), - $conf->get('redirector.encode_url') - ); - $link['timestamp'] = $link['created']->getTimestamp(); - if (startsWith($link['url'], '?')) { - $link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute + foreach ($bookmarks as $key => $bookmark) { + $formattedBookmarks[$key] = $formatter->format($bookmark); + // This page is a bit specific, we need raw description to calculate the length + $formattedBookmarks[$key]['formatedDescription'] = $formattedBookmarks[$key]['description']; + $formattedBookmarks[$key]['description'] = $bookmark->getDescription(); + + if ($bookmark->isNote()) { + $link['url'] = index_url($_SERVER) . $bookmark->getUrl(); // make permalink URL absolute } } // Then build the HTML for this day: - $tpl = new RainTPL; + $tpl = new RainTPL(); $tpl->assign('title', $conf->get('general.title')); $tpl->assign('daydate', $dayDate->getTimestamp()); $tpl->assign('absurl', $absurl); - $tpl->assign('links', $links); + $tpl->assign('links', $formattedBookmarks); $tpl->assign('rssdate', escape($dayDate->format(DateTime::RSS))); $tpl->assign('hide_timestamps', $conf->get('privacy.hide_timestamps', false)); $tpl->assign('index_url', $pageaddr); @@ -385,23 +401,30 @@ function showDailyRSS($conf, $loginManager) { /** * Show the 'Daily' page. * - * @param PageBuilder $pageBuilder Template engine wrapper. - * @param LinkDB $LINKSDB LinkDB instance. - * @param ConfigManager $conf Configuration Manager instance. - * @param PluginManager $pluginManager Plugin Manager instance. - * @param LoginManager $loginManager Login Manager instance + * @param PageBuilder $pageBuilder Template engine wrapper. + * @param BookmarkServiceInterface $bookmarkService instance. + * @param ConfigManager $conf Configuration Manager instance. + * @param PluginManager $pluginManager Plugin Manager instance. + * @param LoginManager $loginManager Login Manager instance */ -function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager) +function showDaily($pageBuilder, $bookmarkService, $conf, $pluginManager, $loginManager) { - $day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD. if (isset($_GET['day'])) { - $day = $_GET['day']; + $day = $_GET['day']; + if ($day === date('Ymd', strtotime('now'))) { + $pageBuilder->assign('dayDesc', t('Today')); + } elseif ($day === date('Ymd', strtotime('-1 days'))) { + $pageBuilder->assign('dayDesc', t('Yesterday')); + } + } else { + $day = date('Ymd', strtotime('now')); // Today, in format YYYYMMDD. + $pageBuilder->assign('dayDesc', t('Today')); } - $days = $LINKSDB->days(); + $days = $bookmarkService->days(); $i = array_search($day, $days); if ($i === false && count($days)) { - // no links for day, but at least one day with links + // no bookmarks for day, but at least one day with bookmarks $i = count($days) - 1; $day = $days[$i]; } @@ -410,33 +433,30 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager) if ($i !== false) { if ($i >= 1) { - $previousday=$days[$i - 1]; + $previousday = $days[$i - 1]; } if ($i < count($days) - 1) { - $nextday = $days[$i + 1]; + $nextday = $days[$i + 1]; } } try { - $linksToDisplay = $LINKSDB->filterDay($day); + $linksToDisplay = $bookmarkService->filterDay($day); } catch (Exception $exc) { error_log($exc); - $linksToDisplay = array(); + $linksToDisplay = []; } + $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); + $formatter = $factory->getFormatter(); // We pre-format some fields for proper output. - foreach($linksToDisplay as $key => $link) { - $taglist = explode(' ',$link['tags']); - uasort($taglist, 'strcasecmp'); - $linksToDisplay[$key]['taglist']=$taglist; - $linksToDisplay[$key]['formatedDescription'] = format_description( - $link['description'], - $conf->get('redirector.url'), - $conf->get('redirector.encode_url') - ); - $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp(); + foreach ($linksToDisplay as $key => $bookmark) { + $linksToDisplay[$key] = $formatter->format($bookmark); + // This page is a bit specific, we need raw description to calculate the length + $linksToDisplay[$key]['formatedDescription'] = $linksToDisplay[$key]['description']; + $linksToDisplay[$key]['description'] = $bookmark->getDescription(); } - $dayDate = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $day.'_000000'); + $dayDate = DateTime::createFromFormat(Bookmark::LINK_DATE_FORMAT, $day.'_000000'); $data = array( 'pagetitle' => $conf->get('general.title') .' - '. format_date($dayDate, false), 'linksToDisplay' => $linksToDisplay, @@ -457,19 +477,19 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager) */ $columns = array(array(), array(), array()); // Entries to display, for each column. $fill = array(0, 0, 0); // Rough estimate of columns fill. - foreach($data['linksToDisplay'] as $key => $link) { + foreach ($data['linksToDisplay'] as $key => $bookmark) { // Roughly estimate length of entry (by counting characters) // Title: 30 chars = 1 line. 1 line is 30 pixels height. // Description: 836 characters gives roughly 342 pixel height. // This is not perfect, but it's usually OK. - $length = strlen($link['title']) + (342 * strlen($link['description'])) / 836; - if ($link['thumbnail']) { - $length += 100; // 1 thumbnails roughly takes 100 pixels height. + $length = strlen($bookmark['title']) + (342 * strlen($bookmark['description'])) / 836; + if (! empty($bookmark['thumbnail'])) { + $length += 100; // 1 thumbnails roughly takes 100 pixels height. } // Then put in column which is the less filled: $smallest = min($fill); // find smallest value in array. $index = array_search($smallest, $fill); // find index of this smallest value. - array_push($columns[$index], $link); // Put entry in this column. + array_push($columns[$index], $bookmark); // Put entry in this column. $fill[$index] += $length; } @@ -487,59 +507,57 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager) /** * Renders the linklist * - * @param pageBuilder $PAGE pageBuilder instance. - * @param LinkDB $LINKSDB LinkDB instance. - * @param ConfigManager $conf Configuration Manager instance. - * @param PluginManager $pluginManager Plugin Manager instance. + * @param pageBuilder $PAGE pageBuilder instance. + * @param BookmarkServiceInterface $linkDb instance. + * @param ConfigManager $conf Configuration Manager instance. + * @param PluginManager $pluginManager Plugin Manager instance. */ -function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager) { - buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager, $loginManager); +function showLinkList($PAGE, $linkDb, $conf, $pluginManager, $loginManager) +{ + buildLinkList($PAGE, $linkDb, $conf, $pluginManager, $loginManager); $PAGE->renderPage('linklist'); } /** * Render HTML page (according to URL parameters and user rights) * - * @param ConfigManager $conf Configuration Manager instance. - * @param PluginManager $pluginManager Plugin Manager instance, - * @param LinkDB $LINKSDB - * @param History $history instance - * @param SessionManager $sessionManager SessionManager instance - * @param LoginManager $loginManager LoginManager instance + * @param ConfigManager $conf Configuration Manager instance. + * @param PluginManager $pluginManager Plugin Manager instance, + * @param BookmarkServiceInterface $bookmarkService + * @param History $history instance + * @param SessionManager $sessionManager SessionManager instance + * @param LoginManager $loginManager LoginManager instance */ -function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $loginManager) +function renderPage($conf, $pluginManager, $bookmarkService, $history, $sessionManager, $loginManager) { $updater = new Updater( - read_updates_file($conf->get('resource.updates')), - $LINKSDB, + UpdaterUtils::read_updates_file($conf->get('resource.updates')), + $bookmarkService, $conf, - $loginManager->isLoggedIn(), - $_SESSION + $loginManager->isLoggedIn() ); try { $newUpdates = $updater->update(); if (! empty($newUpdates)) { - write_updates_file( + UpdaterUtils::write_updates_file( $conf->get('resource.updates'), $updater->getDoneUpdates() ); } - } - catch(Exception $e) { + } catch (Exception $e) { die($e->getMessage()); } - $PAGE = new PageBuilder($conf, $_SESSION, $LINKSDB, $sessionManager->generateToken(), $loginManager->isLoggedIn()); - $PAGE->assign('linkcount', count($LINKSDB)); - $PAGE->assign('privateLinkcount', count_private($LINKSDB)); + $PAGE = new PageBuilder($conf, $_SESSION, $bookmarkService, $sessionManager->generateToken(), $loginManager->isLoggedIn()); + $PAGE->assign('linkcount', $bookmarkService->count(BookmarkFilter::$ALL)); + $PAGE->assign('privateLinkcount', $bookmarkService->count(BookmarkFilter::$PRIVATE)); $PAGE->assign('plugin_errors', $pluginManager->getErrors()); // Determine which page will be rendered. $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : ''; $targetPage = Router::findPage($query, $_GET, $loginManager->isLoggedIn()); - if ( - // if the user isn't logged in + if (// if the user isn't logged in !$loginManager->isLoggedIn() && // and Shaarli doesn't have public content... $conf->get('privacy.hide_public_links') && @@ -563,9 +581,11 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, 'footer', ); - foreach($common_hooks as $name) { + foreach ($common_hooks as $name) { $plugin_data = array(); - $pluginManager->executeHooks('render_' . $name, $plugin_data, + $pluginManager->executeHooks( + 'render_' . $name, + $plugin_data, array( 'target' => $targetPage, 'loggedin' => $loginManager->isLoggedIn() @@ -575,23 +595,12 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } // -------- Display login form. - if ($targetPage == Router::$PAGE_LOGIN) - { - if ($conf->get('security.open_shaarli')) { header('Location: ?'); exit; } // No need to login for open Shaarli - if (isset($_GET['username'])) { - $PAGE->assign('username', escape($_GET['username'])); - } - $PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):'')); - // add default state of the 'remember me' checkbox - $PAGE->assign('remember_user_default', $conf->get('privacy.remember_user_default')); - $PAGE->assign('user_can_login', $loginManager->canLogin($_SERVER)); - $PAGE->assign('pagetitle', t('Login') .' - '. $conf->get('general.title', 'Shaarli')); - $PAGE->renderPage('loginform'); + if ($targetPage == Router::$PAGE_LOGIN) { + header('Location: ./login'); exit; } // -------- User wants to logout. - if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) - { + if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) { invalidateCaches($conf->get('resource.page_cache')); $sessionManager->logout(); setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, WEB_PATH); @@ -600,8 +609,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } // -------- Picture wall - if ($targetPage == Router::$PAGE_PICWALL) - { + if ($targetPage == Router::$PAGE_PICWALL) { $PAGE->assign('pagetitle', t('Picture wall') .' - '. $conf->get('general.title', 'Shaarli')); if (! $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) === Thumbnailer::MODE_NONE) { $PAGE->assign('linksToDisplay', []); @@ -610,38 +618,37 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } // Optionally filter the results: - $links = $LINKSDB->filterSearch($_GET); - $linksToDisplay = array(); + $links = $bookmarkService->search($_GET); + $linksToDisplay = []; - // Get only links which have a thumbnail. + // Get only bookmarks which have a thumbnail. // Note: we do not retrieve thumbnails here, the request is too heavy. - foreach($links as $key => $link) - { - if (isset($link['thumbnail']) && $link['thumbnail'] !== false) { - $linksToDisplay[] = $link; // Add to array. + $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); + $formatter = $factory->getFormatter(); + foreach ($links as $key => $link) { + if ($link->getThumbnail() !== false) { + $linksToDisplay[] = $formatter->format($link); } } - $data = array( + $data = [ 'linksToDisplay' => $linksToDisplay, - ); - $pluginManager->executeHooks('render_picwall', $data, array('loggedin' => $loginManager->isLoggedIn())); + ]; + $pluginManager->executeHooks('render_picwall', $data, ['loggedin' => $loginManager->isLoggedIn()]); foreach ($data as $key => $value) { $PAGE->assign($key, $value); } - $PAGE->renderPage('picwall'); exit; } // -------- Tag cloud - if ($targetPage == Router::$PAGE_TAGCLOUD) - { + if ($targetPage == Router::$PAGE_TAGCLOUD) { $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : []; - $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility); + $tags = $bookmarkService->bookmarksCountPerTag($filteringTags, $visibility); // We sort tags alphabetically, then choose a font size according to count. // First, find max value. @@ -652,15 +659,16 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, alphabetical_sort($tags, false, true); + $logMaxCount = $maxcount > 1 ? log($maxcount, 30) : 1; $tagList = array(); - foreach($tags as $key => $value) { + foreach ($tags as $key => $value) { if (in_array($key, $filteringTags)) { continue; } // Tag font size scaling: // default 15 and 30 logarithm bases affect scaling, - // 22 and 6 are arbitrary font sizes for max and min sizes. - $size = log($value, 15) / log($maxcount, 30) * 2.2 + 0.8; + // 2.2 and 0.8 are arbitrary font sizes in em. + $size = log($value, 15) / $logMaxCount * 2.2 + 0.8; $tagList[$key] = array( 'count' => $value, 'size' => number_format($size, 2, '.', ''), @@ -685,11 +693,10 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } // -------- Tag list - if ($targetPage == Router::$PAGE_TAGLIST) - { + if ($targetPage == Router::$PAGE_TAGLIST) { $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : []; - $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility); + $tags = $bookmarkService->bookmarksCountPerTag($filteringTags, $visibility); foreach ($filteringTags as $tag) { if (array_key_exists($tag, $tags)) { unset($tags[$tag]); @@ -719,7 +726,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, // Daily page. if ($targetPage == Router::$PAGE_DAILY) { - showDaily($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager); + showDaily($PAGE, $bookmarkService, $conf, $pluginManager, $loginManager); } // ATOM and RSS feed. @@ -732,7 +739,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $cache = new CachedPage( $conf->get('resource.page_cache'), page_url($_SERVER), - startsWith($query,'do='. $targetPage) && !$loginManager->isLoggedIn() + startsWith($query, 'do='. $targetPage) && !$loginManager->isLoggedIn() ); $cached = $cache->cachedVersion(); if (!empty($cached)) { @@ -740,8 +747,16 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, exit; } + $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); // Generate data. - $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, $loginManager->isLoggedIn()); + $feedGenerator = new FeedBuilder( + $bookmarkService, + $factory->getFormatter(), + $feedType, + $_SERVER, + $_GET, + $loginManager->isLoggedIn() + ); $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0))); $feedGenerator->setHideDates($conf->get('privacy.hide_timestamps') && !$loginManager->isLoggedIn()); $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$conf->get('feed.rss_permalinks')); @@ -770,11 +785,14 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } // -------- User clicks on a tag in a link: The tag is added to the list of searched tags (searchtags=...) - if (isset($_GET['addtag'])) - { + if (isset($_GET['addtag'])) { // Get previous URL (http_referer) and add the tag to the searchtags parameters in query. - if (empty($_SERVER['HTTP_REFERER'])) { header('Location: ?searchtags='.urlencode($_GET['addtag'])); exit; } // In case browser does not send HTTP_REFERER - parse_str(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_QUERY), $params); + if (empty($_SERVER['HTTP_REFERER'])) { + // In case browser does not send HTTP_REFERER + header('Location: ?searchtags='.urlencode($_GET['addtag'])); + exit; + } + parse_str(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY), $params); // Prevent redirection loop if (isset($params['addtag'])) { @@ -798,12 +816,14 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, // Append the tag if necessary if (empty($params['searchtags'])) { $params['searchtags'] = trim($_GET['addtag']); - } - elseif ($addtag) { + } elseif ($addtag) { $params['searchtags'] = trim($params['searchtags']).' '.trim($_GET['addtag']); } - unset($params['page']); // We also remove page (keeping the same page has no sense, since the results are different) + // We also remove page (keeping the same page has no sense, since the + // results are different) + unset($params['page']); + header('Location: ?'.http_build_query($params)); exit; } @@ -828,19 +848,21 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $tags = explode(' ', $params['searchtags']); // Remove value from array $tags. $tags = array_diff($tags, array($_GET['removetag'])); - $params['searchtags'] = implode(' ',$tags); + $params['searchtags'] = implode(' ', $tags); if (empty($params['searchtags'])) { unset($params['searchtags']); } - unset($params['page']); // We also remove page (keeping the same page has no sense, since the results are different) + // We also remove page (keeping the same page has no sense, since + // the results are different) + unset($params['page']); } header('Location: ?'.http_build_query($params)); exit; } - // -------- User wants to change the number of links per page (linksperpage=...) + // -------- User wants to change the number of bookmarks per page (linksperpage=...) if (isset($_GET['linksperpage'])) { if (is_numeric($_GET['linksperpage'])) { $_SESSION['LINKS_PER_PAGE']=abs(intval($_GET['linksperpage'])); @@ -855,19 +877,19 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, exit; } - // -------- User wants to see only private links (toggle) + // -------- User wants to see only private bookmarks (toggle) if (isset($_GET['visibility'])) { if ($_GET['visibility'] === 'private') { // Visibility not set or not already private, set private, otherwise reset it if (empty($_SESSION['visibility']) || $_SESSION['visibility'] !== 'private') { - // See only private links + // See only private bookmarks $_SESSION['visibility'] = 'private'; } else { unset($_SESSION['visibility']); } } elseif ($_GET['visibility'] === 'public') { if (empty($_SESSION['visibility']) || $_SESSION['visibility'] !== 'public') { - // See only public links + // See only public bookmarks $_SESSION['visibility'] = 'public'; } else { unset($_SESSION['visibility']); @@ -883,7 +905,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, exit; } - // -------- User wants to see only untagged links (toggle) + // -------- User wants to see only untagged bookmarks (toggle) if (isset($_GET['untaggedonly'])) { $_SESSION['untaggedonly'] = empty($_SESSION['untaggedonly']); @@ -897,14 +919,12 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } // -------- Handle other actions allowed for non-logged in users: - if (!$loginManager->isLoggedIn()) - { + if (!$loginManager->isLoggedIn()) { // User tries to post new link but is not logged in: // Show login screen, then redirect to ?post=... - if (isset($_GET['post'])) - { + if (isset($_GET['post'])) { header( // Redirect to login page, then back to post link. - 'Location: ?do=login&post='.urlencode($_GET['post']). + 'Location: /login?post='.urlencode($_GET['post']). (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):''). (!empty($_GET['description'])?'&description='.urlencode($_GET['description']):''). (!empty($_GET['tags'])?'&tags='.urlencode($_GET['tags']):''). @@ -913,9 +933,9 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, exit; } - showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager); + showLinkList($PAGE, $bookmarkService, $conf, $pluginManager, $loginManager); if (isset($_GET['edit_link'])) { - header('Location: ?do=login&edit_link='. escape($_GET['edit_link'])); + header('Location: /login?edit_link='. escape($_GET['edit_link'])); exit; } @@ -925,8 +945,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, // -------- All other functions are reserved for the registered user: // -------- Display the Tools menu if requested (import/export/bookmarklet...) - if ($targetPage == Router::$PAGE_TOOLS) - { + if ($targetPage == Router::$PAGE_TOOLS) { $data = [ 'pageabsaddr' => index_url($_SERVER), 'sslenabled' => is_https($_SERVER), @@ -943,30 +962,40 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } // -------- User wants to change his/her password. - if ($targetPage == Router::$PAGE_CHANGEPASSWORD) - { + if ($targetPage == Router::$PAGE_CHANGEPASSWORD) { if ($conf->get('security.open_shaarli')) { die(t('You are not supposed to change a password on an Open Shaarli.')); } - if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) - { - if (!$sessionManager->checkToken($_POST['token'])) die(t('Wrong token.')); // Go away! + if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) { + if (!$sessionManager->checkToken($_POST['token'])) { + die(t('Wrong token.')); // Go away! + } // Make sure old password is correct. - $oldhash = sha1($_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt')); - if ($oldhash!= $conf->get('credentials.hash')) { - echo ''; + $oldhash = sha1( + $_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt') + ); + if ($oldhash != $conf->get('credentials.hash')) { + echo ''; exit; } // Save new password // Salt renders rainbow-tables attacks useless. $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand())); - $conf->set('credentials.hash', sha1($_POST['setpassword'] . $conf->get('credentials.login') . $conf->get('credentials.salt'))); + $conf->set( + 'credentials.hash', + sha1( + $_POST['setpassword'] + . $conf->get('credentials.login') + . $conf->get('credentials.salt') + ) + ); try { $conf->write($loginManager->isLoggedIn()); - } - catch(Exception $e) { + } catch (Exception $e) { error_log( 'ERROR while writing config file after changing password.' . PHP_EOL . $e->getMessage() @@ -978,9 +1007,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } echo ''; exit; - } - else // show the change password form. - { + } else { + // show the change password form. $PAGE->assign('pagetitle', t('Change password') .' - '. $conf->get('general.title', 'Shaarli')); $PAGE->renderPage('changepassword'); exit; @@ -988,10 +1016,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } // -------- User wants to change configuration - if ($targetPage == Router::$PAGE_CONFIGURE) - { - if (!empty($_POST['title']) ) - { + if ($targetPage == Router::$PAGE_CONFIGURE) { + if (!empty($_POST['title'])) { if (!$sessionManager->checkToken($_POST['token'])) { die(t('Wrong token.')); // Go away! } @@ -1004,6 +1030,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $conf->set('general.timezone', $tz); $conf->set('general.title', escape($_POST['title'])); $conf->set('general.header_link', escape($_POST['titleLink'])); + $conf->set('general.retrieve_description', !empty($_POST['retrieveDescription'])); $conf->set('resource.theme', escape($_POST['theme'])); $conf->set('security.session_protection_disabled', !empty($_POST['disablesessionprotection'])); $conf->set('privacy.default_private_links', !empty($_POST['privateLinkByDefault'])); @@ -1012,14 +1039,19 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $conf->set('privacy.hide_public_links', !empty($_POST['hidePublicLinks'])); $conf->set('api.enabled', !empty($_POST['enableApi'])); $conf->set('api.secret', escape($_POST['apiSecret'])); - $conf->set('translation.language', escape($_POST['language'])); + $conf->set('formatter', escape($_POST['formatter'])); + + if (! empty($_POST['language'])) { + $conf->set('translation.language', escape($_POST['language'])); + } $thumbnailsMode = extension_loaded('gd') ? $_POST['enableThumbnails'] : Thumbnailer::MODE_NONE; if ($thumbnailsMode !== Thumbnailer::MODE_NONE && $thumbnailsMode !== $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) ) { $_SESSION['warnings'][] = t( - 'You have enabled or changed thumbnails mode. Please synchronize them.' + 'You have enabled or changed thumbnails mode. ' + .'Please synchronize them.' ); } $conf->set('thumbnails.mode', $thumbnailsMode); @@ -1028,8 +1060,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $conf->write($loginManager->isLoggedIn()); $history->updateSettings(); invalidateCaches($conf->get('resource.page_cache')); - } - catch(Exception $e) { + } catch (Exception $e) { error_log( 'ERROR while writing config file after configuration update.' . PHP_EOL . $e->getMessage() @@ -1041,18 +1072,19 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } echo ''; exit; - } - else // Show the configuration form. - { + } else { + // Show the configuration form. $PAGE->assign('title', $conf->get('general.title')); $PAGE->assign('theme', $conf->get('resource.theme')); $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl'))); + $PAGE->assign('formatter_available', ['default', 'markdown']); list($continents, $cities) = generateTimeZoneData( timezone_identifiers_list(), $conf->get('general.timezone') ); $PAGE->assign('continents', $continents); $PAGE->assign('cities', $cities); + $PAGE->assign('retrieve_description', $conf->get('general.retrieve_description')); $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)); @@ -1061,7 +1093,6 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $PAGE->assign('api_enabled', $conf->get('api.enabled', true)); $PAGE->assign('api_secret', $conf->get('api.secret')); $PAGE->assign('languages', Languages::getAvailableLanguages()); - $PAGE->assign('language', $conf->get('translation.language')); $PAGE->assign('gd_enabled', extension_loaded('gd')); $PAGE->assign('thumbnails_mode', $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)); $PAGE->assign('pagetitle', t('Configure') .' - '. $conf->get('general.title', 'Shaarli')); @@ -1071,8 +1102,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } // -------- User wants to rename a tag or delete it - if ($targetPage == Router::$PAGE_CHANGETAG) - { + if ($targetPage == Router::$PAGE_CHANGETAG) { if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) { $PAGE->assign('fromtag', ! empty($_GET['fromtag']) ? escape($_GET['fromtag']) : ''); $PAGE->assign('pagetitle', t('Manage tags') .' - '. $conf->get('general.title', 'Shaarli')); @@ -1085,101 +1115,75 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } $toTag = isset($_POST['totag']) ? escape($_POST['totag']) : null; - $alteredLinks = $LINKSDB->renameTag(escape($_POST['fromtag']), $toTag); - $LINKSDB->save($conf->get('resource.page_cache')); - foreach ($alteredLinks as $link) { - $history->updateLink($link); + $fromTag = escape($_POST['fromtag']); + $count = 0; + $bookmarks = $bookmarkService->search(['searchtags' => $fromTag], BookmarkFilter::$ALL, true); + foreach ($bookmarks as $bookmark) { + if ($toTag) { + $bookmark->renameTag($fromTag, $toTag); + } else { + $bookmark->deleteTag($fromTag); + } + $bookmarkService->set($bookmark, false); + $history->updateLink($bookmark); + $count++; } + $bookmarkService->save(); $delete = empty($_POST['totag']); $redirect = $delete ? 'do=changetag' : 'searchtags='. urlencode(escape($_POST['totag'])); - $count = count($alteredLinks); $alert = $delete - ? sprintf(t('The tag was removed from %d link.', 'The tag was removed from %d links.', $count), $count) - : sprintf(t('The tag was renamed in %d link.', 'The tag was renamed in %d links.', $count), $count); + ? sprintf(t('The tag was removed from %d link.', 'The tag was removed from %d bookmarks.', $count), $count) + : sprintf(t('The tag was renamed in %d link.', 'The tag was renamed in %d bookmarks.', $count), $count); echo ''; exit; } // -------- User wants to add a link without using the bookmarklet: Show form. - if ($targetPage == Router::$PAGE_ADDLINK) - { + if ($targetPage == Router::$PAGE_ADDLINK) { $PAGE->assign('pagetitle', t('Shaare a new link') .' - '. $conf->get('general.title', 'Shaarli')); $PAGE->renderPage('addlink'); exit; } // -------- User clicked the "Save" button when editing a link: Save link to database. - if (isset($_POST['save_edit'])) - { + if (isset($_POST['save_edit'])) { // Go away! if (! $sessionManager->checkToken($_POST['token'])) { die(t('Wrong token.')); } // lf_id should only be present if the link exists. - $id = isset($_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://shaarli.readthedocs.io/en/master/guides/various-hacks/#changing-the-timestamp-for-a-shaare - $linkdate = escape($_POST['lf_linkdate']); - if (isset($LINKSDB[$id])) { + $id = isset($_POST['lf_id']) ? intval(escape($_POST['lf_id'])) : null; + if ($id && $bookmarkService->exists($id)) { // Edit - $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); - $updated = new DateTime(); - $shortUrl = $LINKSDB[$id]['shorturl']; - $new = false; + $bookmark = $bookmarkService->get($id); } else { // New link - $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); - $updated = null; - $shortUrl = link_small_hash($created, $id); - $new = true; - } - - // Remove multiple spaces. - $tags = trim(preg_replace('/\s\s+/', ' ', $_POST['lf_tags'])); - // Remove first '-' char in tags. - $tags = preg_replace('/(^| )\-/', '$1', $tags); - // Remove duplicates. - $tags = implode(' ', array_unique(explode(' ', $tags))); - - if (empty(trim($_POST['lf_url']))) { - $_POST['lf_url'] = '?' . smallHash($linkdate . $id); - } - $url = whitelist_protocols(trim($_POST['lf_url']), $conf->get('security.allowed_protocols')); - - $link = array( - 'id' => $id, - 'title' => trim($_POST['lf_title']), - 'url' => $url, - 'description' => $_POST['lf_description'], - 'private' => (isset($_POST['lf_private']) ? 1 : 0), - 'created' => $created, - 'updated' => $updated, - 'tags' => str_replace(',', ' ', $tags), - 'shorturl' => $shortUrl, - ); - - // If title is empty, use the URL as title. - if ($link['title'] == '') { - $link['title'] = $link['url']; + $bookmark = new Bookmark(); } - if ($conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE) { + $bookmark->setTitle($_POST['lf_title']); + $bookmark->setDescription($_POST['lf_description']); + $bookmark->setUrl($_POST['lf_url'], $conf->get('security.allowed_protocols')); + $bookmark->setPrivate(isset($_POST['lf_private'])); + $bookmark->setTagsString($_POST['lf_tags']); + + if ($conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE + && ! $bookmark->isNote() + ) { $thumbnailer = new Thumbnailer($conf); - $link['thumbnail'] = $thumbnailer->get($url); + $bookmark->setThumbnail($thumbnailer->get($bookmark->getUrl())); } + $bookmarkService->addOrSet($bookmark, false); - $pluginManager->executeHooks('save_link', $link); + // To preserve backward compatibility with 3rd parties, plugins still use arrays + $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); + $formatter = $factory->getFormatter('raw'); + $data = $formatter->format($bookmark); + $pluginManager->executeHooks('save_link', $data); - $LINKSDB[$id] = $link; - $LINKSDB->save($conf->get('resource.page_cache')); - if ($new) { - $history->addLink($link); - } else { - $history->updateLink($link); - } + $bookmark->fromArray($data); + $bookmarkService->set($bookmark); // If we are called from the bookmarklet, we must close the popup: if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { @@ -1190,33 +1194,14 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $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 .= '#' . $link['shorturl']; + $location .= '#' . $bookmark->getShortUrl(); // After saving the link, redirect to the page the user was on. header('Location: '. $location); exit; } - // -------- User clicked the "Cancel" button when editing a link. - if (isset($_POST['cancel_edit'])) - { - $id = isset($_POST['lf_id']) ? (int) escape($_POST['lf_id']) : false; - if (! isset($LINKSDB[$id])) { - header('Location: ?'); - } - // 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[$id]; - $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' ); - // 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 ($targetPage == Router::$PAGE_DELETELINK) { if (! $sessionManager->checkToken($_GET['token'])) { die(t('Wrong token.')); } @@ -1224,34 +1209,45 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $ids = trim($_GET['lf_linkdate']); if (strpos($ids, ' ') !== false) { // multiple, space-separated ids provided - $ids = array_values(array_filter(preg_split('/\s+/', escape($ids)))); + $ids = array_values(array_filter( + preg_split('/\s+/', escape($ids)), + function ($item) { + return $item !== ''; + } + )); } else { // only a single id provided + $shortUrl = $bookmarkService->get($ids)->getShortUrl(); $ids = [$ids]; } // assert at least one id is given - if(!count($ids)){ + if (!count($ids)) { die('no id provided'); } + $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); + $formatter = $factory->getFormatter('raw'); foreach ($ids as $id) { $id = (int) escape($id); - $link = $LINKSDB[$id]; - $pluginManager->executeHooks('delete_link', $link); - $history->deleteLink($link); - unset($LINKSDB[$id]); + $bookmark = $bookmarkService->get($id); + $data = $formatter->format($bookmark); + $pluginManager->executeHooks('delete_link', $data); + $bookmarkService->remove($bookmark, false); } - $LINKSDB->save($conf->get('resource.page_cache')); // save to disk + $bookmarkService->save(); // If we are called from the bookmarklet, we must close the popup: - if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo ''; exit; } + if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { + echo ''; + exit; + } $location = '?'; if (isset($_SERVER['HTTP_REFERER'])) { // Don't redirect to where we were previously if it was a permalink or an edit_link, because it would 404. $location = generateLocation( - $_SERVER['HTTP_REFERER'], - $_SERVER['HTTP_HOST'], - ['delete_link', 'edit_link', $link['shorturl']] + $_SERVER['HTTP_REFERER'], + $_SERVER['HTTP_HOST'], + ['delete_link', 'edit_link', ! empty($shortUrl) ? $shortUrl : null] ); } @@ -1259,18 +1255,81 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, exit; } + // -------- User clicked either "Set public" or "Set private" bulk operation + if ($targetPage == Router::$PAGE_CHANGE_VISIBILITY) { + if (! $sessionManager->checkToken($_GET['token'])) { + die(t('Wrong token.')); + } + + $ids = trim($_GET['ids']); + if (strpos($ids, ' ') !== false) { + // multiple, space-separated ids provided + $ids = array_values(array_filter(preg_split('/\s+/', escape($ids)))); + } else { + // only a single id provided + $ids = [$ids]; + } + + // assert at least one id is given + if (!count($ids)) { + die('no id provided'); + } + // assert that the visibility is valid + if (!isset($_GET['newVisibility']) || !in_array($_GET['newVisibility'], ['public', 'private'])) { + die('invalid visibility'); + } else { + $private = $_GET['newVisibility'] === 'private'; + } + $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); + $formatter = $factory->getFormatter('raw'); + foreach ($ids as $id) { + $id = (int) escape($id); + $bookmark = $bookmarkService->get($id); + $bookmark->setPrivate($private); + + // To preserve backward compatibility with 3rd parties, plugins still use arrays + $data = $formatter->format($bookmark); + $pluginManager->executeHooks('save_link', $data); + $bookmark->fromArray($data); + + $bookmarkService->set($bookmark); + } + $bookmarkService->save(); + + $location = '?'; + if (isset($_SERVER['HTTP_REFERER'])) { + $location = generateLocation( + $_SERVER['HTTP_REFERER'], + $_SERVER['HTTP_HOST'] + ); + } + header('Location: ' . $location); // After deleting the link, redirect to appropriate location + exit; + } + // -------- User clicked the "EDIT" button on a link: Display link edit form. - if (isset($_GET['edit_link'])) - { + if (isset($_GET['edit_link'])) { $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); + try { + $link = $bookmarkService->get($id); // Read database + } catch (BookmarkNotFoundException $e) { + // Link not found in database. + header('Location: ?'); + exit; + } + + $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); + $formatter = $factory->getFormatter('raw'); + $formattedLink = $formatter->format($link); + $tags = $bookmarkService->bookmarksCountPerTag(); + if ($conf->get('formatter') === 'markdown') { + $tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1; + } $data = array( - 'link' => $link, + 'link' => $formattedLink, 'link_is_new' => false, 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), - 'tags' => $LINKSDB->linksCountPerTag(), + 'tags' => $tags, ); $pluginManager->executeHooks('render_editlink', $data); @@ -1289,26 +1348,27 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $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) - { + $bookmark = $bookmarkService->findByUrl($url); + if (! $bookmark) { $link_is_new = true; - $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] $description = empty($_GET['description']) ? '' : escape($_GET['description']); $tags = empty($_GET['tags']) ? '' : escape($_GET['tags']); $private = !empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0; - // If this is an HTTP(S) link, we try go get the page to extract the title (otherwise we will to straight to the edit form.) + + // If this is an HTTP(S) link, we try go get the page to extract + // the title (otherwise we will to straight to the edit form.) if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) { + $retrieveDescription = $conf->get('general.retrieve_description'); // Short timeout to keep the application responsive // The callback will fill $charset and $title with data from the downloaded page. get_http_response( $url, $conf->get('general.download_timeout', 30), $conf->get('general.download_max_size', 4194304), - get_curl_download_callback($charset, $title) + get_curl_download_callback($charset, $title, $description, $tags, $retrieveDescription) ); if (! empty($title) && strtolower($charset) != 'utf-8') { $title = mb_convert_encoding($title, 'utf-8', $charset); @@ -1316,32 +1376,36 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } if ($url == '') { - $url = '?' . smallHash($linkdate . $LINKSDB->getNextId()); $title = $conf->get('general.default_note_title', t('Note: ')); } $url = escape($url); $title = escape($title); - $link = array( - 'linkdate' => $linkdate, + $link = [ 'title' => $title, 'url' => $url, 'description' => $description, 'tags' => $tags, 'private' => $private, - ); + ]; } else { - $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT); + $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); + $formatter = $factory->getFormatter('raw'); + $link = $formatter->format($bookmark); } - $data = array( + $tags = $bookmarkService->bookmarksCountPerTag(); + if ($conf->get('formatter') === 'markdown') { + $tags[BookmarkMarkdownFormatter::NO_MD_TAG] = 1; + } + $data = [ 'link' => $link, 'link_is_new' => $link_is_new, 'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''), 'source' => (isset($_GET['source']) ? $_GET['source'] : ''), - 'tags' => $LINKSDB->linksCountPerTag(), + 'tags' => $tags, 'default_private_links' => $conf->get('privacy.default_private_links', false), - ); + ]; $pluginManager->executeHooks('render_editlink', $data); foreach ($data as $key => $value) { @@ -1353,8 +1417,26 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, exit; } + if ($targetPage == Router::$PAGE_PINLINK) { + if (! isset($_GET['id']) || !$bookmarkService->exists($_GET['id'])) { + // FIXME! Use a proper error system. + $msg = t('Invalid link ID provided'); + echo ''; + exit; + } + if (! $sessionManager->checkToken($_GET['token'])) { + die('Wrong token.'); + } + + $link = $bookmarkService->get($_GET['id']); + $link->setSticky(! $link->isSticky()); + $bookmarkService->set($link); + header('Location: '.index_url($_SERVER)); + exit; + } + if ($targetPage == Router::$PAGE_EXPORT) { - // Export links as a Netscape Bookmarks file + // Export bookmarks as a Netscape Bookmarks file if (empty($_GET['selection'])) { $PAGE->assign('pagetitle', t('Export') .' - '. $conf->get('general.title', 'Shaarli')); @@ -1371,10 +1453,13 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, } try { + $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); + $formatter = $factory->getFormatter('raw'); $PAGE->assign( 'links', NetscapeBookmarkUtils::filterAndFormat( - $LINKSDB, + $bookmarkService, + $formatter, $selection, $prependNoteUrl, index_url($_SERVER) @@ -1389,7 +1474,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, header('Content-Type: text/html; charset=utf-8'); header( 'Content-disposition: attachment; filename=bookmarks_' - .$selection.'_'.$now->format(LinkDB::LINK_DATE_FORMAT).'.html' + .$selection.'_'.$now->format(Bookmark::LINK_DATE_FORMAT).'.html' ); $PAGE->assign('date', $now->format(DateTime::RFC822)); $PAGE->assign('eol', PHP_EOL); @@ -1443,7 +1528,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $status = NetscapeBookmarkUtils::import( $_POST, $_FILES, - $LINKSDB, + $bookmarkService, $conf, $history ); @@ -1457,14 +1542,20 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, $pluginMeta = $pluginManager->getPluginsMeta(); // Split plugins into 2 arrays: ordered enabled plugins and disabled. - $enabledPlugins = array_filter($pluginMeta, function($v) { return $v['order'] !== false; }); + $enabledPlugins = array_filter($pluginMeta, function ($v) { + return $v['order'] !== false; + }); // Load parameters. $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $conf->get('plugins', array())); uasort( $enabledPlugins, - function($a, $b) { return $a['order'] - $b['order']; } + function ($a, $b) { + return $a['order'] - $b['order']; + } ); - $disabledPlugins = array_filter($pluginMeta, function($v) { return $v['order'] === false; }); + $disabledPlugins = array_filter($pluginMeta, function ($v) { + return $v['order'] === false; + }); $PAGE->assign('enabledPlugins', $enabledPlugins); $PAGE->assign('disabledPlugins', $disabledPlugins); @@ -1477,25 +1568,28 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) { try { if (isset($_POST['parameters_form'])) { + $pluginManager->executeHooks('save_plugin_parameters', $_POST); unset($_POST['parameters_form']); foreach ($_POST as $param => $value) { $conf->set('plugins.'. $param, escape($value)); } - } - else { + } else { $conf->set('general.enabled_plugins', save_plugin_config($_POST)); } $conf->write($loginManager->isLoggedIn()); $history->updateSettings(); - } - catch (Exception $e) { + } catch (Exception $e) { error_log( 'ERROR while saving plugin configuration:.' . PHP_EOL . $e->getMessage() ); // TODO: do not handle exceptions/errors in JS. - echo ''; + echo ''; exit; } header('Location: ?do='. Router::$PAGE_PLUGINSADMIN); @@ -1505,19 +1599,19 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, // Get a fresh token if ($targetPage == Router::$GET_TOKEN) { header('Content-Type:text/plain'); - echo $sessionManager->generateToken($conf); + echo $sessionManager->generateToken(); exit; } // -------- Thumbnails Update if ($targetPage == Router::$PAGE_THUMBS_UPDATE) { $ids = []; - foreach ($LINKSDB as $link) { + foreach ($bookmarkService->search() as $bookmark) { // A note or not HTTP(S) - if ($link['url'][0] === '?' || ! startsWith(strtolower($link['url']), 'http')) { + if ($bookmark->isNote() || ! startsWith(strtolower($bookmark->getUrl()), 'http')) { continue; } - $ids[] = $link['id']; + $ids[] = $bookmark->getId(); } $PAGE->assign('ids', $ids); $PAGE->assign('pagetitle', t('Thumbnails update') .' - '. $conf->get('general.title', 'Shaarli')); @@ -1532,37 +1626,40 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager, exit; } $id = (int) $_POST['id']; - if (empty($LINKSDB[$id])) { + if (! $bookmarkService->exists($id)) { http_response_code(404); exit; } $thumbnailer = new Thumbnailer($conf); - $link = $LINKSDB[$id]; - $link['thumbnail'] = $thumbnailer->get($link['url']); - $LINKSDB[$id] = $link; - $LINKSDB->save($conf->get('resource.page_cache')); + $bookmark = $bookmarkService->get($id); + $bookmark->setThumbnail($thumbnailer->get($bookmark->getUrl())); + $bookmarkService->set($bookmark); - echo json_encode($link); + $factory = new FormatterFactory($conf, $loginManager->isLoggedIn()); + echo json_encode($factory->getFormatter('raw')->format($bookmark)); exit; } - // -------- Otherwise, simply display search form and links: - showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager); + // -------- Otherwise, simply display search form and bookmarks: + showLinkList($PAGE, $bookmarkService, $conf, $pluginManager, $loginManager); exit; } /** - * Template for the list of links (