--- /dev/null
+<?php\r
+/**\r
+ * Functions related to configuration management.\r
+ */\r
+\r
+/**\r
+ * Re-write configuration file according to given array.\r
+ * Requires mandatory fields listed in $MANDATORY_FIELDS.\r
+ *\r
+ * @param array $config contains all configuration fields.\r
+ * @param bool $isLoggedIn true if user is logged in.\r
+ *\r
+ * @return void\r
+ *\r
+ * @throws MissingFieldConfigException: a mandatory field has not been provided in $config.\r
+ * @throws UnauthorizedConfigException: user is not authorize to change configuration.\r
+ * @throws Exception: an error occured while writing the new config file.\r
+ */\r
+function writeConfig($config, $isLoggedIn)\r
+{\r
+ // These fields are required in configuration.\r
+ $MANDATORY_FIELDS = [\r
+ 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',\r
+ 'redirector', 'disablesessionprotection', 'privateLinkByDefault'\r
+ ];\r
+\r
+ if (!isset($config['config']['CONFIG_FILE'])) {\r
+ throw new MissingFieldConfigException('CONFIG_FILE');\r
+ }\r
+\r
+ // Only logged in user can alter config.\r
+ if (is_file($config['config']['CONFIG_FILE']) && !$isLoggedIn) {\r
+ throw new UnauthorizedConfigException();\r
+ }\r
+\r
+ // Check that all mandatory fields are provided in $config.\r
+ foreach ($MANDATORY_FIELDS as $field) {\r
+ if (!isset($config[$field])) {\r
+ throw new MissingFieldConfigException($field);\r
+ }\r
+ }\r
+\r
+ $configStr = '<?php '. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'login\'] = '.var_export($config['login'], true).';'. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'hash\'] = '.var_export($config['hash'], true).';'. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'salt\'] = '.var_export($config['salt'], true).'; '. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'timezone\'] = '.var_export($config['timezone'], true).';'. PHP_EOL;\r
+ $configStr .= 'date_default_timezone_set('.var_export($config['timezone'], true).');'. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'title\'] = '.var_export($config['title'], true).';'. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'titleLink\'] = '.var_export($config['titleLink'], true).'; '. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'redirector\'] = '.var_export($config['redirector'], true).'; '. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'disablesessionprotection\'] = '.var_export($config['disablesessionprotection'], true).'; '. PHP_EOL;\r
+ $configStr .= '$GLOBALS[\'privateLinkByDefault\'] = '.var_export($config['privateLinkByDefault'], true).'; '. PHP_EOL;\r
+\r
+ // Store all $config['config']\r
+ foreach ($config['config'] as $key => $value) {\r
+ $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL;\r
+ }\r
+ $configStr .= '?>';\r
+\r
+ if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr)\r
+ || strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0\r
+ ) {\r
+ throw new Exception(\r
+ 'Shaarli could not create the config file.\r
+ Please make sure Shaarli has the right to write in the folder is it installed in.'\r
+ );\r
+ }\r
+}\r
+\r
+/**\r
+ * Milestone 0.9 - shaarli/Shaarli#41: options.php is not supported anymore.\r
+ * ==> if user is loggedIn, merge its content with config.php, then delete options.php.\r
+ *\r
+ * @param array $config contains all configuration fields.\r
+ * @param bool $isLoggedIn true if user is logged in.\r
+ *\r
+ * @return void\r
+ */\r
+function mergeDeprecatedConfig($config, $isLoggedIn)\r
+{\r
+ $config_file = $config['config']['CONFIG_FILE'];\r
+\r
+ if (is_file($config['config']['DATADIR'].'/options.php') && $isLoggedIn) {\r
+ include $config['config']['DATADIR'].'/options.php';\r
+\r
+ // Load GLOBALS into config\r
+ foreach ($GLOBALS as $key => $value) {\r
+ $config[$key] = $value;\r
+ }\r
+ $config['config']['CONFIG_FILE'] = $config_file;\r
+ writeConfig($config, $isLoggedIn);\r
+\r
+ unlink($config['config']['DATADIR'].'/options.php');\r
+ }\r
+}\r
+\r
+/**\r
+ * Exception used if a mandatory field is missing in given configuration.\r
+ */\r
+class MissingFieldConfigException extends Exception\r
+{\r
+ public $field;\r
+\r
+ /**\r
+ * Construct exception.\r
+ *\r
+ * @param string $field field name missing.\r
+ */\r
+ public function __construct($field)\r
+ {\r
+ $this->field = $field;\r
+ $this->message = 'Configuration value is required for '. $this->field;\r
+ }\r
+}\r
+\r
+/**\r
+ * Exception used if an unauthorized attempt to edit configuration has been made.\r
+ */\r
+class UnauthorizedConfigException extends Exception\r
+{\r
+ /**\r
+ * Construct exception.\r
+ */\r
+ public function __construct()\r
+ {\r
+ $this->message = 'You are not authorized to alter config.';\r
+ }\r
+}
\ No newline at end of file
class LinkDB implements Iterator, Countable, ArrayAccess
{
// Links are stored as a PHP serialized string
- private $datastore;
+ private $_datastore;
// Datastore PHP prefix
protected static $phpPrefix = '<?php /* ';
// List of links (associative array)
// - key: link date (e.g. "20110823_124546"),
// - value: associative array (keys: title, description...)
- private $links;
+ private $_links;
// List of all recorded URLs (key=url, value=linkdate)
// for fast reserve search (url-->linkdate)
- private $urls;
+ private $_urls;
// List of linkdate keys (for the Iterator interface implementation)
- private $keys;
+ private $_keys;
- // Position in the $this->keys array (for the Iterator interface)
- private $position;
+ // Position in the $this->_keys array (for the Iterator interface)
+ private $_position;
// Is the user logged in? (used to filter private links)
- private $loggedIn;
+ private $_loggedIn;
// Hide public links
- private $hidePublicLinks;
+ private $_hidePublicLinks;
/**
* Creates a new LinkDB
*/
function __construct($datastore, $isLoggedIn, $hidePublicLinks)
{
- $this->datastore = $datastore;
- $this->loggedIn = $isLoggedIn;
- $this->hidePublicLinks = $hidePublicLinks;
- $this->checkDB();
- $this->readdb();
+ $this->_datastore = $datastore;
+ $this->_loggedIn = $isLoggedIn;
+ $this->_hidePublicLinks = $hidePublicLinks;
+ $this->_checkDB();
+ $this->_readDB();
}
/**
*/
public function count()
{
- return count($this->links);
+ return count($this->_links);
}
/**
public function offsetSet($offset, $value)
{
// TODO: use exceptions instead of "die"
- if (!$this->loggedIn) {
+ if (!$this->_loggedIn) {
die('You are not authorized to add a link.');
}
if (empty($value['linkdate']) || empty($value['url'])) {
if (empty($offset)) {
die('You must specify a key.');
}
- $this->links[$offset] = $value;
- $this->urls[$value['url']]=$offset;
+ $this->_links[$offset] = $value;
+ $this->_urls[$value['url']]=$offset;
}
/**
*/
public function offsetExists($offset)
{
- return array_key_exists($offset, $this->links);
+ return array_key_exists($offset, $this->_links);
}
/**
*/
public function offsetUnset($offset)
{
- if (!$this->loggedIn) {
+ if (!$this->_loggedIn) {
// TODO: raise an exception
die('You are not authorized to delete a link.');
}
- $url = $this->links[$offset]['url'];
- unset($this->urls[$url]);
- unset($this->links[$offset]);
+ $url = $this->_links[$offset]['url'];
+ unset($this->_urls[$url]);
+ unset($this->_links[$offset]);
}
/**
*/
public function offsetGet($offset)
{
- return isset($this->links[$offset]) ? $this->links[$offset] : null;
+ return isset($this->_links[$offset]) ? $this->_links[$offset] : null;
}
/**
*/
function current()
{
- return $this->links[$this->keys[$this->position]];
+ return $this->_links[$this->_keys[$this->_position]];
}
/**
*/
function key()
{
- return $this->keys[$this->position];
+ return $this->_keys[$this->_position];
}
/**
*/
function next()
{
- ++$this->position;
+ ++$this->_position;
}
/**
*/
function rewind()
{
- $this->keys = array_keys($this->links);
- rsort($this->keys);
- $this->position = 0;
+ $this->_keys = array_keys($this->_links);
+ rsort($this->_keys);
+ $this->_position = 0;
}
/**
*/
function valid()
{
- return isset($this->keys[$this->position]);
+ return isset($this->_keys[$this->_position]);
}
/**
*
* If no DB file is found, creates a dummy DB.
*/
- private function checkDB()
+ private function _checkDB()
{
- if (file_exists($this->datastore)) {
+ if (file_exists($this->_datastore)) {
return;
}
// Create a dummy database for example
- $this->links = array();
+ $this->_links = array();
$link = array(
'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone',
'url'=>'https://github.com/shaarli/Shaarli/wiki',
'linkdate'=> date('Ymd_His'),
'tags'=>'opensource software'
);
- $this->links[$link['linkdate']] = $link;
+ $this->_links[$link['linkdate']] = $link;
$link = array(
'title'=>'My secret stuff... - Pastebin.com',
'linkdate'=> date('Ymd_His', strtotime('-1 minute')),
'tags'=>'secretstuff'
);
- $this->links[$link['linkdate']] = $link;
+ $this->_links[$link['linkdate']] = $link;
// Write database to disk
// TODO: raise an exception if the file is not write-able
file_put_contents(
- $this->datastore,
- self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix
+ $this->_datastore,
+ self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix
);
}
/**
* Reads database from disk to memory
*/
- private function readdb()
+ private function _readDB()
{
// Public links are hidden and user not logged in => nothing to show
- if ($this->hidePublicLinks && !$this->loggedIn) {
- $this->links = array();
+ if ($this->_hidePublicLinks && !$this->_loggedIn) {
+ $this->_links = array();
return;
}
// Read data
// Note that gzinflate is faster than gzuncompress.
// See: http://www.php.net/manual/en/function.gzdeflate.php#96439
- $this->links = array();
+ $this->_links = array();
- if (file_exists($this->datastore)) {
- $this->links = unserialize(gzinflate(base64_decode(
- substr(file_get_contents($this->datastore),
+ if (file_exists($this->_datastore)) {
+ $this->_links = unserialize(gzinflate(base64_decode(
+ substr(file_get_contents($this->_datastore),
strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
}
// If user is not logged in, filter private links.
- if (!$this->loggedIn) {
+ if (!$this->_loggedIn) {
$toremove = array();
- foreach ($this->links as $link) {
+ foreach ($this->_links as $link) {
if ($link['private'] != 0) {
$toremove[] = $link['linkdate'];
}
}
foreach ($toremove as $linkdate) {
- unset($this->links[$linkdate]);
+ unset($this->_links[$linkdate]);
}
}
// Keep the list of the mapping URLs-->linkdate up-to-date.
- $this->urls = array();
- foreach ($this->links as $link) {
- $this->urls[$link['url']] = $link['linkdate'];
+ $this->_urls = array();
+ foreach ($this->_links as $link) {
+ $this->_urls[$link['url']] = $link['linkdate'];
}
// Escape links data
- foreach($this->links as &$link) {
+ foreach($this->_links as &$link) {
sanitizeLink($link);
}
}
*/
public function savedb()
{
- if (!$this->loggedIn) {
+ if (!$this->_loggedIn) {
// TODO: raise an Exception instead
die('You are not authorized to change the database.');
}
file_put_contents(
- $this->datastore,
- self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix
+ $this->_datastore,
+ self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix
);
invalidateCaches();
}
*/
public function getLinkFromUrl($url)
{
- if (isset($this->urls[$url])) {
- return $this->links[$this->urls[$url]];
+ if (isset($this->_urls[$url])) {
+ return $this->_links[$this->_urls[$url]];
}
return false;
}
$search = mb_convert_case($searchterms, MB_CASE_LOWER, 'UTF-8');
$keys = array('title', 'description', 'url', 'tags');
- foreach ($this->links as $link) {
+ foreach ($this->_links as $link) {
$found = false;
foreach ($keys as $key) {
$searchtags = explode(' ', $t);
$filtered = array();
- foreach ($this->links as $l) {
+ foreach ($this->_links as $l) {
$linktags = explode(
' ',
($casesensitive ? $l['tags']:mb_convert_case($l['tags'], MB_CASE_LOWER, 'UTF-8'))
}
$filtered = array();
- foreach ($this->links as $l) {
+ foreach ($this->_links as $l) {
if (startsWith($l['linkdate'], $day)) {
$filtered[$l['linkdate']] = $l;
}
public function filterSmallHash($smallHash)
{
$filtered = array();
- foreach ($this->links as $l) {
+ foreach ($this->_links as $l) {
if ($smallHash == smallHash($l['linkdate'])) {
// Yes, this is ugly and slow
$filtered[$l['linkdate']] = $l;
public function allTags()
{
$tags = array();
- foreach ($this->links as $link) {
+ foreach ($this->_links as $link) {
foreach (explode(' ', $link['tags']) as $tag) {
if (!empty($tag)) {
$tags[$tag] = (empty($tags[$tag]) ? 1 : $tags[$tag] + 1);
public function days()
{
$linkDays = array();
- foreach (array_keys($this->links) as $day) {
+ foreach (array_keys($this->_links) as $day) {
$linkDays[substr($day, 0, 8)] = 0;
}
$linkDays = array_keys($linkDays);
date_default_timezone_set('UTC');
// -----------------------------------------------------------------------------------------------
-// Hardcoded parameter (These parameters can be overwritten by creating the file /data/options.php)
+// Hardcoded parameter (These parameters can be overwritten by editing the file /data/config.php)
+// You should not touch any code below (or at your own risks!)
$GLOBALS['config']['DATADIR'] = 'data'; // Data subdirectory
$GLOBALS['config']['CONFIG_FILE'] = $GLOBALS['config']['DATADIR'].'/config.php'; // Configuration file (user login/password)
$GLOBALS['config']['DATASTORE'] = $GLOBALS['config']['DATADIR'].'/datastore.php'; // Data storage file.
$GLOBALS['config']['ENABLE_RSS_PERMALINKS'] = true; // Enable RSS permalinks by default. This corresponds to the default behavior of shaarli before this was added as an option.
$GLOBALS['config']['HIDE_PUBLIC_LINKS'] = false;
// -----------------------------------------------------------------------------------------------
-// You should not touch below (or at your own risks!)
-// Optional config file.
-if (is_file($GLOBALS['config']['DATADIR'].'/options.php')) require($GLOBALS['config']['DATADIR'].'/options.php');
-
define('shaarli_version','0.0.45beta');
// http://server.com/x/shaarli --> /shaarli/
define('WEB_PATH', substr($_SERVER["REQUEST_URI"], 0, 1+strrpos($_SERVER["REQUEST_URI"], '/', 0)));
error_reporting(E_ALL^E_WARNING); // See all error except warnings.
//error_reporting(-1); // See all errors (for debugging only)
+// User configuration
+if (is_file($GLOBALS['config']['CONFIG_FILE'])) {
+ require_once $GLOBALS['config']['CONFIG_FILE'];
+}
+
// Shaarli library
require_once 'application/LinkDB.php';
require_once 'application/Utils.php';
+require_once 'application/Config.php';
include "inc/rain.tpl.class.php"; //include Rain TPL
raintpl::$tpl_dir = $GLOBALS['config']['RAINTPL_TPL']; // template directory
if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get();
if (empty($GLOBALS['redirector'])) $GLOBALS['redirector']='';
if (empty($GLOBALS['disablesessionprotection'])) $GLOBALS['disablesessionprotection']=false;
-if (empty($GLOBALS['disablejquery'])) $GLOBALS['disablejquery']=false;
if (empty($GLOBALS['privateLinkByDefault'])) $GLOBALS['privateLinkByDefault']=false;
if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?';
// I really need to rewrite Shaarli with a proper configuation manager.
// Run config screen if first run:
-if (!is_file($GLOBALS['config']['CONFIG_FILE'])) install();
+if (! is_file($GLOBALS['config']['CONFIG_FILE'])) {
+ install();
+}
-require $GLOBALS['config']['CONFIG_FILE']; // Read login/password hash into $GLOBALS.
$GLOBALS['title'] = !empty($GLOBALS['title']) ? escape($GLOBALS['title']) : '';
$GLOBALS['titleLink'] = !empty($GLOBALS['titleLink']) ? escape($GLOBALS['titleLink']) : '';
$GLOBALS['redirector'] = !empty($GLOBALS['redirector']) ? escape($GLOBALS['redirector']) : '';
// Daily RSS feed: 1 RSS entry per day giving all the links on that day.
// Gives the last 7 days (which have links).
// This RSS feed cannot be filtered.
-function showDailyRSS()
-{
+function showDailyRSS() {
// Cache system
$query = $_SERVER["QUERY_STRING"];
- $cache = new pageCache(pageUrl(),startsWith($query,'do=dailyrss') && !isLoggedIn());
- $cached = $cache->cachedVersion(); if (!empty($cached)) { echo $cached; exit; }
- // If cached was not found (or not usable), then read the database and build the response:
+ $cache = new pageCache(pageUrl(), startsWith($query, 'do=dailyrss') && !isLoggedIn());
+ $cached = $cache->cachedVersion();
+ if (!empty($cached)) {
+ echo $cached;
+ exit;
+ }
-// Read links from database (and filter private links if used it not logged in).
+ // If cached was not found (or not usable), then read the database and build the response:
+ // Read links from database (and filter private links if used it not logged in).
$LINKSDB = new LinkDB(
$GLOBALS['config']['DATASTORE'],
isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'],
/* Some Shaarlies may have very few links, so we need to look
back in time (rsort()) until we have enough days ($nb_of_days).
*/
- $linkdates=array(); foreach($LINKSDB as $linkdate=>$value) { $linkdates[]=$linkdate; }
+ $linkdates = array();
+ foreach ($LINKSDB as $linkdate => $value) {
+ $linkdates[] = $linkdate;
+ }
rsort($linkdates);
- $nb_of_days=7; // We take 7 days.
- $today=Date('Ymd');
- $days=array();
- foreach($linkdates as $linkdate)
- {
- $day=substr($linkdate,0,8); // Extract day (without time)
- if (strcmp($day,$today)<0)
- {
- if (empty($days[$day])) $days[$day]=array();
- $days[$day][]=$linkdate;
+ $nb_of_days = 7; // We take 7 days.
+ $today = Date('Ymd');
+ $days = array();
+
+ foreach ($linkdates as $linkdate) {
+ $day = substr($linkdate, 0, 8); // Extract day (without time)
+ if (strcmp($day,$today) < 0) {
+ if (empty($days[$day])) {
+ $days[$day] = array();
+ }
+ $days[$day][] = $linkdate;
+ }
+
+ if (count($days) > $nb_of_days) {
+ break; // Have we collected enough days?
}
- if (count($days)>$nb_of_days) break; // Have we collected enough days?
}
// Build the RSS feed.
header('Content-Type: application/rss+xml; charset=utf-8');
- $pageaddr=escape(indexUrl());
+ $pageaddr = escape(indexUrl());
echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">';
- echo '<channel><title>Daily - '.$GLOBALS['title'].'</title><link>'.$pageaddr.'</link>';
- echo '<description>Daily shared links</description><language>en-en</language><copyright>'.$pageaddr.'</copyright>'."\n";
-
- foreach($days as $day=>$linkdates) // For each day.
- {
- $daydate = utf8_encode(strftime('%A %d, %B %Y',linkdate2timestamp($day.'_000000'))); // Full text date
+ echo '<channel>';
+ echo '<title>Daily - '. $GLOBALS['title'] . '</title>';
+ echo '<link>'. $pageaddr .'</link>';
+ echo '<description>Daily shared links</description>';
+ echo '<language>en-en</language>';
+ echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL;
+
+ // For each day.
+ foreach ($days as $day => $linkdates) {
+ $daydate = linkdate2timestamp($day.'_000000'); // Full text date
$rfc822date = linkdate2rfc822($day.'_000000');
- $absurl=escape(indexUrl().'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page.
- echo '<item><title>'.$GLOBALS['title'].' - '.$daydate.'</title><guid>'.$absurl.'</guid><link>'.$absurl.'</link>';
- echo '<pubDate>'.escape($rfc822date)."</pubDate>";
+ $absurl = escape(indexUrl().'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page.
// Build the HTML body of this RSS entry.
- $html='';
- $href='';
- $links=array();
+ $html = '';
+ $href = '';
+ $links = array();
+
// We pre-format some fields for proper output.
- foreach($linkdates as $linkdate)
- {
+ foreach ($linkdates as $linkdate) {
$l = $LINKSDB[$linkdate];
- $l['formatedDescription']=nl2br(keepMultipleSpaces(text2clickable($l['description'])));
+ $l['formatedDescription'] = nl2br(keepMultipleSpaces(text2clickable($l['description'])));
$l['thumbnail'] = thumbnail($l['url']);
$l['timestamp'] = linkdate2timestamp($l['linkdate']);
- if (startsWith($l['url'],'?')) $l['url']=indexUrl().$l['url']; // make permalink URL absolute
- $links[$linkdate]=$l;
+ if (startsWith($l['url'], '?')) {
+ $l['url'] = indexUrl() . $l['url']; // make permalink URL absolute
+ }
+ $links[$linkdate] = $l;
}
+
// Then build the HTML for this day:
$tpl = new RainTPL;
- $tpl->assign('links',$links);
- $html = $tpl->draw('dailyrss',$return_string=true);
- echo "\n";
- echo '<description><![CDATA['.$html.']]></description>'."\n</item>\n\n";
+ $tpl->assign('title', $GLOBALS['title']);
+ $tpl->assign('daydate', $daydate);
+ $tpl->assign('absurl', $absurl);
+ $tpl->assign('links', $links);
+ $tpl->assign('rfc822date', escape($rfc822date));
+ $html = $tpl->draw('dailyrss', $return_string=true);
+ echo $html . PHP_EOL;
}
- echo '</channel></rss><!-- Cached version of '.escape(pageUrl()).' -->';
+ echo '</channel></rss><!-- Cached version of '. escape(pageUrl()) .' -->';
$cache->cache(ob_get_contents());
ob_end_flush();
// Check if this tag is already in the search query and ignore it if it is.
// Each tag is always separated by a space
- $current_tags = explode(' ', $params['searchtags']);
+ if (isset($params['searchtags'])) {
+ $current_tags = explode(' ', $params['searchtags']);
+ } else {
+ $current_tags = array();
+ }
$addtag = true;
foreach ($current_tags as $value) {
if ($value === $_GET['addtag']) {
// 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']);
- writeConfig();
+ 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;
}
$GLOBALS['titleLink']=$_POST['titleLink'];
$GLOBALS['redirector']=$_POST['redirector'];
$GLOBALS['disablesessionprotection']=!empty($_POST['disablesessionprotection']);
- $GLOBALS['disablejquery']=!empty($_POST['disablejquery']);
$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']);
- writeConfig();
+ 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;
}
{
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:'))
{
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>You would mind <a href="?">clicking here</a>?';
+ echo '<br>Would you mind <a href="?">clicking here</a>?';
exit;
}
$search_type='permalink';
$GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
$GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.escape(indexUrl()) : $_POST['title'] );
$GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
- writeConfig();
+ try {
+ writeConfig($GLOBALS, isLoggedIn());
+ }
+ catch(Exception $e) {
+ error_log(
+ 'ERROR while writing config file after installation.' . PHP_EOL .
+ $e->getMessage()
+ );
+
+ // TODO: do not handle exceptions/errors in JS.
+ echo '<script>alert("'. $e->getMessage() .'");document.location=\'?\';</script>';
+ exit;
+ }
echo '<script>alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>';
exit;
}
}
}
-// Re-write configuration file according to globals.
-// Requires some $GLOBALS to be set (login,hash,salt,title).
-// If the config file cannot be saved, an error message is displayed and the user is redirected to "Tools" menu.
-// (otherwise, the function simply returns.)
-function writeConfig()
-{
- if (is_file($GLOBALS['config']['CONFIG_FILE']) && !isLoggedIn()) die('You are not authorized to alter config.'); // Only logged in user can alter config.
- $config='<?php $GLOBALS[\'login\']='.var_export($GLOBALS['login'],true).'; $GLOBALS[\'hash\']='.var_export($GLOBALS['hash'],true).'; $GLOBALS[\'salt\']='.var_export($GLOBALS['salt'],true).'; ';
- $config .='$GLOBALS[\'timezone\']='.var_export($GLOBALS['timezone'],true).'; date_default_timezone_set('.var_export($GLOBALS['timezone'],true).'); $GLOBALS[\'title\']='.var_export($GLOBALS['title'],true).';';
- $config .= '$GLOBALS[\'titleLink\']='.var_export($GLOBALS['titleLink'],true).'; ';
- $config .= '$GLOBALS[\'redirector\']='.var_export($GLOBALS['redirector'],true).'; ';
- $config .= '$GLOBALS[\'disablesessionprotection\']='.var_export($GLOBALS['disablesessionprotection'],true).'; ';
- $config .= '$GLOBALS[\'disablejquery\']='.var_export($GLOBALS['disablejquery'],true).'; ';
- $config .= '$GLOBALS[\'privateLinkByDefault\']='.var_export($GLOBALS['privateLinkByDefault'],true).'; ';
- $config .= '$GLOBALS[\'config\'][\'ENABLE_RSS_PERMALINKS\']='.var_export($GLOBALS['config']['ENABLE_RSS_PERMALINKS'], true).'; ';
- $config .= '$GLOBALS[\'config\'][\'ENABLE_UPDATECHECK\']='.var_export($GLOBALS['config']['ENABLE_UPDATECHECK'], true).'; ';
- $config .= '$GLOBALS[\'config\'][\'HIDE_PUBLIC_LINKS\']='.var_export($GLOBALS['config']['HIDE_PUBLIC_LINKS'], true).'; ';
- $config .= ' ?>';
- if (!file_put_contents($GLOBALS['config']['CONFIG_FILE'],$config) || strcmp(file_get_contents($GLOBALS['config']['CONFIG_FILE']),$config)!=0)
- {
- echo '<script>alert("Shaarli could not create the config file. Please make sure Shaarli has the right to write in the folder is it installed in.");document.location=\'?\';</script>';
- exit;
- }
-}
+
/* Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL,
I have deported the thumbnail URL code generation here, otherwise this would slow down page generation.
pageCache::purgeCache(); // Purge page cache shared by sessions.
}
+try {
+ mergeDeprecatedConfig($GLOBALS, isLoggedIn());
+} catch(Exception $e) {
+ error_log(
+ 'ERROR while merging deprecated options.php file.' . PHP_EOL .
+ $e->getMessage()
+ );
+}
+
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database.
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; }
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; }
--- /dev/null
+<?php\r
+/**\r
+ * Config' tests\r
+ */\r
+\r
+require_once 'application/Config.php';\r
+\r
+/**\r
+ * Unitary tests for Shaarli config related functions\r
+ */\r
+class ConfigTest extends PHPUnit_Framework_TestCase\r
+{\r
+ // Configuration input set.\r
+ private static $_configFields;\r
+\r
+ /**\r
+ * Executed before each test.\r
+ */\r
+ public function setUp()\r
+ {\r
+ self::$_configFields = [\r
+ 'login' => 'login',\r
+ 'hash' => 'hash',\r
+ 'salt' => 'salt',\r
+ 'timezone' => 'Europe/Paris',\r
+ 'title' => 'title',\r
+ 'titleLink' => 'titleLink',\r
+ 'redirector' => '',\r
+ 'disablesessionprotection' => false,\r
+ 'privateLinkByDefault' => false,\r
+ 'config' => [\r
+ 'CONFIG_FILE' => 'tests/config.php',\r
+ 'DATADIR' => 'tests',\r
+ 'config1' => 'config1data',\r
+ 'config2' => 'config2data',\r
+ ]\r
+ ];\r
+ }\r
+\r
+ /**\r
+ * Executed after each test.\r
+ *\r
+ * @return void\r
+ */\r
+ public function tearDown()\r
+ {\r
+ if (is_file(self::$_configFields['config']['CONFIG_FILE'])) {\r
+ unlink(self::$_configFields['config']['CONFIG_FILE']);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Test writeConfig function, valid use case, while being logged in.\r
+ */\r
+ public function testWriteConfig()\r
+ {\r
+ writeConfig(self::$_configFields, true);\r
+\r
+ include self::$_configFields['config']['CONFIG_FILE'];\r
+ $this->assertEquals(self::$_configFields['login'], $GLOBALS['login']);\r
+ $this->assertEquals(self::$_configFields['hash'], $GLOBALS['hash']);\r
+ $this->assertEquals(self::$_configFields['salt'], $GLOBALS['salt']);\r
+ $this->assertEquals(self::$_configFields['timezone'], $GLOBALS['timezone']);\r
+ $this->assertEquals(self::$_configFields['title'], $GLOBALS['title']);\r
+ $this->assertEquals(self::$_configFields['titleLink'], $GLOBALS['titleLink']);\r
+ $this->assertEquals(self::$_configFields['redirector'], $GLOBALS['redirector']);\r
+ $this->assertEquals(self::$_configFields['disablesessionprotection'], $GLOBALS['disablesessionprotection']);\r
+ $this->assertEquals(self::$_configFields['privateLinkByDefault'], $GLOBALS['privateLinkByDefault']);\r
+ $this->assertEquals(self::$_configFields['config']['config1'], $GLOBALS['config']['config1']);\r
+ $this->assertEquals(self::$_configFields['config']['config2'], $GLOBALS['config']['config2']);\r
+ }\r
+\r
+ /**\r
+ * Test writeConfig option while logged in:\r
+ * 1. init fields.\r
+ * 2. update fields, add new sub config, add new root config.\r
+ * 3. rewrite config.\r
+ * 4. check result.\r
+ */\r
+ public function testWriteConfigFieldUpdate()\r
+ {\r
+ writeConfig(self::$_configFields, true);\r
+ self::$_configFields['title'] = 'ok';\r
+ self::$_configFields['config']['config1'] = 'ok';\r
+ self::$_configFields['config']['config_new'] = 'ok';\r
+ self::$_configFields['new'] = 'should not be saved';\r
+ writeConfig(self::$_configFields, true);\r
+\r
+ include self::$_configFields['config']['CONFIG_FILE'];\r
+ $this->assertEquals('ok', $GLOBALS['title']);\r
+ $this->assertEquals('ok', $GLOBALS['config']['config1']);\r
+ $this->assertEquals('ok', $GLOBALS['config']['config_new']);\r
+ $this->assertFalse(isset($GLOBALS['new']));\r
+ }\r
+\r
+ /**\r
+ * Test writeConfig function with an empty array.\r
+ *\r
+ * @expectedException MissingFieldConfigException\r
+ */\r
+ public function testWriteConfigEmpty()\r
+ {\r
+ writeConfig(array(), true);\r
+ }\r
+\r
+ /**\r
+ * Test writeConfig function with a missing mandatory field.\r
+ *\r
+ * @expectedException MissingFieldConfigException\r
+ */\r
+ public function testWriteConfigMissingField()\r
+ {\r
+ unset(self::$_configFields['login']);\r
+ writeConfig(self::$_configFields, true);\r
+ }\r
+\r
+ /**\r
+ * Test writeConfig function while being logged out, and there is no config file existing.\r
+ */\r
+ public function testWriteConfigLoggedOutNoFile()\r
+ {\r
+ writeConfig(self::$_configFields, false);\r
+ }\r
+\r
+ /**\r
+ * Test writeConfig function while being logged out, and a config file already exists.\r
+ *\r
+ * @expectedException UnauthorizedConfigException\r
+ */\r
+ public function testWriteConfigLoggedOutWithFile()\r
+ {\r
+ file_put_contents(self::$_configFields['config']['CONFIG_FILE'], '');\r
+ writeConfig(self::$_configFields, false);\r
+ }\r
+\r
+ /**\r
+ * Test mergeDeprecatedConfig while being logged in:\r
+ * 1. init a config file.\r
+ * 2. init a options.php file with update value.\r
+ * 3. merge.\r
+ * 4. check updated value in config file.\r
+ */\r
+ public function testMergeDeprecatedConfig()\r
+ {\r
+ // init\r
+ writeConfig(self::$_configFields, true);\r
+ $configCopy = self::$_configFields;\r
+ $invert = !$configCopy['privateLinkByDefault'];\r
+ $configCopy['privateLinkByDefault'] = $invert;\r
+\r
+ // Use writeConfig to create a options.php\r
+ $configCopy['config']['CONFIG_FILE'] = 'tests/options.php';\r
+ writeConfig($configCopy, true);\r
+\r
+ $this->assertTrue(is_file($configCopy['config']['CONFIG_FILE']));\r
+\r
+ // merge configs\r
+ mergeDeprecatedConfig(self::$_configFields, true);\r
+\r
+ // make sure updated field is changed\r
+ include self::$_configFields['config']['CONFIG_FILE'];\r
+ $this->assertEquals($invert, $GLOBALS['privateLinkByDefault']);\r
+ $this->assertFalse(is_file($configCopy['config']['CONFIG_FILE']));\r
+ }\r
+\r
+ /**\r
+ * Test mergeDeprecatedConfig while being logged in without options file.\r
+ */\r
+ public function testMergeDeprecatedConfigNoFile()\r
+ {\r
+ writeConfig(self::$_configFields, true);\r
+ mergeDeprecatedConfig(self::$_configFields, true);\r
+\r
+ include self::$_configFields['config']['CONFIG_FILE'];\r
+ $this->assertEquals(self::$_configFields['login'], $GLOBALS['login']);\r
+ }\r
+}
\ No newline at end of file
unlink(self::$testDatastore);
$this->assertFileNotExists(self::$testDatastore);
- $checkDB = self::getMethod('checkDB');
+ $checkDB = self::getMethod('_checkDB');
$checkDB->invokeArgs($linkDB, array());
$this->assertFileExists(self::$testDatastore);
$datastoreSize = filesize(self::$testDatastore);
$this->assertGreaterThan(0, $datastoreSize);
- $checkDB = self::getMethod('checkDB');
+ $checkDB = self::getMethod('_checkDB');
$checkDB->invokeArgs($linkDB, array());
// ensure the datastore is left unmodified
*/
class ReferenceLinkDB
{
- private $links = array();
- private $publicCount = 0;
- private $privateCount = 0;
+ private $_links = array();
+ private $_publicCount = 0;
+ private $_privateCount = 0;
/**
* Populates the test DB with reference data
'linkdate' => $date,
'tags' => $tags,
);
- $this->links[$date] = $link;
+ $this->_links[$date] = $link;
if ($private) {
- $this->privateCount++;
+ $this->_privateCount++;
return;
}
- $this->publicCount++;
+ $this->_publicCount++;
}
/**
{
file_put_contents(
$filename,
- '<?php /* '.base64_encode(gzdeflate(serialize($this->links))).' */ ?>'
+ '<?php /* '.base64_encode(gzdeflate(serialize($this->_links))).' */ ?>'
);
}
*/
public function countLinks()
{
- return $this->publicCount + $this->privateCount;
+ return $this->_publicCount + $this->_privateCount;
}
/**
*/
public function countPublicLinks()
{
- return $this->publicCount;
+ return $this->_publicCount;
}
/**
*/
public function countPrivateLinks()
{
- return $this->privateCount;
+ return $this->_privateCount;
}
}
?>
-{loop="links"}
- <h3><a href="{$value.url}">{$value.title}</a></h3>
- <small>{if="!$GLOBALS['config']['HIDE_TIMESTAMPS']"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br>
- {$value.url}</small><br>
- {if="$value.thumbnail"}{$value.thumbnail}{/if}<br>
- {if="$value.description"}{$value.formatedDescription}{/if}
- <br><br><hr>
-{/loop}
\ No newline at end of file
+<item>
+ <title>{$title} - {function="strftime('%A %e %B %Y', $daydate)"}</title>
+ <guid>{$absurl}</guid>
+ <link>{$absurl}</link>
+ <pubDate>{$rfc822date}</pubDate>
+ <description><![CDATA[
+ {loop="links"}
+ <h3><a href="{$value.url}">{$value.title}</a></h3>
+ <small>{if="!$GLOBALS['config']['HIDE_TIMESTAMPS']"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br>
+ {$value.url}</small><br>
+ {if="$value.thumbnail"}{$value.thumbnail}{/if}<br>
+ {if="$value.description"}{$value.formatedDescription}{/if}
+ <br><br><hr>
+ {/loop}
+ ]]></description>
+</item>
\ No newline at end of file
{if="($GLOBALS['config']['OPEN_SHAARLI'] || isLoggedIn())"}
<script>
$ = Awesomplete.$;
- new Awesomplete($('input[data-multiple]'), {
+ awesomplete = new Awesomplete($('input[data-multiple]'), {
filter: function(text, input) {
return Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]);
},
},
minChars: 1
});
+
+ /**
+ * Remove already selected items from autocompletion list.
+ * HTML list is never updated, so removing a tag will add it back to awesomplete.
+ *
+ * FIXME: This a workaround waiting for awesomplete to handle this.
+ * https://github.com/LeaVerou/awesomplete/issues/16749
+ */
+ var input = document.querySelector('#lf_tags');
+ input.addEventListener('input', function()
+ {
+ proposedTags = input.getAttribute('data-list').replace(/,/g, '').split(' ');
+ reg = /(\w+) /g;
+ while((match = reg.exec(input.value)) !== null) {
+ id = proposedTags.indexOf(match[1]);
+ if(id != -1 ) {
+ proposedTags.splice(id, 1);
+ }
+ }
+
+ awesomplete.list = proposedTags;
+ });
</script>
{/if}
</body>