aboutsummaryrefslogtreecommitdiffhomepage
path: root/inc/poche
diff options
context:
space:
mode:
authorNicolas Lœuillet <nicolas.loeuillet@smile.fr>2014-10-10 13:33:54 +0200
committerNicolas Lœuillet <nicolas.loeuillet@smile.fr>2014-10-10 13:33:54 +0200
commit44d35257e805856b4913c63fcbed3c0acb64bae8 (patch)
tree11e9d276c34b1b287706cb61182bdc71729661e2 /inc/poche
parentaf8292c1de1886cd975d79f0f42df40e0bd1c5bd (diff)
parentcf8a5e1eedbed484dbcb1ddc9f7a13fc19b7a27b (diff)
downloadwallabag-1.8.0.tar.gz
wallabag-1.8.0.tar.zst
wallabag-1.8.0.zip
Merge branch 'dev'1.8.0
Diffstat (limited to 'inc/poche')
-rwxr-xr-xinc/poche/Database.class.php181
-rw-r--r--inc/poche/Language.class.php113
-rwxr-xr-xinc/poche/Poche.class.php896
-rwxr-xr-xinc/poche/Routing.class.php151
-rw-r--r--inc/poche/Template.class.php235
-rwxr-xr-xinc/poche/Tools.class.php194
-rw-r--r--inc/poche/Url.class.php2
-rw-r--r--inc/poche/User.class.php11
-rw-r--r--inc/poche/WallabagEpub.class.php135
-rwxr-xr-xinc/poche/config.inc.default.php4
-rwxr-xr-xinc/poche/global.inc.php30
-rw-r--r--inc/poche/pochePictures.php262
12 files changed, 1264 insertions, 950 deletions
diff --git a/inc/poche/Database.class.php b/inc/poche/Database.class.php
index 11cccb72..b5dd2120 100755
--- a/inc/poche/Database.class.php
+++ b/inc/poche/Database.class.php
@@ -5,23 +5,28 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11class Database { 11class Database {
12
12 var $handle; 13 var $handle;
13 private $order = array( 14 private $order = array (
14 'ia' => 'ORDER BY entries.id', 15 'ia' => 'ORDER BY entries.id',
15 'id' => 'ORDER BY entries.id DESC', 16 'id' => 'ORDER BY entries.id DESC',
16 'ta' => 'ORDER BY lower(entries.title)', 17 'ta' => 'ORDER BY lower(entries.title)',
17 'td' => 'ORDER BY lower(entries.title) DESC', 18 'td' => 'ORDER BY lower(entries.title) DESC',
18 'default' => 'ORDER BY entries.id' 19 'default' => 'ORDER BY entries.id'
19 ); 20 );
20 21
21 function __construct() 22 function __construct()
22 { 23 {
23 switch (STORAGE) { 24 switch (STORAGE) {
24 case 'sqlite': 25 case 'sqlite':
26 // Check if /db is writeable
27 if ( !is_writable(STORAGE_SQLITE) || !is_writable(dirname(STORAGE_SQLITE))) {
28 die('An error occured: "db" directory must be writeable for your web server user!');
29 }
25 $db_path = 'sqlite:' . STORAGE_SQLITE; 30 $db_path = 'sqlite:' . STORAGE_SQLITE;
26 $this->handle = new PDO($db_path); 31 $this->handle = new PDO($db_path);
27 break; 32 break;
@@ -38,28 +43,17 @@ class Database {
38 } 43 }
39 44
40 $this->handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 45 $this->handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
46 $this->_checkTags();
41 Tools::logm('storage type ' . STORAGE); 47 Tools::logm('storage type ' . STORAGE);
42 } 48 }
43 49
44 private function getHandle() { 50 private function getHandle()
51 {
45 return $this->handle; 52 return $this->handle;
46 } 53 }
47 54
48 public function isInstalled() { 55 private function _checkTags()
49 $sql = "SELECT username FROM users"; 56 {
50 $query = $this->executeQuery($sql, array());
51 if ($query == false) {
52 die(STORAGE . ' database looks empty. You have to create it (you can find database structure in install folder).');
53 }
54 $hasAdmin = count($query->fetchAll());
55
56 if ($hasAdmin == 0)
57 return false;
58
59 return true;
60 }
61
62 public function checkTags() {
63 57
64 if (STORAGE == 'sqlite') { 58 if (STORAGE == 'sqlite') {
65 $sql = ' 59 $sql = '
@@ -123,9 +117,10 @@ class Database {
123 $query = $this->executeQuery($sql, array()); 117 $query = $this->executeQuery($sql, array());
124 } 118 }
125 119
126 public function install($login, $password) { 120 public function install($login, $password, $email = '')
121 {
127 $sql = 'INSERT INTO users ( username, password, name, email) VALUES (?, ?, ?, ?)'; 122 $sql = 'INSERT INTO users ( username, password, name, email) VALUES (?, ?, ?, ?)';
128 $params = array($login, $password, $login, ' '); 123 $params = array($login, $password, $login, $email);
129 $query = $this->executeQuery($sql, $params); 124 $query = $this->executeQuery($sql, $params);
130 125
131 $sequence = ''; 126 $sequence = '';
@@ -150,7 +145,8 @@ class Database {
150 return TRUE; 145 return TRUE;
151 } 146 }
152 147
153 public function getConfigUser($id) { 148 public function getConfigUser($id)
149 {
154 $sql = "SELECT * FROM users_config WHERE user_id = ?"; 150 $sql = "SELECT * FROM users_config WHERE user_id = ?";
155 $query = $this->executeQuery($sql, array($id)); 151 $query = $this->executeQuery($sql, array($id));
156 $result = $query->fetchAll(); 152 $result = $query->fetchAll();
@@ -163,7 +159,8 @@ class Database {
163 return $user_config; 159 return $user_config;
164 } 160 }
165 161
166 public function userExists($username) { 162 public function userExists($username)
163 {
167 $sql = "SELECT * FROM users WHERE username=?"; 164 $sql = "SELECT * FROM users WHERE username=?";
168 $query = $this->executeQuery($sql, array($username)); 165 $query = $this->executeQuery($sql, array($username));
169 $login = $query->fetchAll(); 166 $login = $query->fetchAll();
@@ -174,13 +171,14 @@ class Database {
174 } 171 }
175 } 172 }
176 173
177 public function login($username, $password, $isauthenticated=false) { 174 public function login($username, $password, $isauthenticated = FALSE)
175 {
178 if ($isauthenticated) { 176 if ($isauthenticated) {
179 $sql = "SELECT * FROM users WHERE username=?"; 177 $sql = "SELECT * FROM users WHERE username=?";
180 $query = $this->executeQuery($sql, array($username)); 178 $query = $this->executeQuery($sql, array($username));
181 } else { 179 } else {
182 $sql = "SELECT * FROM users WHERE username=? AND password=?"; 180 $sql = "SELECT * FROM users WHERE username=? AND password=?";
183 $query = $this->executeQuery($sql, array($username, $password)); 181 $query = $this->executeQuery($sql, array($username, $password));
184 } 182 }
185 $login = $query->fetchAll(); 183 $login = $query->fetchAll();
186 184
@@ -204,7 +202,8 @@ class Database {
204 $query = $this->executeQuery($sql_update, $params_update); 202 $query = $this->executeQuery($sql_update, $params_update);
205 } 203 }
206 204
207 public function updateUserConfig($userId, $key, $value) { 205 public function updateUserConfig($userId, $key, $value)
206 {
208 $config = $this->getConfigUser($userId); 207 $config = $this->getConfigUser($userId);
209 208
210 if (! isset($config[$key])) { 209 if (! isset($config[$key])) {
@@ -218,7 +217,8 @@ class Database {
218 $query = $this->executeQuery($sql, $params); 217 $query = $this->executeQuery($sql, $params);
219 } 218 }
220 219
221 private function executeQuery($sql, $params) { 220 private function executeQuery($sql, $params)
221 {
222 try 222 try
223 { 223 {
224 $query = $this->getHandle()->prepare($sql); 224 $query = $this->getHandle()->prepare($sql);
@@ -232,28 +232,32 @@ class Database {
232 } 232 }
233 } 233 }
234 234
235 public function listUsers($username=null) { 235 public function listUsers($username = NULL)
236 {
236 $sql = 'SELECT count(*) FROM users'.( $username ? ' WHERE username=?' : ''); 237 $sql = 'SELECT count(*) FROM users'.( $username ? ' WHERE username=?' : '');
237 $query = $this->executeQuery($sql, ( $username ? array($username) : array())); 238 $query = $this->executeQuery($sql, ( $username ? array($username) : array()));
238 list($count) = $query->fetch(); 239 list($count) = $query->fetch();
239 return $count; 240 return $count;
240 } 241 }
241 242
242 public function getUserPassword($userID) { 243 public function getUserPassword($userID)
244 {
243 $sql = "SELECT * FROM users WHERE id=?"; 245 $sql = "SELECT * FROM users WHERE id=?";
244 $query = $this->executeQuery($sql, array($userID)); 246 $query = $this->executeQuery($sql, array($userID));
245 $password = $query->fetchAll(); 247 $password = $query->fetchAll();
246 return isset($password[0]['password']) ? $password[0]['password'] : null; 248 return isset($password[0]['password']) ? $password[0]['password'] : null;
247 } 249 }
248 250
249 public function deleteUserConfig($userID) { 251 public function deleteUserConfig($userID)
252 {
250 $sql_action = 'DELETE from users_config WHERE user_id=?'; 253 $sql_action = 'DELETE from users_config WHERE user_id=?';
251 $params_action = array($userID); 254 $params_action = array($userID);
252 $query = $this->executeQuery($sql_action, $params_action); 255 $query = $this->executeQuery($sql_action, $params_action);
253 return $query; 256 return $query;
254 } 257 }
255 258
256 public function deleteTagsEntriesAndEntries($userID) { 259 public function deleteTagsEntriesAndEntries($userID)
260 {
257 $entries = $this->retrieveAll($userID); 261 $entries = $this->retrieveAll($userID);
258 foreach($entries as $entryid) { 262 foreach($entries as $entryid) {
259 $tags = $this->retrieveTagsByEntry($entryid); 263 $tags = $this->retrieveTagsByEntry($entryid);
@@ -264,20 +268,23 @@ class Database {
264 } 268 }
265 } 269 }
266 270
267 public function deleteUser($userID) { 271 public function deleteUser($userID)
272 {
268 $sql_action = 'DELETE from users WHERE id=?'; 273 $sql_action = 'DELETE from users WHERE id=?';
269 $params_action = array($userID); 274 $params_action = array($userID);
270 $query = $this->executeQuery($sql_action, $params_action); 275 $query = $this->executeQuery($sql_action, $params_action);
271 } 276 }
272 277
273 public function updateContentAndTitle($id, $title, $body, $user_id) { 278 public function updateContentAndTitle($id, $title, $body, $user_id)
279 {
274 $sql_action = 'UPDATE entries SET content = ?, title = ? WHERE id=? AND user_id=?'; 280 $sql_action = 'UPDATE entries SET content = ?, title = ? WHERE id=? AND user_id=?';
275 $params_action = array($body, $title, $id, $user_id); 281 $params_action = array($body, $title, $id, $user_id);
276 $query = $this->executeQuery($sql_action, $params_action); 282 $query = $this->executeQuery($sql_action, $params_action);
277 return $query; 283 return $query;
278 } 284 }
279 285
280 public function retrieveUnfetchedEntries($user_id, $limit) { 286 public function retrieveUnfetchedEntries($user_id, $limit)
287 {
281 288
282 $sql_limit = "LIMIT 0,".$limit; 289 $sql_limit = "LIMIT 0,".$limit;
283 if (STORAGE == 'postgres') { 290 if (STORAGE == 'postgres') {
@@ -291,7 +298,8 @@ class Database {
291 return $entries; 298 return $entries;
292 } 299 }
293 300
294 public function retrieveUnfetchedEntriesCount($user_id) { 301 public function retrieveUnfetchedEntriesCount($user_id)
302 {
295 $sql = "SELECT count(*) FROM entries WHERE (content = '' OR content IS NULL) AND title LIKE 'Untitled - Import%' AND user_id=?"; 303 $sql = "SELECT count(*) FROM entries WHERE (content = '' OR content IS NULL) AND title LIKE 'Untitled - Import%' AND user_id=?";
296 $query = $this->executeQuery($sql, array($user_id)); 304 $query = $this->executeQuery($sql, array($user_id));
297 list($count) = $query->fetch(); 305 list($count) = $query->fetch();
@@ -299,7 +307,8 @@ class Database {
299 return $count; 307 return $count;
300 } 308 }
301 309
302 public function retrieveAll($user_id) { 310 public function retrieveAll($user_id)
311 {
303 $sql = "SELECT * FROM entries WHERE user_id=? ORDER BY id"; 312 $sql = "SELECT * FROM entries WHERE user_id=? ORDER BY id";
304 $query = $this->executeQuery($sql, array($user_id)); 313 $query = $this->executeQuery($sql, array($user_id));
305 $entries = $query->fetchAll(); 314 $entries = $query->fetchAll();
@@ -307,7 +316,8 @@ class Database {
307 return $entries; 316 return $entries;
308 } 317 }
309 318
310 public function retrieveOneById($id, $user_id) { 319 public function retrieveOneById($id, $user_id)
320 {
311 $entry = NULL; 321 $entry = NULL;
312 $sql = "SELECT * FROM entries WHERE id=? AND user_id=?"; 322 $sql = "SELECT * FROM entries WHERE id=? AND user_id=?";
313 $params = array(intval($id), $user_id); 323 $params = array(intval($id), $user_id);
@@ -317,7 +327,8 @@ class Database {
317 return isset($entry[0]) ? $entry[0] : null; 327 return isset($entry[0]) ? $entry[0] : null;
318 } 328 }
319 329
320 public function retrieveOneByURL($url, $user_id) { 330 public function retrieveOneByURL($url, $user_id)
331 {
321 $entry = NULL; 332 $entry = NULL;
322 $sql = "SELECT * FROM entries WHERE url=? AND user_id=?"; 333 $sql = "SELECT * FROM entries WHERE url=? AND user_id=?";
323 $params = array($url, $user_id); 334 $params = array($url, $user_id);
@@ -327,13 +338,15 @@ class Database {
327 return isset($entry[0]) ? $entry[0] : null; 338 return isset($entry[0]) ? $entry[0] : null;
328 } 339 }
329 340
330 public function reassignTags($old_entry_id, $new_entry_id) { 341 public function reassignTags($old_entry_id, $new_entry_id)
342 {
331 $sql = "UPDATE tags_entries SET entry_id=? WHERE entry_id=?"; 343 $sql = "UPDATE tags_entries SET entry_id=? WHERE entry_id=?";
332 $params = array($new_entry_id, $old_entry_id); 344 $params = array($new_entry_id, $old_entry_id);
333 $query = $this->executeQuery($sql, $params); 345 $query = $this->executeQuery($sql, $params);
334 } 346 }
335 347
336 public function getEntriesByView($view, $user_id, $limit = '', $tag_id = 0) { 348 public function getEntriesByView($view, $user_id, $limit = '', $tag_id = 0)
349 {
337 switch ($view) { 350 switch ($view) {
338 case 'archive': 351 case 'archive':
339 $sql = "SELECT * FROM entries WHERE user_id=? AND is_read=? "; 352 $sql = "SELECT * FROM entries WHERE user_id=? AND is_read=? ";
@@ -361,9 +374,10 @@ class Database {
361 $entries = $query->fetchAll(); 374 $entries = $query->fetchAll();
362 375
363 return $entries; 376 return $entries;
364 } 377 }
365 378
366 public function getEntriesByViewCount($view, $user_id, $tag_id = 0) { 379 public function getEntriesByViewCount($view, $user_id, $tag_id = 0)
380 {
367 switch ($view) { 381 switch ($view) {
368 case 'archive': 382 case 'archive':
369 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? "; 383 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? ";
@@ -391,7 +405,8 @@ class Database {
391 return $count; 405 return $count;
392 } 406 }
393 407
394 public function updateContent($id, $content, $user_id) { 408 public function updateContent($id, $content, $user_id)
409 {
395 $sql_action = 'UPDATE entries SET content = ? WHERE id=? AND user_id=?'; 410 $sql_action = 'UPDATE entries SET content = ? WHERE id=? AND user_id=?';
396 $params_action = array($content, $id, $user_id); 411 $params_action = array($content, $id, $user_id);
397 $query = $this->executeQuery($sql_action, $params_action); 412 $query = $this->executeQuery($sql_action, $params_action);
@@ -406,7 +421,8 @@ class Database {
406 * @param integer $user_id 421 * @param integer $user_id
407 * @return integer $id of inserted record 422 * @return integer $id of inserted record
408 */ 423 */
409 public function add($url, $title, $content, $user_id, $isFavorite=0, $isRead=0) { 424 public function add($url, $title, $content, $user_id, $isFavorite=0, $isRead=0)
425 {
410 $sql_action = 'INSERT INTO entries ( url, title, content, user_id, is_fav, is_read ) VALUES (?, ?, ?, ?, ?, ?)'; 426 $sql_action = 'INSERT INTO entries ( url, title, content, user_id, is_fav, is_read ) VALUES (?, ?, ?, ?, ?, ?)';
411 $params_action = array($url, $title, $content, $user_id, $isFavorite, $isRead); 427 $params_action = array($url, $title, $content, $user_id, $isFavorite, $isRead);
412 428
@@ -419,36 +435,42 @@ class Database {
419 return $id; 435 return $id;
420 } 436 }
421 437
422 public function deleteById($id, $user_id) { 438 public function deleteById($id, $user_id)
439 {
423 $sql_action = "DELETE FROM entries WHERE id=? AND user_id=?"; 440 $sql_action = "DELETE FROM entries WHERE id=? AND user_id=?";
424 $params_action = array($id, $user_id); 441 $params_action = array($id, $user_id);
425 $query = $this->executeQuery($sql_action, $params_action); 442 $query = $this->executeQuery($sql_action, $params_action);
426 return $query; 443 return $query;
427 } 444 }
428 445
429 public function favoriteById($id, $user_id) { 446 public function favoriteById($id, $user_id)
447 {
430 $sql_action = "UPDATE entries SET is_fav=NOT is_fav WHERE id=? AND user_id=?"; 448 $sql_action = "UPDATE entries SET is_fav=NOT is_fav WHERE id=? AND user_id=?";
431 $params_action = array($id, $user_id); 449 $params_action = array($id, $user_id);
432 $query = $this->executeQuery($sql_action, $params_action); 450 $query = $this->executeQuery($sql_action, $params_action);
433 } 451 }
434 452
435 public function archiveById($id, $user_id) { 453 public function archiveById($id, $user_id)
454 {
436 $sql_action = "UPDATE entries SET is_read=NOT is_read WHERE id=? AND user_id=?"; 455 $sql_action = "UPDATE entries SET is_read=NOT is_read WHERE id=? AND user_id=?";
437 $params_action = array($id, $user_id); 456 $params_action = array($id, $user_id);
438 $query = $this->executeQuery($sql_action, $params_action); 457 $query = $this->executeQuery($sql_action, $params_action);
439 } 458 }
440 459
441 public function archiveAll($user_id) { 460 public function archiveAll($user_id)
461 {
442 $sql_action = "UPDATE entries SET is_read=? WHERE user_id=? AND is_read=?"; 462 $sql_action = "UPDATE entries SET is_read=? WHERE user_id=? AND is_read=?";
443 $params_action = array($user_id, 1, 0); 463 $params_action = array($user_id, 1, 0);
444 $query = $this->executeQuery($sql_action, $params_action); 464 $query = $this->executeQuery($sql_action, $params_action);
445 } 465 }
446 466
447 public function getLastId($column = '') { 467 public function getLastId($column = '')
468 {
448 return $this->getHandle()->lastInsertId($column); 469 return $this->getHandle()->lastInsertId($column);
449 } 470 }
450 471
451 public function search($term, $user_id, $limit = '') { 472 public function search($term, $user_id, $limit = '')
473 {
452 $search = '%'.$term.'%'; 474 $search = '%'.$term.'%';
453 $sql_action = "SELECT * FROM entries WHERE user_id=? AND (content LIKE ? OR title LIKE ? OR url LIKE ?) "; //searches in content, title and URL 475 $sql_action = "SELECT * FROM entries WHERE user_id=? AND (content LIKE ? OR title LIKE ? OR url LIKE ?) "; //searches in content, title and URL
454 $sql_action .= $this->getEntriesOrder().' ' . $limit; 476 $sql_action .= $this->getEntriesOrder().' ' . $limit;
@@ -457,7 +479,8 @@ class Database {
457 return $query->fetchAll(); 479 return $query->fetchAll();
458 } 480 }
459 481
460 public function retrieveAllTags($user_id, $term = null) { 482 public function retrieveAllTags($user_id, $term = NULL)
483 {
461 $sql = "SELECT DISTINCT tags.*, count(entries.id) AS entriescount FROM tags 484 $sql = "SELECT DISTINCT tags.*, count(entries.id) AS entriescount FROM tags
462 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id 485 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id
463 LEFT JOIN entries ON tags_entries.entry_id=entries.id 486 LEFT JOIN entries ON tags_entries.entry_id=entries.id
@@ -471,7 +494,8 @@ class Database {
471 return $tags; 494 return $tags;
472 } 495 }
473 496
474 public function retrieveTag($id, $user_id) { 497 public function retrieveTag($id, $user_id)
498 {
475 $tag = NULL; 499 $tag = NULL;
476 $sql = "SELECT DISTINCT tags.* FROM tags 500 $sql = "SELECT DISTINCT tags.* FROM tags
477 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id 501 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id
@@ -481,10 +505,11 @@ class Database {
481 $query = $this->executeQuery($sql, $params); 505 $query = $this->executeQuery($sql, $params);
482 $tag = $query->fetchAll(); 506 $tag = $query->fetchAll();
483 507
484 return isset($tag[0]) ? $tag[0] : null; 508 return isset($tag[0]) ? $tag[0] : NULL;
485 } 509 }
486 510
487 public function retrieveEntriesByTag($tag_id, $user_id) { 511 public function retrieveEntriesByTag($tag_id, $user_id)
512 {
488 $sql = 513 $sql =
489 "SELECT entries.* FROM entries 514 "SELECT entries.* FROM entries
490 LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id 515 LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id
@@ -495,7 +520,8 @@ class Database {
495 return $entries; 520 return $entries;
496 } 521 }
497 522
498 public function retrieveTagsByEntry($entry_id) { 523 public function retrieveTagsByEntry($entry_id)
524 {
499 $sql = 525 $sql =
500 "SELECT tags.* FROM tags 526 "SELECT tags.* FROM tags
501 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id 527 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id
@@ -506,14 +532,16 @@ class Database {
506 return $tags; 532 return $tags;
507 } 533 }
508 534
509 public function removeTagForEntry($entry_id, $tag_id) { 535 public function removeTagForEntry($entry_id, $tag_id)
536 {
510 $sql_action = "DELETE FROM tags_entries WHERE tag_id=? AND entry_id=?"; 537 $sql_action = "DELETE FROM tags_entries WHERE tag_id=? AND entry_id=?";
511 $params_action = array($tag_id, $entry_id); 538 $params_action = array($tag_id, $entry_id);
512 $query = $this->executeQuery($sql_action, $params_action); 539 $query = $this->executeQuery($sql_action, $params_action);
513 return $query; 540 return $query;
514 } 541 }
515 542
516 public function cleanUnusedTag($tag_id) { 543 public function cleanUnusedTag($tag_id)
544 {
517 $sql_action = "SELECT tags.* FROM tags JOIN tags_entries ON tags_entries.tag_id=tags.id WHERE tags.id=?"; 545 $sql_action = "SELECT tags.* FROM tags JOIN tags_entries ON tags_entries.tag_id=tags.id WHERE tags.id=?";
518 $query = $this->executeQuery($sql_action,array($tag_id)); 546 $query = $this->executeQuery($sql_action,array($tag_id));
519 $tagstokeep = $query->fetchAll(); 547 $tagstokeep = $query->fetchAll();
@@ -532,7 +560,8 @@ class Database {
532 560
533 } 561 }
534 562
535 public function retrieveTagByValue($value) { 563 public function retrieveTagByValue($value)
564 {
536 $tag = NULL; 565 $tag = NULL;
537 $sql = "SELECT * FROM tags WHERE value=?"; 566 $sql = "SELECT * FROM tags WHERE value=?";
538 $params = array($value); 567 $params = array($value);
@@ -542,27 +571,29 @@ class Database {
542 return isset($tag[0]) ? $tag[0] : null; 571 return isset($tag[0]) ? $tag[0] : null;
543 } 572 }
544 573
545 public function createTag($value) { 574 public function createTag($value)
575 {
546 $sql_action = 'INSERT INTO tags ( value ) VALUES (?)'; 576 $sql_action = 'INSERT INTO tags ( value ) VALUES (?)';
547 $params_action = array($value); 577 $params_action = array($value);
548 $query = $this->executeQuery($sql_action, $params_action); 578 $query = $this->executeQuery($sql_action, $params_action);
549 return $query; 579 return $query;
550 } 580 }
551 581
552 public function setTagToEntry($tag_id, $entry_id) { 582 public function setTagToEntry($tag_id, $entry_id)
583 {
553 $sql_action = 'INSERT INTO tags_entries ( tag_id, entry_id ) VALUES (?, ?)'; 584 $sql_action = 'INSERT INTO tags_entries ( tag_id, entry_id ) VALUES (?, ?)';
554 $params_action = array($tag_id, $entry_id); 585 $params_action = array($tag_id, $entry_id);
555 $query = $this->executeQuery($sql_action, $params_action); 586 $query = $this->executeQuery($sql_action, $params_action);
556 return $query; 587 return $query;
557 } 588 }
558 589
559 private function getEntriesOrder() { 590 private function getEntriesOrder()
560 if (isset($_SESSION['sort']) and array_key_exists($_SESSION['sort'], $this->order)) { 591 {
561 return $this->order[$_SESSION['sort']]; 592 if (isset($_SESSION['sort']) and array_key_exists($_SESSION['sort'], $this->order)) {
562 } 593 return $this->order[$_SESSION['sort']];
563 else {
564 return $this->order['default'];
565 }
566 } 594 }
567 595 else {
596 return $this->order['default'];
597 }
598 }
568} 599}
diff --git a/inc/poche/Language.class.php b/inc/poche/Language.class.php
new file mode 100644
index 00000000..8d3912f5
--- /dev/null
+++ b/inc/poche/Language.class.php
@@ -0,0 +1,113 @@
1<?php
2/**
3 * wallabag, self hostable application allowing you to not miss any content anymore
4 *
5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013
8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */
10
11class Language
12{
13 protected $wallabag;
14
15 private $currentLanguage;
16
17 private $languageNames = array(
18 'cs_CZ.utf8' => 'čeština',
19 'de_DE.utf8' => 'German',
20 'en_EN.utf8' => 'English',
21 'es_ES.utf8' => 'Español',
22 'fa_IR.utf8' => 'فارسی',
23 'fr_FR.utf8' => 'Français',
24 'it_IT.utf8' => 'Italiano',
25 'pl_PL.utf8' => 'Polski',
26 'pt_BR.utf8' => 'Português (Brasil)',
27 'ru_RU.utf8' => 'Pусский',
28 'sl_SI.utf8' => 'Slovenščina',
29 'uk_UA.utf8' => 'Українська',
30 );
31
32 public function __construct(Poche $wallabag)
33 {
34 $this->wallabag = $wallabag;
35 $pocheUser = Session::getParam('poche_user');
36 $language = (is_null($pocheUser) ? LANG : $pocheUser->getConfigValue('language'));
37
38 @putenv('LC_ALL=' . $language);
39 setlocale(LC_ALL, $language);
40 bindtextdomain($language, LOCALE);
41 textdomain($language);
42
43 $this->currentLanguage = $language;
44 }
45
46 public function getLanguage() {
47 return $this->currentLanguage;
48 }
49
50 public function getInstalledLanguages() {
51 $handle = opendir(LOCALE);
52 $languages = array();
53
54 while (($language = readdir($handle)) !== false) {
55 # Languages are stored in a directory, so all directory names are languages
56 # @todo move language installation data to database
57 if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.', 'tools'))) {
58 continue;
59 }
60
61 $current = false;
62
63 if ($language === $this->getLanguage()) {
64 $current = true;
65 }
66
67 $languages[] = array('name' => (isset($this->languageNames[$language]) ? $this->languageNames[$language] : $language), 'value' => $language, 'current' => $current);
68 }
69
70 return $languages;
71 }
72
73
74 /**
75 * Update language for current user
76 *
77 * @param $newLanguage
78 */
79 public function updateLanguage($newLanguage)
80 {
81 # we are not going to change it to the current language
82 if ($newLanguage == $this->getLanguage()) {
83 $this->wallabag->messages->add('w', _('still using the "' . $this->getLanguage() . '" language!'));
84 Tools::redirect('?view=config');
85 }
86
87 $languages = $this->getInstalledLanguages();
88 $actualLanguage = false;
89
90 foreach ($languages as $language) {
91 if ($language['value'] == $newLanguage) {
92 $actualLanguage = true;
93 break;
94 }
95 }
96
97 if (!$actualLanguage) {
98 $this->wallabag->messages->add('e', _('that language does not seem to be installed'));
99 Tools::redirect('?view=config');
100 }
101
102 $this->wallabag->store->updateUserConfig($this->wallabag->user->getId(), 'language', $newLanguage);
103 $this->wallabag->messages->add('s', _('you have changed your language preferences'));
104
105 $currentConfig = $_SESSION['poche_user']->config;
106 $currentConfig['language'] = $newLanguage;
107
108 $_SESSION['poche_user']->setConfig($currentConfig);
109
110 Tools::emptyCache();
111 Tools::redirect('?view=config');
112 }
113} \ No newline at end of file
diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php
index 09a9f5ff..8cebafa3 100755
--- a/inc/poche/Poche.class.php
+++ b/inc/poche/Poche.class.php
@@ -5,402 +5,127 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11class Poche 11class Poche
12{ 12{
13 public static $canRenderTemplates = true; 13 /**
14 public static $configFileAvailable = true; 14 * @var User
15 15 */
16 public $user; 16 public $user;
17 /**
18 * @var Database
19 */
17 public $store; 20 public $store;
21 /**
22 * @var Template
23 */
18 public $tpl; 24 public $tpl;
25 /**
26 * @var Language
27 */
28 public $language;
29 /**
30 * @var Routing
31 */
32 public $routing;
33 /**
34 * @var Messages
35 */
19 public $messages; 36 public $messages;
37 /**
38 * @var Paginator
39 */
20 public $pagination; 40 public $pagination;
21 41
22 private $currentTheme = '';
23 private $currentLanguage = '';
24 private $notInstalledMessage = array();
25
26 private $language_names = array(
27 'cs_CZ.utf8' => 'čeština',
28 'de_DE.utf8' => 'German',
29 'en_EN.utf8' => 'English',
30 'es_ES.utf8' => 'Español',
31 'fa_IR.utf8' => 'فارسی',
32 'fr_FR.utf8' => 'Français',
33 'it_IT.utf8' => 'Italiano',
34 'pl_PL.utf8' => 'Polski',
35 'pt_BR.utf8' => 'Português (Brasil)',
36 'ru_RU.utf8' => 'Pусский',
37 'sl_SI.utf8' => 'Slovenščina',
38 'uk_UA.utf8' => 'Українська',
39 );
40 public function __construct() 42 public function __construct()
41 { 43 {
42 if ($this->configFileIsAvailable()) { 44 $this->init();
43 $this->init();
44 }
45
46 if ($this->themeIsInstalled()) {
47 $this->initTpl();
48 }
49
50 if ($this->systemIsInstalled()) {
51 $this->store = new Database();
52 $this->messages = new Messages();
53 # installation
54 if (! $this->store->isInstalled()) {
55 $this->install();
56 }
57 $this->store->checkTags();
58 }
59 } 45 }
60 46
61 private function init() 47 private function init()
62 { 48 {
63 Tools::initPhp(); 49 Tools::initPhp();
64 50
65 if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) { 51 $pocheUser = Session::getParam('poche_user');
66 $this->user = $_SESSION['poche_user']; 52
53 if ($pocheUser && $pocheUser != array()) {
54 $this->user = $pocheUser;
67 } else { 55 } else {
68 # fake user, just for install & login screens 56 // fake user, just for install & login screens
69 $this->user = new User(); 57 $this->user = new User();
70 $this->user->setConfig($this->getDefaultConfig()); 58 $this->user->setConfig($this->getDefaultConfig());
71 } 59 }
72 60
73 # l10n 61 $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p');
74 $language = $this->user->getConfigValue('language'); 62 $this->language = new Language($this);
75 @putenv('LC_ALL=' . $language); 63 $this->tpl = new Template($this);
76 setlocale(LC_ALL, $language); 64 $this->store = new Database();
77 bindtextdomain($language, LOCALE); 65 $this->messages = new Messages();
78 textdomain($language); 66 $this->routing = new Routing($this);
79
80 # Pagination
81 $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p');
82
83 # Set up theme
84 $themeDirectory = $this->user->getConfigValue('theme');
85
86 if ($themeDirectory === false) {
87 $themeDirectory = DEFAULT_THEME;
88 }
89
90 $this->currentTheme = $themeDirectory;
91
92 # Set up language
93 $languageDirectory = $this->user->getConfigValue('language');
94
95 if ($languageDirectory === false) {
96 $languageDirectory = DEFAULT_THEME;
97 }
98
99 $this->currentLanguage = $languageDirectory;
100 } 67 }
101 68
102 public function configFileIsAvailable() { 69 public function run()
103 if (! self::$configFileAvailable) { 70 {
104 $this->notInstalledMessage[] = 'You have to copy (don\'t just rename!) inc/poche/config.inc.default.php to inc/poche/config.inc.php.'; 71 $this->routing->run();
105
106 return false;
107 }
108
109 return true;
110 }
111
112 public function themeIsInstalled() {
113 $passTheme = TRUE;
114 # Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet
115 if (! self::$canRenderTemplates) {
116 $this->notInstalledMessage[] = 'Twig does not seem to be installed. Please initialize the Composer installation to automatically fetch dependencies. You can also download <a href="http://wllbg.org/vendor">vendor.zip</a> and extract it in your wallabag folder.';
117 $passTheme = FALSE;
118 }
119
120 if (! is_writable(CACHE)) {
121 $this->notInstalledMessage[] = 'You don\'t have write access on cache directory.';
122
123 self::$canRenderTemplates = false;
124
125 $passTheme = FALSE;
126 }
127
128 # Check if the selected theme and its requirements are present
129 $theme = $this->getTheme();
130
131 if ($theme != '' && ! is_dir(THEME . '/' . $theme)) {
132 $this->notInstalledMessage[] = 'The currently selected theme (' . $theme . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $theme . ')';
133
134 self::$canRenderTemplates = false;
135
136 $passTheme = FALSE;
137 }
138
139 $themeInfo = $this->getThemeInfo($theme);
140 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
141 foreach ($themeInfo['requirements'] as $requiredTheme) {
142 if (! is_dir(THEME . '/' . $requiredTheme)) {
143 $this->notInstalledMessage[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')';
144
145 self::$canRenderTemplates = false;
146
147 $passTheme = FALSE;
148 }
149 }
150 }
151
152 if (!$passTheme) {
153 return FALSE;
154 }
155
156
157 return true;
158 } 72 }
159 73
160 /** 74 /**
161 * all checks before installation. 75 * Creates a new user
162 * @todo move HTML to template
163 * @return boolean
164 */ 76 */
165 public function systemIsInstalled() 77 public function createNewUser($username, $password, $email = "")
166 {
167 $msg = TRUE;
168
169 $configSalt = defined('SALT') ? constant('SALT') : '';
170
171 if (empty($configSalt)) {
172 $this->notInstalledMessage[] = 'You have not yet filled in the SALT value in the config.inc.php file.';
173 $msg = FALSE;
174 }
175 if (STORAGE == 'sqlite' && ! file_exists(STORAGE_SQLITE)) {
176 Tools::logm('sqlite file doesn\'t exist');
177 $this->notInstalledMessage[] = 'sqlite file doesn\'t exist, you can find it in install folder. Copy it in /db folder.';
178 $msg = FALSE;
179 }
180 if (is_dir(ROOT . '/install') && ! DEBUG_POCHE) {
181 $this->notInstalledMessage[] = 'you have to delete the /install folder before using poche.';
182 $msg = FALSE;
183 }
184 if (STORAGE == 'sqlite' && ! is_writable(STORAGE_SQLITE)) {
185 Tools::logm('you don\'t have write access on sqlite file');
186 $this->notInstalledMessage[] = 'You don\'t have write access on sqlite file.';
187 $msg = FALSE;
188 }
189
190 if (! $msg) {
191 return false;
192 }
193
194 return true;
195 }
196
197 public function getNotInstalledMessage() {
198 return $this->notInstalledMessage;
199 }
200
201 private function initTpl()
202 { 78 {
203 $loaderChain = new Twig_Loader_Chain(); 79 if (!empty($username) && !empty($password)){
204 $theme = $this->getTheme(); 80 $newUsername = filter_var($username, FILTER_SANITIZE_STRING);
205 81 $email = filter_var($email, FILTER_SANITIZE_STRING);
206 # add the current theme as first to the loader chain so Twig will look there first for overridden template files 82 if (!$this->store->userExists($newUsername)){
207 try { 83 if ($this->store->install($newUsername, Tools::encodeString($password . $newUsername), $email)) {
208 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme)); 84 Tools::logm('The new user ' . $newUsername . ' has been installed');
209 } catch (Twig_Error_Loader $e) { 85 $this->messages->add('s', sprintf(_('The new user %s has been installed. Do you want to <a href="?logout">logout ?</a>'), $newUsername));
210 # @todo isInstalled() should catch this, inject Twig later
211 die('The currently selected theme (' . $theme . ') does not seem to be properly installed (' . THEME . '/' . $theme .' is missing)');
212 }
213
214 # add all required themes to the loader chain
215 $themeInfo = $this->getThemeInfo($theme);
216 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
217 foreach ($themeInfo['requirements'] as $requiredTheme) {
218 try {
219 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $requiredTheme));
220 } catch (Twig_Error_Loader $e) {
221 # @todo isInstalled() should catch this, inject Twig later
222 die('The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')');
223 }
224 }
225 }
226
227 if (DEBUG_POCHE) {
228 $twigParams = array();
229 } else {
230 $twigParams = array('cache' => CACHE);
231 }
232
233 $this->tpl = new Twig_Environment($loaderChain, $twigParams);
234 $this->tpl->addExtension(new Twig_Extensions_Extension_I18n());
235
236 # filter to display domain name of an url
237 $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain');
238 $this->tpl->addFilter($filter);
239
240 # filter for reading time
241 $filter = new Twig_SimpleFilter('getReadingTime', 'Tools::getReadingTime');
242 $this->tpl->addFilter($filter);
243 }
244
245 public function createNewUser() {
246 if (isset($_GET['newuser'])){
247 if ($_POST['newusername'] != "" && $_POST['password4newuser'] != ""){
248 $newusername = filter_var($_POST['newusername'], FILTER_SANITIZE_STRING);
249 if (!$this->store->userExists($newusername)){
250 if ($this->store->install($newusername, Tools::encodeString($_POST['password4newuser'] . $newusername))) {
251 Tools::logm('The new user '.$newusername.' has been installed');
252 $this->messages->add('s', sprintf(_('The new user %s has been installed. Do you want to <a href="?logout">logout ?</a>'),$newusername));
253 Tools::redirect();
254 }
255 else {
256 Tools::logm('error during adding new user');
257 Tools::redirect();
258 }
259 }
260 else {
261 $this->messages->add('e', sprintf(_('Error : An user with the name %s already exists !'),$newusername));
262 Tools::logm('An user with the name '.$newusername.' already exists !');
263 Tools::redirect(); 86 Tools::redirect();
264 } 87 }
265 }
266 }
267 }
268
269 public function deleteUser(){
270 if (isset($_GET['deluser'])){
271 if ($this->store->listUsers() > 1) {
272 if (Tools::encodeString($_POST['password4deletinguser'].$this->user->getUsername()) == $this->store->getUserPassword($this->user->getId())) {
273 $username = $this->user->getUsername();
274 $this->store->deleteUserConfig($this->user->getId());
275 Tools::logm('The configuration for user '. $username .' has been deleted !');
276 $this->store->deleteTagsEntriesAndEntries($this->user->getId());
277 Tools::logm('The entries for user '. $username .' has been deleted !');
278 $this->store->deleteUser($this->user->getId());
279 Tools::logm('User '. $username .' has been completely deleted !');
280 Session::logout();
281 Tools::logm('logout');
282 Tools::redirect();
283 $this->messages->add('s', sprintf(_('User %s has been successfully deleted !'),$newusername));
284 }
285 else { 88 else {
286 Tools::logm('Bad password !'); 89 Tools::logm('error during adding new user');
287 $this->messages->add('e', _('Error : The password is wrong !'));
288 }
289 }
290 else {
291 Tools::logm('Only user !');
292 $this->messages->add('e', _('Error : You are the only user, you cannot delete your account !'));
293 }
294 }
295 }
296
297 private function install()
298 {
299 Tools::logm('poche still not installed');
300 echo $this->tpl->render('install.twig', array(
301 'token' => Session::getToken(),
302 'theme' => $this->getTheme(),
303 'poche_url' => Tools::getPocheUrl()
304 ));
305 if (isset($_GET['install'])) {
306 if (($_POST['password'] == $_POST['password_repeat'])
307 && $_POST['password'] != "" && $_POST['login'] != "") {
308 # let's rock, install poche baby !
309 if ($this->store->install($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login'])))
310 {
311 Session::logout();
312 Tools::logm('poche is now installed');
313 Tools::redirect(); 90 Tools::redirect();
314 } 91 }
315 } 92 }
316 else { 93 else {
317 Tools::logm('error during installation'); 94 $this->messages->add('e', sprintf(_('Error : An user with the name %s already exists !'), $newUsername));
95 Tools::logm('An user with the name ' . $newUsername . ' already exists !');
318 Tools::redirect(); 96 Tools::redirect();
319 } 97 }
320 } 98 }
321 exit();
322 }
323
324 public function getTheme() {
325 return $this->currentTheme;
326 } 99 }
327 100
328 /** 101 /**
329 * Provides theme information by parsing theme.ini file if present in the theme's root directory. 102 * Delete an existing user
330 * In all cases, the following data will be returned:
331 * - name: theme's name, or key if the theme is unnamed,
332 * - current: boolean informing if the theme is the current user theme.
333 *
334 * @param string $theme Theme key (directory name)
335 * @return array|boolean Theme information, or false if the theme doesn't exist.
336 */ 103 */
337 public function getThemeInfo($theme) { 104 public function deleteUser($password)
338 if (!is_dir(THEME . '/' . $theme)) { 105 {
339 return false; 106 if ($this->store->listUsers() > 1) {
340 } 107 if (Tools::encodeString($password . $this->user->getUsername()) == $this->store->getUserPassword($this->user->getId())) {
341 108 $username = $this->user->getUsername();
342 $themeIniFile = THEME . '/' . $theme . '/theme.ini'; 109 $this->store->deleteUserConfig($this->user->getId());
343 $themeInfo = array(); 110 Tools::logm('The configuration for user '. $username .' has been deleted !');
344 111 $this->store->deleteTagsEntriesAndEntries($this->user->getId());
345 if (is_file($themeIniFile) && is_readable($themeIniFile)) { 112 Tools::logm('The entries for user '. $username .' has been deleted !');
346 $themeInfo = parse_ini_file($themeIniFile); 113 $this->store->deleteUser($this->user->getId());
347 } 114 Tools::logm('User '. $username .' has been completely deleted !');
348 115 Session::logout();
349 if ($themeInfo === false) { 116 Tools::logm('logout');
350 $themeInfo = array(); 117 Tools::redirect();
351 } 118 $this->messages->add('s', sprintf(_('User %s has been successfully deleted !'), $username));
352 if (!isset($themeInfo['name'])) {
353 $themeInfo['name'] = $theme;
354 }
355 $themeInfo['current'] = ($theme === $this->getTheme());
356
357 return $themeInfo;
358 }
359
360 public function getInstalledThemes() {
361 $handle = opendir(THEME);
362 $themes = array();
363
364 while (($theme = readdir($handle)) !== false) {
365 # Themes are stored in a directory, so all directory names are themes
366 # @todo move theme installation data to database
367 if (!is_dir(THEME . '/' . $theme) || in_array($theme, array('.', '..'))) {
368 continue;
369 }
370
371 $themes[$theme] = $this->getThemeInfo($theme);
372 }
373
374 ksort($themes);
375
376 return $themes;
377 }
378
379 public function getLanguage() {
380 return $this->currentLanguage;
381 }
382
383 public function getInstalledLanguages() {
384 $handle = opendir(LOCALE);
385 $languages = array();
386
387 while (($language = readdir($handle)) !== false) {
388 # Languages are stored in a directory, so all directory names are languages
389 # @todo move language installation data to database
390 if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.', 'tools'))) {
391 continue;
392 } 119 }
393 120 else {
394 $current = false; 121 Tools::logm('Bad password !');
395 122 $this->messages->add('e', _('Error : The password is wrong !'));
396 if ($language === $this->getLanguage()) {
397 $current = true;
398 } 123 }
399
400 $languages[] = array('name' => (isset($this->language_names[$language]) ? $this->language_names[$language] : $language), 'value' => $language, 'current' => $current);
401 } 124 }
402 125 else {
403 return $languages; 126 Tools::logm('Only user !');
127 $this->messages->add('e', _('Error : You are the only user, you cannot delete your account !'));
128 }
404 } 129 }
405 130
406 public function getDefaultConfig() 131 public function getDefaultConfig()
@@ -425,7 +150,7 @@ class Poche
425 $body = $content['rss']['channel']['item']['description']; 150 $body = $content['rss']['channel']['item']['description'];
426 151
427 // clean content from prevent xss attack 152 // clean content from prevent xss attack
428 $purifier = $this->getPurifier(); 153 $purifier = $this->_getPurifier();
429 $title = $purifier->purify($title); 154 $title = $purifier->purify($title);
430 $body = $purifier->purify($body); 155 $body = $purifier->purify($body);
431 156
@@ -437,7 +162,7 @@ class Poche
437 if ( $last_id ) { 162 if ( $last_id ) {
438 Tools::logm('add link ' . $url->getUrl()); 163 Tools::logm('add link ' . $url->getUrl());
439 if (DOWNLOAD_PICTURES) { 164 if (DOWNLOAD_PICTURES) {
440 $content = filtre_picture($body, $url->getUrl(), $last_id); 165 $content = Picture::filterPicture($body, $url->getUrl(), $last_id);
441 Tools::logm('updating content article'); 166 Tools::logm('updating content article');
442 $this->store->updateContent($last_id, $content, $this->user->getId()); 167 $this->store->updateContent($last_id, $content, $this->user->getId());
443 } 168 }
@@ -472,7 +197,7 @@ class Poche
472 $msg = 'delete link #' . $id; 197 $msg = 'delete link #' . $id;
473 if ($this->store->deleteById($id, $this->user->getId())) { 198 if ($this->store->deleteById($id, $this->user->getId())) {
474 if (DOWNLOAD_PICTURES) { 199 if (DOWNLOAD_PICTURES) {
475 remove_directory(ABS_PATH . $id); 200 Picture::removeDirectory(ABS_PATH . $id);
476 } 201 }
477 $this->messages->add('s', _('the link has been deleted successfully')); 202 $this->messages->add('s', _('the link has been deleted successfully'));
478 } 203 }
@@ -590,16 +315,16 @@ class Poche
590 switch ($view) 315 switch ($view)
591 { 316 {
592 case 'config': 317 case 'config':
593 $dev_infos = $this->getPocheVersion('dev'); 318 $dev_infos = $this->_getPocheVersion('dev');
594 $dev = trim($dev_infos[0]); 319 $dev = trim($dev_infos[0]);
595 $check_time_dev = date('d-M-Y H:i', $dev_infos[1]); 320 $check_time_dev = date('d-M-Y H:i', $dev_infos[1]);
596 $prod_infos = $this->getPocheVersion('prod'); 321 $prod_infos = $this->_getPocheVersion('prod');
597 $prod = trim($prod_infos[0]); 322 $prod = trim($prod_infos[0]);
598 $check_time_prod = date('d-M-Y H:i', $prod_infos[1]); 323 $check_time_prod = date('d-M-Y H:i', $prod_infos[1]);
599 $compare_dev = version_compare(POCHE, $dev); 324 $compare_dev = version_compare(POCHE, $dev);
600 $compare_prod = version_compare(POCHE, $prod); 325 $compare_prod = version_compare(POCHE, $prod);
601 $themes = $this->getInstalledThemes(); 326 $themes = $this->tpl->getInstalledThemes();
602 $languages = $this->getInstalledLanguages(); 327 $languages = $this->language->getInstalledLanguages();
603 $token = $this->user->getConfigValue('token'); 328 $token = $this->user->getConfigValue('token');
604 $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false; 329 $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false;
605 $only_user = ($this->store->listUsers() > 1) ? false : true; 330 $only_user = ($this->store->listUsers() > 1) ? false : true;
@@ -703,7 +428,7 @@ class Poche
703 'listmode' => (isset($_COOKIE['listmode']) ? true : false), 428 'listmode' => (isset($_COOKIE['listmode']) ? true : false),
704 ); 429 );
705 430
706 //if id is given - we retrive entries by tag: id is tag id 431 //if id is given - we retrieve entries by tag: id is tag id
707 if ($id) { 432 if ($id) {
708 $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId()); 433 $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId());
709 $tpl_vars['id'] = intval($id); 434 $tpl_vars['id'] = intval($id);
@@ -733,7 +458,7 @@ class Poche
733 * @todo set the new password in function header like this updatePassword($newPassword) 458 * @todo set the new password in function header like this updatePassword($newPassword)
734 * @return boolean 459 * @return boolean
735 */ 460 */
736 public function updatePassword() 461 public function updatePassword($password, $confirmPassword)
737 { 462 {
738 if (MODE_DEMO) { 463 if (MODE_DEMO) {
739 $this->messages->add('i', _('in demo mode, you can\'t update your password')); 464 $this->messages->add('i', _('in demo mode, you can\'t update your password'));
@@ -741,10 +466,10 @@ class Poche
741 Tools::redirect('?view=config'); 466 Tools::redirect('?view=config');
742 } 467 }
743 else { 468 else {
744 if (isset($_POST['password']) && isset($_POST['password_repeat'])) { 469 if (isset($password) && isset($confirmPassword)) {
745 if ($_POST['password'] == $_POST['password_repeat'] && $_POST['password'] != "") { 470 if ($password == $confirmPassword && !empty($password)) {
746 $this->messages->add('s', _('your password has been updated')); 471 $this->messages->add('s', _('your password has been updated'));
747 $this->store->updatePassword($this->user->getId(), Tools::encodeString($_POST['password'] . $this->user->getUsername())); 472 $this->store->updatePassword($this->user->getId(), Tools::encodeString($password . $this->user->getUsername()));
748 Session::logout(); 473 Session::logout();
749 Tools::logm('password updated'); 474 Tools::logm('password updated');
750 Tools::redirect(); 475 Tools::redirect();
@@ -757,102 +482,25 @@ class Poche
757 } 482 }
758 } 483 }
759 484
760 public function updateTheme()
761 {
762 # no data
763 if (empty($_POST['theme'])) {
764 }
765
766 # we are not going to change it to the current theme...
767 if ($_POST['theme'] == $this->getTheme()) {
768 $this->messages->add('w', _('still using the "' . $this->getTheme() . '" theme!'));
769 Tools::redirect('?view=config');
770 }
771
772 $themes = $this->getInstalledThemes();
773 $actualTheme = false;
774
775 foreach (array_keys($themes) as $theme) {
776 if ($theme == $_POST['theme']) {
777 $actualTheme = true;
778 break;
779 }
780 }
781
782 if (! $actualTheme) {
783 $this->messages->add('e', _('that theme does not seem to be installed'));
784 Tools::redirect('?view=config');
785 }
786
787 $this->store->updateUserConfig($this->user->getId(), 'theme', $_POST['theme']);
788 $this->messages->add('s', _('you have changed your theme preferences'));
789
790 $currentConfig = $_SESSION['poche_user']->config;
791 $currentConfig['theme'] = $_POST['theme'];
792
793 $_SESSION['poche_user']->setConfig($currentConfig);
794
795 $this->emptyCache();
796
797 Tools::redirect('?view=config');
798 }
799
800 public function updateLanguage()
801 {
802 # no data
803 if (empty($_POST['language'])) {
804 }
805
806 # we are not going to change it to the current language...
807 if ($_POST['language'] == $this->getLanguage()) {
808 $this->messages->add('w', _('still using the "' . $this->getLanguage() . '" language!'));
809 Tools::redirect('?view=config');
810 }
811
812 $languages = $this->getInstalledLanguages();
813 $actualLanguage = false;
814
815 foreach ($languages as $language) {
816 if ($language['value'] == $_POST['language']) {
817 $actualLanguage = true;
818 break;
819 }
820 }
821
822 if (! $actualLanguage) {
823 $this->messages->add('e', _('that language does not seem to be installed'));
824 Tools::redirect('?view=config');
825 }
826
827 $this->store->updateUserConfig($this->user->getId(), 'language', $_POST['language']);
828 $this->messages->add('s', _('you have changed your language preferences'));
829
830 $currentConfig = $_SESSION['poche_user']->config;
831 $currentConfig['language'] = $_POST['language'];
832
833 $_SESSION['poche_user']->setConfig($currentConfig);
834
835 $this->emptyCache();
836
837 Tools::redirect('?view=config');
838 }
839 /** 485 /**
840 * get credentials from differents sources 486 * Get credentials from differents sources
841 * it redirects the user to the $referer link 487 * It redirects the user to the $referer link
488 *
842 * @return array 489 * @return array
843 */ 490 */
844 private function credentials() { 491 private function credentials()
845 if(isset($_SERVER['PHP_AUTH_USER'])) { 492 {
846 return array($_SERVER['PHP_AUTH_USER'],'php_auth',true); 493 if (isset($_SERVER['PHP_AUTH_USER'])) {
494 return array($_SERVER['PHP_AUTH_USER'], 'php_auth', true);
847 } 495 }
848 if(!empty($_POST['login']) && !empty($_POST['password'])) { 496 if (!empty($_POST['login']) && !empty($_POST['password'])) {
849 return array($_POST['login'],$_POST['password'],false); 497 return array($_POST['login'], $_POST['password'], false);
850 } 498 }
851 if(isset($_SERVER['REMOTE_USER'])) { 499 if (isset($_SERVER['REMOTE_USER'])) {
852 return array($_SERVER['REMOTE_USER'],'http_auth',true); 500 return array($_SERVER['REMOTE_USER'], 'http_auth', true);
853 } 501 }
854 502
855 return array(false,false,false); 503 return array(false, false, false);
856 } 504 }
857 505
858 /** 506 /**
@@ -877,6 +525,14 @@ class Poche
877 $longlastingsession = isset($_POST['longlastingsession']); 525 $longlastingsession = isset($_POST['longlastingsession']);
878 $passwordTest = ($isauthenticated) ? $user['password'] : Tools::encodeString($password . $login); 526 $passwordTest = ($isauthenticated) ? $user['password'] : Tools::encodeString($password . $login);
879 Session::login($user['username'], $user['password'], $login, $passwordTest, $longlastingsession, array('poche_user' => new User($user))); 527 Session::login($user['username'], $user['password'], $login, $passwordTest, $longlastingsession, array('poche_user' => new User($user)));
528
529 # reload l10n
530 $language = $user['config']['language'];
531 @putenv('LC_ALL=' . $language);
532 setlocale(LC_ALL, $language);
533 bindtextdomain($language, LOCALE);
534 textdomain($language);
535
880 $this->messages->add('s', _('welcome to your wallabag')); 536 $this->messages->add('s', _('welcome to your wallabag'));
881 Tools::logm('login successful'); 537 Tools::logm('login successful');
882 Tools::redirect($referer); 538 Tools::redirect($referer);
@@ -901,12 +557,13 @@ class Poche
901 } 557 }
902 558
903 /** 559 /**
904 * import datas into your poche 560 * import datas into your wallabag
905 * @return boolean 561 * @return boolean
906 */ 562 */
563
907 public function import() { 564 public function import() {
908 565
909 if ( isset($_FILES['file']) ) { 566 if ( isset($_FILES['file']) && $_FILES['file']['tmp_name'] ) {
910 Tools::logm('Import stated: parsing file'); 567 Tools::logm('Import stated: parsing file');
911 568
912 // assume, that file is in json format 569 // assume, that file is in json format
@@ -935,95 +592,112 @@ class Poche
935 $read = ((sizeof($data) && $read)?0:1); 592 $read = ((sizeof($data) && $read)?0:1);
936 } 593 }
937 } 594 }
938 } 595 }
939 596
940 //for readability structure 597 // for readability structure
941 foreach ($data as $record) { 598
942 if (is_array($record)) { 599 foreach($data as $record) {
943 $data[] = $record; 600 if (is_array($record)) {
944 foreach ($record as $record2) { 601 $data[] = $record;
945 if (is_array($record2)) { 602 foreach($record as $record2) {
946 $data[] = $record2; 603 if (is_array($record2)) {
947 } 604 $data[] = $record2;
605 }
606 }
607 }
948 } 608 }
949 }
950 }
951 609
952 $urlsInserted = array(); //urls of articles inserted 610 $urlsInserted = array(); //urls of articles inserted
953 foreach ($data as $record) { 611 foreach($data as $record) {
954 $url = trim( isset($record['article__url']) ? $record['article__url'] : (isset($record['url']) ? $record['url'] : '') ); 612 $url = trim(isset($record['article__url']) ? $record['article__url'] : (isset($record['url']) ? $record['url'] : ''));
955 if ( $url and !in_array($url, $urlsInserted) ) { 613 if ($url and !in_array($url, $urlsInserted)) {
956 $title = (isset($record['title']) ? $record['title'] : _('Untitled - Import - ').'</a> <a href="./?import">'._('click to finish import').'</a><a>'); 614 $title = (isset($record['title']) ? $record['title'] : _('Untitled - Import - ') . '</a> <a href="./?import">' . _('click to finish import') . '</a><a>');
957 $body = (isset($record['content']) ? $record['content'] : ''); 615 $body = (isset($record['content']) ? $record['content'] : '');
958 $isRead = (isset($record['is_read']) ? intval($record['is_read']) : (isset($record['archive'])?intval($record['archive']):0)); 616 $isRead = (isset($record['is_read']) ? intval($record['is_read']) : (isset($record['archive']) ? intval($record['archive']) : 0));
959 $isFavorite = (isset($record['is_fav']) ? intval($record['is_fav']) : (isset($record['favorite'])?intval($record['favorite']):0) ); 617 $isFavorite = (isset($record['is_fav']) ? intval($record['is_fav']) : (isset($record['favorite']) ? intval($record['favorite']) : 0));
960 //insert new record
961 $id = $this->store->add($url, $title, $body, $this->user->getId(), $isFavorite, $isRead);
962 if ( $id ) {
963 $urlsInserted[] = $url; //add
964
965 if ( isset($record['tags']) && trim($record['tags']) ) {
966 //@TODO: set tags
967 618
968 } 619 // insert new record
620
621 $id = $this->store->add($url, $title, $body, $this->user->getId() , $isFavorite, $isRead);
622 if ($id) {
623 $urlsInserted[] = $url; //add
624 if (isset($record['tags']) && trim($record['tags'])) {
625
626 // @TODO: set tags
627
628 }
629 }
630 }
631 }
632
633 $i = sizeof($urlsInserted);
634 if ($i > 0) {
635 $this->messages->add('s', _('Articles inserted: ') . $i . _('. Please note, that some may be marked as "read".'));
969 } 636 }
970 }
971 }
972 637
973 $i = sizeof($urlsInserted);
974 if ( $i > 0 ) {
975 $this->messages->add('s', _('Articles inserted: ').$i._('. Please note, that some may be marked as "read".'));
976 }
977 Tools::logm('Import of articles finished: '.$i.' articles added (w/o content if not provided).'); 638 Tools::logm('Import of articles finished: '.$i.' articles added (w/o content if not provided).');
978 } 639 }
979 //file parsing finished here 640 else {
641 $this->messages->add('s', _('Did you forget to select a file?'));
642 }
643 // file parsing finished here
644 // now download article contents if any
645 // check if we need to download any content
980 646
981 //now download article contents if any 647 $recordsDownloadRequired = $this->store->retrieveUnfetchedEntriesCount($this->user->getId());
982 648
983 //check if we need to download any content 649 if ($recordsDownloadRequired == 0) {
984 $recordsDownloadRequired = $this->store->retrieveUnfetchedEntriesCount($this->user->getId());
985 if ( $recordsDownloadRequired == 0 ) {
986 //nothing to download
987 $this->messages->add('s', _('Import finished.'));
988 Tools::logm('Import finished completely');
989 Tools::redirect();
990 }
991 else {
992 //if just inserted - don't download anything, download will start in next reload
993 if ( !isset($_FILES['file']) ) {
994 //download next batch
995 Tools::logm('Fetching next batch of articles...');
996 $items = $this->store->retrieveUnfetchedEntries($this->user->getId(), IMPORT_LIMIT);
997 650
998 $purifier = $this->getPurifier(); 651 // nothing to download
999 652
1000 foreach ($items as $item) { 653 $this->messages->add('s', _('Import finished.'));
1001 $url = new Url(base64_encode($item['url'])); 654 Tools::logm('Import finished completely');
1002 Tools::logm('Fetching article '.$item['id']); 655 Tools::redirect();
1003 $content = Tools::getPageContent($url); 656 }
657 else {
1004 658
1005 $title = (($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled')); 659 // if just inserted - don't download anything, download will start in next reload
1006 $body = (($content['rss']['channel']['item']['description'] != '') ? $content['rss']['channel']['item']['description'] : _('Undefined'));
1007 660
1008 //clean content to prevent xss attack 661 if (!isset($_FILES['file'])) {
1009 $title = $purifier->purify($title);
1010 $body = $purifier->purify($body);
1011 662
1012 $this->store->updateContentAndTitle($item['id'], $title, $body, $this->user->getId()); 663 // download next batch
1013 Tools::logm('Article '.$item['id'].' updated.'); 664
1014 } 665 Tools::logm('Fetching next batch of articles...');
666 $items = $this->store->retrieveUnfetchedEntries($this->user->getId() , IMPORT_LIMIT);
667 $purifier = $this->_getPurifier();
668 foreach($items as $item) {
669 $url = new Url(base64_encode($item['url']));
670 Tools::logm('Fetching article ' . $item['id']);
671 $content = Tools::getPageContent($url);
672 $title = (($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled'));
673 $body = (($content['rss']['channel']['item']['description'] != '') ? $content['rss']['channel']['item']['description'] : _('Undefined'));
674
675 // clean content to prevent xss attack
1015 676
677 $title = $purifier->purify($title);
678 $body = $purifier->purify($body);
679 $this->store->updateContentAndTitle($item['id'], $title, $body, $this->user->getId());
680 Tools::logm('Article ' . $item['id'] . ' updated.');
681 }
682 }
1016 } 683 }
1017 }
1018 684
1019 return array('includeImport'=>true, 'import'=>array('recordsDownloadRequired'=>$recordsDownloadRequired, 'recordsUnderDownload'=> IMPORT_LIMIT, 'delay'=> IMPORT_DELAY * 1000) ); 685 return array(
686 'includeImport' => true,
687 'import' => array(
688 'recordsDownloadRequired' => $recordsDownloadRequired,
689 'recordsUnderDownload' => IMPORT_LIMIT,
690 'delay' => IMPORT_DELAY * 1000
691 )
692 );
1020 } 693 }
1021 694
1022 /** 695 /**
1023 * export poche entries in json 696 * export poche entries in json
1024 * @return json all poche entries 697 * @return json all poche entries
1025 */ 698 */
1026 public function export() { 699 public function export()
700 {
1027 $filename = "wallabag-export-".$this->user->getId()."-".date("Y-m-d").".json"; 701 $filename = "wallabag-export-".$this->user->getId()."-".date("Y-m-d").".json";
1028 header('Content-Disposition: attachment; filename='.$filename); 702 header('Content-Disposition: attachment; filename='.$filename);
1029 703
@@ -1039,7 +713,7 @@ class Poche
1039 * @param string $which 'prod' or 'dev' 713 * @param string $which 'prod' or 'dev'
1040 * @return string latest $which version 714 * @return string latest $which version
1041 */ 715 */
1042 private function getPocheVersion($which = 'prod') { 716 private function _getPocheVersion($which = 'prod') {
1043 $cache_file = CACHE . '/' . $which; 717 $cache_file = CACHE . '/' . $which;
1044 $check_time = time(); 718 $check_time = time();
1045 719
@@ -1054,29 +728,27 @@ class Poche
1054 return array($version, $check_time); 728 return array($version, $check_time);
1055 } 729 }
1056 730
1057 public function generateToken() 731 /**
732 * Update token for current user
733 */
734 public function updateToken()
1058 { 735 {
1059 if (ini_get('open_basedir') === '') { 736 $token = Tools::generateToken();
1060 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 737 $this->store->updateUserConfig($this->user->getId(), 'token', $token);
1061 echo 'This is a server using Windows!'; 738 $currentConfig = $_SESSION['poche_user']->config;
1062 // alternative to /dev/urandom for Windows 739 $currentConfig['token'] = $token;
1063 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20); 740 $_SESSION['poche_user']->setConfig($currentConfig);
1064 } else { 741 Tools::redirect();
1065 $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
1066 }
1067 }
1068 else {
1069 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
1070 }
1071
1072 $token = str_replace('+', '', $token);
1073 $this->store->updateUserConfig($this->user->getId(), 'token', $token);
1074 $currentConfig = $_SESSION['poche_user']->config;
1075 $currentConfig['token'] = $token;
1076 $_SESSION['poche_user']->setConfig($currentConfig);
1077 Tools::redirect();
1078 } 742 }
1079 743
744 /**
745 * Generate RSS feeds for current user
746 *
747 * @param $token
748 * @param $user_id
749 * @param $tag_id
750 * @param string $type
751 */
1080 public function generateFeeds($token, $user_id, $tag_id, $type = 'home') 752 public function generateFeeds($token, $user_id, $tag_id, $type = 'home')
1081 { 753 {
1082 $allowed_types = array('home', 'fav', 'archive', 'tag'); 754 $allowed_types = array('home', 'fav', 'archive', 'tag');
@@ -1086,10 +758,9 @@ class Poche
1086 die(sprintf(_('User with this id (%d) does not exist.'), $user_id)); 758 die(sprintf(_('User with this id (%d) does not exist.'), $user_id));
1087 } 759 }
1088 760
1089 if (!in_array($type, $allowed_types) || $token != $config['token']) { 761 if (!in_array($type, $allowed_types) || !isset($config['token']) || $token != $config['token']) {
1090 die(_('Uh, there is a problem while generating feeds.')); 762 die(_('Uh, there is a problem while generating feed. Wrong token used?'));
1091 } 763 }
1092 // Check the token
1093 764
1094 $feed = new FeedWriter(RSS2); 765 $feed = new FeedWriter(RSS2);
1095 $feed->setTitle('wallabag — ' . $type . ' feed'); 766 $feed->setTitle('wallabag — ' . $type . ' feed');
@@ -1121,147 +792,22 @@ class Poche
1121 exit; 792 exit;
1122 } 793 }
1123 794
1124 public function emptyCache() {
1125 $files = new RecursiveIteratorIterator(
1126 new RecursiveDirectoryIterator(CACHE, RecursiveDirectoryIterator::SKIP_DOTS),
1127 RecursiveIteratorIterator::CHILD_FIRST
1128 );
1129
1130 foreach ($files as $fileinfo) {
1131 $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
1132 $todo($fileinfo->getRealPath());
1133 }
1134 795
1135 Tools::logm('empty cache');
1136 $this->messages->add('s', _('Cache deleted.'));
1137 Tools::redirect();
1138 }
1139 796
1140 /** 797 /**
1141 * return new purifier object with actual config 798 * Returns new purifier object with actual config
1142 */ 799 */
1143 protected function getPurifier() { 800 private function _getPurifier()
1144 $config = HTMLPurifier_Config::createDefault(); 801 {
1145 $config->set('Cache.SerializerPath', CACHE); 802 $config = HTMLPurifier_Config::createDefault();
1146 $config->set('HTML.SafeIframe', true); 803 $config->set('Cache.SerializerPath', CACHE);
804 $config->set('HTML.SafeIframe', true);
1147 805
1148 //allow YouTube, Vimeo and dailymotion videos 806 //allow YouTube, Vimeo and dailymotion videos
1149 $config->set('URI.SafeIframeRegexp', '%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/|www\.dailymotion\.com/embed/video/)%'); 807 $config->set('URI.SafeIframeRegexp', '%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/|www\.dailymotion\.com/embed/video/)%');
1150 808
1151 return new HTMLPurifier($config); 809 return new HTMLPurifier($config);
1152 } 810 }
1153 811
1154 /**
1155 * handle epub
1156 */
1157 public function createEpub() {
1158
1159 switch ($_GET['method']) {
1160 case 'id':
1161 $entryID = filter_var($_GET['id'],FILTER_SANITIZE_NUMBER_INT);
1162 $entry = $this->store->retrieveOneById($entryID, $this->user->getId());
1163 $entries = array($entry);
1164 $bookTitle = $entry['title'];
1165 $bookFileName = substr($bookTitle, 0, 200);
1166 break;
1167 case 'all':
1168 $entries = $this->store->retrieveAll($this->user->getId());
1169 $bookTitle = sprintf(_('All my articles on '), date(_('d.m.y'))); #translatable because each country has it's own date format system
1170 $bookFileName = _('Allarticles') . date(_('dmY'));
1171 break;
1172 case 'tag':
1173 $tag = filter_var($_GET['tag'],FILTER_SANITIZE_STRING);
1174 $tags_id = $this->store->retrieveAllTags($this->user->getId(),$tag);
1175 $tag_id = $tags_id[0]["id"]; // we take the first result, which is supposed to match perfectly. There must be a workaround.
1176 $entries = $this->store->retrieveEntriesByTag($tag_id,$this->user->getId());
1177 $bookTitle = sprintf(_('Articles tagged %s'),$tag);
1178 $bookFileName = substr(sprintf(_('Tag %s'),$tag), 0, 200);
1179 break;
1180 case 'category':
1181 $category = filter_var($_GET['category'],FILTER_SANITIZE_STRING);
1182 $entries = $this->store->getEntriesByView($category,$this->user->getId());
1183 $bookTitle = sprintf(_('All articles in category %s'), $category);
1184 $bookFileName = substr(sprintf(_('Category %s'),$category), 0, 200);
1185 break;
1186 case 'search':
1187 $search = filter_var($_GET['search'],FILTER_SANITIZE_STRING);
1188 $entries = $this->store->search($search,$this->user->getId());
1189 $bookTitle = sprintf(_('All articles for search %s'), $search);
1190 $bookFileName = substr(sprintf(_('Search %s'), $search), 0, 200);
1191 break;
1192 case 'default':
1193 die(_('Uh, there is a problem while generating epub.'));
1194
1195 }
1196
1197 $content_start =
1198 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1199 . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n"
1200 . "<head>"
1201 . "<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n"
1202 . "<title>wallabag articles book</title>\n"
1203 . "</head>\n"
1204 . "<body>\n";
1205
1206 $bookEnd = "</body>\n</html>\n";
1207 812
1208 $log = new Logger("wallabag", TRUE);
1209 $fileDir = CACHE;
1210
1211 $book = new EPub(EPub::BOOK_VERSION_EPUB3, DEBUG_POCHE);
1212 $log->logLine("new EPub()");
1213 $log->logLine("EPub class version: " . EPub::VERSION);
1214 $log->logLine("EPub Req. Zip version: " . EPub::REQ_ZIP_VERSION);
1215 $log->logLine("Zip version: " . Zip::VERSION);
1216 $log->logLine("getCurrentServerURL: " . $book->getCurrentServerURL());
1217 $log->logLine("getCurrentPageURL..: " . $book->getCurrentPageURL());
1218
1219 $book->setTitle(_('wallabag\'s articles'));
1220 $book->setIdentifier("http://$_SERVER[HTTP_HOST]", EPub::IDENTIFIER_URI); // Could also be the ISBN number, prefered for published books, or a UUID.
1221 //$book->setLanguage("en"); // Not needed, but included for the example, Language is mandatory, but EPub defaults to "en". Use RFC3066 Language codes, such as "en", "da", "fr" etc.
1222 $book->setDescription(_("Some articles saved on my wallabag"));
1223 $book->setAuthor("wallabag","wallabag");
1224 $book->setPublisher("wallabag","wallabag"); // I hope this is a non existant address :)
1225 $book->setDate(time()); // Strictly not needed as the book date defaults to time().
1226 //$book->setRights("Copyright and licence information specific for the book."); // As this is generated, this _could_ contain the name or licence information of the user who purchased the book, if needed. If this is used that way, the identifier must also be made unique for the book.
1227 $book->setSourceURL("http://$_SERVER[HTTP_HOST]");
1228
1229 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "PHP");
1230 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "wallabag");
1231
1232 $cssData = "body {\n margin-left: .5em;\n margin-right: .5em;\n text-align: justify;\n}\n\np {\n font-family: serif;\n font-size: 10pt;\n text-align: justify;\n text-indent: 1em;\n margin-top: 0px;\n margin-bottom: 1ex;\n}\n\nh1, h2 {\n font-family: sans-serif;\n font-style: italic;\n text-align: center;\n background-color: #6b879c;\n color: white;\n width: 100%;\n}\n\nh1 {\n margin-bottom: 2px;\n}\n\nh2 {\n margin-top: -2px;\n margin-bottom: 2px;\n}\n";
1233
1234 $log->logLine("Add Cover");
1235
1236 $fullTitle = "<h1> " . $bookTitle . "</h1>\n";
1237
1238 $book->setCoverImage("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png", $fullTitle);
1239
1240 $cover = $content_start . '<div style="text-align:center;"><p>' . _('Produced by wallabag with PHPePub') . '</p><p>'. _('Please open <a href="https://github.com/wallabag/wallabag/issues" >an issue</a> if you have trouble with the display of this E-Book on your device.') . '</p></div>' . $bookEnd;
1241
1242 //$book->addChapter("Table of Contents", "TOC.xhtml", NULL, false, EPub::EXTERNAL_REF_IGNORE);
1243 $book->addChapter("Notices", "Cover2.html", $cover);
1244
1245 $book->buildTOC();
1246
1247 foreach ($entries as $entry) { //set tags as subjects
1248 $tags = $this->store->retrieveTagsByEntry($entry['id']);
1249 foreach ($tags as $tag) {
1250 $book->setSubject($tag['value']);
1251 }
1252
1253 $log->logLine("Set up parameters");
1254
1255 $chapter = $content_start . $entry['content'] . $bookEnd;
1256 $book->addChapter($entry['title'], htmlspecialchars($entry['title']) . ".html", $chapter, true, EPub::EXTERNAL_REF_ADD);
1257 $log->logLine("Added chapter " . $entry['title']);
1258 }
1259
1260 if (DEBUG_POCHE) {
1261 $epuplog = $book->getLog();
1262 $book->addChapter("Log", "Log.html", $content_start . $log->getLog() . "\n</pre>" . $bookEnd); // log generation
1263 }
1264 $book->finalize();
1265 $zipData = $book->sendBook($bookFileName);
1266 }
1267} 813}
diff --git a/inc/poche/Routing.class.php b/inc/poche/Routing.class.php
new file mode 100755
index 00000000..0b373058
--- /dev/null
+++ b/inc/poche/Routing.class.php
@@ -0,0 +1,151 @@
1<?php
2/**
3 * wallabag, self hostable application allowing you to not miss any content anymore
4 *
5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013
8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */
10
11class Routing
12{
13 protected $wallabag;
14 protected $referer;
15 protected $view;
16 protected $action;
17 protected $id;
18 protected $url;
19 protected $file;
20 protected $defaultVars = array();
21 protected $vars = array();
22
23 public function __construct(Poche $wallabag)
24 {
25 $this->wallabag = $wallabag;
26 $this->_init();
27 }
28
29 private function _init()
30 {
31 # Parse GET & REFERER vars
32 $this->referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER'];
33 $this->view = Tools::checkVar('view', 'home');
34 $this->action = Tools::checkVar('action');
35 $this->id = Tools::checkVar('id');
36 $_SESSION['sort'] = Tools::checkVar('sort', 'id');
37 $this->url = new Url((isset ($_GET['url'])) ? $_GET['url'] : '');
38 }
39
40 public function run()
41 {
42 # vars to _always_ send to templates
43 $this->defaultVars = array(
44 'referer' => $this->referer,
45 'view' => $this->view,
46 'poche_url' => Tools::getPocheUrl(),
47 'title' => _('wallabag, a read it later open source system'),
48 'token' => \Session::getToken(),
49 'theme' => $this->wallabag->tpl->getTheme()
50 );
51
52 $this->_launchAction();
53 $this->_defineTplInformation();
54
55 # because messages can be added in $poche->action(), we have to add this entry now (we can add it before)
56 $this->vars = array_merge($this->vars, array('messages' => $this->wallabag->messages->display('all', FALSE)));
57
58 $this->_render($this->file, $this->vars);
59 }
60
61 private function _defineTplInformation()
62 {
63 $tplFile = array();
64 $tplVars = array();
65
66 if (\Session::isLogged()) {
67 $this->wallabag->action($this->action, $this->url, $this->id);
68 $tplFile = Tools::getTplFile($this->view);
69 $tplVars = array_merge($this->vars, $this->wallabag->displayView($this->view, $this->id));
70 } elseif(isset($_SERVER['PHP_AUTH_USER'])) {
71 if($this->wallabag->store->userExists($_SERVER['PHP_AUTH_USER'])) {
72 $this->wallabag->login($this->referer);
73 } else {
74 $this->wallabag->messages->add('e', _('login failed: user doesn\'t exist'));
75 Tools::logm('user doesn\'t exist');
76 $tplFile = Tools::getTplFile('login');
77 $tplVars['http_auth'] = 1;
78 }
79 } elseif(isset($_SERVER['REMOTE_USER'])) {
80 if($this->wallabag->store->userExists($_SERVER['REMOTE_USER'])) {
81 $this->wallabag->login($this->referer);
82 } else {
83 $this->wallabag->messages->add('e', _('login failed: user doesn\'t exist'));
84 Tools::logm('user doesn\'t exist');
85 $tplFile = Tools::getTplFile('login');
86 $tplVars['http_auth'] = 1;
87 }
88 } else {
89 $tplFile = Tools::getTplFile('login');
90 $tplVars['http_auth'] = 0;
91 \Session::logout();
92 }
93
94 $this->file = $tplFile;
95 $this->vars = array_merge($this->defaultVars, $tplVars);
96 }
97
98 private function _launchAction()
99 {
100 if (isset($_GET['login'])) {
101 // hello to you
102 $this->wallabag->login($this->referer);
103 } elseif (isset($_GET['feed']) && isset($_GET['user_id'])) {
104 $tag_id = (isset($_GET['tag_id']) ? intval($_GET['tag_id']) : 0);
105 $this->wallabag->generateFeeds($_GET['token'], filter_var($_GET['user_id'],FILTER_SANITIZE_NUMBER_INT), $tag_id, $_GET['type']);
106 }
107
108 //allowed ONLY to logged in user
109 if (\Session::isLogged() === true)
110 {
111 if (isset($_GET['logout'])) {
112 // see you soon !
113 $this->wallabag->logout();
114 } elseif (isset($_GET['config'])) {
115 // update password
116 $this->wallabag->updatePassword($_POST['password'], $_POST['password_repeat']);
117 } elseif (isset($_GET['newuser'])) {
118 $this->wallabag->createNewUser($_POST['newusername'], $_POST['password4newuser']);
119 } elseif (isset($_GET['deluser'])) {
120 $this->wallabag->deleteUser($_POST['password4deletinguser']);
121 } elseif (isset($_GET['epub'])) {
122 $epub = new WallabagEpub($this->wallabag, $_GET['method'], $_GET['id'], $_GET['value']);
123 $epub->run();
124 } elseif (isset($_GET['import'])) {
125 $import = $this->wallabag->import();
126 $tplVars = array_merge($this->vars, $import);
127 } elseif (isset($_GET['empty-cache'])) {
128 Tools::emptyCache();
129 } elseif (isset($_GET['export'])) {
130 $this->wallabag->export();
131 } elseif (isset($_GET['updatetheme'])) {
132 $this->wallabag->tpl->updateTheme($_POST['theme']);
133 } elseif (isset($_GET['updatelanguage'])) {
134 $this->wallabag->language->updateLanguage($_POST['language']);
135 } elseif (isset($_GET['uploadfile'])) {
136 $this->wallabag->uploadFile();
137 } elseif (isset($_GET['feed']) && isset($_GET['action']) && $_GET['action'] == 'generate') {
138 $this->wallabag->updateToken();
139 }
140 elseif (isset($_GET['plainurl']) && !empty($_GET['plainurl'])) {
141 $plainUrl = new Url(base64_encode($_GET['plainurl']));
142 $this->wallabag->action('add', $plainUrl);
143 }
144 }
145 }
146
147 public function _render($file, $vars)
148 {
149 echo $this->wallabag->tpl->render($file, $vars);
150 }
151} \ No newline at end of file
diff --git a/inc/poche/Template.class.php b/inc/poche/Template.class.php
new file mode 100644
index 00000000..b686f2ec
--- /dev/null
+++ b/inc/poche/Template.class.php
@@ -0,0 +1,235 @@
1<?php
2/**
3 * wallabag, self hostable application allowing you to not miss any content anymore
4 *
5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013
8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */
10
11class Template extends Twig_Environment
12{
13 protected $wallabag;
14
15 private $canRenderTemplates = TRUE;
16 private $currentTheme = '';
17
18 public function __construct(Poche $wallabag)
19 {
20 $this->wallabag = $wallabag;
21
22 // Set up theme
23 $pocheUser = Session::getParam('poche_user');
24
25 $themeDirectory = (is_null($pocheUser) ? DEFAULT_THEME : $pocheUser->getConfigValue('theme'));
26
27 if ($themeDirectory === false) {
28 $themeDirectory = DEFAULT_THEME;
29 }
30
31 $this->currentTheme = $themeDirectory;
32
33 if ($this->_themeIsInstalled() === array()) {
34 $this->_init();
35 }
36 }
37
38 /**
39 * Returns true if selected theme is installed
40 *
41 * @return bool
42 */
43 private function _themeIsInstalled()
44 {
45 $errors = array();
46
47 // Twig is an absolute requirement for wallabag to function.
48 // Abort immediately if the Composer installer hasn't been run yet
49 if (!$this->canRenderTemplates) {
50 $errors[] = 'Twig does not seem to be installed. Please initialize the Composer installation to automatically fetch dependencies. You can also download <a href="http://wllbg.org/vendor">vendor.zip</a> and extract it in your wallabag folder.';
51 }
52
53 // Check if the selected theme and its requirements are present
54 $theme = $this->getTheme();
55 if ($theme != '' && !is_dir(THEME . '/' . $theme)) {
56 $errors[] = 'The currently selected theme (' . $theme . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $theme . ')';
57 $this->canRenderTemplates = FALSE;
58 }
59
60 $themeInfo = $this->getThemeInfo($theme);
61 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
62 foreach ($themeInfo['requirements'] as $requiredTheme) {
63 if (! is_dir(THEME . '/' . $requiredTheme)) {
64 $errors[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')';
65 $this->canRenderTemplates = FALSE;
66 }
67 }
68 }
69
70 $currentErrors = (is_null(Session::getParam('errors'))? array() : Session::getParam('errors'));
71 Session::setParam('errors', array_merge($errors, $currentErrors));
72
73 return $errors;
74 }
75
76 /**
77 * Initialization for templates
78 */
79 private function _init()
80 {
81 $loaderChain = new Twig_Loader_Chain();
82 $theme = $this->getTheme();
83
84 // add the current theme as first to the loader chain
85 // so Twig will look there first for overridden template files
86 try {
87 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme));
88 } catch (Twig_Error_Loader $e) {
89 # @todo isInstalled() should catch this, inject Twig later
90 die('The currently selected theme (' . $theme . ') does not seem to be properly installed (' . THEME . '/' . $theme .' is missing)');
91 }
92
93 // add all required themes to the loader chain
94 $themeInfo = $this->getThemeInfo($theme);
95 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
96 foreach ($themeInfo['requirements'] as $requiredTheme) {
97 try {
98 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $requiredTheme));
99 } catch (Twig_Error_Loader $e) {
100 # @todo isInstalled() should catch this, inject Twig later
101 die('The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')');
102 }
103 }
104 }
105
106 if (DEBUG_POCHE) {
107 $twigParams = array();
108 } else {
109 $twigParams = array('cache' => CACHE);
110 }
111
112 parent::__construct($loaderChain, $twigParams);
113
114 //$tpl = new Twig_Environment($loaderChain, $twigParams);
115 $this->addExtension(new Twig_Extensions_Extension_I18n());
116
117 # filter to display domain name of an url
118 $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain');
119 $this->addFilter($filter);
120
121 # filter for reading time
122 $filter = new Twig_SimpleFilter('getReadingTime', 'Tools::getReadingTime');
123 $this->addFilter($filter);
124 }
125
126 /**
127 * Returns current theme
128 *
129 * @return string
130 */
131 public function getTheme()
132 {
133 return $this->currentTheme;
134 }
135
136 /**
137 * Provides theme information by parsing theme.ini file if present in the theme's root directory.
138 * In all cases, the following data will be returned:
139 * - name: theme's name, or key if the theme is unnamed,
140 * - current: boolean informing if the theme is the current user theme.
141 *
142 * @param string $theme Theme key (directory name)
143 * @return array|boolean Theme information, or false if the theme doesn't exist.
144 */
145 public function getThemeInfo($theme)
146 {
147 if (!is_dir(THEME . '/' . $theme)) {
148 return false;
149 }
150
151 $themeIniFile = THEME . '/' . $theme . '/theme.ini';
152 $themeInfo = array();
153
154 if (is_file($themeIniFile) && is_readable($themeIniFile)) {
155 $themeInfo = parse_ini_file($themeIniFile);
156 }
157
158 if ($themeInfo === false) {
159 $themeInfo = array();
160 }
161
162 if (!isset($themeInfo['name'])) {
163 $themeInfo['name'] = $theme;
164 }
165
166 $themeInfo['current'] = ($theme === $this->getTheme());
167
168 return $themeInfo;
169 }
170
171 /**
172 * Returns an array with installed themes
173 *
174 * @return array
175 */
176 public function getInstalledThemes()
177 {
178 $handle = opendir(THEME);
179 $themes = array();
180
181 while (($theme = readdir($handle)) !== false) {
182 # Themes are stored in a directory, so all directory names are themes
183 # @todo move theme installation data to database
184 if (!is_dir(THEME . '/' . $theme) || in_array($theme, array('.', '..'))) {
185 continue;
186 }
187
188 $themes[$theme] = $this->getThemeInfo($theme);
189 }
190
191 ksort($themes);
192
193 return $themes;
194 }
195
196 /**
197 * Update theme for the current user
198 *
199 * @param $newTheme
200 */
201 public function updateTheme($newTheme)
202 {
203 # we are not going to change it to the current theme...
204 if ($newTheme == $this->getTheme()) {
205 $this->wallabag->messages->add('w', _('still using the "' . $this->getTheme() . '" theme!'));
206 Tools::redirect('?view=config');
207 }
208
209 $themes = $this->getInstalledThemes();
210 $actualTheme = false;
211
212 foreach (array_keys($themes) as $theme) {
213 if ($theme == $newTheme) {
214 $actualTheme = true;
215 break;
216 }
217 }
218
219 if (!$actualTheme) {
220 $this->wallabag->messages->add('e', _('that theme does not seem to be installed'));
221 Tools::redirect('?view=config');
222 }
223
224 $this->wallabag->store->updateUserConfig($this->wallabag->user->getId(), 'theme', $newTheme);
225 $this->wallabag->messages->add('s', _('you have changed your theme preferences'));
226
227 $currentConfig = $_SESSION['poche_user']->config;
228 $currentConfig['theme'] = $newTheme;
229
230 $_SESSION['poche_user']->setConfig($currentConfig);
231
232 Tools::emptyCache();
233 Tools::redirect('?view=config');
234 }
235} \ No newline at end of file
diff --git a/inc/poche/Tools.class.php b/inc/poche/Tools.class.php
index cc01f403..93ec3fc6 100755
--- a/inc/poche/Tools.class.php
+++ b/inc/poche/Tools.class.php
@@ -5,19 +5,18 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11class Tools 11final class Tools
12{ 12{
13 /**
14 * Initialize PHP environment
15 */
13 public static function initPhp() 16 public static function initPhp()
14 { 17 {
15 define('START_TIME', microtime(true)); 18 define('START_TIME', microtime(true));
16 19
17 if (phpversion() < 5) {
18 die(_('Oops, it seems you don\'t have PHP 5.'));
19 }
20
21 function stripslashesDeep($value) { 20 function stripslashesDeep($value) {
22 return is_array($value) 21 return is_array($value)
23 ? array_map('stripslashesDeep', $value) 22 ? array_map('stripslashesDeep', $value)
@@ -34,6 +33,11 @@ class Tools
34 register_shutdown_function('ob_end_flush'); 33 register_shutdown_function('ob_end_flush');
35 } 34 }
36 35
36 /**
37 * Get wallabag instance URL
38 *
39 * @return string
40 */
37 public static function getPocheUrl() 41 public static function getPocheUrl()
38 { 42 {
39 $https = (!empty($_SERVER['HTTPS']) 43 $https = (!empty($_SERVER['HTTPS'])
@@ -50,6 +54,10 @@ class Tools
50 || ($https && $_SERVER["SERVER_PORT"] == '443') 54 || ($https && $_SERVER["SERVER_PORT"] == '443')
51 || ($https && $_SERVER["SERVER_PORT"]==SSL_PORT) //Custom HTTPS port detection 55 || ($https && $_SERVER["SERVER_PORT"]==SSL_PORT) //Custom HTTPS port detection
52 ? '' : ':' . $_SERVER["SERVER_PORT"]); 56 ? '' : ':' . $_SERVER["SERVER_PORT"]);
57
58 if (isset($_SERVER["HTTP_X_FORWARDED_PORT"])) {
59 $serverport = ':' . $_SERVER["HTTP_X_FORWARDED_PORT"];
60 }
53 61
54 $scriptname = str_replace('/index.php', '/', $_SERVER["SCRIPT_NAME"]); 62 $scriptname = str_replace('/index.php', '/', $_SERVER["SCRIPT_NAME"]);
55 63
@@ -67,6 +75,11 @@ class Tools
67 . $host . $serverport . $scriptname; 75 . $host . $serverport . $scriptname;
68 } 76 }
69 77
78 /**
79 * Redirects to a URL
80 *
81 * @param string $url
82 */
70 public static function redirect($url = '') 83 public static function redirect($url = '')
71 { 84 {
72 if ($url === '') { 85 if ($url === '') {
@@ -87,11 +100,18 @@ class Tools
87 $url = $ref; 100 $url = $ref;
88 } 101 }
89 } 102 }
103
90 self::logm('redirect to ' . $url); 104 self::logm('redirect to ' . $url);
91 header('Location: '.$url); 105 header('Location: '.$url);
92 exit(); 106 exit();
93 } 107 }
94 108
109 /**
110 * Returns name of the template file to display
111 *
112 * @param $view
113 * @return string
114 */
95 public static function getTplFile($view) 115 public static function getTplFile($view)
96 { 116 {
97 $views = array( 117 $views = array(
@@ -99,13 +119,15 @@ class Tools
99 'edit-tags', 'view', 'login', 'error' 119 'edit-tags', 'view', 'login', 'error'
100 ); 120 );
101 121
102 if (in_array($view, $views)) { 122 return (in_array($view, $views) ? $view . '.twig' : 'home.twig');
103 return $view . '.twig';
104 }
105
106 return 'home.twig';
107 } 123 }
108 124
125 /**
126 * Download a file (typically, for downloading pictures on web server)
127 *
128 * @param $url
129 * @return bool|mixed|string
130 */
109 public static function getFile($url) 131 public static function getFile($url)
110 { 132 {
111 $timeout = 15; 133 $timeout = 15;
@@ -186,6 +208,11 @@ class Tools
186 } 208 }
187 } 209 }
188 210
211 /**
212 * Headers for JSON export
213 *
214 * @param $data
215 */
189 public static function renderJson($data) 216 public static function renderJson($data)
190 { 217 {
191 header('Cache-Control: no-cache, must-revalidate'); 218 header('Cache-Control: no-cache, must-revalidate');
@@ -195,6 +222,11 @@ class Tools
195 exit(); 222 exit();
196 } 223 }
197 224
225 /**
226 * Create new line in log file
227 *
228 * @param $message
229 */
198 public static function logm($message) 230 public static function logm($message)
199 { 231 {
200 if (DEBUG_POCHE && php_sapi_name() != 'cli') { 232 if (DEBUG_POCHE && php_sapi_name() != 'cli') {
@@ -204,36 +236,57 @@ class Tools
204 } 236 }
205 } 237 }
206 238
239 /**
240 * Encode a URL by using a salt
241 *
242 * @param $string
243 * @return string
244 */
207 public static function encodeString($string) 245 public static function encodeString($string)
208 { 246 {
209 return sha1($string . SALT); 247 return sha1($string . SALT);
210 } 248 }
211 249
250 /**
251 * Cleans a variable
252 *
253 * @param $var
254 * @param string $default
255 * @return string
256 */
212 public static function checkVar($var, $default = '') 257 public static function checkVar($var, $default = '')
213 { 258 {
214 return ((isset ($_REQUEST["$var"])) ? htmlentities($_REQUEST["$var"]) : $default); 259 return ((isset($_REQUEST["$var"])) ? htmlentities($_REQUEST["$var"]) : $default);
215 } 260 }
216 261
262 /**
263 * Returns the domain name for a URL
264 *
265 * @param $url
266 * @return string
267 */
217 public static function getDomain($url) 268 public static function getDomain($url)
218 { 269 {
219 return parse_url($url, PHP_URL_HOST); 270 return parse_url($url, PHP_URL_HOST);
220 } 271 }
221 272
222 public static function getReadingTime($text) { 273 /**
223 $word = str_word_count(strip_tags($text)); 274 * For a given text, we calculate reading time for an article
224 $minutes = floor($word / 200); 275 *
225 $seconds = floor($word % 200 / (200 / 60)); 276 * @param $text
226 $time = array('minutes' => $minutes, 'seconds' => $seconds); 277 * @return float
227 278 */
228 return $minutes; 279 public static function getReadingTime($text)
229 } 280 {
230 281 return floor(str_word_count(strip_tags($text)) / 200);
231 public static function getDocLanguage($userlanguage) {
232 $lang = explode('.', $userlanguage);
233 return str_replace('_', '-', $lang[0]);
234 } 282 }
235 283
236 public static function status($status_code) 284 /**
285 * Returns the correct header for a status code
286 *
287 * @param $status_code
288 */
289 private static function _status($status_code)
237 { 290 {
238 if (strpos(php_sapi_name(), 'apache') !== false) { 291 if (strpos(php_sapi_name(), 'apache') !== false) {
239 292
@@ -245,29 +298,24 @@ class Tools
245 } 298 }
246 } 299 }
247 300
248 public static function download_db() { 301 /**
249 header('Content-Disposition: attachment; filename="poche.sqlite.gz"'); 302 * Get the content for a given URL (by a call to FullTextFeed)
250 self::status(200); 303 *
251 304 * @param Url $url
252 header('Content-Transfer-Encoding: binary'); 305 * @return mixed
253 header('Content-Type: application/octet-stream'); 306 */
254 echo gzencode(file_get_contents(STORAGE_SQLITE));
255
256 exit;
257 }
258
259 public static function getPageContent(Url $url) 307 public static function getPageContent(Url $url)
260 { 308 {
261 // Saving and clearing context 309 // Saving and clearing context
262 $REAL = array(); 310 $REAL = array();
263 foreach( $GLOBALS as $key => $value ) { 311 foreach( $GLOBALS as $key => $value ) {
264 if( $key != 'GLOBALS' && $key != '_SESSION' && $key != 'HTTP_SESSION_VARS' ) { 312 if( $key != 'GLOBALS' && $key != '_SESSION' && $key != 'HTTP_SESSION_VARS' ) {
265 $GLOBALS[$key] = array(); 313 $GLOBALS[$key] = array();
266 $REAL[$key] = $value; 314 $REAL[$key] = $value;
267 } 315 }
268 } 316 }
269 // Saving and clearing session 317 // Saving and clearing session
270 if ( isset($_SESSION) ) { 318 if (isset($_SESSION)) {
271 $REAL_SESSION = array(); 319 $REAL_SESSION = array();
272 foreach( $_SESSION as $key => $value ) { 320 foreach( $_SESSION as $key => $value ) {
273 $REAL_SESSION[$key] = $value; 321 $REAL_SESSION[$key] = $value;
@@ -279,12 +327,12 @@ class Tools
279 $scope = function() { 327 $scope = function() {
280 extract( func_get_arg(1) ); 328 extract( func_get_arg(1) );
281 $_GET = $_REQUEST = array( 329 $_GET = $_REQUEST = array(
282 "url" => $url->getUrl(), 330 "url" => $url->getUrl(),
283 "max" => 5, 331 "max" => 5,
284 "links" => "preserve", 332 "links" => "preserve",
285 "exc" => "", 333 "exc" => "",
286 "format" => "json", 334 "format" => "json",
287 "submit" => "Create Feed" 335 "submit" => "Create Feed"
288 ); 336 );
289 ob_start(); 337 ob_start();
290 require func_get_arg(0); 338 require func_get_arg(0);
@@ -292,23 +340,26 @@ class Tools
292 ob_end_clean(); 340 ob_end_clean();
293 return $json; 341 return $json;
294 }; 342 };
295 $json = $scope( "inc/3rdparty/makefulltextfeed.php", array("url" => $url) ); 343
344 $json = $scope("inc/3rdparty/makefulltextfeed.php", array("url" => $url));
296 345
297 // Clearing and restoring context 346 // Clearing and restoring context
298 foreach( $GLOBALS as $key => $value ) { 347 foreach ($GLOBALS as $key => $value) {
299 if( $key != "GLOBALS" && $key != "_SESSION" ) { 348 if($key != "GLOBALS" && $key != "_SESSION" ) {
300 unset($GLOBALS[$key]); 349 unset($GLOBALS[$key]);
301 } 350 }
302 } 351 }
303 foreach( $REAL as $key => $value ) { 352 foreach ($REAL as $key => $value) {
304 $GLOBALS[$key] = $value; 353 $GLOBALS[$key] = $value;
305 } 354 }
355
306 // Clearing and restoring session 356 // Clearing and restoring session
307 if ( isset($REAL_SESSION) ) { 357 if (isset($REAL_SESSION)) {
308 foreach( $_SESSION as $key => $value ) { 358 foreach($_SESSION as $key => $value) {
309 unset($_SESSION[$key]); 359 unset($_SESSION[$key]);
310 } 360 }
311 foreach( $REAL_SESSION as $key => $value ) { 361
362 foreach($REAL_SESSION as $key => $value) {
312 $_SESSION[$key] = $value; 363 $_SESSION[$key] = $value;
313 } 364 }
314 } 365 }
@@ -318,11 +369,48 @@ class Tools
318 369
319 /** 370 /**
320 * Returns whether we handle an AJAX (XMLHttpRequest) request. 371 * Returns whether we handle an AJAX (XMLHttpRequest) request.
372 *
321 * @return boolean whether we handle an AJAX (XMLHttpRequest) request. 373 * @return boolean whether we handle an AJAX (XMLHttpRequest) request.
322 */ 374 */
323 public static function isAjaxRequest() 375 public static function isAjaxRequest()
324 { 376 {
325 return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest'; 377 return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest';
378 }
379
380 /*
381 * Empty cache folder
382 */
383 public static function emptyCache()
384 {
385 $files = new RecursiveIteratorIterator(
386 new RecursiveDirectoryIterator(CACHE, RecursiveDirectoryIterator::SKIP_DOTS),
387 RecursiveIteratorIterator::CHILD_FIRST
388 );
389
390 foreach ($files as $fileInfo) {
391 $todo = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
392 $todo($fileInfo->getRealPath());
393 }
394
395 Tools::logm('empty cache');
396 Tools::redirect();
397 }
398
399 public static function generateToken()
400 {
401 if (ini_get('open_basedir') === '') {
402 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
403 // alternative to /dev/urandom for Windows
404 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
405 } else {
406 $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
407 }
408 }
409 else {
410 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
411 }
412
413 return str_replace('+', '', $token);
326 } 414 }
327 415
328} 416}
diff --git a/inc/poche/Url.class.php b/inc/poche/Url.class.php
index aba236fa..d9172b7d 100644
--- a/inc/poche/Url.class.php
+++ b/inc/poche/Url.class.php
@@ -5,7 +5,7 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11class Url 11class Url
diff --git a/inc/poche/User.class.php b/inc/poche/User.class.php
index cc8bec65..eaadd3e5 100644
--- a/inc/poche/User.class.php
+++ b/inc/poche/User.class.php
@@ -5,7 +5,7 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11class User 11class User
@@ -44,7 +44,14 @@ class User
44 $this->config = $config; 44 $this->config = $config;
45 } 45 }
46 46
47 public function getConfigValue($name) { 47 /**
48 * Returns configuration entry for a user
49 *
50 * @param $name
51 * @return bool
52 */
53 public function getConfigValue($name)
54 {
48 return (isset($this->config[$name])) ? $this->config[$name] : FALSE; 55 return (isset($this->config[$name])) ? $this->config[$name] : FALSE;
49 } 56 }
50} \ No newline at end of file 57} \ No newline at end of file
diff --git a/inc/poche/WallabagEpub.class.php b/inc/poche/WallabagEpub.class.php
new file mode 100644
index 00000000..9c4d3566
--- /dev/null
+++ b/inc/poche/WallabagEpub.class.php
@@ -0,0 +1,135 @@
1<?php
2/**
3 * wallabag, self hostable application allowing you to not miss any content anymore
4 *
5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013
8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */
10
11class WallabagEpub
12{
13 protected $wallabag;
14 protected $method;
15 protected $value;
16
17 public function __construct(Poche $wallabag, $method, $value)
18 {
19 $this->wallabag = $wallabag;
20 $this->method = $method;
21 $this->value = $value;
22 }
23
24 /**
25 * handle ePub
26 */
27 public function run()
28 {
29 switch ($this->method) {
30 case 'id':
31 $entryID = filter_var($this->value, FILTER_SANITIZE_NUMBER_INT);
32 $entry = $this->wallabag->store->retrieveOneById($entryID, $this->wallabag->user->getId());
33 $entries = array($entry);
34 $bookTitle = $entry['title'];
35 $bookFileName = substr($bookTitle, 0, 200);
36 break;
37 case 'all':
38 $entries = $this->wallabag->store->retrieveAll($this->wallabag->user->getId());
39 $bookTitle = sprintf(_('All my articles on '), date(_('d.m.y'))); #translatable because each country has it's own date format system
40 $bookFileName = _('Allarticles') . date(_('dmY'));
41 break;
42 case 'tag':
43 $tag = filter_var($this->value, FILTER_SANITIZE_STRING);
44 $tags_id = $this->wallabag->store->retrieveAllTags($this->wallabag->user->getId(), $tag);
45 $tag_id = $tags_id[0]["id"]; // we take the first result, which is supposed to match perfectly. There must be a workaround.
46 $entries = $this->wallabag->store->retrieveEntriesByTag($tag_id, $this->wallabag->user->getId());
47 $bookTitle = sprintf(_('Articles tagged %s'), $tag);
48 $bookFileName = substr(sprintf(_('Tag %s'), $tag), 0, 200);
49 break;
50 case 'category':
51 $category = filter_var($this->value, FILTER_SANITIZE_STRING);
52 $entries = $this->wallabag->store->getEntriesByView($category, $this->wallabag->user->getId());
53 $bookTitle = sprintf(_('All articles in category %s'), $category);
54 $bookFileName = substr(sprintf(_('Category %s'), $category), 0, 200);
55 break;
56 case 'search':
57 $search = filter_var($this->value, FILTER_SANITIZE_STRING);
58 $entries = $this->store->search($search, $this->wallabag->user->getId());
59 $bookTitle = sprintf(_('All articles for search %s'), $search);
60 $bookFileName = substr(sprintf(_('Search %s'), $search), 0, 200);
61 break;
62 case 'default':
63 die(_('Uh, there is a problem while generating epub.'));
64 }
65
66 $content_start =
67 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
68 . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n"
69 . "<head>"
70 . "<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n"
71 . "<title>wallabag articles book</title>\n"
72 . "</head>\n"
73 . "<body>\n";
74
75 $bookEnd = "</body>\n</html>\n";
76
77 $log = new Logger("wallabag", TRUE);
78 $fileDir = CACHE;
79
80 $book = new EPub(EPub::BOOK_VERSION_EPUB3, DEBUG_POCHE);
81 $log->logLine("new EPub()");
82 $log->logLine("EPub class version: " . EPub::VERSION);
83 $log->logLine("EPub Req. Zip version: " . EPub::REQ_ZIP_VERSION);
84 $log->logLine("Zip version: " . Zip::VERSION);
85 $log->logLine("getCurrentServerURL: " . $book->getCurrentServerURL());
86 $log->logLine("getCurrentPageURL..: " . $book->getCurrentPageURL());
87
88 $book->setTitle($bookTitle);
89 $book->setIdentifier("http://$_SERVER[HTTP_HOST]", EPub::IDENTIFIER_URI); // Could also be the ISBN number, prefered for published books, or a UUID.
90 //$book->setLanguage("en"); // Not needed, but included for the example, Language is mandatory, but EPub defaults to "en". Use RFC3066 Language codes, such as "en", "da", "fr" etc.
91 $book->setDescription(_("Some articles saved on my wallabag"));
92 $book->setAuthor("wallabag", "wallabag");
93 $book->setPublisher("wallabag", "wallabag"); // I hope this is a non existant address :)
94 $book->setDate(time()); // Strictly not needed as the book date defaults to time().
95 //$book->setRights("Copyright and licence information specific for the book."); // As this is generated, this _could_ contain the name or licence information of the user who purchased the book, if needed. If this is used that way, the identifier must also be made unique for the book.
96 $book->setSourceURL("http://$_SERVER[HTTP_HOST]");
97
98 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "PHP");
99 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "wallabag");
100
101 $cssData = "body {\n margin-left: .5em;\n margin-right: .5em;\n text-align: justify;\n}\n\np {\n font-family: serif;\n font-size: 10pt;\n text-align: justify;\n text-indent: 1em;\n margin-top: 0px;\n margin-bottom: 1ex;\n}\n\nh1, h2 {\n font-family: sans-serif;\n font-style: italic;\n text-align: center;\n background-color: #6b879c;\n color: white;\n width: 100%;\n}\n\nh1 {\n margin-bottom: 2px;\n}\n\nh2 {\n margin-top: -2px;\n margin-bottom: 2px;\n}\n";
102
103 $log->logLine("Add Cover");
104
105 $fullTitle = "<h1> " . $bookTitle . "</h1>\n";
106
107 $book->setCoverImage("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png", $fullTitle);
108
109 $cover = $content_start . '<div style="text-align:center;"><p>' . _('Produced by wallabag with PHPePub') . '</p><p>'. _('Please open <a href="https://github.com/wallabag/wallabag/issues" >an issue</a> if you have trouble with the display of this E-Book on your device.') . '</p></div>' . $bookEnd;
110
111 //$book->addChapter("Table of Contents", "TOC.xhtml", NULL, false, EPub::EXTERNAL_REF_IGNORE);
112 $book->addChapter("Notices", "Cover2.html", $cover);
113
114 $book->buildTOC();
115
116 foreach ($entries as $entry) { //set tags as subjects
117 $tags = $this->wallabag->store->retrieveTagsByEntry($entry['id']);
118 foreach ($tags as $tag) {
119 $book->setSubject($tag['value']);
120 }
121
122 $log->logLine("Set up parameters");
123
124 $chapter = $content_start . $entry['content'] . $bookEnd;
125 $book->addChapter($entry['title'], htmlspecialchars($entry['title']) . ".html", $chapter, true, EPub::EXTERNAL_REF_ADD);
126 $log->logLine("Added chapter " . $entry['title']);
127 }
128
129 if (DEBUG_POCHE) {
130 $book->addChapter("Log", "Log.html", $content_start . $log->getLog() . "\n</pre>" . $bookEnd); // log generation
131 }
132 $book->finalize();
133 $zipData = $book->sendBook($bookFileName);
134 }
135} \ No newline at end of file
diff --git a/inc/poche/config.inc.default.php b/inc/poche/config.inc.default.php
index 95f727c6..2a458544 100755
--- a/inc/poche/config.inc.default.php
+++ b/inc/poche/config.inc.default.php
@@ -5,7 +5,7 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11@define ('SALT', ''); # put a strong string here 11@define ('SALT', ''); # put a strong string here
@@ -59,7 +59,7 @@
59@define ('LOCALE', ROOT . '/locale'); 59@define ('LOCALE', ROOT . '/locale');
60@define ('CACHE', ROOT . '/cache'); 60@define ('CACHE', ROOT . '/cache');
61 61
62@define ('PAGINATION', '10'); 62@define ('PAGINATION', '12');
63 63
64//limit for download of articles during import 64//limit for download of articles during import
65@define ('IMPORT_LIMIT', 5); 65@define ('IMPORT_LIMIT', 5);
diff --git a/inc/poche/global.inc.php b/inc/poche/global.inc.php
index 8cf86d03..b8c487e3 100755
--- a/inc/poche/global.inc.php
+++ b/inc/poche/global.inc.php
@@ -5,7 +5,7 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11# the poche system root directory (/inc) 11# the poche system root directory (/inc)
@@ -18,6 +18,11 @@ require_once INCLUDES . '/poche/Tools.class.php';
18require_once INCLUDES . '/poche/User.class.php'; 18require_once INCLUDES . '/poche/User.class.php';
19require_once INCLUDES . '/poche/Url.class.php'; 19require_once INCLUDES . '/poche/Url.class.php';
20require_once INCLUDES . '/3rdparty/class.messages.php'; 20require_once INCLUDES . '/3rdparty/class.messages.php';
21require_once ROOT . '/vendor/autoload.php';
22require_once INCLUDES . '/poche/Template.class.php';
23require_once INCLUDES . '/poche/Language.class.php';
24require_once INCLUDES . '/poche/Routing.class.php';
25require_once INCLUDES . '/poche/WallabagEpub.class.php';
21require_once INCLUDES . '/poche/Poche.class.php'; 26require_once INCLUDES . '/poche/Poche.class.php';
22 27
23require_once INCLUDES . '/poche/Database.class.php'; 28require_once INCLUDES . '/poche/Database.class.php';
@@ -36,25 +41,18 @@ require_once INCLUDES . '/3rdparty/libraries/PHPePub/Logger.php';
36require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPub.php'; 41require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPub.php';
37require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPubChapterSplitter.php'; 42require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPubChapterSplitter.php';
38 43
39# Composer its autoloader for automatically loading Twig
40if (! file_exists(ROOT . '/vendor/autoload.php')) {
41 Poche::$canRenderTemplates = false;
42} else {
43 require_once ROOT . '/vendor/autoload.php';
44}
45
46# system configuration; database credentials et caetera 44# system configuration; database credentials et caetera
47if (! file_exists(INCLUDES . '/poche/config.inc.php')) { 45require_once INCLUDES . '/poche/config.inc.php';
48 Poche::$configFileAvailable = false; 46require_once INCLUDES . '/poche/config.inc.default.php';
49} else {
50 require_once INCLUDES . '/poche/config.inc.php';
51 require_once INCLUDES . '/poche/config.inc.default.php';
52}
53 47
54if (Poche::$configFileAvailable && DOWNLOAD_PICTURES) { 48if (DOWNLOAD_PICTURES) {
55 require_once INCLUDES . '/poche/pochePictures.php'; 49 require_once INCLUDES . '/poche/pochePictures.php';
56} 50}
57 51
58if (!ini_get('date.timezone') || !@date_default_timezone_set(ini_get('date.timezone'))) { 52if (!ini_get('date.timezone') || !@date_default_timezone_set(ini_get('date.timezone'))) {
59 date_default_timezone_set('UTC'); 53 date_default_timezone_set('UTC');
60} \ No newline at end of file 54}
55
56if (defined('ERROR_REPORTING')) {
57 error_reporting(ERROR_REPORTING);
58}
diff --git a/inc/poche/pochePictures.php b/inc/poche/pochePictures.php
index 7c319a85..52394c70 100644
--- a/inc/poche/pochePictures.php
+++ b/inc/poche/pochePictures.php
@@ -5,154 +5,164 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11/** 11
12 * On modifie les URLS des images dans le corps de l'article 12final class Picture
13 */
14function filtre_picture($content, $url, $id)
15{ 13{
16 $matches = array(); 14 /**
17 $processing_pictures = array(); // list of processing image to avoid processing the same pictures twice 15 * Changing pictures URL in article content
18 preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER); 16 */
19 foreach($matches as $i => $link) { 17 public static function filterPicture($content, $url, $id)
20 $link[1] = trim($link[1]); 18 {
21 if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1])) { 19 $matches = array();
22 $absolute_path = get_absolute_link($link[2],$url); 20 $processing_pictures = array(); // list of processing image to avoid processing the same pictures twice
23 $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); 21 preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER);
24 $directory = create_assets_directory($id); 22 foreach($matches as $i => $link) {
25 $fullpath = $directory . '/' . $filename; 23 $link[1] = trim($link[1]);
26 24 if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1])) {
27 if (in_array($absolute_path, $processing_pictures) === true) { 25 $absolute_path = self::_getAbsoluteLink($link[2], $url);
28 // replace picture's URL only if processing is OK : already processing -> go to next picture 26 $filename = basename(parse_url($absolute_path, PHP_URL_PATH));
29 continue; 27 $directory = self::_createAssetsDirectory($id);
28 $fullpath = $directory . '/' . $filename;
29
30 if (in_array($absolute_path, $processing_pictures) === true) {
31 // replace picture's URL only if processing is OK : already processing -> go to next picture
32 continue;
33 }
34
35 if (self::_downloadPictures($absolute_path, $fullpath) === true) {
36 $content = str_replace($matches[$i][2], Tools::getPocheUrl() . $fullpath, $content);
37 }
38
39 $processing_pictures[] = $absolute_path;
30 } 40 }
31
32 if (download_pictures($absolute_path, $fullpath) === true) {
33 $content = str_replace($matches[$i][2], $fullpath, $content);
34 }
35
36 $processing_pictures[] = $absolute_path;
37 } 41 }
38 42
43 return $content;
39 } 44 }
40 45
41 return $content; 46 /**
42} 47 * Get absolute URL
48 */
49 private static function _getAbsoluteLink($relativeLink, $url)
50 {
51 /* return if already absolute URL */
52 if (parse_url($relativeLink, PHP_URL_SCHEME) != '') return $relativeLink;
43 53
44/** 54 /* queries and anchors */
45 * Retourne le lien absolu 55 if ($relativeLink[0]=='#' || $relativeLink[0]=='?') return $url . $relativeLink;
46 */
47function get_absolute_link($relative_link, $url) {
48 /* return if already absolute URL */
49 if (parse_url($relative_link, PHP_URL_SCHEME) != '') return $relative_link;
50 56
51 /* queries and anchors */ 57 /* parse base URL and convert to local variables:
52 if ($relative_link[0]=='#' || $relative_link[0]=='?') return $url . $relative_link; 58 $scheme, $host, $path */
59 extract(parse_url($url));
53 60
54 /* parse base URL and convert to local variables: 61 /* remove non-directory element from path */
55 $scheme, $host, $path */ 62 $path = preg_replace('#/[^/]*$#', '', $path);
56 extract(parse_url($url));
57 63
58 /* remove non-directory element from path */ 64 /* destroy path if relative url points to root */
59 $path = preg_replace('#/[^/]*$#', '', $path); 65 if ($relativeLink[0] == '/') $path = '';
60 66
61 /* destroy path if relative url points to root */ 67 /* dirty absolute URL */
62 if ($relative_link[0] == '/') $path = ''; 68 $abs = $host . $path . '/' . $relativeLink;
63 69
64 /* dirty absolute URL */ 70 /* replace '//' or '/./' or '/foo/../' with '/' */
65 $abs = $host . $path . '/' . $relative_link; 71 $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
72 for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {}
66 73
67 /* replace '//' or '/./' or '/foo/../' with '/' */ 74 /* absolute URL is ready! */
68 $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'); 75 return $scheme.'://'.$abs;
69 for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {} 76 }
70 77
71 /* absolute URL is ready! */ 78 /**
72 return $scheme.'://'.$abs; 79 * Downloading pictures
73} 80 *
81 * @return bool true if the download and processing is OK, false else
82 */
83 private static function _downloadPictures($absolute_path, $fullpath)
84 {
85 $rawdata = Tools::getFile($absolute_path);
86 $fullpath = urldecode($fullpath);
87
88 if(file_exists($fullpath)) {
89 unlink($fullpath);
90 }
74 91
75/** 92 // check extension
76 * Téléchargement des images 93 $file_ext = strrchr($fullpath, '.');
77 * 94 $whitelist = array(".jpg",".jpeg",".gif",".png");
78 * @return bool true if the download and processing is OK, false else 95 if (!(in_array($file_ext, $whitelist))) {
79 */ 96 Tools::logm('processed image with not allowed extension. Skipping ' . $fullpath);
80function download_pictures($absolute_path, $fullpath) 97 return false;
81{ 98 }
82 $rawdata = Tools::getFile($absolute_path);
83 $fullpath = urldecode($fullpath);
84 99
85 if(file_exists($fullpath)) { 100 // check headers
86 unlink($fullpath); 101 $imageinfo = getimagesize($absolute_path);
87 } 102 if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg'&& $imageinfo['mime'] != 'image/jpg'&& $imageinfo['mime'] != 'image/png') {
88 103 Tools::logm('processed image with bad header. Skipping ' . $fullpath);
89 // check extension 104 return false;
90 $file_ext = strrchr($fullpath, '.'); 105 }
91 $whitelist = array(".jpg",".jpeg",".gif",".png");
92 if (!(in_array($file_ext, $whitelist))) {
93 Tools::logm('processed image with not allowed extension. Skipping ' . $fullpath);
94 return false;
95 }
96
97 // check headers
98 $imageinfo = getimagesize($absolute_path);
99 if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg'&& $imageinfo['mime'] != 'image/jpg'&& $imageinfo['mime'] != 'image/png') {
100 Tools::logm('processed image with bad header. Skipping ' . $fullpath);
101 return false;
102 }
103
104 // regenerate image
105 $im = imagecreatefromstring($rawdata);
106 if ($im === false) {
107 Tools::logm('error while regenerating image ' . $fullpath);
108 return false;
109 }
110
111 switch ($imageinfo['mime']) {
112 case 'image/gif':
113 $result = imagegif($im, $fullpath);
114 break;
115 case 'image/jpeg':
116 case 'image/jpg':
117 $result = imagejpeg($im, $fullpath, REGENERATE_PICTURES_QUALITY);
118 break;
119 case 'image/png':
120 $result = imagepng($im, $fullpath, ceil(REGENERATE_PICTURES_QUALITY / 100 * 9));
121 break;
122 }
123 imagedestroy($im);
124
125 return $result;
126}
127 106
128/** 107 // regenerate image
129 * Crée un répertoire de médias pour l'article 108 $im = imagecreatefromstring($rawdata);
130 */ 109 if ($im === false) {
131function create_assets_directory($id) 110 Tools::logm('error while regenerating image ' . $fullpath);
132{ 111 return false;
133 $assets_path = ABS_PATH; 112 }
134 if(!is_dir($assets_path)) { 113
135 mkdir($assets_path, 0715); 114 switch ($imageinfo['mime']) {
136 } 115 case 'image/gif':
116 $result = imagegif($im, $fullpath);
117 break;
118 case 'image/jpeg':
119 case 'image/jpg':
120 $result = imagejpeg($im, $fullpath, REGENERATE_PICTURES_QUALITY);
121 break;
122 case 'image/png':
123 $result = imagepng($im, $fullpath, ceil(REGENERATE_PICTURES_QUALITY / 100 * 9));
124 break;
125 }
126 imagedestroy($im);
137 127
138 $article_directory = $assets_path . $id; 128 return $result;
139 if(!is_dir($article_directory)) {
140 mkdir($article_directory, 0715);
141 } 129 }
142 130
143 return $article_directory; 131 /**
144} 132 * Create a directory for an article
133 *
134 * @param $id ID of the article
135 * @return string
136 */
137 private static function _createAssetsDirectory($id)
138 {
139 $assets_path = ABS_PATH;
140 if (!is_dir($assets_path)) {
141 mkdir($assets_path, 0715);
142 }
145 143
146/** 144 $article_directory = $assets_path . $id;
147 * Suppression du répertoire d'images 145 if (!is_dir($article_directory)) {
148 */ 146 mkdir($article_directory, 0715);
149function remove_directory($directory) 147 }
150{ 148
151 if(is_dir($directory)) { 149 return $article_directory;
152 $files = array_diff(scandir($directory), array('.','..')); 150 }
153 foreach ($files as $file) { 151
154 (is_dir("$directory/$file")) ? remove_directory("$directory/$file") : unlink("$directory/$file"); 152 /**
153 * Remove the directory
154 *
155 * @param $directory
156 * @return bool
157 */
158 public static function removeDirectory($directory)
159 {
160 if (is_dir($directory)) {
161 $files = array_diff(scandir($directory), array('.','..'));
162 foreach ($files as $file) {
163 (is_dir("$directory/$file")) ? self::removeDirectory("$directory/$file") : unlink("$directory/$file");
164 }
165 return rmdir($directory);
155 } 166 }
156 return rmdir($directory);
157 } 167 }
158} 168} \ No newline at end of file