aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rwxr-xr-xapplication/Config.php129
-rw-r--r--application/LinkDB.php124
-rw-r--r--index.php205
-rwxr-xr-xtests/ConfigTest.php177
-rw-r--r--tests/LinkDBTest.php4
-rw-r--r--tests/utils/ReferenceLinkDB.php20
-rw-r--r--tpl/dailyrss.html24
-rw-r--r--tpl/editlink.html24
8 files changed, 545 insertions, 162 deletions
diff --git a/application/Config.php b/application/Config.php
new file mode 100755
index 00000000..0b01b524
--- /dev/null
+++ b/application/Config.php
@@ -0,0 +1,129 @@
1<?php
2/**
3 * Functions related to configuration management.
4 */
5
6/**
7 * Re-write configuration file according to given array.
8 * Requires mandatory fields listed in $MANDATORY_FIELDS.
9 *
10 * @param array $config contains all configuration fields.
11 * @param bool $isLoggedIn true if user is logged in.
12 *
13 * @return void
14 *
15 * @throws MissingFieldConfigException: a mandatory field has not been provided in $config.
16 * @throws UnauthorizedConfigException: user is not authorize to change configuration.
17 * @throws Exception: an error occured while writing the new config file.
18 */
19function writeConfig($config, $isLoggedIn)
20{
21 // These fields are required in configuration.
22 $MANDATORY_FIELDS = [
23 'login', 'hash', 'salt', 'timezone', 'title', 'titleLink',
24 'redirector', 'disablesessionprotection', 'privateLinkByDefault'
25 ];
26
27 if (!isset($config['config']['CONFIG_FILE'])) {
28 throw new MissingFieldConfigException('CONFIG_FILE');
29 }
30
31 // Only logged in user can alter config.
32 if (is_file($config['config']['CONFIG_FILE']) && !$isLoggedIn) {
33 throw new UnauthorizedConfigException();
34 }
35
36 // Check that all mandatory fields are provided in $config.
37 foreach ($MANDATORY_FIELDS as $field) {
38 if (!isset($config[$field])) {
39 throw new MissingFieldConfigException($field);
40 }
41 }
42
43 $configStr = '<?php '. PHP_EOL;
44 $configStr .= '$GLOBALS[\'login\'] = '.var_export($config['login'], true).';'. PHP_EOL;
45 $configStr .= '$GLOBALS[\'hash\'] = '.var_export($config['hash'], true).';'. PHP_EOL;
46 $configStr .= '$GLOBALS[\'salt\'] = '.var_export($config['salt'], true).'; '. PHP_EOL;
47 $configStr .= '$GLOBALS[\'timezone\'] = '.var_export($config['timezone'], true).';'. PHP_EOL;
48 $configStr .= 'date_default_timezone_set('.var_export($config['timezone'], true).');'. PHP_EOL;
49 $configStr .= '$GLOBALS[\'title\'] = '.var_export($config['title'], true).';'. PHP_EOL;
50 $configStr .= '$GLOBALS[\'titleLink\'] = '.var_export($config['titleLink'], true).'; '. PHP_EOL;
51 $configStr .= '$GLOBALS[\'redirector\'] = '.var_export($config['redirector'], true).'; '. PHP_EOL;
52 $configStr .= '$GLOBALS[\'disablesessionprotection\'] = '.var_export($config['disablesessionprotection'], true).'; '. PHP_EOL;
53 $configStr .= '$GLOBALS[\'privateLinkByDefault\'] = '.var_export($config['privateLinkByDefault'], true).'; '. PHP_EOL;
54
55 // Store all $config['config']
56 foreach ($config['config'] as $key => $value) {
57 $configStr .= '$GLOBALS[\'config\'][\''. $key .'\'] = '.var_export($config['config'][$key], true).';'. PHP_EOL;
58 }
59 $configStr .= '?>';
60
61 if (!file_put_contents($config['config']['CONFIG_FILE'], $configStr)
62 || strcmp(file_get_contents($config['config']['CONFIG_FILE']), $configStr) != 0
63 ) {
64 throw new Exception(
65 'Shaarli could not create the config file.
66 Please make sure Shaarli has the right to write in the folder is it installed in.'
67 );
68 }
69}
70
71/**
72 * Milestone 0.9 - shaarli/Shaarli#41: options.php is not supported anymore.
73 * ==> if user is loggedIn, merge its content with config.php, then delete options.php.
74 *
75 * @param array $config contains all configuration fields.
76 * @param bool $isLoggedIn true if user is logged in.
77 *
78 * @return void
79 */
80function mergeDeprecatedConfig($config, $isLoggedIn)
81{
82 $config_file = $config['config']['CONFIG_FILE'];
83
84 if (is_file($config['config']['DATADIR'].'/options.php') && $isLoggedIn) {
85 include $config['config']['DATADIR'].'/options.php';
86
87 // Load GLOBALS into config
88 foreach ($GLOBALS as $key => $value) {
89 $config[$key] = $value;
90 }
91 $config['config']['CONFIG_FILE'] = $config_file;
92 writeConfig($config, $isLoggedIn);
93
94 unlink($config['config']['DATADIR'].'/options.php');
95 }
96}
97
98/**
99 * Exception used if a mandatory field is missing in given configuration.
100 */
101class MissingFieldConfigException extends Exception
102{
103 public $field;
104
105 /**
106 * Construct exception.
107 *
108 * @param string $field field name missing.
109 */
110 public function __construct($field)
111 {
112 $this->field = $field;
113 $this->message = 'Configuration value is required for '. $this->field;
114 }
115}
116
117/**
118 * Exception used if an unauthorized attempt to edit configuration has been made.
119 */
120class UnauthorizedConfigException extends Exception
121{
122 /**
123 * Construct exception.
124 */
125 public function __construct()
126 {
127 $this->message = 'You are not authorized to alter config.';
128 }
129} \ No newline at end of file
diff --git a/application/LinkDB.php b/application/LinkDB.php
index 82763618..1e16fef1 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -28,7 +28,7 @@
28class LinkDB implements Iterator, Countable, ArrayAccess 28class LinkDB implements Iterator, Countable, ArrayAccess
29{ 29{
30 // Links are stored as a PHP serialized string 30 // Links are stored as a PHP serialized string
31 private $datastore; 31 private $_datastore;
32 32
33 // Datastore PHP prefix 33 // Datastore PHP prefix
34 protected static $phpPrefix = '<?php /* '; 34 protected static $phpPrefix = '<?php /* ';
@@ -39,23 +39,23 @@ class LinkDB implements Iterator, Countable, ArrayAccess
39 // List of links (associative array) 39 // List of links (associative array)
40 // - key: link date (e.g. "20110823_124546"), 40 // - key: link date (e.g. "20110823_124546"),
41 // - value: associative array (keys: title, description...) 41 // - value: associative array (keys: title, description...)
42 private $links; 42 private $_links;
43 43
44 // List of all recorded URLs (key=url, value=linkdate) 44 // List of all recorded URLs (key=url, value=linkdate)
45 // for fast reserve search (url-->linkdate) 45 // for fast reserve search (url-->linkdate)
46 private $urls; 46 private $_urls;
47 47
48 // List of linkdate keys (for the Iterator interface implementation) 48 // List of linkdate keys (for the Iterator interface implementation)
49 private $keys; 49 private $_keys;
50 50
51 // Position in the $this->keys array (for the Iterator interface) 51 // Position in the $this->_keys array (for the Iterator interface)
52 private $position; 52 private $_position;
53 53
54 // Is the user logged in? (used to filter private links) 54 // Is the user logged in? (used to filter private links)
55 private $loggedIn; 55 private $_loggedIn;
56 56
57 // Hide public links 57 // Hide public links
58 private $hidePublicLinks; 58 private $_hidePublicLinks;
59 59
60 /** 60 /**
61 * Creates a new LinkDB 61 * Creates a new LinkDB
@@ -66,11 +66,11 @@ class LinkDB implements Iterator, Countable, ArrayAccess
66 */ 66 */
67 function __construct($datastore, $isLoggedIn, $hidePublicLinks) 67 function __construct($datastore, $isLoggedIn, $hidePublicLinks)
68 { 68 {
69 $this->datastore = $datastore; 69 $this->_datastore = $datastore;
70 $this->loggedIn = $isLoggedIn; 70 $this->_loggedIn = $isLoggedIn;
71 $this->hidePublicLinks = $hidePublicLinks; 71 $this->_hidePublicLinks = $hidePublicLinks;
72 $this->checkDB(); 72 $this->_checkDB();
73 $this->readdb(); 73 $this->_readDB();
74 } 74 }
75 75
76 /** 76 /**
@@ -78,7 +78,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
78 */ 78 */
79 public function count() 79 public function count()
80 { 80 {
81 return count($this->links); 81 return count($this->_links);
82 } 82 }
83 83
84 /** 84 /**
@@ -87,7 +87,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
87 public function offsetSet($offset, $value) 87 public function offsetSet($offset, $value)
88 { 88 {
89 // TODO: use exceptions instead of "die" 89 // TODO: use exceptions instead of "die"
90 if (!$this->loggedIn) { 90 if (!$this->_loggedIn) {
91 die('You are not authorized to add a link.'); 91 die('You are not authorized to add a link.');
92 } 92 }
93 if (empty($value['linkdate']) || empty($value['url'])) { 93 if (empty($value['linkdate']) || empty($value['url'])) {
@@ -96,8 +96,8 @@ class LinkDB implements Iterator, Countable, ArrayAccess
96 if (empty($offset)) { 96 if (empty($offset)) {
97 die('You must specify a key.'); 97 die('You must specify a key.');
98 } 98 }
99 $this->links[$offset] = $value; 99 $this->_links[$offset] = $value;
100 $this->urls[$value['url']]=$offset; 100 $this->_urls[$value['url']]=$offset;
101 } 101 }
102 102
103 /** 103 /**
@@ -105,7 +105,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
105 */ 105 */
106 public function offsetExists($offset) 106 public function offsetExists($offset)
107 { 107 {
108 return array_key_exists($offset, $this->links); 108 return array_key_exists($offset, $this->_links);
109 } 109 }
110 110
111 /** 111 /**
@@ -113,13 +113,13 @@ class LinkDB implements Iterator, Countable, ArrayAccess
113 */ 113 */
114 public function offsetUnset($offset) 114 public function offsetUnset($offset)
115 { 115 {
116 if (!$this->loggedIn) { 116 if (!$this->_loggedIn) {
117 // TODO: raise an exception 117 // TODO: raise an exception
118 die('You are not authorized to delete a link.'); 118 die('You are not authorized to delete a link.');
119 } 119 }
120 $url = $this->links[$offset]['url']; 120 $url = $this->_links[$offset]['url'];
121 unset($this->urls[$url]); 121 unset($this->_urls[$url]);
122 unset($this->links[$offset]); 122 unset($this->_links[$offset]);
123 } 123 }
124 124
125 /** 125 /**
@@ -127,7 +127,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
127 */ 127 */
128 public function offsetGet($offset) 128 public function offsetGet($offset)
129 { 129 {
130 return isset($this->links[$offset]) ? $this->links[$offset] : null; 130 return isset($this->_links[$offset]) ? $this->_links[$offset] : null;
131 } 131 }
132 132
133 /** 133 /**
@@ -135,7 +135,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
135 */ 135 */
136 function current() 136 function current()
137 { 137 {
138 return $this->links[$this->keys[$this->position]]; 138 return $this->_links[$this->_keys[$this->_position]];
139 } 139 }
140 140
141 /** 141 /**
@@ -143,7 +143,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
143 */ 143 */
144 function key() 144 function key()
145 { 145 {
146 return $this->keys[$this->position]; 146 return $this->_keys[$this->_position];
147 } 147 }
148 148
149 /** 149 /**
@@ -151,7 +151,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
151 */ 151 */
152 function next() 152 function next()
153 { 153 {
154 ++$this->position; 154 ++$this->_position;
155 } 155 }
156 156
157 /** 157 /**
@@ -161,9 +161,9 @@ class LinkDB implements Iterator, Countable, ArrayAccess
161 */ 161 */
162 function rewind() 162 function rewind()
163 { 163 {
164 $this->keys = array_keys($this->links); 164 $this->_keys = array_keys($this->_links);
165 rsort($this->keys); 165 rsort($this->_keys);
166 $this->position = 0; 166 $this->_position = 0;
167 } 167 }
168 168
169 /** 169 /**
@@ -171,7 +171,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
171 */ 171 */
172 function valid() 172 function valid()
173 { 173 {
174 return isset($this->keys[$this->position]); 174 return isset($this->_keys[$this->_position]);
175 } 175 }
176 176
177 /** 177 /**
@@ -179,14 +179,14 @@ class LinkDB implements Iterator, Countable, ArrayAccess
179 * 179 *
180 * If no DB file is found, creates a dummy DB. 180 * If no DB file is found, creates a dummy DB.
181 */ 181 */
182 private function checkDB() 182 private function _checkDB()
183 { 183 {
184 if (file_exists($this->datastore)) { 184 if (file_exists($this->_datastore)) {
185 return; 185 return;
186 } 186 }
187 187
188 // Create a dummy database for example 188 // Create a dummy database for example
189 $this->links = array(); 189 $this->_links = array();
190 $link = array( 190 $link = array(
191 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone', 191 'title'=>' Shaarli: the personal, minimalist, super-fast, no-database delicious clone',
192 'url'=>'https://github.com/shaarli/Shaarli/wiki', 192 'url'=>'https://github.com/shaarli/Shaarli/wiki',
@@ -199,7 +199,7 @@ You use the community supported version of the original Shaarli project, by Seba
199 'linkdate'=> date('Ymd_His'), 199 'linkdate'=> date('Ymd_His'),
200 'tags'=>'opensource software' 200 'tags'=>'opensource software'
201 ); 201 );
202 $this->links[$link['linkdate']] = $link; 202 $this->_links[$link['linkdate']] = $link;
203 203
204 $link = array( 204 $link = array(
205 'title'=>'My secret stuff... - Pastebin.com', 205 'title'=>'My secret stuff... - Pastebin.com',
@@ -209,60 +209,60 @@ You use the community supported version of the original Shaarli project, by Seba
209 'linkdate'=> date('Ymd_His', strtotime('-1 minute')), 209 'linkdate'=> date('Ymd_His', strtotime('-1 minute')),
210 'tags'=>'secretstuff' 210 'tags'=>'secretstuff'
211 ); 211 );
212 $this->links[$link['linkdate']] = $link; 212 $this->_links[$link['linkdate']] = $link;
213 213
214 // Write database to disk 214 // Write database to disk
215 // TODO: raise an exception if the file is not write-able 215 // TODO: raise an exception if the file is not write-able
216 file_put_contents( 216 file_put_contents(
217 $this->datastore, 217 $this->_datastore,
218 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix 218 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix
219 ); 219 );
220 } 220 }
221 221
222 /** 222 /**
223 * Reads database from disk to memory 223 * Reads database from disk to memory
224 */ 224 */
225 private function readdb() 225 private function _readDB()
226 { 226 {
227 227
228 // Public links are hidden and user not logged in => nothing to show 228 // Public links are hidden and user not logged in => nothing to show
229 if ($this->hidePublicLinks && !$this->loggedIn) { 229 if ($this->_hidePublicLinks && !$this->_loggedIn) {
230 $this->links = array(); 230 $this->_links = array();
231 return; 231 return;
232 } 232 }
233 233
234 // Read data 234 // Read data
235 // Note that gzinflate is faster than gzuncompress. 235 // Note that gzinflate is faster than gzuncompress.
236 // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 236 // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
237 $this->links = array(); 237 $this->_links = array();
238 238
239 if (file_exists($this->datastore)) { 239 if (file_exists($this->_datastore)) {
240 $this->links = unserialize(gzinflate(base64_decode( 240 $this->_links = unserialize(gzinflate(base64_decode(
241 substr(file_get_contents($this->datastore), 241 substr(file_get_contents($this->_datastore),
242 strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); 242 strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
243 } 243 }
244 244
245 // If user is not logged in, filter private links. 245 // If user is not logged in, filter private links.
246 if (!$this->loggedIn) { 246 if (!$this->_loggedIn) {
247 $toremove = array(); 247 $toremove = array();
248 foreach ($this->links as $link) { 248 foreach ($this->_links as $link) {
249 if ($link['private'] != 0) { 249 if ($link['private'] != 0) {
250 $toremove[] = $link['linkdate']; 250 $toremove[] = $link['linkdate'];
251 } 251 }
252 } 252 }
253 foreach ($toremove as $linkdate) { 253 foreach ($toremove as $linkdate) {
254 unset($this->links[$linkdate]); 254 unset($this->_links[$linkdate]);
255 } 255 }
256 } 256 }
257 257
258 // Keep the list of the mapping URLs-->linkdate up-to-date. 258 // Keep the list of the mapping URLs-->linkdate up-to-date.
259 $this->urls = array(); 259 $this->_urls = array();
260 foreach ($this->links as $link) { 260 foreach ($this->_links as $link) {
261 $this->urls[$link['url']] = $link['linkdate']; 261 $this->_urls[$link['url']] = $link['linkdate'];
262 } 262 }
263 263
264 // Escape links data 264 // Escape links data
265 foreach($this->links as &$link) { 265 foreach($this->_links as &$link) {
266 sanitizeLink($link); 266 sanitizeLink($link);
267 } 267 }
268 } 268 }
@@ -272,13 +272,13 @@ You use the community supported version of the original Shaarli project, by Seba
272 */ 272 */
273 public function savedb() 273 public function savedb()
274 { 274 {
275 if (!$this->loggedIn) { 275 if (!$this->_loggedIn) {
276 // TODO: raise an Exception instead 276 // TODO: raise an Exception instead
277 die('You are not authorized to change the database.'); 277 die('You are not authorized to change the database.');
278 } 278 }
279 file_put_contents( 279 file_put_contents(
280 $this->datastore, 280 $this->_datastore,
281 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix 281 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix
282 ); 282 );
283 invalidateCaches(); 283 invalidateCaches();
284 } 284 }
@@ -288,8 +288,8 @@ You use the community supported version of the original Shaarli project, by Seba
288 */ 288 */
289 public function getLinkFromUrl($url) 289 public function getLinkFromUrl($url)
290 { 290 {
291 if (isset($this->urls[$url])) { 291 if (isset($this->_urls[$url])) {
292 return $this->links[$this->urls[$url]]; 292 return $this->_links[$this->_urls[$url]];
293 } 293 }
294 return false; 294 return false;
295 } 295 }
@@ -316,7 +316,7 @@ You use the community supported version of the original Shaarli project, by Seba
316 $search = mb_convert_case($searchterms, MB_CASE_LOWER, 'UTF-8'); 316 $search = mb_convert_case($searchterms, MB_CASE_LOWER, 'UTF-8');
317 $keys = array('title', 'description', 'url', 'tags'); 317 $keys = array('title', 'description', 'url', 'tags');
318 318
319 foreach ($this->links as $link) { 319 foreach ($this->_links as $link) {
320 $found = false; 320 $found = false;
321 321
322 foreach ($keys as $key) { 322 foreach ($keys as $key) {
@@ -352,7 +352,7 @@ You use the community supported version of the original Shaarli project, by Seba
352 $searchtags = explode(' ', $t); 352 $searchtags = explode(' ', $t);
353 $filtered = array(); 353 $filtered = array();
354 354
355 foreach ($this->links as $l) { 355 foreach ($this->_links as $l) {
356 $linktags = explode( 356 $linktags = explode(
357 ' ', 357 ' ',
358 ($casesensitive ? $l['tags']:mb_convert_case($l['tags'], MB_CASE_LOWER, 'UTF-8')) 358 ($casesensitive ? $l['tags']:mb_convert_case($l['tags'], MB_CASE_LOWER, 'UTF-8'))
@@ -380,7 +380,7 @@ You use the community supported version of the original Shaarli project, by Seba
380 } 380 }
381 381
382 $filtered = array(); 382 $filtered = array();
383 foreach ($this->links as $l) { 383 foreach ($this->_links as $l) {
384 if (startsWith($l['linkdate'], $day)) { 384 if (startsWith($l['linkdate'], $day)) {
385 $filtered[$l['linkdate']] = $l; 385 $filtered[$l['linkdate']] = $l;
386 } 386 }
@@ -395,7 +395,7 @@ You use the community supported version of the original Shaarli project, by Seba
395 public function filterSmallHash($smallHash) 395 public function filterSmallHash($smallHash)
396 { 396 {
397 $filtered = array(); 397 $filtered = array();
398 foreach ($this->links as $l) { 398 foreach ($this->_links as $l) {
399 if ($smallHash == smallHash($l['linkdate'])) { 399 if ($smallHash == smallHash($l['linkdate'])) {
400 // Yes, this is ugly and slow 400 // Yes, this is ugly and slow
401 $filtered[$l['linkdate']] = $l; 401 $filtered[$l['linkdate']] = $l;
@@ -412,7 +412,7 @@ You use the community supported version of the original Shaarli project, by Seba
412 public function allTags() 412 public function allTags()
413 { 413 {
414 $tags = array(); 414 $tags = array();
415 foreach ($this->links as $link) { 415 foreach ($this->_links as $link) {
416 foreach (explode(' ', $link['tags']) as $tag) { 416 foreach (explode(' ', $link['tags']) as $tag) {
417 if (!empty($tag)) { 417 if (!empty($tag)) {
418 $tags[$tag] = (empty($tags[$tag]) ? 1 : $tags[$tag] + 1); 418 $tags[$tag] = (empty($tags[$tag]) ? 1 : $tags[$tag] + 1);
@@ -431,7 +431,7 @@ You use the community supported version of the original Shaarli project, by Seba
431 public function days() 431 public function days()
432 { 432 {
433 $linkDays = array(); 433 $linkDays = array();
434 foreach (array_keys($this->links) as $day) { 434 foreach (array_keys($this->_links) as $day) {
435 $linkDays[substr($day, 0, 8)] = 0; 435 $linkDays[substr($day, 0, 8)] = 0;
436 } 436 }
437 $linkDays = array_keys($linkDays); 437 $linkDays = array_keys($linkDays);
diff --git a/index.php b/index.php
index 8e1552c1..bf0b99e0 100644
--- a/index.php
+++ b/index.php
@@ -11,7 +11,8 @@
11date_default_timezone_set('UTC'); 11date_default_timezone_set('UTC');
12 12
13// ----------------------------------------------------------------------------------------------- 13// -----------------------------------------------------------------------------------------------
14// Hardcoded parameter (These parameters can be overwritten by creating the file /data/options.php) 14// Hardcoded parameter (These parameters can be overwritten by editing the file /data/config.php)
15// You should not touch any code below (or at your own risks!)
15$GLOBALS['config']['DATADIR'] = 'data'; // Data subdirectory 16$GLOBALS['config']['DATADIR'] = 'data'; // Data subdirectory
16$GLOBALS['config']['CONFIG_FILE'] = $GLOBALS['config']['DATADIR'].'/config.php'; // Configuration file (user login/password) 17$GLOBALS['config']['CONFIG_FILE'] = $GLOBALS['config']['DATADIR'].'/config.php'; // Configuration file (user login/password)
17$GLOBALS['config']['DATASTORE'] = $GLOBALS['config']['DATADIR'].'/datastore.php'; // Data storage file. 18$GLOBALS['config']['DATASTORE'] = $GLOBALS['config']['DATADIR'].'/datastore.php'; // Data storage file.
@@ -36,10 +37,6 @@ $GLOBALS['config']['ARCHIVE_ORG'] = false; // For each link, add a link to an ar
36$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. 37$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.
37$GLOBALS['config']['HIDE_PUBLIC_LINKS'] = false; 38$GLOBALS['config']['HIDE_PUBLIC_LINKS'] = false;
38// ----------------------------------------------------------------------------------------------- 39// -----------------------------------------------------------------------------------------------
39// You should not touch below (or at your own risks!)
40// Optional config file.
41if (is_file($GLOBALS['config']['DATADIR'].'/options.php')) require($GLOBALS['config']['DATADIR'].'/options.php');
42
43define('shaarli_version','0.0.45beta'); 40define('shaarli_version','0.0.45beta');
44// http://server.com/x/shaarli --> /shaarli/ 41// http://server.com/x/shaarli --> /shaarli/
45define('WEB_PATH', substr($_SERVER["REQUEST_URI"], 0, 1+strrpos($_SERVER["REQUEST_URI"], '/', 0))); 42define('WEB_PATH', substr($_SERVER["REQUEST_URI"], 0, 1+strrpos($_SERVER["REQUEST_URI"], '/', 0)));
@@ -66,9 +63,15 @@ checkphpversion();
66error_reporting(E_ALL^E_WARNING); // See all error except warnings. 63error_reporting(E_ALL^E_WARNING); // See all error except warnings.
67//error_reporting(-1); // See all errors (for debugging only) 64//error_reporting(-1); // See all errors (for debugging only)
68 65
66// User configuration
67if (is_file($GLOBALS['config']['CONFIG_FILE'])) {
68 require_once $GLOBALS['config']['CONFIG_FILE'];
69}
70
69// Shaarli library 71// Shaarli library
70require_once 'application/LinkDB.php'; 72require_once 'application/LinkDB.php';
71require_once 'application/Utils.php'; 73require_once 'application/Utils.php';
74require_once 'application/Config.php';
72 75
73include "inc/rain.tpl.class.php"; //include Rain TPL 76include "inc/rain.tpl.class.php"; //include Rain TPL
74raintpl::$tpl_dir = $GLOBALS['config']['RAINTPL_TPL']; // template directory 77raintpl::$tpl_dir = $GLOBALS['config']['RAINTPL_TPL']; // template directory
@@ -100,15 +103,15 @@ if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.escape(indexU
100if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get(); 103if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get();
101if (empty($GLOBALS['redirector'])) $GLOBALS['redirector']=''; 104if (empty($GLOBALS['redirector'])) $GLOBALS['redirector']='';
102if (empty($GLOBALS['disablesessionprotection'])) $GLOBALS['disablesessionprotection']=false; 105if (empty($GLOBALS['disablesessionprotection'])) $GLOBALS['disablesessionprotection']=false;
103if (empty($GLOBALS['disablejquery'])) $GLOBALS['disablejquery']=false;
104if (empty($GLOBALS['privateLinkByDefault'])) $GLOBALS['privateLinkByDefault']=false; 106if (empty($GLOBALS['privateLinkByDefault'])) $GLOBALS['privateLinkByDefault']=false;
105if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?'; 107if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?';
106// I really need to rewrite Shaarli with a proper configuation manager. 108// I really need to rewrite Shaarli with a proper configuation manager.
107 109
108// Run config screen if first run: 110// Run config screen if first run:
109if (!is_file($GLOBALS['config']['CONFIG_FILE'])) install(); 111if (! is_file($GLOBALS['config']['CONFIG_FILE'])) {
112 install();
113}
110 114
111require $GLOBALS['config']['CONFIG_FILE']; // Read login/password hash into $GLOBALS.
112$GLOBALS['title'] = !empty($GLOBALS['title']) ? escape($GLOBALS['title']) : ''; 115$GLOBALS['title'] = !empty($GLOBALS['title']) ? escape($GLOBALS['title']) : '';
113$GLOBALS['titleLink'] = !empty($GLOBALS['titleLink']) ? escape($GLOBALS['titleLink']) : ''; 116$GLOBALS['titleLink'] = !empty($GLOBALS['titleLink']) ? escape($GLOBALS['titleLink']) : '';
114$GLOBALS['redirector'] = !empty($GLOBALS['redirector']) ? escape($GLOBALS['redirector']) : ''; 117$GLOBALS['redirector'] = !empty($GLOBALS['redirector']) ? escape($GLOBALS['redirector']) : '';
@@ -856,15 +859,18 @@ function showATOM()
856// Daily RSS feed: 1 RSS entry per day giving all the links on that day. 859// Daily RSS feed: 1 RSS entry per day giving all the links on that day.
857// Gives the last 7 days (which have links). 860// Gives the last 7 days (which have links).
858// This RSS feed cannot be filtered. 861// This RSS feed cannot be filtered.
859function showDailyRSS() 862function showDailyRSS() {
860{
861 // Cache system 863 // Cache system
862 $query = $_SERVER["QUERY_STRING"]; 864 $query = $_SERVER["QUERY_STRING"];
863 $cache = new pageCache(pageUrl(),startsWith($query,'do=dailyrss') && !isLoggedIn()); 865 $cache = new pageCache(pageUrl(), startsWith($query, 'do=dailyrss') && !isLoggedIn());
864 $cached = $cache->cachedVersion(); if (!empty($cached)) { echo $cached; exit; } 866 $cached = $cache->cachedVersion();
865 // If cached was not found (or not usable), then read the database and build the response: 867 if (!empty($cached)) {
868 echo $cached;
869 exit;
870 }
866 871
867// Read links from database (and filter private links if used it not logged in). 872 // If cached was not found (or not usable), then read the database and build the response:
873 // Read links from database (and filter private links if used it not logged in).
868 $LINKSDB = new LinkDB( 874 $LINKSDB = new LinkDB(
869 $GLOBALS['config']['DATASTORE'], 875 $GLOBALS['config']['DATASTORE'],
870 isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'], 876 isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'],
@@ -874,60 +880,75 @@ function showDailyRSS()
874 /* Some Shaarlies may have very few links, so we need to look 880 /* Some Shaarlies may have very few links, so we need to look
875 back in time (rsort()) until we have enough days ($nb_of_days). 881 back in time (rsort()) until we have enough days ($nb_of_days).
876 */ 882 */
877 $linkdates=array(); foreach($LINKSDB as $linkdate=>$value) { $linkdates[]=$linkdate; } 883 $linkdates = array();
884 foreach ($LINKSDB as $linkdate => $value) {
885 $linkdates[] = $linkdate;
886 }
878 rsort($linkdates); 887 rsort($linkdates);
879 $nb_of_days=7; // We take 7 days. 888 $nb_of_days = 7; // We take 7 days.
880 $today=Date('Ymd'); 889 $today = Date('Ymd');
881 $days=array(); 890 $days = array();
882 foreach($linkdates as $linkdate) 891
883 { 892 foreach ($linkdates as $linkdate) {
884 $day=substr($linkdate,0,8); // Extract day (without time) 893 $day = substr($linkdate, 0, 8); // Extract day (without time)
885 if (strcmp($day,$today)<0) 894 if (strcmp($day,$today) < 0) {
886 { 895 if (empty($days[$day])) {
887 if (empty($days[$day])) $days[$day]=array(); 896 $days[$day] = array();
888 $days[$day][]=$linkdate; 897 }
898 $days[$day][] = $linkdate;
899 }
900
901 if (count($days) > $nb_of_days) {
902 break; // Have we collected enough days?
889 } 903 }
890 if (count($days)>$nb_of_days) break; // Have we collected enough days?
891 } 904 }
892 905
893 // Build the RSS feed. 906 // Build the RSS feed.
894 header('Content-Type: application/rss+xml; charset=utf-8'); 907 header('Content-Type: application/rss+xml; charset=utf-8');
895 $pageaddr=escape(indexUrl()); 908 $pageaddr = escape(indexUrl());
896 echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">'; 909 echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">';
897 echo '<channel><title>Daily - '.$GLOBALS['title'].'</title><link>'.$pageaddr.'</link>'; 910 echo '<channel>';
898 echo '<description>Daily shared links</description><language>en-en</language><copyright>'.$pageaddr.'</copyright>'."\n"; 911 echo '<title>Daily - '. $GLOBALS['title'] . '</title>';
899 912 echo '<link>'. $pageaddr .'</link>';
900 foreach($days as $day=>$linkdates) // For each day. 913 echo '<description>Daily shared links</description>';
901 { 914 echo '<language>en-en</language>';
902 $daydate = utf8_encode(strftime('%A %d, %B %Y',linkdate2timestamp($day.'_000000'))); // Full text date 915 echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL;
916
917 // For each day.
918 foreach ($days as $day => $linkdates) {
919 $daydate = linkdate2timestamp($day.'_000000'); // Full text date
903 $rfc822date = linkdate2rfc822($day.'_000000'); 920 $rfc822date = linkdate2rfc822($day.'_000000');
904 $absurl=escape(indexUrl().'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page. 921 $absurl = escape(indexUrl().'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page.
905 echo '<item><title>'.$GLOBALS['title'].' - '.$daydate.'</title><guid>'.$absurl.'</guid><link>'.$absurl.'</link>';
906 echo '<pubDate>'.escape($rfc822date)."</pubDate>";
907 922
908 // Build the HTML body of this RSS entry. 923 // Build the HTML body of this RSS entry.
909 $html=''; 924 $html = '';
910 $href=''; 925 $href = '';
911 $links=array(); 926 $links = array();
927
912 // We pre-format some fields for proper output. 928 // We pre-format some fields for proper output.
913 foreach($linkdates as $linkdate) 929 foreach ($linkdates as $linkdate) {
914 {
915 $l = $LINKSDB[$linkdate]; 930 $l = $LINKSDB[$linkdate];
916 $l['formatedDescription']=nl2br(keepMultipleSpaces(text2clickable($l['description']))); 931 $l['formatedDescription'] = nl2br(keepMultipleSpaces(text2clickable($l['description'])));
917 $l['thumbnail'] = thumbnail($l['url']); 932 $l['thumbnail'] = thumbnail($l['url']);
918 $l['timestamp'] = linkdate2timestamp($l['linkdate']); 933 $l['timestamp'] = linkdate2timestamp($l['linkdate']);
919 if (startsWith($l['url'],'?')) $l['url']=indexUrl().$l['url']; // make permalink URL absolute 934 if (startsWith($l['url'], '?')) {
920 $links[$linkdate]=$l; 935 $l['url'] = indexUrl() . $l['url']; // make permalink URL absolute
936 }
937 $links[$linkdate] = $l;
921 } 938 }
939
922 // Then build the HTML for this day: 940 // Then build the HTML for this day:
923 $tpl = new RainTPL; 941 $tpl = new RainTPL;
924 $tpl->assign('links',$links); 942 $tpl->assign('title', $GLOBALS['title']);
925 $html = $tpl->draw('dailyrss',$return_string=true); 943 $tpl->assign('daydate', $daydate);
926 echo "\n"; 944 $tpl->assign('absurl', $absurl);
927 echo '<description><![CDATA['.$html.']]></description>'."\n</item>\n\n"; 945 $tpl->assign('links', $links);
946 $tpl->assign('rfc822date', escape($rfc822date));
947 $html = $tpl->draw('dailyrss', $return_string=true);
928 948
949 echo $html . PHP_EOL;
929 } 950 }
930 echo '</channel></rss><!-- Cached version of '.escape(pageUrl()).' -->'; 951 echo '</channel></rss><!-- Cached version of '. escape(pageUrl()) .' -->';
931 952
932 $cache->cache(ob_get_contents()); 953 $cache->cache(ob_get_contents());
933 ob_end_flush(); 954 ob_end_flush();
@@ -1106,7 +1127,11 @@ function renderPage()
1106 1127
1107 // Check if this tag is already in the search query and ignore it if it is. 1128 // Check if this tag is already in the search query and ignore it if it is.
1108 // Each tag is always separated by a space 1129 // Each tag is always separated by a space
1109 $current_tags = explode(' ', $params['searchtags']); 1130 if (isset($params['searchtags'])) {
1131 $current_tags = explode(' ', $params['searchtags']);
1132 } else {
1133 $current_tags = array();
1134 }
1110 $addtag = true; 1135 $addtag = true;
1111 foreach ($current_tags as $value) { 1136 foreach ($current_tags as $value) {
1112 if ($value === $_GET['addtag']) { 1137 if ($value === $_GET['addtag']) {
@@ -1229,7 +1254,19 @@ function renderPage()
1229 // Save new password 1254 // Save new password
1230 $GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless. 1255 $GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless.
1231 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']); 1256 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
1232 writeConfig(); 1257 try {
1258 writeConfig($GLOBALS, isLoggedIn());
1259 }
1260 catch(Exception $e) {
1261 error_log(
1262 'ERROR while writing config file after changing password.' . PHP_EOL .
1263 $e->getMessage()
1264 );
1265
1266 // TODO: do not handle exceptions/errors in JS.
1267 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>';
1268 exit;
1269 }
1233 echo '<script>alert("Your password has been changed.");document.location=\'?do=tools\';</script>'; 1270 echo '<script>alert("Your password has been changed.");document.location=\'?do=tools\';</script>';
1234 exit; 1271 exit;
1235 } 1272 }
@@ -1258,12 +1295,23 @@ function renderPage()
1258 $GLOBALS['titleLink']=$_POST['titleLink']; 1295 $GLOBALS['titleLink']=$_POST['titleLink'];
1259 $GLOBALS['redirector']=$_POST['redirector']; 1296 $GLOBALS['redirector']=$_POST['redirector'];
1260 $GLOBALS['disablesessionprotection']=!empty($_POST['disablesessionprotection']); 1297 $GLOBALS['disablesessionprotection']=!empty($_POST['disablesessionprotection']);
1261 $GLOBALS['disablejquery']=!empty($_POST['disablejquery']);
1262 $GLOBALS['privateLinkByDefault']=!empty($_POST['privateLinkByDefault']); 1298 $GLOBALS['privateLinkByDefault']=!empty($_POST['privateLinkByDefault']);
1263 $GLOBALS['config']['ENABLE_RSS_PERMALINKS']= !empty($_POST['enableRssPermalinks']); 1299 $GLOBALS['config']['ENABLE_RSS_PERMALINKS']= !empty($_POST['enableRssPermalinks']);
1264 $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']); 1300 $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
1265 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] = !empty($_POST['hidePublicLinks']); 1301 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] = !empty($_POST['hidePublicLinks']);
1266 writeConfig(); 1302 try {
1303 writeConfig($GLOBALS, isLoggedIn());
1304 }
1305 catch(Exception $e) {
1306 error_log(
1307 'ERROR while writing config file after configuration update.' . PHP_EOL .
1308 $e->getMessage()
1309 );
1310
1311 // TODO: do not handle exceptions/errors in JS.
1312 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>';
1313 exit;
1314 }
1267 echo '<script>alert("Configuration was saved.");document.location=\'?do=tools\';</script>'; 1315 echo '<script>alert("Configuration was saved.");document.location=\'?do=tools\';</script>';
1268 exit; 1316 exit;
1269 } 1317 }
@@ -1345,6 +1393,7 @@ function renderPage()
1345 { 1393 {
1346 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away! 1394 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away!
1347 $tags = trim(preg_replace('/\s\s+/',' ', $_POST['lf_tags'])); // Remove multiple spaces. 1395 $tags = trim(preg_replace('/\s\s+/',' ', $_POST['lf_tags'])); // Remove multiple spaces.
1396 $tags = implode(' ', array_unique(explode(' ', $tags))); // Remove duplicates.
1348 $linkdate=$_POST['lf_linkdate']; 1397 $linkdate=$_POST['lf_linkdate'];
1349 $url = trim($_POST['lf_url']); 1398 $url = trim($_POST['lf_url']);
1350 if (!startsWith($url,'http:') && !startsWith($url,'https:') && !startsWith($url,'ftp:') && !startsWith($url,'magnet:') && !startsWith($url,'?') && !startsWith($url,'javascript:')) 1399 if (!startsWith($url,'http:') && !startsWith($url,'https:') && !startsWith($url,'ftp:') && !startsWith($url,'magnet:') && !startsWith($url,'?') && !startsWith($url,'javascript:'))
@@ -1714,7 +1763,7 @@ function buildLinkList($PAGE,$LINKSDB)
1714 { 1763 {
1715 header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found"); 1764 header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
1716 echo '<h1>404 Not found.</h1>Oh crap. The link you are trying to reach does not exist or has been deleted.'; 1765 echo '<h1>404 Not found.</h1>Oh crap. The link you are trying to reach does not exist or has been deleted.';
1717 echo '<br>You would mind <a href="?">clicking here</a>?'; 1766 echo '<br>Would you mind <a href="?">clicking here</a>?';
1718 exit; 1767 exit;
1719 } 1768 }
1720 $search_type='permalink'; 1769 $search_type='permalink';
@@ -2020,7 +2069,19 @@ function install()
2020 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']); 2069 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
2021 $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.escape(indexUrl()) : $_POST['title'] ); 2070 $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.escape(indexUrl()) : $_POST['title'] );
2022 $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']); 2071 $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
2023 writeConfig(); 2072 try {
2073 writeConfig($GLOBALS, isLoggedIn());
2074 }
2075 catch(Exception $e) {
2076 error_log(
2077 'ERROR while writing config file after installation.' . PHP_EOL .
2078 $e->getMessage()
2079 );
2080
2081 // TODO: do not handle exceptions/errors in JS.
2082 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?\';</script>';
2083 exit;
2084 }
2024 echo '<script>alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>'; 2085 echo '<script>alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>';
2025 exit; 2086 exit;
2026 } 2087 }
@@ -2134,30 +2195,7 @@ if (!function_exists('json_encode')) {
2134 } 2195 }
2135} 2196}
2136 2197
2137// Re-write configuration file according to globals. 2198
2138// Requires some $GLOBALS to be set (login,hash,salt,title).
2139// If the config file cannot be saved, an error message is displayed and the user is redirected to "Tools" menu.
2140// (otherwise, the function simply returns.)
2141function writeConfig()
2142{
2143 if (is_file($GLOBALS['config']['CONFIG_FILE']) && !isLoggedIn()) die('You are not authorized to alter config.'); // Only logged in user can alter config.
2144 $config='<?php $GLOBALS[\'login\']='.var_export($GLOBALS['login'],true).'; $GLOBALS[\'hash\']='.var_export($GLOBALS['hash'],true).'; $GLOBALS[\'salt\']='.var_export($GLOBALS['salt'],true).'; ';
2145 $config .='$GLOBALS[\'timezone\']='.var_export($GLOBALS['timezone'],true).'; date_default_timezone_set('.var_export($GLOBALS['timezone'],true).'); $GLOBALS[\'title\']='.var_export($GLOBALS['title'],true).';';
2146 $config .= '$GLOBALS[\'titleLink\']='.var_export($GLOBALS['titleLink'],true).'; ';
2147 $config .= '$GLOBALS[\'redirector\']='.var_export($GLOBALS['redirector'],true).'; ';
2148 $config .= '$GLOBALS[\'disablesessionprotection\']='.var_export($GLOBALS['disablesessionprotection'],true).'; ';
2149 $config .= '$GLOBALS[\'disablejquery\']='.var_export($GLOBALS['disablejquery'],true).'; ';
2150 $config .= '$GLOBALS[\'privateLinkByDefault\']='.var_export($GLOBALS['privateLinkByDefault'],true).'; ';
2151 $config .= '$GLOBALS[\'config\'][\'ENABLE_RSS_PERMALINKS\']='.var_export($GLOBALS['config']['ENABLE_RSS_PERMALINKS'], true).'; ';
2152 $config .= '$GLOBALS[\'config\'][\'ENABLE_UPDATECHECK\']='.var_export($GLOBALS['config']['ENABLE_UPDATECHECK'], true).'; ';
2153 $config .= '$GLOBALS[\'config\'][\'HIDE_PUBLIC_LINKS\']='.var_export($GLOBALS['config']['HIDE_PUBLIC_LINKS'], true).'; ';
2154 $config .= ' ?>';
2155 if (!file_put_contents($GLOBALS['config']['CONFIG_FILE'],$config) || strcmp(file_get_contents($GLOBALS['config']['CONFIG_FILE']),$config)!=0)
2156 {
2157 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>';
2158 exit;
2159 }
2160}
2161 2199
2162/* Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL, 2200/* Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL,
2163 I have deported the thumbnail URL code generation here, otherwise this would slow down page generation. 2201 I have deported the thumbnail URL code generation here, otherwise this would slow down page generation.
@@ -2386,6 +2424,15 @@ function invalidateCaches()
2386 pageCache::purgeCache(); // Purge page cache shared by sessions. 2424 pageCache::purgeCache(); // Purge page cache shared by sessions.
2387} 2425}
2388 2426
2427try {
2428 mergeDeprecatedConfig($GLOBALS, isLoggedIn());
2429} catch(Exception $e) {
2430 error_log(
2431 'ERROR while merging deprecated options.php file.' . PHP_EOL .
2432 $e->getMessage()
2433 );
2434}
2435
2389if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database. 2436if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database.
2390if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; } 2437if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; }
2391if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; } 2438if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; }
diff --git a/tests/ConfigTest.php b/tests/ConfigTest.php
new file mode 100755
index 00000000..4279c57e
--- /dev/null
+++ b/tests/ConfigTest.php
@@ -0,0 +1,177 @@
1<?php
2/**
3 * Config' tests
4 */
5
6require_once 'application/Config.php';
7
8/**
9 * Unitary tests for Shaarli config related functions
10 */
11class ConfigTest extends PHPUnit_Framework_TestCase
12{
13 // Configuration input set.
14 private static $_configFields;
15
16 /**
17 * Executed before each test.
18 */
19 public function setUp()
20 {
21 self::$_configFields = [
22 'login' => 'login',
23 'hash' => 'hash',
24 'salt' => 'salt',
25 'timezone' => 'Europe/Paris',
26 'title' => 'title',
27 'titleLink' => 'titleLink',
28 'redirector' => '',
29 'disablesessionprotection' => false,
30 'privateLinkByDefault' => false,
31 'config' => [
32 'CONFIG_FILE' => 'tests/config.php',
33 'DATADIR' => 'tests',
34 'config1' => 'config1data',
35 'config2' => 'config2data',
36 ]
37 ];
38 }
39
40 /**
41 * Executed after each test.
42 *
43 * @return void
44 */
45 public function tearDown()
46 {
47 if (is_file(self::$_configFields['config']['CONFIG_FILE'])) {
48 unlink(self::$_configFields['config']['CONFIG_FILE']);
49 }
50 }
51
52 /**
53 * Test writeConfig function, valid use case, while being logged in.
54 */
55 public function testWriteConfig()
56 {
57 writeConfig(self::$_configFields, true);
58
59 include self::$_configFields['config']['CONFIG_FILE'];
60 $this->assertEquals(self::$_configFields['login'], $GLOBALS['login']);
61 $this->assertEquals(self::$_configFields['hash'], $GLOBALS['hash']);
62 $this->assertEquals(self::$_configFields['salt'], $GLOBALS['salt']);
63 $this->assertEquals(self::$_configFields['timezone'], $GLOBALS['timezone']);
64 $this->assertEquals(self::$_configFields['title'], $GLOBALS['title']);
65 $this->assertEquals(self::$_configFields['titleLink'], $GLOBALS['titleLink']);
66 $this->assertEquals(self::$_configFields['redirector'], $GLOBALS['redirector']);
67 $this->assertEquals(self::$_configFields['disablesessionprotection'], $GLOBALS['disablesessionprotection']);
68 $this->assertEquals(self::$_configFields['privateLinkByDefault'], $GLOBALS['privateLinkByDefault']);
69 $this->assertEquals(self::$_configFields['config']['config1'], $GLOBALS['config']['config1']);
70 $this->assertEquals(self::$_configFields['config']['config2'], $GLOBALS['config']['config2']);
71 }
72
73 /**
74 * Test writeConfig option while logged in:
75 * 1. init fields.
76 * 2. update fields, add new sub config, add new root config.
77 * 3. rewrite config.
78 * 4. check result.
79 */
80 public function testWriteConfigFieldUpdate()
81 {
82 writeConfig(self::$_configFields, true);
83 self::$_configFields['title'] = 'ok';
84 self::$_configFields['config']['config1'] = 'ok';
85 self::$_configFields['config']['config_new'] = 'ok';
86 self::$_configFields['new'] = 'should not be saved';
87 writeConfig(self::$_configFields, true);
88
89 include self::$_configFields['config']['CONFIG_FILE'];
90 $this->assertEquals('ok', $GLOBALS['title']);
91 $this->assertEquals('ok', $GLOBALS['config']['config1']);
92 $this->assertEquals('ok', $GLOBALS['config']['config_new']);
93 $this->assertFalse(isset($GLOBALS['new']));
94 }
95
96 /**
97 * Test writeConfig function with an empty array.
98 *
99 * @expectedException MissingFieldConfigException
100 */
101 public function testWriteConfigEmpty()
102 {
103 writeConfig(array(), true);
104 }
105
106 /**
107 * Test writeConfig function with a missing mandatory field.
108 *
109 * @expectedException MissingFieldConfigException
110 */
111 public function testWriteConfigMissingField()
112 {
113 unset(self::$_configFields['login']);
114 writeConfig(self::$_configFields, true);
115 }
116
117 /**
118 * Test writeConfig function while being logged out, and there is no config file existing.
119 */
120 public function testWriteConfigLoggedOutNoFile()
121 {
122 writeConfig(self::$_configFields, false);
123 }
124
125 /**
126 * Test writeConfig function while being logged out, and a config file already exists.
127 *
128 * @expectedException UnauthorizedConfigException
129 */
130 public function testWriteConfigLoggedOutWithFile()
131 {
132 file_put_contents(self::$_configFields['config']['CONFIG_FILE'], '');
133 writeConfig(self::$_configFields, false);
134 }
135
136 /**
137 * Test mergeDeprecatedConfig while being logged in:
138 * 1. init a config file.
139 * 2. init a options.php file with update value.
140 * 3. merge.
141 * 4. check updated value in config file.
142 */
143 public function testMergeDeprecatedConfig()
144 {
145 // init
146 writeConfig(self::$_configFields, true);
147 $configCopy = self::$_configFields;
148 $invert = !$configCopy['privateLinkByDefault'];
149 $configCopy['privateLinkByDefault'] = $invert;
150
151 // Use writeConfig to create a options.php
152 $configCopy['config']['CONFIG_FILE'] = 'tests/options.php';
153 writeConfig($configCopy, true);
154
155 $this->assertTrue(is_file($configCopy['config']['CONFIG_FILE']));
156
157 // merge configs
158 mergeDeprecatedConfig(self::$_configFields, true);
159
160 // make sure updated field is changed
161 include self::$_configFields['config']['CONFIG_FILE'];
162 $this->assertEquals($invert, $GLOBALS['privateLinkByDefault']);
163 $this->assertFalse(is_file($configCopy['config']['CONFIG_FILE']));
164 }
165
166 /**
167 * Test mergeDeprecatedConfig while being logged in without options file.
168 */
169 public function testMergeDeprecatedConfigNoFile()
170 {
171 writeConfig(self::$_configFields, true);
172 mergeDeprecatedConfig(self::$_configFields, true);
173
174 include self::$_configFields['config']['CONFIG_FILE'];
175 $this->assertEquals(self::$_configFields['login'], $GLOBALS['login']);
176 }
177} \ No newline at end of file
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index 8b0bd23b..d34ea4f5 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -103,7 +103,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
103 unlink(self::$testDatastore); 103 unlink(self::$testDatastore);
104 $this->assertFileNotExists(self::$testDatastore); 104 $this->assertFileNotExists(self::$testDatastore);
105 105
106 $checkDB = self::getMethod('checkDB'); 106 $checkDB = self::getMethod('_checkDB');
107 $checkDB->invokeArgs($linkDB, array()); 107 $checkDB->invokeArgs($linkDB, array());
108 $this->assertFileExists(self::$testDatastore); 108 $this->assertFileExists(self::$testDatastore);
109 109
@@ -120,7 +120,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
120 $datastoreSize = filesize(self::$testDatastore); 120 $datastoreSize = filesize(self::$testDatastore);
121 $this->assertGreaterThan(0, $datastoreSize); 121 $this->assertGreaterThan(0, $datastoreSize);
122 122
123 $checkDB = self::getMethod('checkDB'); 123 $checkDB = self::getMethod('_checkDB');
124 $checkDB->invokeArgs($linkDB, array()); 124 $checkDB->invokeArgs($linkDB, array());
125 125
126 // ensure the datastore is left unmodified 126 // ensure the datastore is left unmodified
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php
index 59ba671f..0b225720 100644
--- a/tests/utils/ReferenceLinkDB.php
+++ b/tests/utils/ReferenceLinkDB.php
@@ -4,9 +4,9 @@
4 */ 4 */
5class ReferenceLinkDB 5class ReferenceLinkDB
6{ 6{
7 private $links = array(); 7 private $_links = array();
8 private $publicCount = 0; 8 private $_publicCount = 0;
9 private $privateCount = 0; 9 private $_privateCount = 0;
10 10
11 /** 11 /**
12 * Populates the test DB with reference data 12 * Populates the test DB with reference data
@@ -81,13 +81,13 @@ class ReferenceLinkDB
81 'linkdate' => $date, 81 'linkdate' => $date,
82 'tags' => $tags, 82 'tags' => $tags,
83 ); 83 );
84 $this->links[$date] = $link; 84 $this->_links[$date] = $link;
85 85
86 if ($private) { 86 if ($private) {
87 $this->privateCount++; 87 $this->_privateCount++;
88 return; 88 return;
89 } 89 }
90 $this->publicCount++; 90 $this->_publicCount++;
91 } 91 }
92 92
93 /** 93 /**
@@ -97,7 +97,7 @@ class ReferenceLinkDB
97 { 97 {
98 file_put_contents( 98 file_put_contents(
99 $filename, 99 $filename,
100 '<?php /* '.base64_encode(gzdeflate(serialize($this->links))).' */ ?>' 100 '<?php /* '.base64_encode(gzdeflate(serialize($this->_links))).' */ ?>'
101 ); 101 );
102 } 102 }
103 103
@@ -106,7 +106,7 @@ class ReferenceLinkDB
106 */ 106 */
107 public function countLinks() 107 public function countLinks()
108 { 108 {
109 return $this->publicCount + $this->privateCount; 109 return $this->_publicCount + $this->_privateCount;
110 } 110 }
111 111
112 /** 112 /**
@@ -114,7 +114,7 @@ class ReferenceLinkDB
114 */ 114 */
115 public function countPublicLinks() 115 public function countPublicLinks()
116 { 116 {
117 return $this->publicCount; 117 return $this->_publicCount;
118 } 118 }
119 119
120 /** 120 /**
@@ -122,7 +122,7 @@ class ReferenceLinkDB
122 */ 122 */
123 public function countPrivateLinks() 123 public function countPrivateLinks()
124 { 124 {
125 return $this->privateCount; 125 return $this->_privateCount;
126 } 126 }
127} 127}
128?> 128?>
diff --git a/tpl/dailyrss.html b/tpl/dailyrss.html
index 1b7ab8e9..d959d6be 100644
--- a/tpl/dailyrss.html
+++ b/tpl/dailyrss.html
@@ -1,8 +1,16 @@
1{loop="links"} 1<item>
2 <h3><a href="{$value.url}">{$value.title}</a></h3> 2 <title>{$title} - {function="strftime('%A %e %B %Y', $daydate)"}</title>
3 <small>{if="!$GLOBALS['config']['HIDE_TIMESTAMPS']"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br> 3 <guid>{$absurl}</guid>
4 {$value.url}</small><br> 4 <link>{$absurl}</link>
5 {if="$value.thumbnail"}{$value.thumbnail}{/if}<br> 5 <pubDate>{$rfc822date}</pubDate>
6 {if="$value.description"}{$value.formatedDescription}{/if} 6 <description><![CDATA[
7 <br><br><hr> 7 {loop="links"}
8{/loop} \ No newline at end of file 8 <h3><a href="{$value.url}">{$value.title}</a></h3>
9 <small>{if="!$GLOBALS['config']['HIDE_TIMESTAMPS']"}{function="strftime('%c', $value.timestamp)"} - {/if}{if="$value.tags"}{$value.tags}{/if}<br>
10 {$value.url}</small><br>
11 {if="$value.thumbnail"}{$value.thumbnail}{/if}<br>
12 {if="$value.description"}{$value.formatedDescription}{/if}
13 <br><br><hr>
14 {/loop}
15 ]]></description>
16</item> \ No newline at end of file
diff --git a/tpl/editlink.html b/tpl/editlink.html
index a32748ab..3733ca21 100644
--- a/tpl/editlink.html
+++ b/tpl/editlink.html
@@ -42,7 +42,7 @@
42{if="($GLOBALS['config']['OPEN_SHAARLI'] || isLoggedIn())"} 42{if="($GLOBALS['config']['OPEN_SHAARLI'] || isLoggedIn())"}
43<script> 43<script>
44 $ = Awesomplete.$; 44 $ = Awesomplete.$;
45 new Awesomplete($('input[data-multiple]'), { 45 awesomplete = new Awesomplete($('input[data-multiple]'), {
46 filter: function(text, input) { 46 filter: function(text, input) {
47 return Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]); 47 return Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]);
48 }, 48 },
@@ -52,6 +52,28 @@
52 }, 52 },
53 minChars: 1 53 minChars: 1
54 }); 54 });
55
56 /**
57 * Remove already selected items from autocompletion list.
58 * HTML list is never updated, so removing a tag will add it back to awesomplete.
59 *
60 * FIXME: This a workaround waiting for awesomplete to handle this.
61 * https://github.com/LeaVerou/awesomplete/issues/16749
62 */
63 var input = document.querySelector('#lf_tags');
64 input.addEventListener('input', function()
65 {
66 proposedTags = input.getAttribute('data-list').replace(/,/g, '').split(' ');
67 reg = /(\w+) /g;
68 while((match = reg.exec(input.value)) !== null) {
69 id = proposedTags.indexOf(match[1]);
70 if(id != -1 ) {
71 proposedTags.splice(id, 1);
72 }
73 }
74
75 awesomplete.list = proposedTags;
76 });
55</script> 77</script>
56{/if} 78{/if}
57</body> 79</body>