/** * Template for the list of links (<div id="linklist">) * This function fills all the necessary fields in the $PAGE for the template 'linklist.html' * * @param pageBuilder $PAGE pageBuilder instance. * @param LinkDB $LINKSDB LinkDB instance. */ function buildLinkList($PAGE, $LINKSDB) { // Used in templates $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : ''; $searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : ''; // Smallhash filter if (!empty($_SERVER['QUERY_STRING']) && preg_match('/^[a-zA-Z0-9-_@]{6}($|&|#)/', $_SERVER['QUERY_STRING'])) { try { $linksToDisplay = $LINKSDB->filterHash($_SERVER['QUERY_STRING']); } catch (LinkNotFoundException $e) { $PAGE->render404($e->getMessage()); exit; } } else { // Filter links according search parameters. $privateonly = !empty($_SESSION['privateonly']); $linksToDisplay = $LINKSDB->filterSearch($_GET, false, $privateonly); } // ---- Handle paging. $keys = array(); foreach ($linksToDisplay as $key => $value) { $keys[] = $key; } // If there is only a single link, we change on-the-fly the title of the page. if (count($linksToDisplay) == 1) { $GLOBALS['pagetitle'] = $linksToDisplay[$keys[0]]['title'] . ' - ' . $GLOBALS['title']; } // Select articles according to paging. $pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']); $pagecount = $pagecount == 0 ? 1 : $pagecount; $page = empty($_GET['page']) ? 1 : intval($_GET['page']); $page = $page < 1 ? 1 : $page; $page = $page > $pagecount ? $pagecount : $page; // Start index. $i = ($page - 1) * $_SESSION['LINKS_PER_PAGE']; $end = $i + $_SESSION['LINKS_PER_PAGE']; $linkDisp = array(); while ($i < $end && $i < count($keys)) { $link = $linksToDisplay[$keys[$i]]; $link['description'] = format_description($link['description'], $GLOBALS['redirector']); $classLi = $i % 2 != 0 ? '' : 'publicLinkHightLight'; $link['class'] = $link['private'] == 0 ? $classLi : 'private'; $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); $link['timestamp'] = $date->getTimestamp(); $taglist = explode(' ', $link['tags']); uasort($taglist, 'strcasecmp'); $link['taglist'] = $taglist; $link['shorturl'] = smallHash($link['linkdate']); // Check for both signs of a note: starting with ? and 7 chars long. if ($link['url'][0] === '?' && strlen($link['url']) === 7) { $link['url'] = index_url($_SERVER) . $link['url']; } $linkDisp[$keys[$i]] = $link; $i++; } // Compute paging navigation $searchtagsUrl = empty($searchtags) ? '' : '&searchtags=' . urlencode($searchtags); $searchtermUrl = empty($searchterm) ? '' : '&searchterm=' . urlencode($searchterm); $previous_page_url = ''; if ($i != count($keys)) { $previous_page_url = '?page=' . ($page + 1) . $searchtermUrl . $searchtagsUrl; } $next_page_url = ''; if ($page > 1) { $next_page_url = '?page=' . ($page - 1) . $searchtermUrl . $searchtagsUrl; } $token = isLoggedIn() ? getToken() : ''; // Fill all template fields. $data = array('previous_page_url' => $previous_page_url, 'next_page_url' => $next_page_url, 'page_current' => $page, 'page_max' => $pagecount, 'result_count' => count($linksToDisplay), 'search_term' => $searchterm, 'search_tags' => $searchtags, 'redirector' => empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'], 'token' => $token, 'links' => $linkDisp, 'tags' => $LINKSDB->allTags()); // FIXME! temporary fix - see #399. if (!empty($GLOBALS['pagetitle']) && count($linkDisp) == 1) { $data['pagetitle'] = $GLOBALS['pagetitle']; } $pluginManager = PluginManager::getInstance(); $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => isLoggedIn())); foreach ($data as $key => $value) { $PAGE->assign($key, $value); } return; }
function buildLinkList($PAGE, $LINKSDB) { // ---- Filter link database according to parameters $search_type = ''; $search_crits = ''; $privateonly = !empty($_SESSION['privateonly']) ? true : false; // Fulltext search if (isset($_GET['searchterm'])) { $search_crits = escape(trim($_GET['searchterm'])); $search_type = LinkFilter::$FILTER_TEXT; $linksToDisplay = $LINKSDB->filter($search_type, $search_crits, false, $privateonly); } elseif (isset($_GET['searchtags'])) { $search_crits = explode(' ', escape(trim($_GET['searchtags']))); $search_type = LinkFilter::$FILTER_TAG; $linksToDisplay = $LINKSDB->filter($search_type, $search_crits, false, $privateonly); } elseif (isset($_SERVER['QUERY_STRING']) && preg_match('/[a-zA-Z0-9-_@]{6}(&.+?)?/', $_SERVER['QUERY_STRING'])) { $search_type = LinkFilter::$FILTER_HASH; $search_crits = substr(trim($_SERVER["QUERY_STRING"], '/'), 0, 6); $linksToDisplay = $LINKSDB->filter($search_type, $search_crits); if (count($linksToDisplay) == 0) { header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); echo '<h1>404 Not found.</h1>Oh crap. The link you are trying to reach does not exist or has been deleted.'; echo '<br>Would you mind <a href="?">clicking here</a>?'; exit; } } else { $linksToDisplay = $LINKSDB->filter('', '', false, $privateonly); } // ---- Handle paging. $keys = array(); foreach ($linksToDisplay as $key => $value) { $keys[] = $key; } // If there is only a single link, we change on-the-fly the title of the page. if (count($linksToDisplay) == 1) { $GLOBALS['pagetitle'] = $linksToDisplay[$keys[0]]['title'] . ' - ' . $GLOBALS['title']; } // Select articles according to paging. $pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']); $pagecount = $pagecount == 0 ? 1 : $pagecount; $page = empty($_GET['page']) ? 1 : intval($_GET['page']); $page = $page < 1 ? 1 : $page; $page = $page > $pagecount ? $pagecount : $page; // Start index. $i = ($page - 1) * $_SESSION['LINKS_PER_PAGE']; $end = $i + $_SESSION['LINKS_PER_PAGE']; $linkDisp = array(); while ($i < $end && $i < count($keys)) { $link = $linksToDisplay[$keys[$i]]; $link['description'] = format_description($link['description'], $GLOBALS['redirector']); $classLi = $i % 2 != 0 ? '' : 'publicLinkHightLight'; $link['class'] = $link['private'] == 0 ? $classLi : 'private'; $link['timestamp'] = linkdate2timestamp($link['linkdate']); $taglist = explode(' ', $link['tags']); uasort($taglist, 'strcasecmp'); $link['taglist'] = $taglist; $link['shorturl'] = smallHash($link['linkdate']); // Check for both signs of a note: starting with ? and 7 chars long. if ($link['url'][0] === '?' && strlen($link['url']) === 7) { $link['url'] = index_url($_SERVER) . $link['url']; } $linkDisp[$keys[$i]] = $link; $i++; } // Compute paging navigation $searchterm = empty($_GET['searchterm']) ? '' : '&searchterm=' . $_GET['searchterm']; $searchtags = empty($_GET['searchtags']) ? '' : '&searchtags=' . $_GET['searchtags']; $previous_page_url = ''; if ($i != count($keys)) { $previous_page_url = '?page=' . ($page + 1) . $searchterm . $searchtags; } $next_page_url = ''; if ($page > 1) { $next_page_url = '?page=' . ($page - 1) . $searchterm . $searchtags; } $token = ''; if (isLoggedIn()) { $token = getToken(); } // Fill all template fields. $data = array('linkcount' => count($LINKSDB), 'previous_page_url' => $previous_page_url, 'next_page_url' => $next_page_url, 'page_current' => $page, 'page_max' => $pagecount, 'result_count' => count($linksToDisplay), 'search_type' => $search_type, 'search_crits' => $search_crits, 'redirector' => empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'], 'token' => $token, 'links' => $linkDisp, 'tags' => $LINKSDB->allTags()); // FIXME! temporary fix - see #399. if (!empty($GLOBALS['pagetitle']) && count($linkDisp) == 1) { $data['pagetitle'] = $GLOBALS['pagetitle']; } $pluginManager = PluginManager::getInstance(); $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => isLoggedIn())); foreach ($data as $key => $value) { $PAGE->assign($key, $value); } return; }
/** * Represent a link by its hash */ public function testSmallHash() { $this->assertEquals('CyAAJw', smallHash('http://test.io')); $this->assertEquals(6, strlen(smallHash('https://github.com'))); }
function renderPage() { $LINKSDB = new LinkDB($GLOBALS['config']['DATASTORE'], isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'], $GLOBALS['config']['HIDE_PUBLIC_LINKS']); // -------- Display login form. if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"], 'do=login')) { if ($GLOBALS['config']['OPEN_SHAARLI']) { header('Location: ?'); exit; } // No need to login for open Shaarli $token = ''; if (ban_canLogin()) { $token = getToken(); } // Do not waste token generation if not useful. $PAGE = new pageBuilder(); $PAGE->assign('token', $token); $PAGE->assign('returnurl', isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''); $PAGE->renderPage('loginform'); exit; } // -------- User wants to logout. if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"], 'do=logout')) { invalidateCaches($GLOBALS['config']['PAGECACHE']); logout(); header('Location: ?'); exit; } // -------- Picture wall if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"], 'do=picwall')) { // Optionally filter the results: $links = array(); if (!empty($_GET['searchterm'])) { $links = $LINKSDB->filterFulltext($_GET['searchterm']); } elseif (!empty($_GET['searchtags'])) { $links = $LINKSDB->filterTags(trim($_GET['searchtags'])); } else { $links = $LINKSDB; } $body = ''; $linksToDisplay = array(); // Get only links which have a thumbnail. foreach ($links as $link) { $permalink = '?' . escape(smallhash($link['linkdate'])); $thumb = lazyThumbnail($link['url'], $permalink); if ($thumb != '') { $link['thumbnail'] = $thumb; // Thumbnail HTML code. $linksToDisplay[] = $link; // Add to array. } } $PAGE = new pageBuilder(); $PAGE->assign('linkcount', count($LINKSDB)); $PAGE->assign('linksToDisplay', $linksToDisplay); $PAGE->renderPage('picwall'); exit; } // -------- Tag cloud if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"], 'do=tagcloud')) { $tags = $LINKSDB->allTags(); // We sort tags alphabetically, then choose a font size according to count. // First, find max value. $maxcount = 0; foreach ($tags as $key => $value) { $maxcount = max($maxcount, $value); } ksort($tags); $tagList = array(); foreach ($tags as $key => $value) { $tagList[$key] = array('count' => $value, 'size' => log($value, 15) / log($maxcount, 30) * (22 - 6) + 6); } $PAGE = new pageBuilder(); $PAGE->assign('linkcount', count($LINKSDB)); $PAGE->assign('tags', $tagList); $PAGE->renderPage('tagcloud'); exit; } // -------- User clicks on a tag in a link: The tag is added to the list of searched tags (searchtags=...) 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); // Prevent redirection loop if (isset($params['addtag'])) { unset($params['addtag']); } // Check if this tag is already in the search query and ignore it if it is. // Each tag is always separated by a space if (isset($params['searchtags'])) { $current_tags = explode(' ', $params['searchtags']); } else { $current_tags = array(); } $addtag = true; foreach ($current_tags as $value) { if ($value === $_GET['addtag']) { $addtag = false; break; } } // Append the tag if necessary if (empty($params['searchtags'])) { $params['searchtags'] = trim($_GET['addtag']); } else { if ($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) header('Location: ?' . http_build_query($params)); exit; } // -------- User clicks on a tag in result count: Remove the tag from the list of searched tags (searchtags=...) if (isset($_GET['removetag'])) { // Get previous URL (http_referer) and remove the tag from the searchtags parameters in query. if (empty($_SERVER['HTTP_REFERER'])) { header('Location: ?'); exit; } // In case browser does not send HTTP_REFERER parse_str(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY), $params); // Prevent redirection loop if (isset($params['removetag'])) { unset($params['removetag']); } if (isset($params['searchtags'])) { $tags = explode(' ', $params['searchtags']); $tags = array_diff($tags, array($_GET['removetag'])); // Remove value from array $tags. if (count($tags) == 0) { unset($params['searchtags']); } else { $params['searchtags'] = implode(' ', $tags); } unset($params['page']); // We also remove page (keeping the same page has no sense, since the results are different) } header('Location: ?' . http_build_query($params)); exit; } // -------- User wants to change the number of links per page (linksperpage=...) if (isset($_GET['linksperpage'])) { if (is_numeric($_GET['linksperpage'])) { $_SESSION['LINKS_PER_PAGE'] = abs(intval($_GET['linksperpage'])); } header('Location: ' . generateLocation($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST'], array('linksperpage'))); exit; } // -------- User wants to see only private links (toggle) if (isset($_GET['privateonly'])) { if (empty($_SESSION['privateonly'])) { $_SESSION['privateonly'] = 1; // See only private links } else { unset($_SESSION['privateonly']); // See all links } header('Location: ' . generateLocation($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST'], array('privateonly'))); exit; } // -------- Handle other actions allowed for non-logged in users: if (!isLoggedIn()) { // User tries to post new link but is not logged in: // Show login screen, then redirect to ?post=... if (isset($_GET['post'])) { header('Location: ?do=login&post=' . urlencode($_GET['post']) . (!empty($_GET['title']) ? '&title=' . urlencode($_GET['title']) : '') . (!empty($_GET['description']) ? '&description=' . urlencode($_GET['description']) : '') . (!empty($_GET['source']) ? '&source=' . urlencode($_GET['source']) : '')); // Redirect to login page, then back to post link. exit; } // Same case as above except that user tried to access ?do=addlink without being logged in // Note: passing empty parameters makes Shaarli generate default URLs and descriptions. if (isset($_GET['do']) && $_GET['do'] === 'addlink') { header('Location: ?do=login&post='); exit; } if (isset($_GET['edit_link'])) { header('Location: ?do=login&edit_link=' . escape($_GET['edit_link'])); exit; } $PAGE = new pageBuilder(); buildLinkList($PAGE, $LINKSDB); // Compute list of links to display $PAGE->renderPage('linklist'); exit; // Never remove this one! All operations below are reserved for logged in user. } // -------- All other functions are reserved for the registered user: // -------- Display the Tools menu if requested (import/export/bookmarklet...) if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"], 'do=tools')) { $PAGE = new pageBuilder(); $PAGE->assign('linkcount', count($LINKSDB)); $PAGE->assign('pageabsaddr', indexUrl()); $PAGE->renderPage('tools'); exit; } // -------- User wants to change his/her password. if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"], 'do=changepasswd')) { if ($GLOBALS['config']['OPEN_SHAARLI']) { die('You are not supposed to change a password on an Open Shaarli.'); } if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) { if (!tokenOk($_POST['token'])) { die('Wrong token.'); } // Go away! // Make sure old password is correct. $oldhash = sha1($_POST['oldpassword'] . $GLOBALS['login'] . $GLOBALS['salt']); if ($oldhash != $GLOBALS['hash']) { echo '<script>alert("The old password is not correct.");document.location=\'?do=changepasswd\';</script>'; exit; } // Save new password $GLOBALS['salt'] = sha1(uniqid('', true) . '_' . mt_rand()); // Salt renders rainbow-tables attacks useless. $GLOBALS['hash'] = sha1($_POST['setpassword'] . $GLOBALS['login'] . $GLOBALS['salt']); try { writeConfig($GLOBALS, isLoggedIn()); } catch (Exception $e) { error_log('ERROR while writing config file after changing password.' . PHP_EOL . $e->getMessage()); // TODO: do not handle exceptions/errors in JS. echo '<script>alert("' . $e->getMessage() . '");document.location=\'?do=tools\';</script>'; exit; } echo '<script>alert("Your password has been changed.");document.location=\'?do=tools\';</script>'; exit; } else { $PAGE = new pageBuilder(); $PAGE->assign('linkcount', count($LINKSDB)); $PAGE->assign('token', getToken()); $PAGE->renderPage('changepassword'); exit; } } // -------- User wants to change configuration if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"], 'do=configure')) { if (!empty($_POST['title'])) { if (!tokenOk($_POST['token'])) { die('Wrong token.'); } // Go away! $tz = 'UTC'; if (!empty($_POST['continent']) && !empty($_POST['city'])) { if (isTimeZoneValid($_POST['continent'], $_POST['city'])) { $tz = $_POST['continent'] . '/' . $_POST['city']; } } $GLOBALS['timezone'] = $tz; $GLOBALS['title'] = $_POST['title']; $GLOBALS['titleLink'] = $_POST['titleLink']; $GLOBALS['redirector'] = $_POST['redirector']; $GLOBALS['disablesessionprotection'] = !empty($_POST['disablesessionprotection']); $GLOBALS['privateLinkByDefault'] = !empty($_POST['privateLinkByDefault']); $GLOBALS['config']['ENABLE_RSS_PERMALINKS'] = !empty($_POST['enableRssPermalinks']); $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']); $GLOBALS['config']['HIDE_PUBLIC_LINKS'] = !empty($_POST['hidePublicLinks']); try { writeConfig($GLOBALS, isLoggedIn()); } catch (Exception $e) { error_log('ERROR while writing config file after configuration update.' . PHP_EOL . $e->getMessage()); // TODO: do not handle exceptions/errors in JS. echo '<script>alert("' . $e->getMessage() . '");document.location=\'?do=tools\';</script>'; exit; } echo '<script>alert("Configuration was saved.");document.location=\'?do=tools\';</script>'; exit; } else { $PAGE = new pageBuilder(); $PAGE->assign('linkcount', count($LINKSDB)); $PAGE->assign('token', getToken()); $PAGE->assign('title', empty($GLOBALS['title']) ? '' : $GLOBALS['title']); $PAGE->assign('redirector', empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector']); list($timezone_form, $timezone_js) = generateTimeZoneForm($GLOBALS['timezone']); $PAGE->assign('timezone_form', $timezone_form); $PAGE->assign('timezone_js', $timezone_js); $PAGE->renderPage('configure'); exit; } } // -------- User wants to rename a tag or delete it if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"], 'do=changetag')) { if (empty($_POST['fromtag'])) { $PAGE = new pageBuilder(); $PAGE->assign('linkcount', count($LINKSDB)); $PAGE->assign('token', getToken()); $PAGE->assign('tags', $LINKSDB->allTags()); $PAGE->renderPage('changetag'); exit; } if (!tokenOk($_POST['token'])) { die('Wrong token.'); } // Delete a tag: if (!empty($_POST['deletetag']) && !empty($_POST['fromtag'])) { $needle = trim($_POST['fromtag']); $linksToAlter = $LINKSDB->filterTags($needle, true); // True for case-sensitive tag search. foreach ($linksToAlter as $key => $value) { $tags = explode(' ', trim($value['tags'])); unset($tags[array_search($needle, $tags)]); // Remove tag. $value['tags'] = trim(implode(' ', $tags)); $LINKSDB[$key] = $value; } $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk. echo '<script>alert("Tag was removed from ' . count($linksToAlter) . ' links.");document.location=\'?\';</script>'; exit; } // Rename a tag: if (!empty($_POST['renametag']) && !empty($_POST['fromtag']) && !empty($_POST['totag'])) { $needle = trim($_POST['fromtag']); $linksToAlter = $LINKSDB->filterTags($needle, true); // true for case-sensitive tag search. foreach ($linksToAlter as $key => $value) { $tags = explode(' ', trim($value['tags'])); $tags[array_search($needle, $tags)] = trim($_POST['totag']); // Replace tags value. $value['tags'] = trim(implode(' ', $tags)); $LINKSDB[$key] = $value; } $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk. echo '<script>alert("Tag was renamed in ' . count($linksToAlter) . ' links.");document.location=\'?searchtags=' . urlencode($_POST['totag']) . '\';</script>'; exit; } } // -------- User wants to add a link without using the bookmarklet: Show form. if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"], 'do=addlink')) { $PAGE = new pageBuilder(); $PAGE->assign('linkcount', count($LINKSDB)); $PAGE->renderPage('addlink'); exit; } // -------- User clicked the "Save" button when editing a link: Save link to database. if (isset($_POST['save_edit'])) { if (!tokenOk($_POST['token'])) { die('Wrong token.'); } // Go away! $tags = trim(preg_replace('/\\s\\s+/', ' ', $_POST['lf_tags'])); // Remove multiple spaces. $tags = implode(' ', array_unique(explode(' ', $tags))); // Remove duplicates. $linkdate = $_POST['lf_linkdate']; $url = trim($_POST['lf_url']); if (!startsWith($url, 'http:') && !startsWith($url, 'https:') && !startsWith($url, 'ftp:') && !startsWith($url, 'magnet:') && !startsWith($url, '?') && !startsWith($url, 'javascript:')) { $url = 'http://' . $url; } $link = array('title' => trim($_POST['lf_title']), 'url' => $url, 'description' => trim($_POST['lf_description']), 'private' => isset($_POST['lf_private']) ? 1 : 0, 'linkdate' => $linkdate, 'tags' => str_replace(',', ' ', $tags)); if ($link['title'] == '') { $link['title'] = $link['url']; } // If title is empty, use the URL as title. $LINKSDB[$linkdate] = $link; $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk. pubsubhub(); // If we are called from the bookmarklet, we must close the popup: if (isset($_GET['source']) && ($_GET['source'] == 'bookmarklet' || $_GET['source'] == 'firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } $returnurl = !empty($_POST['returnurl']) ? escape($_POST['returnurl']) : '?'; $returnurl .= '#' . smallHash($_POST['lf_linkdate']); // Scroll to the link which has been edited. $location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link')); header('Location: ' . $location); // After saving the link, redirect to the page the user was on. exit; } // -------- User clicked the "Cancel" button when editing a link. if (isset($_POST['cancel_edit'])) { // If we are called from the bookmarklet, we must close the popup: if (isset($_GET['source']) && ($_GET['source'] == 'bookmarklet' || $_GET['source'] == 'firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } $returnurl = isset($_POST['returnurl']) ? $_POST['returnurl'] : '?'; $returnurl .= '#' . smallHash($_POST['lf_linkdate']); // Scroll to the link which has been edited. $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 (isset($_POST['delete_link'])) { if (!tokenOk($_POST['token'])) { die('Wrong token.'); } // We do not need to ask for confirmation: // - confirmation is handled by JavaScript // - we are protected from XSRF by the token. $linkdate = $_POST['lf_linkdate']; unset($LINKSDB[$linkdate]); $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // save to disk // If we are called from the bookmarklet, we must close the popup: if (isset($_GET['source']) && ($_GET['source'] == 'bookmarklet' || $_GET['source'] == 'firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } // Pick where we're going to redirect // ============================================================= // Basically, we can't redirect to where we were previously if it was a permalink // or an edit_link, because it would 404. // Cases: // - / : nothing in $_GET, redirect to self // - /?page : redirect to self // - /?searchterm : redirect to self (there might be other links) // - /?searchtags : redirect to self // - /permalink : redirect to / (the link does not exist anymore) // - /?edit_link : redirect to / (the link does not exist anymore) // PHP treats the permalink as a $_GET variable, so we need to check if every condition for self // redirect is not satisfied, and only then redirect to / $location = "?"; // Self redirection if (count($_GET) == 0 || isset($_GET['page']) || isset($_GET['searchterm']) || isset($_GET['searchtags'])) { if (isset($_POST['returnurl'])) { $location = $_POST['returnurl']; // Handle redirects given by the form } else { $location = generateLocation($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST'], array('delete_link')); } } 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'])) { $link = $LINKSDB[$_GET['edit_link']]; // Read database if (!$link) { header('Location: ?'); exit; } // Link not found in database. $PAGE = new pageBuilder(); $PAGE->assign('linkcount', count($LINKSDB)); $PAGE->assign('link', $link); $PAGE->assign('link_is_new', false); $PAGE->assign('token', getToken()); // XSRF protection. $PAGE->assign('http_referer', isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''); $PAGE->assign('tags', $LINKSDB->allTags()); $PAGE->renderPage('editlink'); exit; } // -------- User want to post a new link: Display link edit form. if (isset($_GET['post'])) { $url = new Url($_GET['post']); $url->cleanup(); $link_is_new = false; $link = $LINKSDB->getLinkFromUrl($url); // Check if URL is not already in database (in this case, we will edit the existing link) if (!$link) { $link_is_new = true; // This is a new link $linkdate = strval(date('Ymd_His')); $title = empty($_GET['title']) ? '' : $_GET['title']; // Get title if it was provided in URL (by the bookmarklet). $description = empty($_GET['description']) ? '' : $_GET['description']; // Get description if it was provided in URL (by the bookmarklet). [Bronco added that] $tags = empty($_GET['tags']) ? '' : $_GET['tags']; // Get tags if it was provided in URL $private = !empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0; // Get private if it was provided in URL if ($url != '' && parse_url($url, PHP_URL_SCHEME) == '') { $url = 'http://' . $url; } // If this is an HTTP link, we try go get the page to extract the title (otherwise we will to straight to the edit form.) if (empty($title) && parse_url($url, PHP_URL_SCHEME) == 'http') { list($status, $headers, $data) = getHTTP($url, 4); // Short timeout to keep the application responsive. // FIXME: Decode charset according to specified in either 1) HTTP response headers or 2) <head> in html if (strpos($status, '200 OK') !== false) { // Look for charset in html header. preg_match('#<meta .*charset=.*>#Usi', $data, $meta); // If found, extract encoding. if (!empty($meta[0])) { // Get encoding specified in header. preg_match('#charset="?(.*)"#si', $meta[0], $enc); // If charset not found, use utf-8. $html_charset = !empty($enc[1]) ? strtolower($enc[1]) : 'utf-8'; } else { $html_charset = 'utf-8'; } // Extract title $title = html_extract_title($data); if (!empty($title)) { // Re-encode title in utf-8 if necessary. $title = $html_charset == 'iso-8859-1' ? utf8_encode($title) : $title; } } } if ($url == '') { $url = '?' . smallHash($linkdate); $title = 'Note: '; } $link = array('linkdate' => $linkdate, 'title' => $title, 'url' => $url, 'description' => $description, 'tags' => $tags, 'private' => $private); } $PAGE = new pageBuilder(); $PAGE->assign('linkcount', count($LINKSDB)); $PAGE->assign('link', $link); $PAGE->assign('link_is_new', $link_is_new); $PAGE->assign('token', getToken()); // XSRF protection. $PAGE->assign('http_referer', isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''); $PAGE->assign('source', isset($_GET['source']) ? $_GET['source'] : ''); $PAGE->assign('tags', $LINKSDB->allTags()); $PAGE->renderPage('editlink'); exit; } // -------- Export as Netscape Bookmarks HTML file. if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"], 'do=export')) { if (empty($_GET['what'])) { $PAGE = new pageBuilder(); $PAGE->assign('linkcount', count($LINKSDB)); $PAGE->renderPage('export'); exit; } $exportWhat = $_GET['what']; if (!array_intersect(array('all', 'public', 'private'), array($exportWhat))) { die('What are you trying to export???'); } header('Content-Type: text/html; charset=utf-8'); header('Content-disposition: attachment; filename=bookmarks_' . $exportWhat . '_' . strval(date('Ymd_His')) . '.html'); $currentdate = date('Y/m/d H:i:s'); echo <<<HTML <!DOCTYPE NETSCAPE-Bookmark-file-1> <!-- This is an automatically generated file. It will be read and overwritten. DO NOT EDIT! --> <!-- Shaarli {$exportWhat} bookmarks export on {$currentdate} --> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8"> <TITLE>Bookmarks</TITLE> <H1>Bookmarks</H1> HTML; foreach ($LINKSDB as $link) { if ($exportWhat == 'all' || $exportWhat == 'private' && $link['private'] != 0 || $exportWhat == 'public' && $link['private'] == 0) { echo '<DT><A HREF="' . $link['url'] . '" ADD_DATE="' . linkdate2timestamp($link['linkdate']) . '" PRIVATE="' . $link['private'] . '"'; if ($link['tags'] != '') { echo ' TAGS="' . str_replace(' ', ',', $link['tags']) . '"'; } echo '>' . $link['title'] . "</A>\n"; if ($link['description'] != '') { echo '<DD>' . $link['description'] . "\n"; } } } exit; } // -------- User is uploading a file for import if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"], 'do=upload')) { // If file is too big, some form field may be missing. if (!isset($_POST['token']) || !isset($_FILES) || isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size'] == 0) { $returnurl = empty($_SERVER['HTTP_REFERER']) ? '?' : $_SERVER['HTTP_REFERER']; echo '<script>alert("The file you are trying to upload is probably bigger than what this webserver can accept (' . getMaxFileSize() . ' bytes). Please upload in smaller chunks.");document.location=\'' . escape($returnurl) . '\';</script>'; exit; } if (!tokenOk($_POST['token'])) { die('Wrong token.'); } importFile(); exit; } // -------- Show upload/import dialog: if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"], 'do=import')) { $PAGE = new pageBuilder(); $PAGE->assign('linkcount', count($LINKSDB)); $PAGE->assign('token', getToken()); $PAGE->assign('maxfilesize', getMaxFileSize()); $PAGE->renderPage('import'); exit; } // -------- Otherwise, simply display search form and links: $PAGE = new pageBuilder(); buildLinkList($PAGE, $LINKSDB); // Compute list of links to display $PAGE->renderPage('linklist'); exit; }
public function filterSmallHash($smallHash) { $filtered = array(); foreach ($this->links as $l) { if ($smallHash == smallHash($l['linkdate'])) { $filtered[$l['linkdate']] = $l; return $filtered; } } return $filtered; }
/** * Returns the shaare corresponding to a smallHash. * * @param string $smallHash permalink hash. * * @return array $filtered array containing permalink data. */ private function filterSmallHash($smallHash) { $filtered = array(); foreach ($this->links as $l) { if ($smallHash == smallHash($l['linkdate'])) { // Yes, this is ugly and slow $filtered[$l['linkdate']] = $l; return $filtered; } } return $filtered; }
/** * Returns the shaare corresponding to a smallHash. * * @param string $smallHash permalink hash. * * @return array $filtered array containing permalink data. * * @throws LinkNotFoundException if the smallhash doesn't match any link. */ private function filterSmallHash($smallHash) { $filtered = array(); foreach ($this->links as $l) { if ($smallHash == smallHash($l['linkdate'])) { // Yes, this is ugly and slow $filtered[$l['linkdate']] = $l; return $filtered; } } if (empty($filtered)) { throw new LinkNotFoundException(); } return $filtered; }
function buildLinkList($PAGE, $LINKSDB) { // ---- Filter link database according to parameters $linksToDisplay = array(); $search_type = ''; $search_crits = ''; if (isset($_GET['searchterm'])) { $linksToDisplay = $LINKSDB->filterFulltext(trim($_GET['searchterm'])); $search_crits = escape(trim($_GET['searchterm'])); $search_type = 'fulltext'; } elseif (isset($_GET['searchtags'])) { $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags'])); $search_crits = explode(' ', escape(trim($_GET['searchtags']))); $search_type = 'tags'; } elseif (isset($_SERVER['QUERY_STRING']) && preg_match('/[a-zA-Z0-9-_@]{6}(&.+?)?/', $_SERVER['QUERY_STRING'])) { $linksToDisplay = $LINKSDB->filterSmallHash(substr(trim($_SERVER["QUERY_STRING"], '/'), 0, 6)); if (count($linksToDisplay) == 0) { header($_SERVER["SERVER_PROTOCOL"] . " 404 Not Found"); echo '<h1>404 Not found.</h1>Oh crap. The link you are trying to reach does not exist or has been deleted.'; echo '<br>Would you mind <a href="?">clicking here</a>?'; exit; } $search_type = 'permalink'; } else { $linksToDisplay = $LINKSDB; } // Otherwise, display without filtering. // Option: Show only private links if (!empty($_SESSION['privateonly'])) { $tmp = array(); foreach ($linksToDisplay as $linkdate => $link) { if ($link['private'] != 0) { $tmp[$linkdate] = $link; } } $linksToDisplay = $tmp; } // ---- Handle paging. /* Can someone explain to me why you get the following error when using array_keys() on an object which implements the interface ArrayAccess??? "Warning: array_keys() expects parameter 1 to be array, object given in ... " If my class implements ArrayAccess, why won't array_keys() accept it ? ( $keys=array_keys($linksToDisplay); ) */ $keys = array(); foreach ($linksToDisplay as $key => $value) { $keys[] = $key; } // Stupid and ugly. Thanks PHP. // If there is only a single link, we change on-the-fly the title of the page. if (count($linksToDisplay) == 1) { $GLOBALS['pagetitle'] = $linksToDisplay[$keys[0]]['title'] . ' - ' . $GLOBALS['title']; } // Select articles according to paging. $pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']); $pagecount = $pagecount == 0 ? 1 : $pagecount; $page = empty($_GET['page']) ? 1 : intval($_GET['page']); $page = $page < 1 ? 1 : $page; $page = $page > $pagecount ? $pagecount : $page; $i = ($page - 1) * $_SESSION['LINKS_PER_PAGE']; // Start index. $end = $i + $_SESSION['LINKS_PER_PAGE']; $linkDisp = array(); // Links to display while ($i < $end && $i < count($keys)) { $link = $linksToDisplay[$keys[$i]]; $link['description'] = format_description($link['description'], $GLOBALS['redirector']); $classLi = $i % 2 != 0 ? '' : 'publicLinkHightLight'; $link['class'] = $link['private'] == 0 ? $classLi : 'private'; $link['timestamp'] = linkdate2timestamp($link['linkdate']); $taglist = explode(' ', $link['tags']); uasort($taglist, 'strcasecmp'); $link['taglist'] = $taglist; $link['shorturl'] = smallHash($link['linkdate']); if ($link["url"][0] === '?' && strlen($link["url"]) === 7) { $link["url"] = index_url($_SERVER) . $link["url"]; } $linkDisp[$keys[$i]] = $link; $i++; } // Compute paging navigation $searchterm = empty($_GET['searchterm']) ? '' : '&searchterm=' . $_GET['searchterm']; $searchtags = empty($_GET['searchtags']) ? '' : '&searchtags=' . $_GET['searchtags']; $paging = ''; $previous_page_url = ''; if ($i != count($keys)) { $previous_page_url = '?page=' . ($page + 1) . $searchterm . $searchtags; } $next_page_url = ''; if ($page > 1) { $next_page_url = '?page=' . ($page - 1) . $searchterm . $searchtags; } $token = ''; if (isLoggedIn()) { $token = getToken(); } // Fill all template fields. $data = array('pagetitle' => $GLOBALS['pagetitle'], 'linkcount' => count($LINKSDB), 'previous_page_url' => $previous_page_url, 'next_page_url' => $next_page_url, 'page_current' => $page, 'page_max' => $pagecount, 'result_count' => count($linksToDisplay), 'search_type' => $search_type, 'search_crits' => $search_crits, 'redirector' => empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'], 'token' => $token, 'links' => $linkDisp, 'tags' => $LINKSDB->allTags()); $pluginManager = PluginManager::getInstance(); $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => isLoggedIn())); foreach ($data as $key => $value) { $PAGE->assign($key, $value); } return; }
/** * Build a feed item (one per shaare). * * @param array $link Single link array extracted from LinkDB. * @param string $pageaddr Index URL. * * @return array Link array with feed attributes. */ protected function buildItem($link, $pageaddr) { $link['guid'] = $pageaddr . '?' . smallHash($link['linkdate']); // Check for both signs of a note: starting with ? and 7 chars long. if ($link['url'][0] === '?' && strlen($link['url']) === 7) { $link['url'] = $pageaddr . $link['url']; } if ($this->usePermalinks === true) { $permalink = '<a href="' . $link['url'] . '" title="Direct link">Direct link</a>'; } else { $permalink = '<a href="' . $link['guid'] . '" title="Permalink">Permalink</a>'; } $link['description'] = format_description($link['description']) . PHP_EOL . '<br>— ' . $permalink; $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); if ($this->feedType == self::$FEED_RSS) { $link['iso_date'] = $date->format(DateTime::RSS); } else { $link['iso_date'] = $date->format(DateTime::ATOM); } // Save the more recent item. if (empty($this->latestDate) || $this->latestDate < $date) { $this->latestDate = $date; } $taglist = array_filter(explode(' ', $link['tags']), 'strlen'); uasort($taglist, 'strcasecmp'); $link['taglist'] = $taglist; return $link; }