aboutsummaryrefslogtreecommitdiffhomepage
path: root/index.php
diff options
context:
space:
mode:
Diffstat (limited to 'index.php')
-rw-r--r--index.php874
1 files changed, 301 insertions, 573 deletions
diff --git a/index.php b/index.php
index 29d67f62..633ab89e 100644
--- a/index.php
+++ b/index.php
@@ -28,7 +28,7 @@ if (date_default_timezone_get() == '') {
28define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0))); 28define('WEB_PATH', substr($_SERVER['REQUEST_URI'], 0, 1+strrpos($_SERVER['REQUEST_URI'], '/', 0)));
29 29
30// High execution time in case of problematic imports/exports. 30// High execution time in case of problematic imports/exports.
31ini_set('max_input_time','60'); 31ini_set('max_input_time', '60');
32 32
33// Try to set max upload file size and read 33// Try to set max upload file size and read
34ini_set('memory_limit', '128M'); 34ini_set('memory_limit', '128M');
@@ -56,35 +56,38 @@ require_once 'inc/rain.tpl.class.php';
56require_once __DIR__ . '/vendor/autoload.php'; 56require_once __DIR__ . '/vendor/autoload.php';
57 57
58// Shaarli library 58// Shaarli library
59require_once 'application/ApplicationUtils.php'; 59require_once 'application/bookmark/LinkUtils.php';
60require_once 'application/Cache.php';
61require_once 'application/CachedPage.php';
62require_once 'application/config/ConfigPlugin.php'; 60require_once 'application/config/ConfigPlugin.php';
63require_once 'application/FeedBuilder.php'; 61require_once 'application/feed/Cache.php';
62require_once 'application/http/HttpUtils.php';
63require_once 'application/http/UrlUtils.php';
64require_once 'application/updater/UpdaterUtils.php';
64require_once 'application/FileUtils.php'; 65require_once 'application/FileUtils.php';
65require_once 'application/History.php';
66require_once 'application/HttpUtils.php';
67require_once 'application/LinkDB.php';
68require_once 'application/LinkFilter.php';
69require_once 'application/LinkUtils.php';
70require_once 'application/NetscapeBookmarkUtils.php';
71require_once 'application/PageBuilder.php';
72require_once 'application/TimeZone.php'; 66require_once 'application/TimeZone.php';
73require_once 'application/Url.php';
74require_once 'application/Utils.php'; 67require_once 'application/Utils.php';
75require_once 'application/PluginManager.php'; 68
76require_once 'application/Router.php'; 69use \Shaarli\ApplicationUtils;
77require_once 'application/Updater.php'; 70use \Shaarli\Bookmark\Exception\LinkNotFoundException;
78use \Shaarli\Languages; 71use \Shaarli\Bookmark\LinkDB;
79use \Shaarli\ThemeUtils;
80use \Shaarli\Config\ConfigManager; 72use \Shaarli\Config\ConfigManager;
73use \Shaarli\Feed\CachedPage;
74use \Shaarli\Feed\FeedBuilder;
75use \Shaarli\History;
76use \Shaarli\Languages;
77use \Shaarli\Netscape\NetscapeBookmarkUtils;
78use \Shaarli\Plugin\PluginManager;
79use \Shaarli\Render\PageBuilder;
80use \Shaarli\Render\ThemeUtils;
81use \Shaarli\Router;
81use \Shaarli\Security\LoginManager; 82use \Shaarli\Security\LoginManager;
82use \Shaarli\Security\SessionManager; 83use \Shaarli\Security\SessionManager;
84use \Shaarli\Thumbnailer;
85use \Shaarli\Updater\Updater;
83 86
84// Ensure the PHP version is supported 87// Ensure the PHP version is supported
85try { 88try {
86 ApplicationUtils::checkPHPVersion('5.5', PHP_VERSION); 89 ApplicationUtils::checkPHPVersion('5.5', PHP_VERSION);
87} catch(Exception $exc) { 90} catch (Exception $exc) {
88 header('Content-Type: text/plain; charset=utf-8'); 91 header('Content-Type: text/plain; charset=utf-8');
89 echo $exc->getMessage(); 92 echo $exc->getMessage();
90 exit; 93 exit;
@@ -110,7 +113,7 @@ ini_set('session.use_trans_sid', false);
110 113
111session_name('shaarli'); 114session_name('shaarli');
112// Start session if needed (Some server auto-start sessions). 115// Start session if needed (Some server auto-start sessions).
113if (session_id() == '') { 116if (session_status() == PHP_SESSION_NONE) {
114 session_start(); 117 session_start();
115} 118}
116 119
@@ -222,7 +225,6 @@ if (isset($_POST['login'])) {
222 $expirationTime, 225 $expirationTime,
223 WEB_PATH 226 WEB_PATH
224 ); 227 );
225
226 } else { 228 } else {
227 // Standard session expiration (=when browser closes) 229 // Standard session expiration (=when browser closes)
228 $expirationTime = 0; 230 $expirationTime = 0;
@@ -256,7 +258,8 @@ if (isset($_POST['login'])) {
256 exit; 258 exit;
257 } 259 }
258 } 260 }
259 header('Location: ?'); exit; 261 header('Location: ?');
262 exit;
260 } else { 263 } else {
261 $loginManager->handleFailedLogin($_SERVER); 264 $loginManager->handleFailedLogin($_SERVER);
262 $redir = '&username='. urlencode($_POST['login']); 265 $redir = '&username='. urlencode($_POST['login']);
@@ -277,7 +280,9 @@ if (isset($_POST['login'])) {
277// ------------------------------------------------------------------------------------------ 280// ------------------------------------------------------------------------------------------
278// Token management for XSRF protection 281// Token management for XSRF protection
279// Token should be used in any form which acts on data (create,update,delete,import...). 282// Token should be used in any form which acts on data (create,update,delete,import...).
280if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session. 283if (!isset($_SESSION['tokens'])) {
284 $_SESSION['tokens']=array(); // Token are attached to the session.
285}
281 286
282/** 287/**
283 * Daily RSS feed: 1 RSS entry per day giving all the links on that day. 288 * Daily RSS feed: 1 RSS entry per day giving all the links on that day.
@@ -287,13 +292,14 @@ if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are atta
287 * @param ConfigManager $conf Configuration Manager instance 292 * @param ConfigManager $conf Configuration Manager instance
288 * @param LoginManager $loginManager LoginManager instance 293 * @param LoginManager $loginManager LoginManager instance
289 */ 294 */
290function showDailyRSS($conf, $loginManager) { 295function showDailyRSS($conf, $loginManager)
296{
291 // Cache system 297 // Cache system
292 $query = $_SERVER['QUERY_STRING']; 298 $query = $_SERVER['QUERY_STRING'];
293 $cache = new CachedPage( 299 $cache = new CachedPage(
294 $conf->get('config.PAGE_CACHE'), 300 $conf->get('config.PAGE_CACHE'),
295 page_url($_SERVER), 301 page_url($_SERVER),
296 startsWith($query,'do=dailyrss') && !$loginManager->isLoggedIn() 302 startsWith($query, 'do=dailyrss') && !$loginManager->isLoggedIn()
297 ); 303 );
298 $cached = $cache->cachedVersion(); 304 $cached = $cache->cachedVersion();
299 if (!empty($cached)) { 305 if (!empty($cached)) {
@@ -355,7 +361,6 @@ function showDailyRSS($conf, $loginManager) {
355 $conf->get('redirector.url'), 361 $conf->get('redirector.url'),
356 $conf->get('redirector.encode_url') 362 $conf->get('redirector.encode_url')
357 ); 363 );
358 $link['thumbnail'] = thumbnail($conf, $link['url']);
359 $link['timestamp'] = $link['created']->getTimestamp(); 364 $link['timestamp'] = $link['created']->getTimestamp();
360 if (startsWith($link['url'], '?')) { 365 if (startsWith($link['url'], '?')) {
361 $link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute 366 $link['url'] = index_url($_SERVER) . $link['url']; // make permalink URL absolute
@@ -370,6 +375,7 @@ function showDailyRSS($conf, $loginManager) {
370 $tpl->assign('links', $links); 375 $tpl->assign('links', $links);
371 $tpl->assign('rssdate', escape($dayDate->format(DateTime::RSS))); 376 $tpl->assign('rssdate', escape($dayDate->format(DateTime::RSS)));
372 $tpl->assign('hide_timestamps', $conf->get('privacy.hide_timestamps', false)); 377 $tpl->assign('hide_timestamps', $conf->get('privacy.hide_timestamps', false));
378 $tpl->assign('index_url', $pageaddr);
373 $html = $tpl->draw('dailyrss', true); 379 $html = $tpl->draw('dailyrss', true);
374 380
375 echo $html . PHP_EOL; 381 echo $html . PHP_EOL;
@@ -394,7 +400,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
394{ 400{
395 $day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD. 401 $day = date('Ymd', strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
396 if (isset($_GET['day'])) { 402 if (isset($_GET['day'])) {
397 $day = $_GET['day']; 403 $day = $_GET['day'];
398 } 404 }
399 405
400 $days = $LINKSDB->days(); 406 $days = $LINKSDB->days();
@@ -412,7 +418,7 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
412 $previousday=$days[$i - 1]; 418 $previousday=$days[$i - 1];
413 } 419 }
414 if ($i < count($days) - 1) { 420 if ($i < count($days) - 1) {
415 $nextday = $days[$i + 1]; 421 $nextday = $days[$i + 1];
416 } 422 }
417 } 423 }
418 try { 424 try {
@@ -423,8 +429,8 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
423 } 429 }
424 430
425 // We pre-format some fields for proper output. 431 // We pre-format some fields for proper output.
426 foreach($linksToDisplay as $key => $link) { 432 foreach ($linksToDisplay as $key => $link) {
427 $taglist = explode(' ',$link['tags']); 433 $taglist = explode(' ', $link['tags']);
428 uasort($taglist, 'strcasecmp'); 434 uasort($taglist, 'strcasecmp');
429 $linksToDisplay[$key]['taglist']=$taglist; 435 $linksToDisplay[$key]['taglist']=$taglist;
430 $linksToDisplay[$key]['formatedDescription'] = format_description( 436 $linksToDisplay[$key]['formatedDescription'] = format_description(
@@ -432,7 +438,6 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
432 $conf->get('redirector.url'), 438 $conf->get('redirector.url'),
433 $conf->get('redirector.encode_url') 439 $conf->get('redirector.encode_url')
434 ); 440 );
435 $linksToDisplay[$key]['thumbnail'] = thumbnail($conf, $link['url']);
436 $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp(); 441 $linksToDisplay[$key]['timestamp'] = $link['created']->getTimestamp();
437 } 442 }
438 443
@@ -457,14 +462,14 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
457 */ 462 */
458 $columns = array(array(), array(), array()); // Entries to display, for each column. 463 $columns = array(array(), array(), array()); // Entries to display, for each column.
459 $fill = array(0, 0, 0); // Rough estimate of columns fill. 464 $fill = array(0, 0, 0); // Rough estimate of columns fill.
460 foreach($data['linksToDisplay'] as $key => $link) { 465 foreach ($data['linksToDisplay'] as $key => $link) {
461 // Roughly estimate length of entry (by counting characters) 466 // Roughly estimate length of entry (by counting characters)
462 // Title: 30 chars = 1 line. 1 line is 30 pixels height. 467 // Title: 30 chars = 1 line. 1 line is 30 pixels height.
463 // Description: 836 characters gives roughly 342 pixel height. 468 // Description: 836 characters gives roughly 342 pixel height.
464 // This is not perfect, but it's usually OK. 469 // This is not perfect, but it's usually OK.
465 $length = strlen($link['title']) + (342 * strlen($link['description'])) / 836; 470 $length = strlen($link['title']) + (342 * strlen($link['description'])) / 836;
466 if ($link['thumbnail']) { 471 if ($link['thumbnail']) {
467 $length += 100; // 1 thumbnails roughly takes 100 pixels height. 472 $length += 100; // 1 thumbnails roughly takes 100 pixels height.
468 } 473 }
469 // Then put in column which is the less filled: 474 // Then put in column which is the less filled:
470 $smallest = min($fill); // find smallest value in array. 475 $smallest = min($fill); // find smallest value in array.
@@ -492,8 +497,9 @@ function showDaily($pageBuilder, $LINKSDB, $conf, $pluginManager, $loginManager)
492 * @param ConfigManager $conf Configuration Manager instance. 497 * @param ConfigManager $conf Configuration Manager instance.
493 * @param PluginManager $pluginManager Plugin Manager instance. 498 * @param PluginManager $pluginManager Plugin Manager instance.
494 */ 499 */
495function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager) { 500function showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
496 buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager, $loginManager); 501{
502 buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
497 $PAGE->renderPage('linklist'); 503 $PAGE->renderPage('linklist');
498} 504}
499 505
@@ -513,7 +519,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
513 read_updates_file($conf->get('resource.updates')), 519 read_updates_file($conf->get('resource.updates')),
514 $LINKSDB, 520 $LINKSDB,
515 $conf, 521 $conf,
516 $loginManager->isLoggedIn() 522 $loginManager->isLoggedIn(),
523 $_SESSION
517 ); 524 );
518 try { 525 try {
519 $newUpdates = $updater->update(); 526 $newUpdates = $updater->update();
@@ -523,12 +530,11 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
523 $updater->getDoneUpdates() 530 $updater->getDoneUpdates()
524 ); 531 );
525 } 532 }
526 } 533 } catch (Exception $e) {
527 catch(Exception $e) {
528 die($e->getMessage()); 534 die($e->getMessage());
529 } 535 }
530 536
531 $PAGE = new PageBuilder($conf, $LINKSDB, $sessionManager->generateToken(), $loginManager->isLoggedIn()); 537 $PAGE = new PageBuilder($conf, $_SESSION, $LINKSDB, $sessionManager->generateToken(), $loginManager->isLoggedIn());
532 $PAGE->assign('linkcount', count($LINKSDB)); 538 $PAGE->assign('linkcount', count($LINKSDB));
533 $PAGE->assign('privateLinkcount', count_private($LINKSDB)); 539 $PAGE->assign('privateLinkcount', count_private($LINKSDB));
534 $PAGE->assign('plugin_errors', $pluginManager->getErrors()); 540 $PAGE->assign('plugin_errors', $pluginManager->getErrors());
@@ -537,8 +543,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
537 $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : ''; 543 $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : '';
538 $targetPage = Router::findPage($query, $_GET, $loginManager->isLoggedIn()); 544 $targetPage = Router::findPage($query, $_GET, $loginManager->isLoggedIn());
539 545
540 if ( 546 if (// if the user isn't logged in
541 // if the user isn't logged in
542 !$loginManager->isLoggedIn() && 547 !$loginManager->isLoggedIn() &&
543 // and Shaarli doesn't have public content... 548 // and Shaarli doesn't have public content...
544 $conf->get('privacy.hide_public_links') && 549 $conf->get('privacy.hide_public_links') &&
@@ -562,9 +567,11 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
562 'footer', 567 'footer',
563 ); 568 );
564 569
565 foreach($common_hooks as $name) { 570 foreach ($common_hooks as $name) {
566 $plugin_data = array(); 571 $plugin_data = array();
567 $pluginManager->executeHooks('render_' . $name, $plugin_data, 572 $pluginManager->executeHooks(
573 'render_' . $name,
574 $plugin_data,
568 array( 575 array(
569 'target' => $targetPage, 576 'target' => $targetPage,
570 'loggedin' => $loginManager->isLoggedIn() 577 'loggedin' => $loginManager->isLoggedIn()
@@ -574,13 +581,15 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
574 } 581 }
575 582
576 // -------- Display login form. 583 // -------- Display login form.
577 if ($targetPage == Router::$PAGE_LOGIN) 584 if ($targetPage == Router::$PAGE_LOGIN) {
578 { 585 if ($conf->get('security.open_shaarli')) {
579 if ($conf->get('security.open_shaarli')) { header('Location: ?'); exit; } // No need to login for open Shaarli 586 header('Location: ?');
587 exit;
588 } // No need to login for open Shaarli
580 if (isset($_GET['username'])) { 589 if (isset($_GET['username'])) {
581 $PAGE->assign('username', escape($_GET['username'])); 590 $PAGE->assign('username', escape($_GET['username']));
582 } 591 }
583 $PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):'')); 592 $PAGE->assign('returnurl', (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):''));
584 // add default state of the 'remember me' checkbox 593 // add default state of the 'remember me' checkbox
585 $PAGE->assign('remember_user_default', $conf->get('privacy.remember_user_default')); 594 $PAGE->assign('remember_user_default', $conf->get('privacy.remember_user_default'));
586 $PAGE->assign('user_can_login', $loginManager->canLogin($_SERVER)); 595 $PAGE->assign('user_can_login', $loginManager->canLogin($_SERVER));
@@ -589,8 +598,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
589 exit; 598 exit;
590 } 599 }
591 // -------- User wants to logout. 600 // -------- User wants to logout.
592 if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) 601 if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=logout')) {
593 {
594 invalidateCaches($conf->get('resource.page_cache')); 602 invalidateCaches($conf->get('resource.page_cache'));
595 $sessionManager->logout(); 603 $sessionManager->logout();
596 setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, WEB_PATH); 604 setcookie(LoginManager::$STAY_SIGNED_IN_COOKIE, 'false', 0, WEB_PATH);
@@ -599,21 +607,23 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
599 } 607 }
600 608
601 // -------- Picture wall 609 // -------- Picture wall
602 if ($targetPage == Router::$PAGE_PICWALL) 610 if ($targetPage == Router::$PAGE_PICWALL) {
603 { 611 $PAGE->assign('pagetitle', t('Picture wall') .' - '. $conf->get('general.title', 'Shaarli'));
612 if (! $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) === Thumbnailer::MODE_NONE) {
613 $PAGE->assign('linksToDisplay', []);
614 $PAGE->renderPage('picwall');
615 exit;
616 }
617
604 // Optionally filter the results: 618 // Optionally filter the results:
605 $links = $LINKSDB->filterSearch($_GET); 619 $links = $LINKSDB->filterSearch($_GET);
606 $linksToDisplay = array(); 620 $linksToDisplay = array();
607 621
608 // Get only links which have a thumbnail. 622 // Get only links which have a thumbnail.
609 foreach($links as $link) 623 // Note: we do not retrieve thumbnails here, the request is too heavy.
610 { 624 foreach ($links as $key => $link) {
611 $permalink='?'.$link['shorturl']; 625 if (isset($link['thumbnail']) && $link['thumbnail'] !== false) {
612 $thumb=lazyThumbnail($conf, $link['url'],$permalink); 626 $linksToDisplay[] = $link; // Add to array.
613 if ($thumb!='') // Only output links which have a thumbnail.
614 {
615 $link['thumbnail']=$thumb; // Thumbnail HTML code.
616 $linksToDisplay[]=$link; // Add to array.
617 } 627 }
618 } 628 }
619 629
@@ -626,14 +636,13 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
626 $PAGE->assign($key, $value); 636 $PAGE->assign($key, $value);
627 } 637 }
628 638
629 $PAGE->assign('pagetitle', t('Picture wall') .' - '. $conf->get('general.title', 'Shaarli')); 639
630 $PAGE->renderPage('picwall'); 640 $PAGE->renderPage('picwall');
631 exit; 641 exit;
632 } 642 }
633 643
634 // -------- Tag cloud 644 // -------- Tag cloud
635 if ($targetPage == Router::$PAGE_TAGCLOUD) 645 if ($targetPage == Router::$PAGE_TAGCLOUD) {
636 {
637 $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; 646 $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '';
638 $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : []; 647 $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : [];
639 $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility); 648 $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility);
@@ -648,7 +657,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
648 alphabetical_sort($tags, false, true); 657 alphabetical_sort($tags, false, true);
649 658
650 $tagList = array(); 659 $tagList = array();
651 foreach($tags as $key => $value) { 660 foreach ($tags as $key => $value) {
652 if (in_array($key, $filteringTags)) { 661 if (in_array($key, $filteringTags)) {
653 continue; 662 continue;
654 } 663 }
@@ -680,8 +689,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
680 } 689 }
681 690
682 // -------- Tag list 691 // -------- Tag list
683 if ($targetPage == Router::$PAGE_TAGLIST) 692 if ($targetPage == Router::$PAGE_TAGLIST) {
684 {
685 $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : ''; 693 $visibility = ! empty($_SESSION['visibility']) ? $_SESSION['visibility'] : '';
686 $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : []; 694 $filteringTags = isset($_GET['searchtags']) ? explode(' ', $_GET['searchtags']) : [];
687 $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility); 695 $tags = $LINKSDB->linksCountPerTag($filteringTags, $visibility);
@@ -727,7 +735,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
727 $cache = new CachedPage( 735 $cache = new CachedPage(
728 $conf->get('resource.page_cache'), 736 $conf->get('resource.page_cache'),
729 page_url($_SERVER), 737 page_url($_SERVER),
730 startsWith($query,'do='. $targetPage) && !$loginManager->isLoggedIn() 738 startsWith($query, 'do='. $targetPage) && !$loginManager->isLoggedIn()
731 ); 739 );
732 $cached = $cache->cachedVersion(); 740 $cached = $cache->cachedVersion();
733 if (!empty($cached)) { 741 if (!empty($cached)) {
@@ -765,11 +773,14 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
765 } 773 }
766 774
767 // -------- User clicks on a tag in a link: The tag is added to the list of searched tags (searchtags=...) 775 // -------- User clicks on a tag in a link: The tag is added to the list of searched tags (searchtags=...)
768 if (isset($_GET['addtag'])) 776 if (isset($_GET['addtag'])) {
769 {
770 // Get previous URL (http_referer) and add the tag to the searchtags parameters in query. 777 // Get previous URL (http_referer) and add the tag to the searchtags parameters in query.
771 if (empty($_SERVER['HTTP_REFERER'])) { header('Location: ?searchtags='.urlencode($_GET['addtag'])); exit; } // In case browser does not send HTTP_REFERER 778 if (empty($_SERVER['HTTP_REFERER'])) {
772 parse_str(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_QUERY), $params); 779 // In case browser does not send HTTP_REFERER
780 header('Location: ?searchtags='.urlencode($_GET['addtag']));
781 exit;
782 }
783 parse_str(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY), $params);
773 784
774 // Prevent redirection loop 785 // Prevent redirection loop
775 if (isset($params['addtag'])) { 786 if (isset($params['addtag'])) {
@@ -793,12 +804,14 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
793 // Append the tag if necessary 804 // Append the tag if necessary
794 if (empty($params['searchtags'])) { 805 if (empty($params['searchtags'])) {
795 $params['searchtags'] = trim($_GET['addtag']); 806 $params['searchtags'] = trim($_GET['addtag']);
796 } 807 } elseif ($addtag) {
797 elseif ($addtag) {
798 $params['searchtags'] = trim($params['searchtags']).' '.trim($_GET['addtag']); 808 $params['searchtags'] = trim($params['searchtags']).' '.trim($_GET['addtag']);
799 } 809 }
800 810
801 unset($params['page']); // We also remove page (keeping the same page has no sense, since the results are different) 811 // We also remove page (keeping the same page has no sense, since the
812 // results are different)
813 unset($params['page']);
814
802 header('Location: ?'.http_build_query($params)); 815 header('Location: ?'.http_build_query($params));
803 exit; 816 exit;
804 } 817 }
@@ -823,13 +836,15 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
823 $tags = explode(' ', $params['searchtags']); 836 $tags = explode(' ', $params['searchtags']);
824 // Remove value from array $tags. 837 // Remove value from array $tags.
825 $tags = array_diff($tags, array($_GET['removetag'])); 838 $tags = array_diff($tags, array($_GET['removetag']));
826 $params['searchtags'] = implode(' ',$tags); 839 $params['searchtags'] = implode(' ', $tags);
827 840
828 if (empty($params['searchtags'])) { 841 if (empty($params['searchtags'])) {
829 unset($params['searchtags']); 842 unset($params['searchtags']);
830 } 843 }
831 844
832 unset($params['page']); // We also remove page (keeping the same page has no sense, since the results are different) 845 // We also remove page (keeping the same page has no sense, since
846 // the results are different)
847 unset($params['page']);
833 } 848 }
834 header('Location: ?'.http_build_query($params)); 849 header('Location: ?'.http_build_query($params));
835 exit; 850 exit;
@@ -892,12 +907,10 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
892 } 907 }
893 908
894 // -------- Handle other actions allowed for non-logged in users: 909 // -------- Handle other actions allowed for non-logged in users:
895 if (!$loginManager->isLoggedIn()) 910 if (!$loginManager->isLoggedIn()) {
896 {
897 // User tries to post new link but is not logged in: 911 // User tries to post new link but is not logged in:
898 // Show login screen, then redirect to ?post=... 912 // Show login screen, then redirect to ?post=...
899 if (isset($_GET['post'])) 913 if (isset($_GET['post'])) {
900 {
901 header( // Redirect to login page, then back to post link. 914 header( // Redirect to login page, then back to post link.
902 'Location: ?do=login&post='.urlencode($_GET['post']). 915 'Location: ?do=login&post='.urlencode($_GET['post']).
903 (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):''). 916 (!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').
@@ -920,8 +933,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
920 // -------- All other functions are reserved for the registered user: 933 // -------- All other functions are reserved for the registered user:
921 934
922 // -------- Display the Tools menu if requested (import/export/bookmarklet...) 935 // -------- Display the Tools menu if requested (import/export/bookmarklet...)
923 if ($targetPage == Router::$PAGE_TOOLS) 936 if ($targetPage == Router::$PAGE_TOOLS) {
924 {
925 $data = [ 937 $data = [
926 'pageabsaddr' => index_url($_SERVER), 938 'pageabsaddr' => index_url($_SERVER),
927 'sslenabled' => is_https($_SERVER), 939 'sslenabled' => is_https($_SERVER),
@@ -938,30 +950,40 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
938 } 950 }
939 951
940 // -------- User wants to change his/her password. 952 // -------- User wants to change his/her password.
941 if ($targetPage == Router::$PAGE_CHANGEPASSWORD) 953 if ($targetPage == Router::$PAGE_CHANGEPASSWORD) {
942 {
943 if ($conf->get('security.open_shaarli')) { 954 if ($conf->get('security.open_shaarli')) {
944 die(t('You are not supposed to change a password on an Open Shaarli.')); 955 die(t('You are not supposed to change a password on an Open Shaarli.'));
945 } 956 }
946 957
947 if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) 958 if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) {
948 { 959 if (!$sessionManager->checkToken($_POST['token'])) {
949 if (!$sessionManager->checkToken($_POST['token'])) die(t('Wrong token.')); // Go away! 960 die(t('Wrong token.')); // Go away!
961 }
950 962
951 // Make sure old password is correct. 963 // Make sure old password is correct.
952 $oldhash = sha1($_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt')); 964 $oldhash = sha1(
953 if ($oldhash!= $conf->get('credentials.hash')) { 965 $_POST['oldpassword'].$conf->get('credentials.login').$conf->get('credentials.salt')
954 echo '<script>alert("'. t('The old password is not correct.') .'");document.location=\'?do=changepasswd\';</script>'; 966 );
967 if ($oldhash != $conf->get('credentials.hash')) {
968 echo '<script>alert("'
969 . t('The old password is not correct.')
970 .'");document.location=\'?do=changepasswd\';</script>';
955 exit; 971 exit;
956 } 972 }
957 // Save new password 973 // Save new password
958 // Salt renders rainbow-tables attacks useless. 974 // Salt renders rainbow-tables attacks useless.
959 $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand())); 975 $conf->set('credentials.salt', sha1(uniqid('', true) .'_'. mt_rand()));
960 $conf->set('credentials.hash', sha1($_POST['setpassword'] . $conf->get('credentials.login') . $conf->get('credentials.salt'))); 976 $conf->set(
977 'credentials.hash',
978 sha1(
979 $_POST['setpassword']
980 . $conf->get('credentials.login')
981 . $conf->get('credentials.salt')
982 )
983 );
961 try { 984 try {
962 $conf->write($loginManager->isLoggedIn()); 985 $conf->write($loginManager->isLoggedIn());
963 } 986 } catch (Exception $e) {
964 catch(Exception $e) {
965 error_log( 987 error_log(
966 'ERROR while writing config file after changing password.' . PHP_EOL . 988 'ERROR while writing config file after changing password.' . PHP_EOL .
967 $e->getMessage() 989 $e->getMessage()
@@ -973,9 +995,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
973 } 995 }
974 echo '<script>alert("'. t('Your password has been changed') .'");document.location=\'?do=tools\';</script>'; 996 echo '<script>alert("'. t('Your password has been changed') .'");document.location=\'?do=tools\';</script>';
975 exit; 997 exit;
976 } 998 } else {
977 else // show the change password form. 999 // show the change password form.
978 {
979 $PAGE->assign('pagetitle', t('Change password') .' - '. $conf->get('general.title', 'Shaarli')); 1000 $PAGE->assign('pagetitle', t('Change password') .' - '. $conf->get('general.title', 'Shaarli'));
980 $PAGE->renderPage('changepassword'); 1001 $PAGE->renderPage('changepassword');
981 exit; 1002 exit;
@@ -983,10 +1004,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
983 } 1004 }
984 1005
985 // -------- User wants to change configuration 1006 // -------- User wants to change configuration
986 if ($targetPage == Router::$PAGE_CONFIGURE) 1007 if ($targetPage == Router::$PAGE_CONFIGURE) {
987 { 1008 if (!empty($_POST['title'])) {
988 if (!empty($_POST['title']) )
989 {
990 if (!$sessionManager->checkToken($_POST['token'])) { 1009 if (!$sessionManager->checkToken($_POST['token'])) {
991 die(t('Wrong token.')); // Go away! 1010 die(t('Wrong token.')); // Go away!
992 } 1011 }
@@ -1009,12 +1028,22 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1009 $conf->set('api.secret', escape($_POST['apiSecret'])); 1028 $conf->set('api.secret', escape($_POST['apiSecret']));
1010 $conf->set('translation.language', escape($_POST['language'])); 1029 $conf->set('translation.language', escape($_POST['language']));
1011 1030
1031 $thumbnailsMode = extension_loaded('gd') ? $_POST['enableThumbnails'] : Thumbnailer::MODE_NONE;
1032 if ($thumbnailsMode !== Thumbnailer::MODE_NONE
1033 && $thumbnailsMode !== $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE)
1034 ) {
1035 $_SESSION['warnings'][] = t(
1036 'You have enabled or changed thumbnails mode. '
1037 .'<a href="?do=thumbs_update">Please synchronize them</a>.'
1038 );
1039 }
1040 $conf->set('thumbnails.mode', $thumbnailsMode);
1041
1012 try { 1042 try {
1013 $conf->write($loginManager->isLoggedIn()); 1043 $conf->write($loginManager->isLoggedIn());
1014 $history->updateSettings(); 1044 $history->updateSettings();
1015 invalidateCaches($conf->get('resource.page_cache')); 1045 invalidateCaches($conf->get('resource.page_cache'));
1016 } 1046 } catch (Exception $e) {
1017 catch(Exception $e) {
1018 error_log( 1047 error_log(
1019 'ERROR while writing config file after configuration update.' . PHP_EOL . 1048 'ERROR while writing config file after configuration update.' . PHP_EOL .
1020 $e->getMessage() 1049 $e->getMessage()
@@ -1026,9 +1055,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1026 } 1055 }
1027 echo '<script>alert("'. t('Configuration was saved.') .'");document.location=\'?do=configure\';</script>'; 1056 echo '<script>alert("'. t('Configuration was saved.') .'");document.location=\'?do=configure\';</script>';
1028 exit; 1057 exit;
1029 } 1058 } else {
1030 else // Show the configuration form. 1059 // Show the configuration form.
1031 {
1032 $PAGE->assign('title', $conf->get('general.title')); 1060 $PAGE->assign('title', $conf->get('general.title'));
1033 $PAGE->assign('theme', $conf->get('resource.theme')); 1061 $PAGE->assign('theme', $conf->get('resource.theme'));
1034 $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl'))); 1062 $PAGE->assign('theme_available', ThemeUtils::getThemes($conf->get('resource.raintpl_tpl')));
@@ -1047,6 +1075,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1047 $PAGE->assign('api_secret', $conf->get('api.secret')); 1075 $PAGE->assign('api_secret', $conf->get('api.secret'));
1048 $PAGE->assign('languages', Languages::getAvailableLanguages()); 1076 $PAGE->assign('languages', Languages::getAvailableLanguages());
1049 $PAGE->assign('language', $conf->get('translation.language')); 1077 $PAGE->assign('language', $conf->get('translation.language'));
1078 $PAGE->assign('gd_enabled', extension_loaded('gd'));
1079 $PAGE->assign('thumbnails_mode', $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE));
1050 $PAGE->assign('pagetitle', t('Configure') .' - '. $conf->get('general.title', 'Shaarli')); 1080 $PAGE->assign('pagetitle', t('Configure') .' - '. $conf->get('general.title', 'Shaarli'));
1051 $PAGE->renderPage('configure'); 1081 $PAGE->renderPage('configure');
1052 exit; 1082 exit;
@@ -1054,8 +1084,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1054 } 1084 }
1055 1085
1056 // -------- User wants to rename a tag or delete it 1086 // -------- User wants to rename a tag or delete it
1057 if ($targetPage == Router::$PAGE_CHANGETAG) 1087 if ($targetPage == Router::$PAGE_CHANGETAG) {
1058 {
1059 if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) { 1088 if (empty($_POST['fromtag']) || (empty($_POST['totag']) && isset($_POST['renametag']))) {
1060 $PAGE->assign('fromtag', ! empty($_GET['fromtag']) ? escape($_GET['fromtag']) : ''); 1089 $PAGE->assign('fromtag', ! empty($_GET['fromtag']) ? escape($_GET['fromtag']) : '');
1061 $PAGE->assign('pagetitle', t('Manage tags') .' - '. $conf->get('general.title', 'Shaarli')); 1090 $PAGE->assign('pagetitle', t('Manage tags') .' - '. $conf->get('general.title', 'Shaarli'));
@@ -1067,7 +1096,8 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1067 die(t('Wrong token.')); 1096 die(t('Wrong token.'));
1068 } 1097 }
1069 1098
1070 $alteredLinks = $LINKSDB->renameTag(escape($_POST['fromtag']), escape($_POST['totag'])); 1099 $toTag = isset($_POST['totag']) ? escape($_POST['totag']) : null;
1100 $alteredLinks = $LINKSDB->renameTag(escape($_POST['fromtag']), $toTag);
1071 $LINKSDB->save($conf->get('resource.page_cache')); 1101 $LINKSDB->save($conf->get('resource.page_cache'));
1072 foreach ($alteredLinks as $link) { 1102 foreach ($alteredLinks as $link) {
1073 $history->updateLink($link); 1103 $history->updateLink($link);
@@ -1083,16 +1113,14 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1083 } 1113 }
1084 1114
1085 // -------- User wants to add a link without using the bookmarklet: Show form. 1115 // -------- User wants to add a link without using the bookmarklet: Show form.
1086 if ($targetPage == Router::$PAGE_ADDLINK) 1116 if ($targetPage == Router::$PAGE_ADDLINK) {
1087 {
1088 $PAGE->assign('pagetitle', t('Shaare a new link') .' - '. $conf->get('general.title', 'Shaarli')); 1117 $PAGE->assign('pagetitle', t('Shaare a new link') .' - '. $conf->get('general.title', 'Shaarli'));
1089 $PAGE->renderPage('addlink'); 1118 $PAGE->renderPage('addlink');
1090 exit; 1119 exit;
1091 } 1120 }
1092 1121
1093 // -------- User clicked the "Save" button when editing a link: Save link to database. 1122 // -------- User clicked the "Save" button when editing a link: Save link to database.
1094 if (isset($_POST['save_edit'])) 1123 if (isset($_POST['save_edit'])) {
1095 {
1096 // Go away! 1124 // Go away!
1097 if (! $sessionManager->checkToken($_POST['token'])) { 1125 if (! $sessionManager->checkToken($_POST['token'])) {
1098 die(t('Wrong token.')); 1126 die(t('Wrong token.'));
@@ -1103,7 +1131,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1103 // Linkdate is kept here to: 1131 // Linkdate is kept here to:
1104 // - use the same permalink for notes as they're displayed when creating them 1132 // - use the same permalink for notes as they're displayed when creating them
1105 // - let users hack creation date of their posts 1133 // - let users hack creation date of their posts
1106 // See: https://shaarli.readthedocs.io/en/master/Various-hacks/#changing-the-timestamp-for-a-shaare 1134 // See: https://shaarli.readthedocs.io/en/master/guides/various-hacks/#changing-the-timestamp-for-a-shaare
1107 $linkdate = escape($_POST['lf_linkdate']); 1135 $linkdate = escape($_POST['lf_linkdate']);
1108 if (isset($LINKSDB[$id])) { 1136 if (isset($LINKSDB[$id])) {
1109 // Edit 1137 // Edit
@@ -1148,6 +1176,11 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1148 $link['title'] = $link['url']; 1176 $link['title'] = $link['url'];
1149 } 1177 }
1150 1178
1179 if ($conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE) {
1180 $thumbnailer = new Thumbnailer($conf);
1181 $link['thumbnail'] = $thumbnailer->get($url);
1182 }
1183
1151 $pluginManager->executeHooks('save_link', $link); 1184 $pluginManager->executeHooks('save_link', $link);
1152 1185
1153 $LINKSDB[$id] = $link; 1186 $LINKSDB[$id] = $link;
@@ -1174,14 +1207,16 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1174 } 1207 }
1175 1208
1176 // -------- User clicked the "Cancel" button when editing a link. 1209 // -------- User clicked the "Cancel" button when editing a link.
1177 if (isset($_POST['cancel_edit'])) 1210 if (isset($_POST['cancel_edit'])) {
1178 {
1179 $id = isset($_POST['lf_id']) ? (int) escape($_POST['lf_id']) : false; 1211 $id = isset($_POST['lf_id']) ? (int) escape($_POST['lf_id']) : false;
1180 if (! isset($LINKSDB[$id])) { 1212 if (! isset($LINKSDB[$id])) {
1181 header('Location: ?'); 1213 header('Location: ?');
1182 } 1214 }
1183 // If we are called from the bookmarklet, we must close the popup: 1215 // If we are called from the bookmarklet, we must close the popup:
1184 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1216 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
1217 echo '<script>self.close();</script>';
1218 exit;
1219 }
1185 $link = $LINKSDB[$id]; 1220 $link = $LINKSDB[$id];
1186 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' ); 1221 $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
1187 // Scroll to the link which has been edited. 1222 // Scroll to the link which has been edited.
@@ -1192,8 +1227,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1192 } 1227 }
1193 1228
1194 // -------- User clicked the "Delete" button when editing a link: Delete link from database. 1229 // -------- User clicked the "Delete" button when editing a link: Delete link from database.
1195 if ($targetPage == Router::$PAGE_DELETELINK) 1230 if ($targetPage == Router::$PAGE_DELETELINK) {
1196 {
1197 if (! $sessionManager->checkToken($_GET['token'])) { 1231 if (! $sessionManager->checkToken($_GET['token'])) {
1198 die(t('Wrong token.')); 1232 die(t('Wrong token.'));
1199 } 1233 }
@@ -1207,28 +1241,31 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1207 $ids = [$ids]; 1241 $ids = [$ids];
1208 } 1242 }
1209 // assert at least one id is given 1243 // assert at least one id is given
1210 if(!count($ids)){ 1244 if (!count($ids)) {
1211 die('no id provided'); 1245 die('no id provided');
1212 } 1246 }
1213 foreach ($ids as $id) { 1247 foreach ($ids as $id) {
1214 $id = (int) escape($id); 1248 $id = (int) escape($id);
1215 $link = $LINKSDB[$id]; 1249 $link = $LINKSDB[$id];
1216 $pluginManager->executeHooks('delete_link', $link); 1250 $pluginManager->executeHooks('delete_link', $link);
1251 $history->deleteLink($link);
1217 unset($LINKSDB[$id]); 1252 unset($LINKSDB[$id]);
1218 } 1253 }
1219 $LINKSDB->save($conf->get('resource.page_cache')); // save to disk 1254 $LINKSDB->save($conf->get('resource.page_cache')); // save to disk
1220 $history->deleteLink($link);
1221 1255
1222 // If we are called from the bookmarklet, we must close the popup: 1256 // If we are called from the bookmarklet, we must close the popup:
1223 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1257 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
1258 echo '<script>self.close();</script>';
1259 exit;
1260 }
1224 1261
1225 $location = '?'; 1262 $location = '?';
1226 if (isset($_SERVER['HTTP_REFERER'])) { 1263 if (isset($_SERVER['HTTP_REFERER'])) {
1227 // Don't redirect to where we were previously if it was a permalink or an edit_link, because it would 404. 1264 // Don't redirect to where we were previously if it was a permalink or an edit_link, because it would 404.
1228 $location = generateLocation( 1265 $location = generateLocation(
1229 $_SERVER['HTTP_REFERER'], 1266 $_SERVER['HTTP_REFERER'],
1230 $_SERVER['HTTP_HOST'], 1267 $_SERVER['HTTP_HOST'],
1231 ['delete_link', 'edit_link', $link['shorturl']] 1268 ['delete_link', 'edit_link', $link['shorturl']]
1232 ); 1269 );
1233 } 1270 }
1234 1271
@@ -1237,11 +1274,13 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1237 } 1274 }
1238 1275
1239 // -------- User clicked the "EDIT" button on a link: Display link edit form. 1276 // -------- User clicked the "EDIT" button on a link: Display link edit form.
1240 if (isset($_GET['edit_link'])) 1277 if (isset($_GET['edit_link'])) {
1241 {
1242 $id = (int) escape($_GET['edit_link']); 1278 $id = (int) escape($_GET['edit_link']);
1243 $link = $LINKSDB[$id]; // Read database 1279 $link = $LINKSDB[$id]; // Read database
1244 if (!$link) { header('Location: ?'); exit; } // Link not found in database. 1280 if (!$link) {
1281 header('Location: ?');
1282 exit;
1283 } // Link not found in database.
1245 $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT); 1284 $link['linkdate'] = $link['created']->format(LinkDB::LINK_DATE_FORMAT);
1246 $data = array( 1285 $data = array(
1247 'link' => $link, 1286 'link' => $link,
@@ -1267,8 +1306,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1267 $link_is_new = false; 1306 $link_is_new = false;
1268 // Check if URL is not already in database (in this case, we will edit the existing link) 1307 // Check if URL is not already in database (in this case, we will edit the existing link)
1269 $link = $LINKSDB->getLinkFromUrl($url); 1308 $link = $LINKSDB->getLinkFromUrl($url);
1270 if (! $link) 1309 if (! $link) {
1271 {
1272 $link_is_new = true; 1310 $link_is_new = true;
1273 $linkdate = strval(date(LinkDB::LINK_DATE_FORMAT)); 1311 $linkdate = strval(date(LinkDB::LINK_DATE_FORMAT));
1274 // Get title if it was provided in URL (by the bookmarklet). 1312 // Get title if it was provided in URL (by the bookmarklet).
@@ -1277,7 +1315,9 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1277 $description = empty($_GET['description']) ? '' : escape($_GET['description']); 1315 $description = empty($_GET['description']) ? '' : escape($_GET['description']);
1278 $tags = empty($_GET['tags']) ? '' : escape($_GET['tags']); 1316 $tags = empty($_GET['tags']) ? '' : escape($_GET['tags']);
1279 $private = !empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0; 1317 $private = !empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0;
1280 // 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.) 1318
1319 // If this is an HTTP(S) link, we try go get the page to extract
1320 // the title (otherwise we will to straight to the edit form.)
1281 if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) { 1321 if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) {
1282 // Short timeout to keep the application responsive 1322 // Short timeout to keep the application responsive
1283 // The callback will fill $charset and $title with data from the downloaded page. 1323 // The callback will fill $charset and $title with data from the downloaded page.
@@ -1330,6 +1370,25 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1330 exit; 1370 exit;
1331 } 1371 }
1332 1372
1373 if ($targetPage == Router::$PAGE_PINLINK) {
1374 if (! isset($_GET['id']) || empty($LINKSDB[$_GET['id']])) {
1375 // FIXME! Use a proper error system.
1376 $msg = t('Invalid link ID provided');
1377 echo '<script>alert("'. $msg .'");document.location=\''. index_url($_SERVER) .'\';</script>';
1378 exit;
1379 }
1380 if (! $sessionManager->checkToken($_GET['token'])) {
1381 die('Wrong token.');
1382 }
1383
1384 $link = $LINKSDB[$_GET['id']];
1385 $link['sticky'] = ! $link['sticky'];
1386 $LINKSDB[(int) $_GET['id']] = $link;
1387 $LINKSDB->save($conf->get('resource.page_cache'));
1388 header('Location: '.index_url($_SERVER));
1389 exit;
1390 }
1391
1333 if ($targetPage == Router::$PAGE_EXPORT) { 1392 if ($targetPage == Router::$PAGE_EXPORT) {
1334 // Export links as a Netscape Bookmarks file 1393 // Export links as a Netscape Bookmarks file
1335 1394
@@ -1366,7 +1425,7 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1366 header('Content-Type: text/html; charset=utf-8'); 1425 header('Content-Type: text/html; charset=utf-8');
1367 header( 1426 header(
1368 'Content-disposition: attachment; filename=bookmarks_' 1427 'Content-disposition: attachment; filename=bookmarks_'
1369 .$selection.'_'.$now->format(LinkDB::LINK_DATE_FORMAT).'.html' 1428 .$selection.'_'.$now->format(LinkDB::LINK_DATE_FORMAT).'.html'
1370 ); 1429 );
1371 $PAGE->assign('date', $now->format(DateTime::RFC822)); 1430 $PAGE->assign('date', $now->format(DateTime::RFC822));
1372 $PAGE->assign('eol', PHP_EOL); 1431 $PAGE->assign('eol', PHP_EOL);
@@ -1434,14 +1493,20 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1434 $pluginMeta = $pluginManager->getPluginsMeta(); 1493 $pluginMeta = $pluginManager->getPluginsMeta();
1435 1494
1436 // Split plugins into 2 arrays: ordered enabled plugins and disabled. 1495 // Split plugins into 2 arrays: ordered enabled plugins and disabled.
1437 $enabledPlugins = array_filter($pluginMeta, function($v) { return $v['order'] !== false; }); 1496 $enabledPlugins = array_filter($pluginMeta, function ($v) {
1497 return $v['order'] !== false;
1498 });
1438 // Load parameters. 1499 // Load parameters.
1439 $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $conf->get('plugins', array())); 1500 $enabledPlugins = load_plugin_parameter_values($enabledPlugins, $conf->get('plugins', array()));
1440 uasort( 1501 uasort(
1441 $enabledPlugins, 1502 $enabledPlugins,
1442 function($a, $b) { return $a['order'] - $b['order']; } 1503 function ($a, $b) {
1504 return $a['order'] - $b['order'];
1505 }
1443 ); 1506 );
1444 $disabledPlugins = array_filter($pluginMeta, function($v) { return $v['order'] === false; }); 1507 $disabledPlugins = array_filter($pluginMeta, function ($v) {
1508 return $v['order'] === false;
1509 });
1445 1510
1446 $PAGE->assign('enabledPlugins', $enabledPlugins); 1511 $PAGE->assign('enabledPlugins', $enabledPlugins);
1447 $PAGE->assign('disabledPlugins', $disabledPlugins); 1512 $PAGE->assign('disabledPlugins', $disabledPlugins);
@@ -1458,21 +1523,23 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1458 foreach ($_POST as $param => $value) { 1523 foreach ($_POST as $param => $value) {
1459 $conf->set('plugins.'. $param, escape($value)); 1524 $conf->set('plugins.'. $param, escape($value));
1460 } 1525 }
1461 } 1526 } else {
1462 else {
1463 $conf->set('general.enabled_plugins', save_plugin_config($_POST)); 1527 $conf->set('general.enabled_plugins', save_plugin_config($_POST));
1464 } 1528 }
1465 $conf->write($loginManager->isLoggedIn()); 1529 $conf->write($loginManager->isLoggedIn());
1466 $history->updateSettings(); 1530 $history->updateSettings();
1467 } 1531 } catch (Exception $e) {
1468 catch (Exception $e) {
1469 error_log( 1532 error_log(
1470 'ERROR while saving plugin configuration:.' . PHP_EOL . 1533 'ERROR while saving plugin configuration:.' . PHP_EOL .
1471 $e->getMessage() 1534 $e->getMessage()
1472 ); 1535 );
1473 1536
1474 // TODO: do not handle exceptions/errors in JS. 1537 // TODO: do not handle exceptions/errors in JS.
1475 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do='. Router::$PAGE_PLUGINSADMIN .'\';</script>'; 1538 echo '<script>alert("'
1539 . $e->getMessage()
1540 .'");document.location=\'?do='
1541 . Router::$PAGE_PLUGINSADMIN
1542 .'\';</script>';
1476 exit; 1543 exit;
1477 } 1544 }
1478 header('Location: ?do='. Router::$PAGE_PLUGINSADMIN); 1545 header('Location: ?do='. Router::$PAGE_PLUGINSADMIN);
@@ -1486,6 +1553,43 @@ function renderPage($conf, $pluginManager, $LINKSDB, $history, $sessionManager,
1486 exit; 1553 exit;
1487 } 1554 }
1488 1555
1556 // -------- Thumbnails Update
1557 if ($targetPage == Router::$PAGE_THUMBS_UPDATE) {
1558 $ids = [];
1559 foreach ($LINKSDB as $link) {
1560 // A note or not HTTP(S)
1561 if ($link['url'][0] === '?' || ! startsWith(strtolower($link['url']), 'http')) {
1562 continue;
1563 }
1564 $ids[] = $link['id'];
1565 }
1566 $PAGE->assign('ids', $ids);
1567 $PAGE->assign('pagetitle', t('Thumbnails update') .' - '. $conf->get('general.title', 'Shaarli'));
1568 $PAGE->renderPage('thumbnails');
1569 exit;
1570 }
1571
1572 // -------- Single Thumbnail Update
1573 if ($targetPage == Router::$AJAX_THUMB_UPDATE) {
1574 if (! isset($_POST['id']) || ! ctype_digit($_POST['id'])) {
1575 http_response_code(400);
1576 exit;
1577 }
1578 $id = (int) $_POST['id'];
1579 if (empty($LINKSDB[$id])) {
1580 http_response_code(404);
1581 exit;
1582 }
1583 $thumbnailer = new Thumbnailer($conf);
1584 $link = $LINKSDB[$id];
1585 $link['thumbnail'] = $thumbnailer->get($link['url']);
1586 $LINKSDB[$id] = $link;
1587 $LINKSDB->save($conf->get('resource.page_cache'));
1588
1589 echo json_encode($link);
1590 exit;
1591 }
1592
1489 // -------- Otherwise, simply display search form and links: 1593 // -------- Otherwise, simply display search form and links:
1490 showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager); 1594 showLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager);
1491 exit; 1595 exit;
@@ -1549,9 +1653,14 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
1549 // Start index. 1653 // Start index.
1550 $i = ($page-1) * $_SESSION['LINKS_PER_PAGE']; 1654 $i = ($page-1) * $_SESSION['LINKS_PER_PAGE'];
1551 $end = $i + $_SESSION['LINKS_PER_PAGE']; 1655 $end = $i + $_SESSION['LINKS_PER_PAGE'];
1656
1657 $thumbnailsEnabled = $conf->get('thumbnails.mode', Thumbnailer::MODE_NONE) !== Thumbnailer::MODE_NONE;
1658 if ($thumbnailsEnabled) {
1659 $thumbnailer = new Thumbnailer($conf);
1660 }
1661
1552 $linkDisp = array(); 1662 $linkDisp = array();
1553 while ($i<$end && $i<count($keys)) 1663 while ($i<$end && $i<count($keys)) {
1554 {
1555 $link = $linksToDisplay[$keys[$i]]; 1664 $link = $linksToDisplay[$keys[$i]];
1556 $link['description'] = format_description( 1665 $link['description'] = format_description(
1557 $link['description'], 1666 $link['description'],
@@ -1569,9 +1678,21 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
1569 $taglist = preg_split('/\s+/', $link['tags'], -1, PREG_SPLIT_NO_EMPTY); 1678 $taglist = preg_split('/\s+/', $link['tags'], -1, PREG_SPLIT_NO_EMPTY);
1570 uasort($taglist, 'strcasecmp'); 1679 uasort($taglist, 'strcasecmp');
1571 $link['taglist'] = $taglist; 1680 $link['taglist'] = $taglist;
1681
1682 // Logged in, thumbnails enabled, not a note,
1683 // and (never retrieved yet or no valid cache file)
1684 if ($loginManager->isLoggedIn() && $thumbnailsEnabled && $link['url'][0] != '?'
1685 && (! isset($link['thumbnail']) || ($link['thumbnail'] !== false && ! is_file($link['thumbnail'])))
1686 ) {
1687 $elem = $LINKSDB[$keys[$i]];
1688 $elem['thumbnail'] = $thumbnailer->get($link['url']);
1689 $LINKSDB[$keys[$i]] = $elem;
1690 $updateDB = true;
1691 $link['thumbnail'] = $elem['thumbnail'];
1692 }
1693
1572 // Check for both signs of a note: starting with ? and 7 chars long. 1694 // Check for both signs of a note: starting with ? and 7 chars long.
1573 if ($link['url'][0] === '?' && 1695 if ($link['url'][0] === '?' && strlen($link['url']) === 7) {
1574 strlen($link['url']) === 7) {
1575 $link['url'] = index_url($_SERVER) . $link['url']; 1696 $link['url'] = index_url($_SERVER) . $link['url'];
1576 } 1697 }
1577 1698
@@ -1579,6 +1700,11 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
1579 $i++; 1700 $i++;
1580 } 1701 }
1581 1702
1703 // If we retrieved new thumbnails, we update the database.
1704 if (!empty($updateDB)) {
1705 $LINKSDB->save($conf->get('resource.page_cache'));
1706 }
1707
1582 // Compute paging navigation 1708 // Compute paging navigation
1583 $searchtagsUrl = $searchtags === '' ? '' : '&searchtags=' . urlencode($searchtags); 1709 $searchtagsUrl = $searchtags === '' ? '' : '&searchtags=' . urlencode($searchtags);
1584 $searchtermUrl = empty($searchterm) ? '' : '&searchterm=' . urlencode($searchterm); 1710 $searchtermUrl = empty($searchterm) ? '' : '&searchterm=' . urlencode($searchterm);
@@ -1630,194 +1756,6 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
1630} 1756}
1631 1757
1632/** 1758/**
1633 * Compute the thumbnail for a link.
1634 *
1635 * With a link to the original URL.
1636 * Understands various services (youtube.com...)
1637 * Input: $url = URL for which the thumbnail must be found.
1638 * $href = if provided, this URL will be followed instead of $url
1639 * Returns an associative array with thumbnail attributes (src,href,width,height,style,alt)
1640 * Some of them may be missing.
1641 * Return an empty array if no thumbnail available.
1642 *
1643 * @param ConfigManager $conf Configuration Manager instance.
1644 * @param string $url
1645 * @param string|bool $href
1646 *
1647 * @return array
1648 */
1649function computeThumbnail($conf, $url, $href = false)
1650{
1651 if (!$conf->get('thumbnail.enable_thumbnails')) return array();
1652 if ($href==false) $href=$url;
1653
1654 // For most hosts, the URL of the thumbnail can be easily deduced from the URL of the link.
1655 // (e.g. http://www.youtube.com/watch?v=spVypYk4kto ---> http://img.youtube.com/vi/spVypYk4kto/default.jpg )
1656 // ^^^^^^^^^^^ ^^^^^^^^^^^
1657 $domain = parse_url($url,PHP_URL_HOST);
1658 if ($domain=='youtube.com' || $domain=='www.youtube.com')
1659 {
1660 parse_str(parse_url($url,PHP_URL_QUERY), $params); // Extract video ID and get thumbnail
1661 if (!empty($params['v'])) return array('src'=>'https://img.youtube.com/vi/'.$params['v'].'/default.jpg',
1662 'href'=>$href,'width'=>'120','height'=>'90','alt'=>'YouTube thumbnail');
1663 }
1664 if ($domain=='youtu.be') // Youtube short links
1665 {
1666 $path = parse_url($url,PHP_URL_PATH);
1667 return array('src'=>'https://img.youtube.com/vi'.$path.'/default.jpg',
1668 'href'=>$href,'width'=>'120','height'=>'90','alt'=>'YouTube thumbnail');
1669 }
1670 if ($domain=='pix.toile-libre.org') // pix.toile-libre.org image hosting
1671 {
1672 parse_str(parse_url($url,PHP_URL_QUERY), $params); // Extract image filename.
1673 if (!empty($params) && !empty($params['img'])) return array('src'=>'http://pix.toile-libre.org/upload/thumb/'.urlencode($params['img']),
1674 'href'=>$href,'style'=>'max-width:120px; max-height:150px','alt'=>'pix.toile-libre.org thumbnail');
1675 }
1676
1677 if ($domain=='imgur.com')
1678 {
1679 $path = parse_url($url,PHP_URL_PATH);
1680 if (startsWith($path,'/a/')) return array(); // Thumbnails for albums are not available.
1681 if (startsWith($path,'/r/')) return array('src'=>'https://i.imgur.com/'.basename($path).'s.jpg',
1682 'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
1683 if (startsWith($path,'/gallery/')) return array('src'=>'https://i.imgur.com'.substr($path,8).'s.jpg',
1684 'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
1685
1686 if (substr_count($path,'/')==1) return array('src'=>'https://i.imgur.com/'.substr($path,1).'s.jpg',
1687 'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
1688 }
1689 if ($domain=='i.imgur.com')
1690 {
1691 $pi = pathinfo(parse_url($url,PHP_URL_PATH));
1692 if (!empty($pi['filename'])) return array('src'=>'https://i.imgur.com/'.$pi['filename'].'s.jpg',
1693 'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
1694 }
1695 if ($domain=='dailymotion.com' || $domain=='www.dailymotion.com')
1696 {
1697 if (strpos($url,'dailymotion.com/video/')!==false)
1698 {
1699 $thumburl=str_replace('dailymotion.com/video/','dailymotion.com/thumbnail/video/',$url);
1700 return array('src'=>$thumburl,
1701 'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'DailyMotion thumbnail');
1702 }
1703 }
1704 if (endsWith($domain,'.imageshack.us'))
1705 {
1706 $ext=strtolower(pathinfo($url,PATHINFO_EXTENSION));
1707 if ($ext=='jpg' || $ext=='jpeg' || $ext=='png' || $ext=='gif')
1708 {
1709 $thumburl = substr($url,0,strlen($url)-strlen($ext)).'th.'.$ext;
1710 return array('src'=>$thumburl,
1711 'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'imageshack.us thumbnail');
1712 }
1713 }
1714
1715 // Some other hosts are SLOW AS HELL and usually require an extra HTTP request to get the thumbnail URL.
1716 // So we deport the thumbnail generation in order not to slow down page generation
1717 // (and we also cache the thumbnail)
1718
1719 if (! $conf->get('thumbnail.enable_localcache')) return array(); // If local cache is disabled, no thumbnails for services which require the use a local cache.
1720
1721 if ($domain=='flickr.com' || endsWith($domain,'.flickr.com')
1722 || $domain=='vimeo.com'
1723 || $domain=='ted.com' || endsWith($domain,'.ted.com')
1724 || $domain=='xkcd.com' || endsWith($domain,'.xkcd.com')
1725 )
1726 {
1727 if ($domain=='vimeo.com')
1728 { // Make sure this vimeo URL points to a video (/xxx... where xxx is numeric)
1729 $path = parse_url($url,PHP_URL_PATH);
1730 if (!preg_match('!/\d+.+?!',$path)) return array(); // This is not a single video URL.
1731 }
1732 if ($domain=='xkcd.com' || endsWith($domain,'.xkcd.com'))
1733 { // Make sure this URL points to a single comic (/xxx... where xxx is numeric)
1734 $path = parse_url($url,PHP_URL_PATH);
1735 if (!preg_match('!/\d+.+?!',$path)) return array();
1736 }
1737 if ($domain=='ted.com' || endsWith($domain,'.ted.com'))
1738 { // Make sure this TED URL points to a video (/talks/...)
1739 $path = parse_url($url,PHP_URL_PATH);
1740 if ("/talks/" !== substr($path,0,7)) return array(); // This is not a single video URL.
1741 }
1742 $sign = hash_hmac('sha256', $url, $conf->get('credentials.salt')); // We use the salt to sign data (it's random, secret, and specific to each installation)
1743 return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
1744 'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail');
1745 }
1746
1747 // For all other, we try to make a thumbnail of links ending with .jpg/jpeg/png/gif
1748 // Technically speaking, we should download ALL links and check their Content-Type to see if they are images.
1749 // But using the extension will do.
1750 $ext=strtolower(pathinfo($url,PATHINFO_EXTENSION));
1751 if ($ext=='jpg' || $ext=='jpeg' || $ext=='png' || $ext=='gif')
1752 {
1753 $sign = hash_hmac('sha256', $url, $conf->get('credentials.salt')); // We use the salt to sign data (it's random, secret, and specific to each installation)
1754 return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
1755 'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail');
1756 }
1757 return array(); // No thumbnail.
1758
1759}
1760
1761
1762// Returns the HTML code to display a thumbnail for a link
1763// with a link to the original URL.
1764// Understands various services (youtube.com...)
1765// Input: $url = URL for which the thumbnail must be found.
1766// $href = if provided, this URL will be followed instead of $url
1767// Returns '' if no thumbnail available.
1768function thumbnail($url,$href=false)
1769{
1770 // FIXME!
1771 global $conf;
1772 $t = computeThumbnail($conf, $url,$href);
1773 if (count($t)==0) return ''; // Empty array = no thumbnail for this URL.
1774
1775 $html='<a href="'.escape($t['href']).'"><img src="'.escape($t['src']).'"';
1776 if (!empty($t['width'])) $html.=' width="'.escape($t['width']).'"';
1777 if (!empty($t['height'])) $html.=' height="'.escape($t['height']).'"';
1778 if (!empty($t['style'])) $html.=' style="'.escape($t['style']).'"';
1779 if (!empty($t['alt'])) $html.=' alt="'.escape($t['alt']).'"';
1780 $html.='></a>';
1781 return $html;
1782}
1783
1784// Returns the HTML code to display a thumbnail for a link
1785// for the picture wall (using lazy image loading)
1786// Understands various services (youtube.com...)
1787// Input: $url = URL for which the thumbnail must be found.
1788// $href = if provided, this URL will be followed instead of $url
1789// Returns '' if no thumbnail available.
1790function lazyThumbnail($conf, $url,$href=false)
1791{
1792 // FIXME!
1793 global $conf;
1794 $t = computeThumbnail($conf, $url,$href);
1795 if (count($t)==0) return ''; // Empty array = no thumbnail for this URL.
1796
1797 $html='<a href="'.escape($t['href']).'">';
1798
1799 // Lazy image
1800 $html.='<img class="b-lazy" src="#" data-src="'.escape($t['src']).'"';
1801
1802 if (!empty($t['width'])) $html.=' width="'.escape($t['width']).'"';
1803 if (!empty($t['height'])) $html.=' height="'.escape($t['height']).'"';
1804 if (!empty($t['style'])) $html.=' style="'.escape($t['style']).'"';
1805 if (!empty($t['alt'])) $html.=' alt="'.escape($t['alt']).'"';
1806 $html.='>';
1807
1808 // No-JavaScript fallback.
1809 $html.='<noscript><img src="'.escape($t['src']).'"';
1810 if (!empty($t['width'])) $html.=' width="'.escape($t['width']).'"';
1811 if (!empty($t['height'])) $html.=' height="'.escape($t['height']).'"';
1812 if (!empty($t['style'])) $html.=' style="'.escape($t['style']).'"';
1813 if (!empty($t['alt'])) $html.=' alt="'.escape($t['alt']).'"';
1814 $html.='></noscript></a>';
1815
1816 return $html;
1817}
1818
1819
1820/**
1821 * Installation 1759 * Installation
1822 * This function should NEVER be called if the file data/config.php exists. 1760 * This function should NEVER be called if the file data/config.php exists.
1823 * 1761 *
@@ -1825,16 +1763,19 @@ function lazyThumbnail($conf, $url,$href=false)
1825 * @param SessionManager $sessionManager SessionManager instance 1763 * @param SessionManager $sessionManager SessionManager instance
1826 * @param LoginManager $loginManager LoginManager instance 1764 * @param LoginManager $loginManager LoginManager instance
1827 */ 1765 */
1828function install($conf, $sessionManager, $loginManager) { 1766function install($conf, $sessionManager, $loginManager)
1767{
1829 // On free.fr host, make sure the /sessions directory exists, otherwise login will not work. 1768 // On free.fr host, make sure the /sessions directory exists, otherwise login will not work.
1830 if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705); 1769 if (endsWith($_SERVER['HTTP_HOST'], '.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) {
1770 mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions', 0705);
1771 }
1831 1772
1832 1773
1833 // This part makes sure sessions works correctly. 1774 // This part makes sure sessions works correctly.
1834 // (Because on some hosts, session.save_path may not be set correctly, 1775 // (Because on some hosts, session.save_path may not be set correctly,
1835 // or we may not have write access to it.) 1776 // or we may not have write access to it.)
1836 if (isset($_GET['test_session']) && ( !isset($_SESSION) || !isset($_SESSION['session_tested']) || $_SESSION['session_tested']!='Working')) 1777 if (isset($_GET['test_session'])
1837 { 1778 && ( !isset($_SESSION) || !isset($_SESSION['session_tested']) || $_SESSION['session_tested']!='Working')) {
1838 // Step 2: Check if data in session is correct. 1779 // Step 2: Check if data in session is correct.
1839 $msg = t( 1780 $msg = t(
1840 '<pre>Sessions do not seem to work correctly on your server.<br>'. 1781 '<pre>Sessions do not seem to work correctly on your server.<br>'.
@@ -1850,19 +1791,18 @@ function install($conf, $sessionManager, $loginManager) {
1850 echo '<br><a href="?">'. t('Click to try again.') .'</a></pre>'; 1791 echo '<br><a href="?">'. t('Click to try again.') .'</a></pre>';
1851 die; 1792 die;
1852 } 1793 }
1853 if (!isset($_SESSION['session_tested'])) 1794 if (!isset($_SESSION['session_tested'])) {
1854 { // Step 1 : Try to store data in session and reload page. 1795 // Step 1 : Try to store data in session and reload page.
1855 $_SESSION['session_tested'] = 'Working'; // Try to set a variable in session. 1796 $_SESSION['session_tested'] = 'Working'; // Try to set a variable in session.
1856 header('Location: '.index_url($_SERVER).'?test_session'); // Redirect to check stored data. 1797 header('Location: '.index_url($_SERVER).'?test_session'); // Redirect to check stored data.
1857 } 1798 }
1858 if (isset($_GET['test_session'])) 1799 if (isset($_GET['test_session'])) {
1859 { // Step 3: Sessions are OK. Remove test parameter from URL. 1800 // Step 3: Sessions are OK. Remove test parameter from URL.
1860 header('Location: '.index_url($_SERVER)); 1801 header('Location: '.index_url($_SERVER));
1861 } 1802 }
1862 1803
1863 1804
1864 if (!empty($_POST['setlogin']) && !empty($_POST['setpassword'])) 1805 if (!empty($_POST['setlogin']) && !empty($_POST['setpassword'])) {
1865 {
1866 $tz = 'UTC'; 1806 $tz = 'UTC';
1867 if (!empty($_POST['continent']) && !empty($_POST['city']) 1807 if (!empty($_POST['continent']) && !empty($_POST['city'])
1868 && isTimeZoneValid($_POST['continent'], $_POST['city']) 1808 && isTimeZoneValid($_POST['continent'], $_POST['city'])
@@ -1893,22 +1833,24 @@ function install($conf, $sessionManager, $loginManager) {
1893 try { 1833 try {
1894 // Everything is ok, let's create config file. 1834 // Everything is ok, let's create config file.
1895 $conf->write($loginManager->isLoggedIn()); 1835 $conf->write($loginManager->isLoggedIn());
1896 } 1836 } catch (Exception $e) {
1897 catch(Exception $e) {
1898 error_log( 1837 error_log(
1899 'ERROR while writing config file after installation.' . PHP_EOL . 1838 'ERROR while writing config file after installation.' . PHP_EOL .
1900 $e->getMessage() 1839 $e->getMessage()
1901 ); 1840 );
1902 1841
1903 // TODO: do not handle exceptions/errors in JS. 1842 // TODO: do not handle exceptions/errors in JS.
1904 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?\';</script>'; 1843 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?\';</script>';
1905 exit; 1844 exit;
1906 } 1845 }
1907 echo '<script>alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>'; 1846 echo '<script>alert('
1847 .'"Shaarli is now configured. '
1848 .'Please enter your login/password and start shaaring your links!"'
1849 .');document.location=\'?do=login\';</script>';
1908 exit; 1850 exit;
1909 } 1851 }
1910 1852
1911 $PAGE = new PageBuilder($conf, null, $sessionManager->generateToken()); 1853 $PAGE = new PageBuilder($conf, $_SESSION, null, $sessionManager->generateToken());
1912 list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get()); 1854 list($continents, $cities) = generateTimeZoneData(timezone_identifiers_list(), date_default_timezone_get());
1913 $PAGE->assign('continents', $continents); 1855 $PAGE->assign('continents', $continents);
1914 $PAGE->assign('cities', $cities); 1856 $PAGE->assign('cities', $cities);
@@ -1917,240 +1859,18 @@ function install($conf, $sessionManager, $loginManager) {
1917 exit; 1859 exit;
1918} 1860}
1919 1861
1920/** 1862if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) {
1921 * Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL, 1863 showDailyRSS($conf, $loginManager);
1922 * I have deported the thumbnail URL code generation here, otherwise this would slow down page generation. 1864 exit;
1923 * The following function takes the URL a link (e.g. a flickr page) and return the proper thumbnail.
1924 * This function is called by passing the URL:
1925 * http://mywebsite.com/shaarli/?do=genthumbnail&hmac=[HMAC]&url=[URL]
1926 * [URL] is the URL of the link (e.g. a flickr page)
1927 * [HMAC] is the signature for the [URL] (so that these URL cannot be forged).
1928 * The function below will fetch the image from the webservice and store it in the cache.
1929 *
1930 * @param ConfigManager $conf Configuration Manager instance,
1931 */
1932function genThumbnail($conf)
1933{
1934 // Make sure the parameters in the URL were generated by us.
1935 $sign = hash_hmac('sha256', $_GET['url'], $conf->get('credentials.salt'));
1936 if ($sign!=$_GET['hmac']) die('Naughty boy!');
1937
1938 $cacheDir = $conf->get('resource.thumbnails_cache', 'cache');
1939 // Let's see if we don't already have the image for this URL in the cache.
1940 $thumbname=hash('sha1',$_GET['url']).'.jpg';
1941 if (is_file($cacheDir .'/'. $thumbname))
1942 { // We have the thumbnail, just serve it:
1943 header('Content-Type: image/jpeg');
1944 echo file_get_contents($cacheDir .'/'. $thumbname);
1945 return;
1946 }
1947 // We may also serve a blank image (if service did not respond)
1948 $blankname=hash('sha1',$_GET['url']).'.gif';
1949 if (is_file($cacheDir .'/'. $blankname))
1950 {
1951 header('Content-Type: image/gif');
1952 echo file_get_contents($cacheDir .'/'. $blankname);
1953 return;
1954 }
1955
1956 // Otherwise, generate the thumbnail.
1957 $url = $_GET['url'];
1958 $domain = parse_url($url,PHP_URL_HOST);
1959
1960 if ($domain=='flickr.com' || endsWith($domain,'.flickr.com'))
1961 {
1962 // Crude replacement to handle new flickr domain policy (They prefer www. now)
1963 $url = str_replace('http://flickr.com/','http://www.flickr.com/',$url);
1964
1965 // Is this a link to an image, or to a flickr page ?
1966 $imageurl='';
1967 if (endsWith(parse_url($url, PHP_URL_PATH), '.jpg'))
1968 { // This is a direct link to an image. e.g. http://farm1.staticflickr.com/5/5921913_ac83ed27bd_o.jpg
1969 preg_match('!(http://farm\d+\.staticflickr\.com/\d+/\d+_\w+_)\w.jpg!',$url,$matches);
1970 if (!empty($matches[1])) $imageurl=$matches[1].'m.jpg';
1971 }
1972 else // This is a flickr page (html)
1973 {
1974 // Get the flickr html page.
1975 list($headers, $content) = get_http_response($url, 20);
1976 if (strpos($headers[0], '200 OK') !== false)
1977 {
1978 // flickr now nicely provides the URL of the thumbnail in each flickr page.
1979 preg_match('!<link rel=\"image_src\" href=\"(.+?)\"!', $content, $matches);
1980 if (!empty($matches[1])) $imageurl=$matches[1];
1981
1982 // In albums (and some other pages), the link rel="image_src" is not provided,
1983 // but flickr provides:
1984 // <meta property="og:image" content="http://farm4.staticflickr.com/3398/3239339068_25d13535ff_z.jpg" />
1985 if ($imageurl=='')
1986 {
1987 preg_match('!<meta property=\"og:image\" content=\"(.+?)\"!', $content, $matches);
1988 if (!empty($matches[1])) $imageurl=$matches[1];
1989 }
1990 }
1991 }
1992
1993 if ($imageurl!='')
1994 { // Let's download the image.
1995 // Image is 240x120, so 10 seconds to download should be enough.
1996 list($headers, $content) = get_http_response($imageurl, 10);
1997 if (strpos($headers[0], '200 OK') !== false) {
1998 // Save image to cache.
1999 file_put_contents($cacheDir .'/'. $thumbname, $content);
2000 header('Content-Type: image/jpeg');
2001 echo $content;
2002 return;
2003 }
2004 }
2005 }
2006
2007 elseif ($domain=='vimeo.com' )
2008 {
2009 // This is more complex: we have to perform a HTTP request, then parse the result.
2010 // Maybe we should deport this to JavaScript ? Example: http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-vimeo/4285098#4285098
2011 $vid = substr(parse_url($url,PHP_URL_PATH),1);
2012 list($headers, $content) = get_http_response('https://vimeo.com/api/v2/video/'.escape($vid).'.php', 5);
2013 if (strpos($headers[0], '200 OK') !== false) {
2014 $t = unserialize($content);
2015 $imageurl = $t[0]['thumbnail_medium'];
2016 // Then we download the image and serve it to our client.
2017 list($headers, $content) = get_http_response($imageurl, 10);
2018 if (strpos($headers[0], '200 OK') !== false) {
2019 // Save image to cache.
2020 file_put_contents($cacheDir .'/'. $thumbname, $content);
2021 header('Content-Type: image/jpeg');
2022 echo $content;
2023 return;
2024 }
2025 }
2026 }
2027
2028 elseif ($domain=='ted.com' || endsWith($domain,'.ted.com'))
2029 {
2030 // The thumbnail for TED talks is located in the <link rel="image_src" [...]> tag on that page
2031 // http://www.ted.com/talks/mikko_hypponen_fighting_viruses_defending_the_net.html
2032 // <link rel="image_src" href="http://images.ted.com/images/ted/28bced335898ba54d4441809c5b1112ffaf36781_389x292.jpg" />
2033 list($headers, $content) = get_http_response($url, 5);
2034 if (strpos($headers[0], '200 OK') !== false) {
2035 // Extract the link to the thumbnail
2036 preg_match('!link rel="image_src" href="(http://images.ted.com/images/ted/.+_\d+x\d+\.jpg)"!', $content, $matches);
2037 if (!empty($matches[1]))
2038 { // Let's download the image.
2039 $imageurl=$matches[1];
2040 // No control on image size, so wait long enough
2041 list($headers, $content) = get_http_response($imageurl, 20);
2042 if (strpos($headers[0], '200 OK') !== false) {
2043 $filepath = $cacheDir .'/'. $thumbname;
2044 file_put_contents($filepath, $content); // Save image to cache.
2045 if (resizeImage($filepath))
2046 {
2047 header('Content-Type: image/jpeg');
2048 echo file_get_contents($filepath);
2049 return;
2050 }
2051 }
2052 }
2053 }
2054 }
2055
2056 elseif ($domain=='xkcd.com' || endsWith($domain,'.xkcd.com'))
2057 {
2058 // There is no thumbnail available for xkcd comics, so download the whole image and resize it.
2059 // http://xkcd.com/327/
2060 // <img src="http://imgs.xkcd.com/comics/exploits_of_a_mom.png" title="<BLABLA>" alt="<BLABLA>" />
2061 list($headers, $content) = get_http_response($url, 5);
2062 if (strpos($headers[0], '200 OK') !== false) {
2063 // Extract the link to the thumbnail
2064 preg_match('!<img src="(http://imgs.xkcd.com/comics/.*)" title="[^s]!', $content, $matches);
2065 if (!empty($matches[1]))
2066 { // Let's download the image.
2067 $imageurl=$matches[1];
2068 // No control on image size, so wait long enough
2069 list($headers, $content) = get_http_response($imageurl, 20);
2070 if (strpos($headers[0], '200 OK') !== false) {
2071 $filepath = $cacheDir.'/'.$thumbname;
2072 // Save image to cache.
2073 file_put_contents($filepath, $content);
2074 if (resizeImage($filepath))
2075 {
2076 header('Content-Type: image/jpeg');
2077 echo file_get_contents($filepath);
2078 return;
2079 }
2080 }
2081 }
2082 }
2083 }
2084
2085 else
2086 {
2087 // For all other domains, we try to download the image and make a thumbnail.
2088 // We allow 30 seconds max to download (and downloads are limited to 4 Mb)
2089 list($headers, $content) = get_http_response($url, 30);
2090 if (strpos($headers[0], '200 OK') !== false) {
2091 $filepath = $cacheDir .'/'.$thumbname;
2092 // Save image to cache.
2093 file_put_contents($filepath, $content);
2094 if (resizeImage($filepath))
2095 {
2096 header('Content-Type: image/jpeg');
2097 echo file_get_contents($filepath);
2098 return;
2099 }
2100 }
2101 }
2102
2103
2104 // Otherwise, return an empty image (8x8 transparent gif)
2105 $blankgif = base64_decode('R0lGODlhCAAIAIAAAP///////yH5BAEKAAEALAAAAAAIAAgAAAIHjI+py+1dAAA7');
2106 // Also put something in cache so that this URL is not requested twice.
2107 file_put_contents($cacheDir .'/'. $blankname, $blankgif);
2108 header('Content-Type: image/gif');
2109 echo $blankgif;
2110}
2111
2112// Make a thumbnail of the image (to width: 120 pixels)
2113// Returns true if success, false otherwise.
2114function resizeImage($filepath)
2115{
2116 if (!function_exists('imagecreatefromjpeg')) return false; // GD not present: no thumbnail possible.
2117
2118 // Trick: some stupid people rename GIF as JPEG... or else.
2119 // So we really try to open each image type whatever the extension is.
2120 $header=file_get_contents($filepath,false,NULL,0,256); // Read first 256 bytes and try to sniff file type.
2121 $im=false;
2122 $i=strpos($header,'GIF8'); if (($i!==false) && ($i==0)) $im = imagecreatefromgif($filepath); // Well this is crude, but it should be enough.
2123 $i=strpos($header,'PNG'); if (($i!==false) && ($i==1)) $im = imagecreatefrompng($filepath);
2124 $i=strpos($header,'JFIF'); if ($i!==false) $im = imagecreatefromjpeg($filepath);
2125 if (!$im) return false; // Unable to open image (corrupted or not an image)
2126 $w = imagesx($im);
2127 $h = imagesy($im);
2128 $ystart = 0; $yheight=$h;
2129 if ($h>$w) { $ystart= ($h/2)-($w/2); $yheight=$w/2; }
2130 $nw = 120; // Desired width
2131 $nh = min(floor(($h*$nw)/$w),120); // Compute new width/height, but maximum 120 pixels height.
2132 // Resize image:
2133 $im2 = imagecreatetruecolor($nw,$nh);
2134 imagecopyresampled($im2, $im, 0, 0, 0, $ystart, $nw, $nh, $w, $yheight);
2135 imageinterlace($im2,true); // For progressive JPEG.
2136 $tempname=$filepath.'_TEMP.jpg';
2137 imagejpeg($im2, $tempname, 90);
2138 imagedestroy($im);
2139 imagedestroy($im2);
2140 unlink($filepath);
2141 rename($tempname,$filepath); // Overwrite original picture with thumbnail.
2142 return true;
2143} 1865}
2144 1866
2145if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=genthumbnail')) { genThumbnail($conf); exit; } // Thumbnail generation/cache does not need the link database.
2146if (isset($_SERVER['QUERY_STRING']) && startsWith($_SERVER['QUERY_STRING'], 'do=dailyrss')) { showDailyRSS($conf); exit; }
2147if (!isset($_SESSION['LINKS_PER_PAGE'])) { 1867if (!isset($_SESSION['LINKS_PER_PAGE'])) {
2148 $_SESSION['LINKS_PER_PAGE'] = $conf->get('general.links_per_page', 20); 1868 $_SESSION['LINKS_PER_PAGE'] = $conf->get('general.links_per_page', 20);
2149} 1869}
2150 1870
2151try { 1871try {
2152 $history = new History($conf->get('resource.history')); 1872 $history = new History($conf->get('resource.history'));
2153} catch(Exception $e) { 1873} catch (Exception $e) {
2154 die($e->getMessage()); 1874 die($e->getMessage());
2155} 1875}
2156 1876
@@ -2169,7 +1889,7 @@ $container['history'] = $history;
2169$app = new \Slim\App($container); 1889$app = new \Slim\App($container);
2170 1890
2171// REST API routes 1891// REST API routes
2172$app->group('/api/v1', function() { 1892$app->group('/api/v1', function () {
2173 $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo'); 1893 $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo');
2174 $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks')->setName('getLinks'); 1894 $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks')->setName('getLinks');
2175 $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink')->setName('getLink'); 1895 $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink')->setName('getLink');
@@ -2186,6 +1906,7 @@ $app->group('/api/v1', function() {
2186})->add('\Shaarli\Api\ApiMiddleware'); 1906})->add('\Shaarli\Api\ApiMiddleware');
2187 1907
2188$response = $app->run(true); 1908$response = $app->run(true);
1909
2189// Hack to make Slim and Shaarli router work together: 1910// Hack to make Slim and Shaarli router work together:
2190// If a Slim route isn't found and NOT API call, we call renderPage(). 1911// If a Slim route isn't found and NOT API call, we call renderPage().
2191if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) { 1912if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) {
@@ -2193,5 +1914,12 @@ if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v
2193 header('Content-Type: text/html; charset=utf-8'); 1914 header('Content-Type: text/html; charset=utf-8');
2194 renderPage($conf, $pluginManager, $linkDb, $history, $sessionManager, $loginManager); 1915 renderPage($conf, $pluginManager, $linkDb, $history, $sessionManager, $loginManager);
2195} else { 1916} else {
1917 $response = $response
1918 ->withHeader('Access-Control-Allow-Origin', '*')
1919 ->withHeader(
1920 'Access-Control-Allow-Headers',
1921 'X-Requested-With, Content-Type, Accept, Origin, Authorization'
1922 )
1923 ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
2196 $app->respond($response); 1924 $app->respond($response);
2197} 1925}