aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rwxr-xr-xapplication/Config.php129
-rw-r--r--application/LinkDB.php129
-rw-r--r--application/Utils.php15
-rw-r--r--index.php208
-rwxr-xr-xtests/ConfigTest.php177
-rw-r--r--tests/LinkDBTest.php27
-rw-r--r--tests/UtilsTest.php19
-rw-r--r--tests/utils/ReferenceLinkDB.php20
-rw-r--r--tpl/dailyrss.html24
9 files changed, 575 insertions, 173 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 a673b086..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'))
@@ -375,9 +375,12 @@ You use the community supported version of the original Shaarli project, by Seba
375 */ 375 */
376 public function filterDay($day) 376 public function filterDay($day)
377 { 377 {
378 // TODO: check input format 378 if (! checkDateFormat('Ymd', $day)) {
379 throw new Exception('Invalid date format');
380 }
381
379 $filtered = array(); 382 $filtered = array();
380 foreach ($this->links as $l) { 383 foreach ($this->_links as $l) {
381 if (startsWith($l['linkdate'], $day)) { 384 if (startsWith($l['linkdate'], $day)) {
382 $filtered[$l['linkdate']] = $l; 385 $filtered[$l['linkdate']] = $l;
383 } 386 }
@@ -392,7 +395,7 @@ You use the community supported version of the original Shaarli project, by Seba
392 public function filterSmallHash($smallHash) 395 public function filterSmallHash($smallHash)
393 { 396 {
394 $filtered = array(); 397 $filtered = array();
395 foreach ($this->links as $l) { 398 foreach ($this->_links as $l) {
396 if ($smallHash == smallHash($l['linkdate'])) { 399 if ($smallHash == smallHash($l['linkdate'])) {
397 // Yes, this is ugly and slow 400 // Yes, this is ugly and slow
398 $filtered[$l['linkdate']] = $l; 401 $filtered[$l['linkdate']] = $l;
@@ -409,7 +412,7 @@ You use the community supported version of the original Shaarli project, by Seba
409 public function allTags() 412 public function allTags()
410 { 413 {
411 $tags = array(); 414 $tags = array();
412 foreach ($this->links as $link) { 415 foreach ($this->_links as $link) {
413 foreach (explode(' ', $link['tags']) as $tag) { 416 foreach (explode(' ', $link['tags']) as $tag) {
414 if (!empty($tag)) { 417 if (!empty($tag)) {
415 $tags[$tag] = (empty($tags[$tag]) ? 1 : $tags[$tag] + 1); 418 $tags[$tag] = (empty($tags[$tag]) ? 1 : $tags[$tag] + 1);
@@ -428,7 +431,7 @@ You use the community supported version of the original Shaarli project, by Seba
428 public function days() 431 public function days()
429 { 432 {
430 $linkDays = array(); 433 $linkDays = array();
431 foreach (array_keys($this->links) as $day) { 434 foreach (array_keys($this->_links) as $day) {
432 $linkDays[substr($day, 0, 8)] = 0; 435 $linkDays[substr($day, 0, 8)] = 0;
433 } 436 }
434 $linkDays = array_keys($linkDays); 437 $linkDays = array_keys($linkDays);
diff --git a/application/Utils.php b/application/Utils.php
index 82220bfc..a1e97b35 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -69,4 +69,19 @@ function sanitizeLink(&$link)
69 $link['description'] = escape($link['description']); 69 $link['description'] = escape($link['description']);
70 $link['tags'] = escape($link['tags']); 70 $link['tags'] = escape($link['tags']);
71} 71}
72
73/**
74 * Checks if a string represents a valid date
75 *
76 * @param string a string-formatted date
77 * @param format the expected DateTime format of the string
78 * @return whether the string is a valid date
79 * @see http://php.net/manual/en/class.datetime.php
80 * @see http://php.net/manual/en/datetime.createfromformat.php
81 */
82function checkDateFormat($format, $string)
83{
84 $date = DateTime::createFromFormat($format, $string);
85 return $date && $date->format($string) == $string;
86}
72?> 87?>
diff --git a/index.php b/index.php
index 87d9116a..5f173f4d 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();
@@ -948,16 +969,22 @@ function showDaily()
948 969
949 $days = $LINKSDB->days(); 970 $days = $LINKSDB->days();
950 $i = array_search($day,$days); 971 $i = array_search($day,$days);
951 if ($i==false) { $i=count($days)-1; $day=$days[$i]; } 972 if ($i===false) { $i=count($days)-1; $day=$days[$i]; }
952 $previousday=''; 973 $previousday='';
953 $nextday=''; 974 $nextday='';
954 if ($i!==false) 975 if ($i!==false)
955 { 976 {
956 if ($i>1) $previousday=$days[$i-1]; 977 if ($i>=1) $previousday=$days[$i-1];
957 if ($i<count($days)-1) $nextday=$days[$i+1]; 978 if ($i<count($days)-1) $nextday=$days[$i+1];
958 } 979 }
959 980
960 $linksToDisplay=$LINKSDB->filterDay($day); 981 try {
982 $linksToDisplay = $LINKSDB->filterDay($day);
983 } catch (Exception $exc) {
984 error_log($exc);
985 $linksToDisplay = [];
986 }
987
961 // We pre-format some fields for proper output. 988 // We pre-format some fields for proper output.
962 foreach($linksToDisplay as $key=>$link) 989 foreach($linksToDisplay as $key=>$link)
963 { 990 {
@@ -1214,7 +1241,19 @@ function renderPage()
1214 // Save new password 1241 // Save new password
1215 $GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless. 1242 $GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless.
1216 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']); 1243 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
1217 writeConfig(); 1244 try {
1245 writeConfig($GLOBALS, isLoggedIn());
1246 }
1247 catch(Exception $e) {
1248 error_log(
1249 'ERROR while writing config file after changing password.' . PHP_EOL .
1250 $e->getMessage()
1251 );
1252
1253 // TODO: do not handle exceptions/errors in JS.
1254 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>';
1255 exit;
1256 }
1218 echo '<script>alert("Your password has been changed.");document.location=\'?do=tools\';</script>'; 1257 echo '<script>alert("Your password has been changed.");document.location=\'?do=tools\';</script>';
1219 exit; 1258 exit;
1220 } 1259 }
@@ -1243,12 +1282,23 @@ function renderPage()
1243 $GLOBALS['titleLink']=$_POST['titleLink']; 1282 $GLOBALS['titleLink']=$_POST['titleLink'];
1244 $GLOBALS['redirector']=$_POST['redirector']; 1283 $GLOBALS['redirector']=$_POST['redirector'];
1245 $GLOBALS['disablesessionprotection']=!empty($_POST['disablesessionprotection']); 1284 $GLOBALS['disablesessionprotection']=!empty($_POST['disablesessionprotection']);
1246 $GLOBALS['disablejquery']=!empty($_POST['disablejquery']);
1247 $GLOBALS['privateLinkByDefault']=!empty($_POST['privateLinkByDefault']); 1285 $GLOBALS['privateLinkByDefault']=!empty($_POST['privateLinkByDefault']);
1248 $GLOBALS['config']['ENABLE_RSS_PERMALINKS']= !empty($_POST['enableRssPermalinks']); 1286 $GLOBALS['config']['ENABLE_RSS_PERMALINKS']= !empty($_POST['enableRssPermalinks']);
1249 $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']); 1287 $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
1250 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] = !empty($_POST['hidePublicLinks']); 1288 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] = !empty($_POST['hidePublicLinks']);
1251 writeConfig(); 1289 try {
1290 writeConfig($GLOBALS, isLoggedIn());
1291 }
1292 catch(Exception $e) {
1293 error_log(
1294 'ERROR while writing config file after configuration update.' . PHP_EOL .
1295 $e->getMessage()
1296 );
1297
1298 // TODO: do not handle exceptions/errors in JS.
1299 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>';
1300 exit;
1301 }
1252 echo '<script>alert("Configuration was saved.");document.location=\'?do=tools\';</script>'; 1302 echo '<script>alert("Configuration was saved.");document.location=\'?do=tools\';</script>';
1253 exit; 1303 exit;
1254 } 1304 }
@@ -2008,7 +2058,19 @@ function install()
2008 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']); 2058 $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
2009 $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.escape(indexUrl()) : $_POST['title'] ); 2059 $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.escape(indexUrl()) : $_POST['title'] );
2010 $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']); 2060 $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
2011 writeConfig(); 2061 try {
2062 writeConfig($GLOBALS, isLoggedIn());
2063 }
2064 catch(Exception $e) {
2065 error_log(
2066 'ERROR while writing config file after installation.' . PHP_EOL .
2067 $e->getMessage()
2068 );
2069
2070 // TODO: do not handle exceptions/errors in JS.
2071 echo '<script>alert("'. $e->getMessage() .'");document.location=\'?\';</script>';
2072 exit;
2073 }
2012 echo '<script>alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>'; 2074 echo '<script>alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>';
2013 exit; 2075 exit;
2014 } 2076 }
@@ -2122,30 +2184,7 @@ if (!function_exists('json_encode')) {
2122 } 2184 }
2123} 2185}
2124 2186
2125// Re-write configuration file according to globals. 2187
2126// Requires some $GLOBALS to be set (login,hash,salt,title).
2127// If the config file cannot be saved, an error message is displayed and the user is redirected to "Tools" menu.
2128// (otherwise, the function simply returns.)
2129function writeConfig()
2130{
2131 if (is_file($GLOBALS['config']['CONFIG_FILE']) && !isLoggedIn()) die('You are not authorized to alter config.'); // Only logged in user can alter config.
2132 $config='<?php $GLOBALS[\'login\']='.var_export($GLOBALS['login'],true).'; $GLOBALS[\'hash\']='.var_export($GLOBALS['hash'],true).'; $GLOBALS[\'salt\']='.var_export($GLOBALS['salt'],true).'; ';
2133 $config .='$GLOBALS[\'timezone\']='.var_export($GLOBALS['timezone'],true).'; date_default_timezone_set('.var_export($GLOBALS['timezone'],true).'); $GLOBALS[\'title\']='.var_export($GLOBALS['title'],true).';';
2134 $config .= '$GLOBALS[\'titleLink\']='.var_export($GLOBALS['titleLink'],true).'; ';
2135 $config .= '$GLOBALS[\'redirector\']='.var_export($GLOBALS['redirector'],true).'; ';
2136 $config .= '$GLOBALS[\'disablesessionprotection\']='.var_export($GLOBALS['disablesessionprotection'],true).'; ';
2137 $config .= '$GLOBALS[\'disablejquery\']='.var_export($GLOBALS['disablejquery'],true).'; ';
2138 $config .= '$GLOBALS[\'privateLinkByDefault\']='.var_export($GLOBALS['privateLinkByDefault'],true).'; ';
2139 $config .= '$GLOBALS[\'config\'][\'ENABLE_RSS_PERMALINKS\']='.var_export($GLOBALS['config']['ENABLE_RSS_PERMALINKS'], true).'; ';
2140 $config .= '$GLOBALS[\'config\'][\'ENABLE_UPDATECHECK\']='.var_export($GLOBALS['config']['ENABLE_UPDATECHECK'], true).'; ';
2141 $config .= '$GLOBALS[\'config\'][\'HIDE_PUBLIC_LINKS\']='.var_export($GLOBALS['config']['HIDE_PUBLIC_LINKS'], true).'; ';
2142 $config .= ' ?>';
2143 if (!file_put_contents($GLOBALS['config']['CONFIG_FILE'],$config) || strcmp(file_get_contents($GLOBALS['config']['CONFIG_FILE']),$config)!=0)
2144 {
2145 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>';
2146 exit;
2147 }
2148}
2149 2188
2150/* Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL, 2189/* Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL,
2151 I have deported the thumbnail URL code generation here, otherwise this would slow down page generation. 2190 I have deported the thumbnail URL code generation here, otherwise this would slow down page generation.
@@ -2374,6 +2413,15 @@ function invalidateCaches()
2374 pageCache::purgeCache(); // Purge page cache shared by sessions. 2413 pageCache::purgeCache(); // Purge page cache shared by sessions.
2375} 2414}
2376 2415
2416try {
2417 mergeDeprecatedConfig($GLOBALS, isLoggedIn());
2418} catch(Exception $e) {
2419 error_log(
2420 'ERROR while merging deprecated options.php file.' . PHP_EOL .
2421 $e->getMessage()
2422 );
2423}
2424
2377if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database. 2425if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database.
2378if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; } 2426if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; }
2379if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; } 2427if (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 ee8dbee3..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
@@ -396,19 +396,22 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
396 396
397 /** 397 /**
398 * Use an invalid date format 398 * Use an invalid date format
399 * @expectedException Exception
400 * @expectedExceptionMessageRegExp /Invalid date format/
399 */ 401 */
400 public function testFilterInvalidDay() 402 public function testFilterInvalidDayWithChars()
401 { 403 {
402 $this->assertEquals( 404 self::$privateLinkDB->filterDay('Rainy day, dream away');
403 0, 405 }
404 sizeof(self::$privateLinkDB->filterDay('Rainy day, dream away'))
405 );
406 406
407 // TODO: check input format 407 /**
408 $this->assertEquals( 408 * Use an invalid date format
409 6, 409 * @expectedException Exception
410 sizeof(self::$privateLinkDB->filterDay('20')) 410 * @expectedExceptionMessageRegExp /Invalid date format/
411 ); 411 */
412 public function testFilterInvalidDayDigits()
413 {
414 self::$privateLinkDB->filterDay('20');
412 } 415 }
413 416
414 /** 417 /**
diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php
index bbba99f2..90392dfb 100644
--- a/tests/UtilsTest.php
+++ b/tests/UtilsTest.php
@@ -74,5 +74,24 @@ class UtilsTest extends PHPUnit_Framework_TestCase
74 $this->assertTrue(endsWith('å!ùµ', 'ùµ', false)); 74 $this->assertTrue(endsWith('å!ùµ', 'ùµ', false));
75 $this->assertTrue(endsWith('µ$åù', 'åù', true)); 75 $this->assertTrue(endsWith('µ$åù', 'åù', true));
76 } 76 }
77
78 /**
79 * Check valid date strings, according to a DateTime format
80 */
81 public function testCheckValidDateFormat()
82 {
83 $this->assertTrue(checkDateFormat('Ymd', '20150627'));
84 $this->assertTrue(checkDateFormat('Y-m-d', '2015-06-27'));
85 }
86
87 /**
88 * Check erroneous date strings, according to a DateTime format
89 */
90 public function testCheckInvalidDateFormat()
91 {
92 $this->assertFalse(checkDateFormat('Ymd', '2015'));
93 $this->assertFalse(checkDateFormat('Y-m-d', '2015-06'));
94 $this->assertFalse(checkDateFormat('Ymd', 'DeLorean'));
95 }
77} 96}
78?> 97?>
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