aboutsummaryrefslogtreecommitdiffhomepage
path: root/inc/poche
diff options
context:
space:
mode:
Diffstat (limited to 'inc/poche')
-rwxr-xr-xinc/poche/Database.class.php182
-rwxr-xr-xinc/poche/Poche.class.php947
-rwxr-xr-x[-rw-r--r--]inc/poche/Tools.class.php93
-rwxr-xr-xinc/poche/config.inc.default.php68
-rwxr-xr-xinc/poche/config.inc.php.new63
-rwxr-xr-x[-rw-r--r--]inc/poche/global.inc.php8
-rw-r--r--inc/poche/pochePictures.php58
7 files changed, 874 insertions, 545 deletions
diff --git a/inc/poche/Database.class.php b/inc/poche/Database.class.php
index c998fe14..11cccb72 100755
--- a/inc/poche/Database.class.php
+++ b/inc/poche/Database.class.php
@@ -18,7 +18,7 @@ class Database {
18 'default' => 'ORDER BY entries.id' 18 'default' => 'ORDER BY entries.id'
19 ); 19 );
20 20
21 function __construct() 21 function __construct()
22 { 22 {
23 switch (STORAGE) { 23 switch (STORAGE) {
24 case 'sqlite': 24 case 'sqlite':
@@ -27,12 +27,14 @@ class Database {
27 break; 27 break;
28 case 'mysql': 28 case 'mysql':
29 $db_path = 'mysql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB; 29 $db_path = 'mysql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB;
30 $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD); 30 $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD);
31 break; 31 break;
32 case 'postgres': 32 case 'postgres':
33 $db_path = 'pgsql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB; 33 $db_path = 'pgsql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB;
34 $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD); 34 $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD);
35 break; 35 break;
36 default:
37 die(STORAGE . ' is not a recognised database system !');
36 } 38 }
37 39
38 $this->handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 40 $this->handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
@@ -51,7 +53,7 @@ class Database {
51 } 53 }
52 $hasAdmin = count($query->fetchAll()); 54 $hasAdmin = count($query->fetchAll());
53 55
54 if ($hasAdmin == 0) 56 if ($hasAdmin == 0)
55 return false; 57 return false;
56 58
57 return true; 59 return true;
@@ -77,7 +79,7 @@ class Database {
77 } 79 }
78 else { 80 else {
79 $sql = ' 81 $sql = '
80 CREATE TABLE tags ( 82 CREATE TABLE IF NOT EXISTS tags (
81 id bigserial primary key, 83 id bigserial primary key,
82 value varchar(255) NOT NULL 84 value varchar(255) NOT NULL
83 ); 85 );
@@ -110,7 +112,7 @@ class Database {
110 } 112 }
111 else { 113 else {
112 $sql = ' 114 $sql = '
113 CREATE TABLE tags_entries ( 115 CREATE TABLE IF NOT EXISTS tags_entries (
114 id bigserial primary key, 116 id bigserial primary key,
115 entry_id integer NOT NULL, 117 entry_id integer NOT NULL,
116 tag_id integer NOT NULL 118 tag_id integer NOT NULL
@@ -140,7 +142,7 @@ class Database {
140 $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)'; 142 $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)';
141 $params = array($id_user, 'language', LANG); 143 $params = array($id_user, 'language', LANG);
142 $query = $this->executeQuery($sql, $params); 144 $query = $this->executeQuery($sql, $params);
143 145
144 $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)'; 146 $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)';
145 $params = array($id_user, 'theme', DEFAULT_THEME); 147 $params = array($id_user, 'theme', DEFAULT_THEME);
146 $query = $this->executeQuery($sql, $params); 148 $query = $this->executeQuery($sql, $params);
@@ -153,7 +155,7 @@ class Database {
153 $query = $this->executeQuery($sql, array($id)); 155 $query = $this->executeQuery($sql, array($id));
154 $result = $query->fetchAll(); 156 $result = $query->fetchAll();
155 $user_config = array(); 157 $user_config = array();
156 158
157 foreach ($result as $key => $value) { 159 foreach ($result as $key => $value) {
158 $user_config[$value['name']] = $value['value']; 160 $user_config[$value['name']] = $value['value'];
159 } 161 }
@@ -201,10 +203,10 @@ class Database {
201 $params_update = array($password, $userId); 203 $params_update = array($password, $userId);
202 $query = $this->executeQuery($sql_update, $params_update); 204 $query = $this->executeQuery($sql_update, $params_update);
203 } 205 }
204 206
205 public function updateUserConfig($userId, $key, $value) { 207 public function updateUserConfig($userId, $key, $value) {
206 $config = $this->getConfigUser($userId); 208 $config = $this->getConfigUser($userId);
207 209
208 if (! isset($config[$key])) { 210 if (! isset($config[$key])) {
209 $sql = "INSERT INTO users_config (value, user_id, name) VALUES (?, ?, ?)"; 211 $sql = "INSERT INTO users_config (value, user_id, name) VALUES (?, ?, ?)";
210 } 212 }
@@ -229,6 +231,73 @@ class Database {
229 return FALSE; 231 return FALSE;
230 } 232 }
231 } 233 }
234
235 public function listUsers($username=null) {
236 $sql = 'SELECT count(*) FROM users'.( $username ? ' WHERE username=?' : '');
237 $query = $this->executeQuery($sql, ( $username ? array($username) : array()));
238 list($count) = $query->fetch();
239 return $count;
240 }
241
242 public function getUserPassword($userID) {
243 $sql = "SELECT * FROM users WHERE id=?";
244 $query = $this->executeQuery($sql, array($userID));
245 $password = $query->fetchAll();
246 return isset($password[0]['password']) ? $password[0]['password'] : null;
247 }
248
249 public function deleteUserConfig($userID) {
250 $sql_action = 'DELETE from users_config WHERE user_id=?';
251 $params_action = array($userID);
252 $query = $this->executeQuery($sql_action, $params_action);
253 return $query;
254 }
255
256 public function deleteTagsEntriesAndEntries($userID) {
257 $entries = $this->retrieveAll($userID);
258 foreach($entries as $entryid) {
259 $tags = $this->retrieveTagsByEntry($entryid);
260 foreach($tags as $tag) {
261 $this->removeTagForEntry($entryid,$tags);
262 }
263 $this->deleteById($entryid,$userID);
264 }
265 }
266
267 public function deleteUser($userID) {
268 $sql_action = 'DELETE from users WHERE id=?';
269 $params_action = array($userID);
270 $query = $this->executeQuery($sql_action, $params_action);
271 }
272
273 public function updateContentAndTitle($id, $title, $body, $user_id) {
274 $sql_action = 'UPDATE entries SET content = ?, title = ? WHERE id=? AND user_id=?';
275 $params_action = array($body, $title, $id, $user_id);
276 $query = $this->executeQuery($sql_action, $params_action);
277 return $query;
278 }
279
280 public function retrieveUnfetchedEntries($user_id, $limit) {
281
282 $sql_limit = "LIMIT 0,".$limit;
283 if (STORAGE == 'postgres') {
284 $sql_limit = "LIMIT ".$limit." OFFSET 0";
285 }
286
287 $sql = "SELECT * FROM entries WHERE (content = '' OR content IS NULL) AND title LIKE 'Untitled - Import%' AND user_id=? ORDER BY id " . $sql_limit;
288 $query = $this->executeQuery($sql, array($user_id));
289 $entries = $query->fetchAll();
290
291 return $entries;
292 }
293
294 public function retrieveUnfetchedEntriesCount($user_id) {
295 $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));
297 list($count) = $query->fetch();
298
299 return $count;
300 }
232 301
233 public function retrieveAll($user_id) { 302 public function retrieveAll($user_id) {
234 $sql = "SELECT * FROM entries WHERE user_id=? ORDER BY id"; 303 $sql = "SELECT * FROM entries WHERE user_id=? ORDER BY id";
@@ -294,24 +363,24 @@ class Database {
294 return $entries; 363 return $entries;
295 } 364 }
296 365
297 public function getEntriesByViewCount($view, $user_id, $tag_id = 0) { 366 public function getEntriesByViewCount($view, $user_id, $tag_id = 0) {
298 switch ($view) { 367 switch ($view) {
299 case 'archive': 368 case 'archive':
300 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? "; 369 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? ";
301 $params = array($user_id, 1); 370 $params = array($user_id, 1);
302 break; 371 break;
303 case 'fav' : 372 case 'fav' :
304 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_fav=? "; 373 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_fav=? ";
305 $params = array($user_id, 1); 374 $params = array($user_id, 1);
306 break; 375 break;
307 case 'tag' : 376 case 'tag' :
308 $sql = "SELECT count(*) FROM entries 377 $sql = "SELECT count(*) FROM entries
309 LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id 378 LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id
310 WHERE entries.user_id=? AND tags_entries.tag_id = ? "; 379 WHERE entries.user_id=? AND tags_entries.tag_id = ? ";
311 $params = array($user_id, $tag_id); 380 $params = array($user_id, $tag_id);
312 break; 381 break;
313 default: 382 default:
314 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? "; 383 $sql = "SELECT count(*) FROM entries WHERE user_id=? AND is_read=? ";
315 $params = array($user_id, 0); 384 $params = array($user_id, 0);
316 break; 385 break;
317 } 386 }
@@ -319,7 +388,7 @@ class Database {
319 $query = $this->executeQuery($sql, $params); 388 $query = $this->executeQuery($sql, $params);
320 list($count) = $query->fetch(); 389 list($count) = $query->fetch();
321 390
322 return $count; 391 return $count;
323 } 392 }
324 393
325 public function updateContent($id, $content, $user_id) { 394 public function updateContent($id, $content, $user_id) {
@@ -329,11 +398,25 @@ class Database {
329 return $query; 398 return $query;
330 } 399 }
331 400
332 public function add($url, $title, $content, $user_id) { 401 /**
333 $sql_action = 'INSERT INTO entries ( url, title, content, user_id ) VALUES (?, ?, ?, ?)'; 402 *
334 $params_action = array($url, $title, $content, $user_id); 403 * @param string $url
335 $query = $this->executeQuery($sql_action, $params_action); 404 * @param string $title
336 return $query; 405 * @param string $content
406 * @param integer $user_id
407 * @return integer $id of inserted record
408 */
409 public function add($url, $title, $content, $user_id, $isFavorite=0, $isRead=0) {
410 $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);
412
413 if ( !$this->executeQuery($sql_action, $params_action) ) {
414 $id = null;
415 }
416 else {
417 $id = intval($this->getLastId( (STORAGE == 'postgres') ? 'entries_id_seq' : '') );
418 }
419 return $id;
337 } 420 }
338 421
339 public function deleteById($id, $user_id) { 422 public function deleteById($id, $user_id) {
@@ -365,12 +448,24 @@ class Database {
365 return $this->getHandle()->lastInsertId($column); 448 return $this->getHandle()->lastInsertId($column);
366 } 449 }
367 450
368 public function retrieveAllTags($user_id) { 451 public function search($term, $user_id, $limit = '') {
369 $sql = "SELECT DISTINCT tags.* FROM tags 452 $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
454 $sql_action .= $this->getEntriesOrder().' ' . $limit;
455 $params_action = array($user_id, $search, $search, $search);
456 $query = $this->executeQuery($sql_action, $params_action);
457 return $query->fetchAll();
458 }
459
460 public function retrieveAllTags($user_id, $term = null) {
461 $sql = "SELECT DISTINCT tags.*, count(entries.id) AS entriescount FROM tags
370 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id 462 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id
371 LEFT JOIN entries ON tags_entries.entry_id=entries.id 463 LEFT JOIN entries ON tags_entries.entry_id=entries.id
372 WHERE entries.user_id=?"; 464 WHERE entries.user_id=?
373 $query = $this->executeQuery($sql, array($user_id)); 465 ". (($term) ? "AND lower(tags.value) LIKE ?" : '') ."
466 GROUP BY tags.id, tags.value
467 ORDER BY tags.value";
468 $query = $this->executeQuery($sql, (($term)? array($user_id, strtolower('%'.$term.'%')) : array($user_id) ));
374 $tags = $query->fetchAll(); 469 $tags = $query->fetchAll();
375 470
376 return $tags; 471 return $tags;
@@ -390,10 +485,10 @@ class Database {
390 } 485 }
391 486
392 public function retrieveEntriesByTag($tag_id, $user_id) { 487 public function retrieveEntriesByTag($tag_id, $user_id) {
393 $sql = 488 $sql =
394 "SELECT entries.* FROM entries 489 "SELECT entries.* FROM entries
395 LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id 490 LEFT JOIN tags_entries ON tags_entries.entry_id=entries.id
396 WHERE tags_entries.tag_id = ? AND entries.user_id=?"; 491 WHERE tags_entries.tag_id = ? AND entries.user_id=? ORDER by entries.id DESC";
397 $query = $this->executeQuery($sql, array($tag_id, $user_id)); 492 $query = $this->executeQuery($sql, array($tag_id, $user_id));
398 $entries = $query->fetchAll(); 493 $entries = $query->fetchAll();
399 494
@@ -401,7 +496,7 @@ class Database {
401 } 496 }
402 497
403 public function retrieveTagsByEntry($entry_id) { 498 public function retrieveTagsByEntry($entry_id) {
404 $sql = 499 $sql =
405 "SELECT tags.* FROM tags 500 "SELECT tags.* FROM tags
406 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id 501 LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id
407 WHERE tags_entries.entry_id = ?"; 502 WHERE tags_entries.entry_id = ?";
@@ -417,6 +512,25 @@ class Database {
417 $query = $this->executeQuery($sql_action, $params_action); 512 $query = $this->executeQuery($sql_action, $params_action);
418 return $query; 513 return $query;
419 } 514 }
515
516 public function cleanUnusedTag($tag_id) {
517 $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));
519 $tagstokeep = $query->fetchAll();
520 $sql_action = "SELECT tags.* FROM tags LEFT JOIN tags_entries ON tags_entries.tag_id=tags.id WHERE tags.id=?";
521 $query = $this->executeQuery($sql_action,array($tag_id));
522 $alltags = $query->fetchAll();
523
524 foreach ($alltags as $tag) {
525 if ($tag && !in_array($tag,$tagstokeep)) {
526 $sql_action = "DELETE FROM tags WHERE id=?";
527 $params_action = array($tag[0]);
528 $this->executeQuery($sql_action, $params_action);
529 return true;
530 }
531 }
532
533 }
420 534
421 public function retrieveTagByValue($value) { 535 public function retrieveTagByValue($value) {
422 $tag = NULL; 536 $tag = NULL;
diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php
index 8a9de488..09a9f5ff 100755
--- a/inc/poche/Poche.class.php
+++ b/inc/poche/Poche.class.php
@@ -18,7 +18,7 @@ class Poche
18 public $tpl; 18 public $tpl;
19 public $messages; 19 public $messages;
20 public $pagination; 20 public $pagination;
21 21
22 private $currentTheme = ''; 22 private $currentTheme = '';
23 private $currentLanguage = ''; 23 private $currentLanguage = '';
24 private $notInstalledMessage = array(); 24 private $notInstalledMessage = array();
@@ -32,20 +32,21 @@ class Poche
32 'fr_FR.utf8' => 'Français', 32 'fr_FR.utf8' => 'Français',
33 'it_IT.utf8' => 'Italiano', 33 'it_IT.utf8' => 'Italiano',
34 'pl_PL.utf8' => 'Polski', 34 'pl_PL.utf8' => 'Polski',
35 'pt_BR.utf8' => 'Português (Brasil)',
35 'ru_RU.utf8' => 'Pусский', 36 'ru_RU.utf8' => 'Pусский',
36 'sl_SI.utf8' => 'Slovenščina', 37 'sl_SI.utf8' => 'Slovenščina',
37 'uk_UA.utf8' => 'Українськй', 38 'uk_UA.utf8' => 'Українськ',
38 ); 39 );
39 public function __construct() 40 public function __construct()
40 { 41 {
41 if ($this->configFileIsAvailable()) { 42 if ($this->configFileIsAvailable()) {
42 $this->init(); 43 $this->init();
43 } 44 }
44 45
45 if ($this->themeIsInstalled()) { 46 if ($this->themeIsInstalled()) {
46 $this->initTpl(); 47 $this->initTpl();
47 } 48 }
48 49
49 if ($this->systemIsInstalled()) { 50 if ($this->systemIsInstalled()) {
50 $this->store = new Database(); 51 $this->store = new Database();
51 $this->messages = new Messages(); 52 $this->messages = new Messages();
@@ -56,12 +57,10 @@ class Poche
56 $this->store->checkTags(); 57 $this->store->checkTags();
57 } 58 }
58 } 59 }
59 60
60 private function init() 61 private function init()
61 { 62 {
62 Tools::initPhp(); 63 Tools::initPhp();
63 Session::$sessionName = 'poche';
64 Session::init();
65 64
66 if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) { 65 if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) {
67 $this->user = $_SESSION['poche_user']; 66 $this->user = $_SESSION['poche_user'];
@@ -73,43 +72,43 @@ class Poche
73 72
74 # l10n 73 # l10n
75 $language = $this->user->getConfigValue('language'); 74 $language = $this->user->getConfigValue('language');
76 putenv('LC_ALL=' . $language); 75 @putenv('LC_ALL=' . $language);
77 setlocale(LC_ALL, $language); 76 setlocale(LC_ALL, $language);
78 bindtextdomain($language, LOCALE); 77 bindtextdomain($language, LOCALE);
79 textdomain($language); 78 textdomain($language);
80 79
81 # Pagination 80 # Pagination
82 $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p'); 81 $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p');
83 82
84 # Set up theme 83 # Set up theme
85 $themeDirectory = $this->user->getConfigValue('theme'); 84 $themeDirectory = $this->user->getConfigValue('theme');
86 85
87 if ($themeDirectory === false) { 86 if ($themeDirectory === false) {
88 $themeDirectory = DEFAULT_THEME; 87 $themeDirectory = DEFAULT_THEME;
89 } 88 }
90 89
91 $this->currentTheme = $themeDirectory; 90 $this->currentTheme = $themeDirectory;
92 91
93 # Set up language 92 # Set up language
94 $languageDirectory = $this->user->getConfigValue('language'); 93 $languageDirectory = $this->user->getConfigValue('language');
95 94
96 if ($languageDirectory === false) { 95 if ($languageDirectory === false) {
97 $languageDirectory = DEFAULT_THEME; 96 $languageDirectory = DEFAULT_THEME;
98 } 97 }
99 98
100 $this->currentLanguage = $languageDirectory; 99 $this->currentLanguage = $languageDirectory;
101 } 100 }
102 101
103 public function configFileIsAvailable() { 102 public function configFileIsAvailable() {
104 if (! self::$configFileAvailable) { 103 if (! self::$configFileAvailable) {
105 $this->notInstalledMessage[] = 'You have to rename inc/poche/config.inc.php.new to inc/poche/config.inc.php.'; 104 $this->notInstalledMessage[] = 'You have to copy (don\'t just rename!) inc/poche/config.inc.default.php to inc/poche/config.inc.php.';
106 105
107 return false; 106 return false;
108 } 107 }
109 108
110 return true; 109 return true;
111 } 110 }
112 111
113 public function themeIsInstalled() { 112 public function themeIsInstalled() {
114 $passTheme = TRUE; 113 $passTheme = TRUE;
115 # Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet 114 # Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet
@@ -124,27 +123,27 @@ class Poche
124 self::$canRenderTemplates = false; 123 self::$canRenderTemplates = false;
125 124
126 $passTheme = FALSE; 125 $passTheme = FALSE;
127 } 126 }
128 127
129 # Check if the selected theme and its requirements are present 128 # Check if the selected theme and its requirements are present
130 $theme = $this->getTheme(); 129 $theme = $this->getTheme();
131 130
132 if ($theme != '' && ! is_dir(THEME . '/' . $theme)) { 131 if ($theme != '' && ! is_dir(THEME . '/' . $theme)) {
133 $this->notInstalledMessage[] = 'The currently selected theme (' . $theme . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $theme . ')'; 132 $this->notInstalledMessage[] = 'The currently selected theme (' . $theme . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $theme . ')';
134 133
135 self::$canRenderTemplates = false; 134 self::$canRenderTemplates = false;
136 135
137 $passTheme = FALSE; 136 $passTheme = FALSE;
138 } 137 }
139 138
140 $themeInfo = $this->getThemeInfo($theme); 139 $themeInfo = $this->getThemeInfo($theme);
141 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) { 140 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
142 foreach ($themeInfo['requirements'] as $requiredTheme) { 141 foreach ($themeInfo['requirements'] as $requiredTheme) {
143 if (! is_dir(THEME . '/' . $requiredTheme)) { 142 if (! is_dir(THEME . '/' . $requiredTheme)) {
144 $this->notInstalledMessage[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')'; 143 $this->notInstalledMessage[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')';
145 144
146 self::$canRenderTemplates = false; 145 self::$canRenderTemplates = false;
147 146
148 $passTheme = FALSE; 147 $passTheme = FALSE;
149 } 148 }
150 } 149 }
@@ -154,21 +153,21 @@ class Poche
154 return FALSE; 153 return FALSE;
155 } 154 }
156 155
157 156
158 return true; 157 return true;
159 } 158 }
160 159
161 /** 160 /**
162 * all checks before installation. 161 * all checks before installation.
163 * @todo move HTML to template 162 * @todo move HTML to template
164 * @return boolean 163 * @return boolean
165 */ 164 */
166 public function systemIsInstalled() 165 public function systemIsInstalled()
167 { 166 {
168 $msg = TRUE; 167 $msg = TRUE;
169 168
170 $configSalt = defined('SALT') ? constant('SALT') : ''; 169 $configSalt = defined('SALT') ? constant('SALT') : '';
171 170
172 if (empty($configSalt)) { 171 if (empty($configSalt)) {
173 $this->notInstalledMessage[] = 'You have not yet filled in the SALT value in the config.inc.php file.'; 172 $this->notInstalledMessage[] = 'You have not yet filled in the SALT value in the config.inc.php file.';
174 $msg = FALSE; 173 $msg = FALSE;
@@ -194,7 +193,7 @@ class Poche
194 193
195 return true; 194 return true;
196 } 195 }
197 196
198 public function getNotInstalledMessage() { 197 public function getNotInstalledMessage() {
199 return $this->notInstalledMessage; 198 return $this->notInstalledMessage;
200 } 199 }
@@ -203,7 +202,7 @@ class Poche
203 { 202 {
204 $loaderChain = new Twig_Loader_Chain(); 203 $loaderChain = new Twig_Loader_Chain();
205 $theme = $this->getTheme(); 204 $theme = $this->getTheme();
206 205
207 # add the current theme as first to the loader chain so Twig will look there first for overridden template files 206 # add the current theme as first to the loader chain so Twig will look there first for overridden template files
208 try { 207 try {
209 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme)); 208 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme));
@@ -211,7 +210,7 @@ class Poche
211 # @todo isInstalled() should catch this, inject Twig later 210 # @todo isInstalled() should catch this, inject Twig later
212 die('The currently selected theme (' . $theme . ') does not seem to be properly installed (' . THEME . '/' . $theme .' is missing)'); 211 die('The currently selected theme (' . $theme . ') does not seem to be properly installed (' . THEME . '/' . $theme .' is missing)');
213 } 212 }
214 213
215 # add all required themes to the loader chain 214 # add all required themes to the loader chain
216 $themeInfo = $this->getThemeInfo($theme); 215 $themeInfo = $this->getThemeInfo($theme);
217 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) { 216 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
@@ -224,16 +223,16 @@ class Poche
224 } 223 }
225 } 224 }
226 } 225 }
227 226
228 if (DEBUG_POCHE) { 227 if (DEBUG_POCHE) {
229 $twigParams = array(); 228 $twigParams = array();
230 } else { 229 } else {
231 $twigParams = array('cache' => CACHE); 230 $twigParams = array('cache' => CACHE);
232 } 231 }
233 232
234 $this->tpl = new Twig_Environment($loaderChain, $twigParams); 233 $this->tpl = new Twig_Environment($loaderChain, $twigParams);
235 $this->tpl->addExtension(new Twig_Extensions_Extension_I18n()); 234 $this->tpl->addExtension(new Twig_Extensions_Extension_I18n());
236 235
237 # filter to display domain name of an url 236 # filter to display domain name of an url
238 $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain'); 237 $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain');
239 $this->tpl->addFilter($filter); 238 $this->tpl->addFilter($filter);
@@ -243,6 +242,58 @@ class Poche
243 $this->tpl->addFilter($filter); 242 $this->tpl->addFilter($filter);
244 } 243 }
245 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();
264 }
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 {
286 Tools::logm('Bad password !');
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
246 private function install() 297 private function install()
247 { 298 {
248 Tools::logm('poche still not installed'); 299 Tools::logm('poche still not installed');
@@ -252,7 +303,7 @@ class Poche
252 'poche_url' => Tools::getPocheUrl() 303 'poche_url' => Tools::getPocheUrl()
253 )); 304 ));
254 if (isset($_GET['install'])) { 305 if (isset($_GET['install'])) {
255 if (($_POST['password'] == $_POST['password_repeat']) 306 if (($_POST['password'] == $_POST['password_repeat'])
256 && $_POST['password'] != "" && $_POST['login'] != "") { 307 && $_POST['password'] != "" && $_POST['login'] != "") {
257 # let's rock, install poche baby ! 308 # let's rock, install poche baby !
258 if ($this->store->install($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login']))) 309 if ($this->store->install($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login'])))
@@ -269,7 +320,7 @@ class Poche
269 } 320 }
270 exit(); 321 exit();
271 } 322 }
272 323
273 public function getTheme() { 324 public function getTheme() {
274 return $this->currentTheme; 325 return $this->currentTheme;
275 } 326 }
@@ -294,7 +345,7 @@ class Poche
294 if (is_file($themeIniFile) && is_readable($themeIniFile)) { 345 if (is_file($themeIniFile) && is_readable($themeIniFile)) {
295 $themeInfo = parse_ini_file($themeIniFile); 346 $themeInfo = parse_ini_file($themeIniFile);
296 } 347 }
297 348
298 if ($themeInfo === false) { 349 if ($themeInfo === false) {
299 $themeInfo = array(); 350 $themeInfo = array();
300 } 351 }
@@ -305,7 +356,7 @@ class Poche
305 356
306 return $themeInfo; 357 return $themeInfo;
307 } 358 }
308 359
309 public function getInstalledThemes() { 360 public function getInstalledThemes() {
310 $handle = opendir(THEME); 361 $handle = opendir(THEME);
311 $themes = array(); 362 $themes = array();
@@ -332,28 +383,28 @@ class Poche
332 public function getInstalledLanguages() { 383 public function getInstalledLanguages() {
333 $handle = opendir(LOCALE); 384 $handle = opendir(LOCALE);
334 $languages = array(); 385 $languages = array();
335 386
336 while (($language = readdir($handle)) !== false) { 387 while (($language = readdir($handle)) !== false) {
337 # Languages are stored in a directory, so all directory names are languages 388 # Languages are stored in a directory, so all directory names are languages
338 # @todo move language installation data to database 389 # @todo move language installation data to database
339 if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.'))) { 390 if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.', 'tools'))) {
340 continue; 391 continue;
341 } 392 }
342 393
343 $current = false; 394 $current = false;
344 395
345 if ($language === $this->getLanguage()) { 396 if ($language === $this->getLanguage()) {
346 $current = true; 397 $current = true;
347 } 398 }
348 399
349 $languages[] = array('name' => $this->language_names[$language], 'value' => $language, 'current' => $current); 400 $languages[] = array('name' => (isset($this->language_names[$language]) ? $this->language_names[$language] : $language), 'value' => $language, 'current' => $current);
350 } 401 }
351 402
352 return $languages; 403 return $languages;
353 } 404 }
354 405
355 public function getDefaultConfig() 406 public function getDefaultConfig()
356 { 407 {
357 return array( 408 return array(
358 'pager' => PAGINATION, 409 'pager' => PAGINATION,
359 'language' => LANG, 410 'language' => LANG,
@@ -361,60 +412,6 @@ class Poche
361 ); 412 );
362 } 413 }
363 414
364 protected function getPageContent(Url $url)
365 {
366 // Saving and clearing context
367 $REAL = array();
368 foreach( $GLOBALS as $key => $value ) {
369 if( $key != "GLOBALS" && $key != "_SESSION" ) {
370 $GLOBALS[$key] = array();
371 $REAL[$key] = $value;
372 }
373 }
374 // Saving and clearing session
375 $REAL_SESSION = array();
376 foreach( $_SESSION as $key => $value ) {
377 $REAL_SESSION[$key] = $value;
378 unset($_SESSION[$key]);
379 }
380
381 // Running code in different context
382 $scope = function() {
383 extract( func_get_arg(1) );
384 $_GET = $_REQUEST = array(
385 "url" => $url->getUrl(),
386 "max" => 5,
387 "links" => "preserve",
388 "exc" => "",
389 "format" => "json",
390 "submit" => "Create Feed"
391 );
392 ob_start();
393 require func_get_arg(0);
394 $json = ob_get_flush();
395 return $json;
396 };
397 $json = $scope( "inc/3rdparty/makefulltextfeed.php", array("url" => $url) );
398
399 // Clearing and restoring context
400 foreach( $GLOBALS as $key => $value ) {
401 if( $key != "GLOBALS" && $key != "_SESSION" ) {
402 unset($GLOBALS[$key]);
403 }
404 }
405 foreach( $REAL as $key => $value ) {
406 $GLOBALS[$key] = $value;
407 }
408 // Clearing and restoring session
409 foreach( $_SESSION as $key => $value ) {
410 unset($_SESSION[$key]);
411 }
412 foreach( $REAL_SESSION as $key => $value ) {
413 $_SESSION[$key] = $value;
414 }
415 return json_decode($json, true);
416 }
417
418 /** 415 /**
419 * Call action (mark as fav, archive, delete, etc.) 416 * Call action (mark as fav, archive, delete, etc.)
420 */ 417 */
@@ -423,28 +420,22 @@ class Poche
423 switch ($action) 420 switch ($action)
424 { 421 {
425 case 'add': 422 case 'add':
426 $content = $this->getPageContent($url); 423 $content = Tools::getPageContent($url);
427 $title = ($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled'); 424 $title = ($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled');
428 $body = $content['rss']['channel']['item']['description']; 425 $body = $content['rss']['channel']['item']['description'];
429 426
430 // clean content from prevent xss attack 427 // clean content from prevent xss attack
431 $config = HTMLPurifier_Config::createDefault(); 428 $purifier = $this->getPurifier();
432 $purifier = new HTMLPurifier($config);
433 $title = $purifier->purify($title); 429 $title = $purifier->purify($title);
434 $body = $purifier->purify($body); 430 $body = $purifier->purify($body);
435 431
436 //search for possible duplicate if not in import mode 432 //search for possible duplicate
437 if (!$import) { 433 $duplicate = NULL;
438 $duplicate = $this->store->retrieveOneByURL($url->getUrl(), $this->user->getId()); 434 $duplicate = $this->store->retrieveOneByURL($url->getUrl(), $this->user->getId());
439 }
440 435
441 if ($this->store->add($url->getUrl(), $title, $body, $this->user->getId())) { 436 $last_id = $this->store->add($url->getUrl(), $title, $body, $this->user->getId());
437 if ( $last_id ) {
442 Tools::logm('add link ' . $url->getUrl()); 438 Tools::logm('add link ' . $url->getUrl());
443 $sequence = '';
444 if (STORAGE == 'postgres') {
445 $sequence = 'entries_id_seq';
446 }
447 $last_id = $this->store->getLastId($sequence);
448 if (DOWNLOAD_PICTURES) { 439 if (DOWNLOAD_PICTURES) {
449 $content = filtre_picture($body, $url->getUrl(), $last_id); 440 $content = filtre_picture($body, $url->getUrl(), $last_id);
450 Tools::logm('updating content article'); 441 Tools::logm('updating content article');
@@ -464,23 +455,17 @@ class Poche
464 } 455 }
465 } 456 }
466 457
467 if (!$import) { 458 $this->messages->add('s', _('the link has been added successfully'));
468 $this->messages->add('s', _('the link has been added successfully'));
469 }
470 } 459 }
471 else { 460 else {
472 if (!$import) { 461 $this->messages->add('e', _('error during insertion : the link wasn\'t added'));
473 $this->messages->add('e', _('error during insertion : the link wasn\'t added')); 462 Tools::logm('error during insertion : the link wasn\'t added ' . $url->getUrl());
474 Tools::logm('error during insertion : the link wasn\'t added ' . $url->getUrl());
475 }
476 } 463 }
477 464
478 if (!$import) { 465 if ($autoclose == TRUE) {
479 if ($autoclose == TRUE) { 466 Tools::redirect('?view=home');
480 Tools::redirect('?view=home'); 467 } else {
481 } else { 468 Tools::redirect('?view=home&closewin=true');
482 Tools::redirect('?view=home&closewin=true');
483 }
484 } 469 }
485 break; 470 break;
486 case 'delete': 471 case 'delete':
@@ -501,62 +486,81 @@ class Poche
501 case 'toggle_fav' : 486 case 'toggle_fav' :
502 $this->store->favoriteById($id, $this->user->getId()); 487 $this->store->favoriteById($id, $this->user->getId());
503 Tools::logm('mark as favorite link #' . $id); 488 Tools::logm('mark as favorite link #' . $id);
504 if (!$import) { 489 if ( Tools::isAjaxRequest() ) {
505 Tools::redirect(); 490 echo 1;
491 exit;
492 }
493 else {
494 Tools::redirect();
506 } 495 }
507 break; 496 break;
508 case 'toggle_archive' : 497 case 'toggle_archive' :
509 $this->store->archiveById($id, $this->user->getId()); 498 $this->store->archiveById($id, $this->user->getId());
510 Tools::logm('archive link #' . $id); 499 Tools::logm('archive link #' . $id);
511 if (!$import) { 500 if ( Tools::isAjaxRequest() ) {
512 Tools::redirect(); 501 echo 1;
502 exit;
503 }
504 else {
505 Tools::redirect();
513 } 506 }
514 break; 507 break;
515 case 'archive_all' : 508 case 'archive_all' :
516 $this->store->archiveAll($this->user->getId()); 509 $this->store->archiveAll($this->user->getId());
517 Tools::logm('archive all links'); 510 Tools::logm('archive all links');
518 if (!$import) { 511 Tools::redirect();
519 Tools::redirect();
520 }
521 break; 512 break;
522 case 'add_tag' : 513 case 'add_tag' :
523 if($import){ 514 if (isset($_GET['search'])) {
524 $entry_id = $id; 515 //when we want to apply a tag to a search
525 $tags = explode(',', $tags); 516 $tags = array($_GET['search']);
526 } 517 $allentry_ids = $this->store->search($tags[0], $this->user->getId());
527 else{ 518 $entry_ids = array();
519 foreach ($allentry_ids as $eachentry) {
520 $entry_ids[] = $eachentry[0];
521 }
522 } else { //add a tag to a single article
528 $tags = explode(',', $_POST['value']); 523 $tags = explode(',', $_POST['value']);
529 $entry_id = $_POST['entry_id']; 524 $entry_ids = array($_POST['entry_id']);
530 } 525 }
531 $entry = $this->store->retrieveOneById($entry_id, $this->user->getId()); 526 foreach($entry_ids as $entry_id) {
532 if (!$entry) { 527 $entry = $this->store->retrieveOneById($entry_id, $this->user->getId());
533 $this->messages->add('e', _('Article not found!')); 528 if (!$entry) {
534 Tools::logm('error : article not found'); 529 $this->messages->add('e', _('Article not found!'));
535 Tools::redirect(); 530 Tools::logm('error : article not found');
536 } 531 Tools::redirect();
537 foreach($tags as $key => $tag_value) {
538 $value = trim($tag_value);
539 $tag = $this->store->retrieveTagByValue($value);
540
541 if (is_null($tag)) {
542 # we create the tag
543 $tag = $this->store->createTag($value);
544 $sequence = '';
545 if (STORAGE == 'postgres') {
546 $sequence = 'tags_id_seq';
547 }
548 $tag_id = $this->store->getLastId($sequence);
549 } 532 }
550 else { 533 //get all already set tags to preven duplicates
551 $tag_id = $tag['id']; 534 $already_set_tags = array();
535 $entry_tags = $this->store->retrieveTagsByEntry($entry_id);
536 foreach ($entry_tags as $tag) {
537 $already_set_tags[] = $tag['value'];
538 }
539 foreach($tags as $key => $tag_value) {
540 $value = trim($tag_value);
541 if ($value && !in_array($value, $already_set_tags)) {
542 $tag = $this->store->retrieveTagByValue($value);
543 if (is_null($tag)) {
544 # we create the tag
545 $tag = $this->store->createTag($value);
546 $sequence = '';
547 if (STORAGE == 'postgres') {
548 $sequence = 'tags_id_seq';
549 }
550 $tag_id = $this->store->getLastId($sequence);
551 }
552 else {
553 $tag_id = $tag['id'];
554 }
555
556 # we assign the tag to the article
557 $this->store->setTagToEntry($tag_id, $entry_id);
558 }
552 } 559 }
553
554 # we assign the tag to the article
555 $this->store->setTagToEntry($tag_id, $entry_id);
556 }
557 if(!$import) {
558 Tools::redirect();
559 } 560 }
561 $this->messages->add('s', _('The tag has been applied successfully'));
562 Tools::logm('The tag has been applied successfully');
563 Tools::redirect();
560 break; 564 break;
561 case 'remove_tag' : 565 case 'remove_tag' :
562 $tag_id = $_GET['tag_id']; 566 $tag_id = $_GET['tag_id'];
@@ -567,6 +571,11 @@ class Poche
567 Tools::redirect(); 571 Tools::redirect();
568 } 572 }
569 $this->store->removeTagForEntry($id, $tag_id); 573 $this->store->removeTagForEntry($id, $tag_id);
574 Tools::logm('tag entry deleted');
575 if ($this->store->cleanUnusedTag($tag_id)) {
576 Tools::logm('tag deleted');
577 }
578 $this->messages->add('s', _('The tag has been successfully deleted'));
570 Tools::redirect(); 579 Tools::redirect();
571 break; 580 break;
572 default: 581 default:
@@ -581,24 +590,32 @@ class Poche
581 switch ($view) 590 switch ($view)
582 { 591 {
583 case 'config': 592 case 'config':
584 $dev = trim($this->getPocheVersion('dev')); 593 $dev_infos = $this->getPocheVersion('dev');
585 $prod = trim($this->getPocheVersion('prod')); 594 $dev = trim($dev_infos[0]);
595 $check_time_dev = date('d-M-Y H:i', $dev_infos[1]);
596 $prod_infos = $this->getPocheVersion('prod');
597 $prod = trim($prod_infos[0]);
598 $check_time_prod = date('d-M-Y H:i', $prod_infos[1]);
586 $compare_dev = version_compare(POCHE, $dev); 599 $compare_dev = version_compare(POCHE, $dev);
587 $compare_prod = version_compare(POCHE, $prod); 600 $compare_prod = version_compare(POCHE, $prod);
588 $themes = $this->getInstalledThemes(); 601 $themes = $this->getInstalledThemes();
589 $languages = $this->getInstalledLanguages(); 602 $languages = $this->getInstalledLanguages();
590 $token = $this->user->getConfigValue('token'); 603 $token = $this->user->getConfigValue('token');
591 $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false; 604 $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false;
605 $only_user = ($this->store->listUsers() > 1) ? false : true;
592 $tpl_vars = array( 606 $tpl_vars = array(
593 'themes' => $themes, 607 'themes' => $themes,
594 'languages' => $languages, 608 'languages' => $languages,
595 'dev' => $dev, 609 'dev' => $dev,
596 'prod' => $prod, 610 'prod' => $prod,
611 'check_time_dev' => $check_time_dev,
612 'check_time_prod' => $check_time_prod,
597 'compare_dev' => $compare_dev, 613 'compare_dev' => $compare_dev,
598 'compare_prod' => $compare_prod, 614 'compare_prod' => $compare_prod,
599 'token' => $token, 615 'token' => $token,
600 'user_id' => $this->user->getId(), 616 'user_id' => $this->user->getId(),
601 'http_auth' => $http_auth, 617 'http_auth' => $http_auth,
618 'only_user' => $only_user
602 ); 619 );
603 Tools::logm('config view'); 620 Tools::logm('config view');
604 break; 621 break;
@@ -619,13 +636,36 @@ class Poche
619 break; 636 break;
620 case 'tags': 637 case 'tags':
621 $token = $this->user->getConfigValue('token'); 638 $token = $this->user->getConfigValue('token');
622 $tags = $this->store->retrieveAllTags($this->user->getId()); 639 //if term is set - search tags for this term
640 $term = Tools::checkVar('term');
641 $tags = $this->store->retrieveAllTags($this->user->getId(), $term);
642 if (Tools::isAjaxRequest()) {
643 $result = array();
644 foreach ($tags as $tag) {
645 $result[] = $tag['value'];
646 }
647 echo json_encode($result);
648 exit;
649 }
623 $tpl_vars = array( 650 $tpl_vars = array(
624 'token' => $token, 651 'token' => $token,
625 'user_id' => $this->user->getId(), 652 'user_id' => $this->user->getId(),
626 'tags' => $tags, 653 'tags' => $tags,
627 ); 654 );
628 break; 655 break;
656 case 'search':
657 if (isset($_GET['search'])) {
658 $search = filter_var($_GET['search'], FILTER_SANITIZE_STRING);
659 $tpl_vars['entries'] = $this->store->search($search, $this->user->getId());
660 $count = count($tpl_vars['entries']);
661 $this->pagination->set_total($count);
662 $page_links = str_replace(array('previous', 'next'), array(_('previous'), _('next')),
663 $this->pagination->page_links('?view=' . $view . '?search=' . $search . '&sort=' . $_SESSION['sort'] . '&' ));
664 $tpl_vars['page_links'] = $page_links;
665 $tpl_vars['nb_results'] = $count;
666 $tpl_vars['search_term'] = $search;
667 }
668 break;
629 case 'view': 669 case 'view':
630 $entry = $this->store->retrieveOneById($id, $this->user->getId()); 670 $entry = $this->store->retrieveOneById($id, $this->user->getId());
631 if ($entry != NULL) { 671 if ($entry != NULL) {
@@ -660,8 +700,9 @@ class Poche
660 'entries' => '', 700 'entries' => '',
661 'page_links' => '', 701 'page_links' => '',
662 'nb_results' => '', 702 'nb_results' => '',
703 'listmode' => (isset($_COOKIE['listmode']) ? true : false),
663 ); 704 );
664 705
665 //if id is given - we retrive entries by tag: id is tag id 706 //if id is given - we retrive entries by tag: id is tag id
666 if ($id) { 707 if ($id) {
667 $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId()); 708 $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId());
@@ -686,8 +727,8 @@ class Poche
686 } 727 }
687 728
688 /** 729 /**
689 * update the password of the current user. 730 * update the password of the current user.
690 * if MODE_DEMO is TRUE, the password can't be updated. 731 * if MODE_DEMO is TRUE, the password can't be updated.
691 * @todo add the return value 732 * @todo add the return value
692 * @todo set the new password in function header like this updatePassword($newPassword) 733 * @todo set the new password in function header like this updatePassword($newPassword)
693 * @return boolean 734 * @return boolean
@@ -715,42 +756,44 @@ class Poche
715 } 756 }
716 } 757 }
717 } 758 }
718 759
719 public function updateTheme() 760 public function updateTheme()
720 { 761 {
721 # no data 762 # no data
722 if (empty($_POST['theme'])) { 763 if (empty($_POST['theme'])) {
723 } 764 }
724 765
725 # we are not going to change it to the current theme... 766 # we are not going to change it to the current theme...
726 if ($_POST['theme'] == $this->getTheme()) { 767 if ($_POST['theme'] == $this->getTheme()) {
727 $this->messages->add('w', _('still using the "' . $this->getTheme() . '" theme!')); 768 $this->messages->add('w', _('still using the "' . $this->getTheme() . '" theme!'));
728 Tools::redirect('?view=config'); 769 Tools::redirect('?view=config');
729 } 770 }
730 771
731 $themes = $this->getInstalledThemes(); 772 $themes = $this->getInstalledThemes();
732 $actualTheme = false; 773 $actualTheme = false;
733 774
734 foreach (array_keys($themes) as $theme) { 775 foreach (array_keys($themes) as $theme) {
735 if ($theme == $_POST['theme']) { 776 if ($theme == $_POST['theme']) {
736 $actualTheme = true; 777 $actualTheme = true;
737 break; 778 break;
738 } 779 }
739 } 780 }
740 781
741 if (! $actualTheme) { 782 if (! $actualTheme) {
742 $this->messages->add('e', _('that theme does not seem to be installed')); 783 $this->messages->add('e', _('that theme does not seem to be installed'));
743 Tools::redirect('?view=config'); 784 Tools::redirect('?view=config');
744 } 785 }
745 786
746 $this->store->updateUserConfig($this->user->getId(), 'theme', $_POST['theme']); 787 $this->store->updateUserConfig($this->user->getId(), 'theme', $_POST['theme']);
747 $this->messages->add('s', _('you have changed your theme preferences')); 788 $this->messages->add('s', _('you have changed your theme preferences'));
748 789
749 $currentConfig = $_SESSION['poche_user']->config; 790 $currentConfig = $_SESSION['poche_user']->config;
750 $currentConfig['theme'] = $_POST['theme']; 791 $currentConfig['theme'] = $_POST['theme'];
751 792
752 $_SESSION['poche_user']->setConfig($currentConfig); 793 $_SESSION['poche_user']->setConfig($currentConfig);
753 794
795 $this->emptyCache();
796
754 Tools::redirect('?view=config'); 797 Tools::redirect('?view=config');
755 } 798 }
756 799
@@ -759,39 +802,40 @@ class Poche
759 # no data 802 # no data
760 if (empty($_POST['language'])) { 803 if (empty($_POST['language'])) {
761 } 804 }
762 805
763 # we are not going to change it to the current language... 806 # we are not going to change it to the current language...
764 if ($_POST['language'] == $this->getLanguage()) { 807 if ($_POST['language'] == $this->getLanguage()) {
765 $this->messages->add('w', _('still using the "' . $this->getLanguage() . '" language!')); 808 $this->messages->add('w', _('still using the "' . $this->getLanguage() . '" language!'));
766 Tools::redirect('?view=config'); 809 Tools::redirect('?view=config');
767 } 810 }
768 811
769 $languages = $this->getInstalledLanguages(); 812 $languages = $this->getInstalledLanguages();
770 $actualLanguage = false; 813 $actualLanguage = false;
771 814
772 foreach ($languages as $language) { 815 foreach ($languages as $language) {
773 if ($language['value'] == $_POST['language']) { 816 if ($language['value'] == $_POST['language']) {
774 $actualLanguage = true; 817 $actualLanguage = true;
775 break; 818 break;
776 } 819 }
777 } 820 }
778 821
779 if (! $actualLanguage) { 822 if (! $actualLanguage) {
780 $this->messages->add('e', _('that language does not seem to be installed')); 823 $this->messages->add('e', _('that language does not seem to be installed'));
781 Tools::redirect('?view=config'); 824 Tools::redirect('?view=config');
782 } 825 }
783 826
784 $this->store->updateUserConfig($this->user->getId(), 'language', $_POST['language']); 827 $this->store->updateUserConfig($this->user->getId(), 'language', $_POST['language']);
785 $this->messages->add('s', _('you have changed your language preferences')); 828 $this->messages->add('s', _('you have changed your language preferences'));
786 829
787 $currentConfig = $_SESSION['poche_user']->config; 830 $currentConfig = $_SESSION['poche_user']->config;
788 $currentConfig['language'] = $_POST['language']; 831 $currentConfig['language'] = $_POST['language'];
789 832
790 $_SESSION['poche_user']->setConfig($currentConfig); 833 $_SESSION['poche_user']->setConfig($currentConfig);
791 834
835 $this->emptyCache();
836
792 Tools::redirect('?view=config'); 837 Tools::redirect('?view=config');
793 } 838 }
794
795 /** 839 /**
796 * get credentials from differents sources 840 * get credentials from differents sources
797 * it redirects the user to the $referer link 841 * it redirects the user to the $referer link
@@ -846,7 +890,7 @@ class Poche
846 /** 890 /**
847 * log out the poche user. It cleans the session. 891 * log out the poche user. It cleans the session.
848 * @todo add the return value 892 * @todo add the return value
849 * @return boolean 893 * @return boolean
850 */ 894 */
851 public function logout() 895 public function logout()
852 { 896 {
@@ -857,238 +901,137 @@ class Poche
857 } 901 }
858 902
859 /** 903 /**
860 * import from Instapaper. poche needs a ./instapaper-export.html file 904 * import datas into your poche
861 * @todo add the return value
862 * @param string $targetFile the file used for importing
863 * @return boolean 905 * @return boolean
864 */ 906 */
865 private function importFromInstapaper($targetFile) 907 public function import() {
866 { 908
867 # TODO gestion des articles favs 909 if ( isset($_FILES['file']) ) {
868 $html = new simple_html_dom(); 910 Tools::logm('Import stated: parsing file');
869 $html->load_file($targetFile); 911
870 Tools::logm('starting import from instapaper'); 912 // assume, that file is in json format
871 913 $str_data = file_get_contents($_FILES['file']['tmp_name']);
872 $read = 0; 914 $data = json_decode($str_data, true);
873 $errors = array(); 915
874 foreach($html->find('ol') as $ul) 916 if ( $data === null ) {
875 { 917 //not json - assume html
876 foreach($ul->find('li') as $li) 918 $html = new simple_html_dom();
877 { 919 $html->load_file($_FILES['file']['tmp_name']);
878 $a = $li->find('a'); 920 $data = array();
879 $url = new Url(base64_encode($a[0]->href)); 921 $read = 0;
880 $this->action('add', $url, 0, TRUE); 922 foreach (array('ol','ul') as $list) {
881 if ($read == '1') { 923 foreach ($html->find($list) as $ul) {
882 $sequence = ''; 924 foreach ($ul->find('li') as $li) {
883 if (STORAGE == 'postgres') { 925 $tmpEntry = array();
884 $sequence = 'entries_id_seq'; 926 $a = $li->find('a');
885 } 927 $tmpEntry['url'] = $a[0]->href;
886 $last_id = $this->store->getLastId($sequence); 928 $tmpEntry['tags'] = $a[0]->tags;
887 $this->action('toggle_archive', $url, $last_id, TRUE); 929 $tmpEntry['is_read'] = $read;
888 } 930 if ($tmpEntry['url']) {
931 $data[] = $tmpEntry;
932 }
933 }
934 # the second <ol/ul> is for read links
935 $read = ((sizeof($data) && $read)?0:1);
889 } 936 }
890 937 }
891 # the second <ol> is for read links
892 $read = 1;
893 } 938 }
894 $this->messages->add('s', _('import from instapaper completed'));
895 Tools::logm('import from instapaper completed');
896 Tools::redirect();
897 }
898 939
899 /** 940 //for readability structure
900 * import from Pocket. poche needs a ./ril_export.html file 941 foreach ($data as $record) {
901 * @todo add the return value 942 if (is_array($record)) {
902 * @param string $targetFile the file used for importing 943 $data[] = $record;
903 * @return boolean 944 foreach ($record as $record2) {
904 */ 945 if (is_array($record2)) {
905 private function importFromPocket($targetFile) 946 $data[] = $record2;
906 { 947 }
907 # TODO gestion des articles favs 948 }
908 $html = new simple_html_dom(); 949 }
909 $html->load_file($targetFile); 950 }
910 Tools::logm('starting import from pocket'); 951
911 952 $urlsInserted = array(); //urls of articles inserted
912 $read = 0; 953 foreach ($data as $record) {
913 $errors = array(); 954 $url = trim( isset($record['article__url']) ? $record['article__url'] : (isset($record['url']) ? $record['url'] : '') );
914 foreach($html->find('ul') as $ul) 955 if ( $url and !in_array($url, $urlsInserted) ) {
915 { 956 $title = (isset($record['title']) ? $record['title'] : _('Untitled - Import - ').'</a> <a href="./?import">'._('click to finish import').'</a><a>');
916 foreach($ul->find('li') as $li) 957 $body = (isset($record['content']) ? $record['content'] : '');
917 { 958 $isRead = (isset($record['is_read']) ? intval($record['is_read']) : (isset($record['archive'])?intval($record['archive']):0));
918 $a = $li->find('a'); 959 $isFavorite = (isset($record['is_fav']) ? intval($record['is_fav']) : (isset($record['favorite'])?intval($record['favorite']):0) );
919 $url = new Url(base64_encode($a[0]->href)); 960 //insert new record
920 $this->action('add', $url, 0, TRUE); 961 $id = $this->store->add($url, $title, $body, $this->user->getId(), $isFavorite, $isRead);
921 $sequence = ''; 962 if ( $id ) {
922 if (STORAGE == 'postgres') { 963 $urlsInserted[] = $url; //add
923 $sequence = 'entries_id_seq'; 964
924 } 965 if ( isset($record['tags']) && trim($record['tags']) ) {
925 $last_id = $this->store->getLastId($sequence); 966 //@TODO: set tags
926 if ($read == '1') { 967
927 $this->action('toggle_archive', $url, $last_id, TRUE); 968 }
928 }
929 $tags = $a[0]->tags;
930 if(!empty($tags)) {
931 $this->action('add_tag',$url,$last_id,true,false,$tags);
932 }
933 } 969 }
934 970 }
935 # the second <ul> is for read links
936 $read = 1;
937 } 971 }
938 $this->messages->add('s', _('import from pocket completed'));
939 Tools::logm('import from pocket completed');
940 Tools::redirect();
941 }
942
943 /**
944 * import from Readability. poche needs a ./readability file
945 * @todo add the return value
946 * @param string $targetFile the file used for importing
947 * @return boolean
948 */
949 private function importFromReadability($targetFile)
950 {
951 # TODO gestion des articles lus / favs
952 $str_data = file_get_contents($targetFile);
953 $data = json_decode($str_data,true);
954 Tools::logm('starting import from Readability');
955 $count = 0;
956 foreach ($data as $key => $value) {
957 $url = NULL;
958 $favorite = FALSE;
959 $archive = FALSE;
960 foreach ($value as $item) {
961 foreach ($item as $attr => $value) {
962 if ($attr == 'article__url') {
963 $url = new Url(base64_encode($value));
964 }
965 $sequence = '';
966 if (STORAGE == 'postgres') {
967 $sequence = 'entries_id_seq';
968 }
969 if ($value == 'true') {
970 if ($attr == 'favorite') {
971 $favorite = TRUE;
972 }
973 if ($attr == 'archive') {
974 $archive = TRUE;
975 }
976 }
977 }
978 972
979 # we can add the url 973 $i = sizeof($urlsInserted);
980 if (!is_null($url) && $url->isCorrect()) { 974 if ( $i > 0 ) {
981 $this->action('add', $url, 0, TRUE); 975 $this->messages->add('s', _('Articles inserted: ').$i._('. Please note, that some may be marked as "read".'));
982 $count++;
983 if ($favorite) {
984 $last_id = $this->store->getLastId($sequence);
985 $this->action('toggle_fav', $url, $last_id, TRUE);
986 }
987 if ($archive) {
988 $last_id = $this->store->getLastId($sequence);
989 $this->action('toggle_archive', $url, $last_id, TRUE);
990 }
991 }
992 }
993 } 976 }
994 $this->messages->add('s', _('import from Readability completed. ' . $count . ' new links.')); 977 Tools::logm('Import of articles finished: '.$i.' articles added (w/o content if not provided).');
995 Tools::logm('import from Readability completed'); 978 }
979 //file parsing finished here
980
981 //now download article contents if any
982
983 //check if we need to download any content
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');
996 Tools::redirect(); 989 Tools::redirect();
997 } 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);
998 997
999 /** 998 $purifier = $this->getPurifier();
1000 * import from Poche exported file
1001 * @param string $targetFile the file used for importing
1002 * @return boolean
1003 */
1004 private function importFromPoche($targetFile)
1005 {
1006 $str_data = file_get_contents($targetFile);
1007 $data = json_decode($str_data,true);
1008 Tools::logm('starting import from Poche');
1009 999
1000 foreach ($items as $item) {
1001 $url = new Url(base64_encode($item['url']));
1002 Tools::logm('Fetching article '.$item['id']);
1003 $content = Tools::getPageContent($url);
1010 1004
1011 $sequence = ''; 1005 $title = (($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled'));
1012 if (STORAGE == 'postgres') { 1006 $body = (($content['rss']['channel']['item']['description'] != '') ? $content['rss']['channel']['item']['description'] : _('Undefined'));
1013 $sequence = 'entries_id_seq';
1014 }
1015 1007
1016 $count = 0; 1008 //clean content to prevent xss attack
1017 foreach ($data as $value) { 1009 $title = $purifier->purify($title);
1018 1010 $body = $purifier->purify($body);
1019 $url = new Url(base64_encode($value['url'])); 1011
1020 $favorite = ($value['is_fav'] == -1); 1012 $this->store->updateContentAndTitle($item['id'], $title, $body, $this->user->getId());
1021 $archive = ($value['is_read'] == -1); 1013 Tools::logm('Article '.$item['id'].' updated.');
1022 1014 }
1023 # we can add the url
1024 if (!is_null($url) && $url->isCorrect()) {
1025
1026 $this->action('add', $url, 0, TRUE);
1027
1028 $count++;
1029 if ($favorite) {
1030 $last_id = $this->store->getLastId($sequence);
1031 $this->action('toggle_fav', $url, $last_id, TRUE);
1032 }
1033 if ($archive) {
1034 $last_id = $this->store->getLastId($sequence);
1035 $this->action('toggle_archive', $url, $last_id, TRUE);
1036 }
1037 }
1038
1039 }
1040 $this->messages->add('s', _('import from Poche completed. ' . $count . ' new links.'));
1041 Tools::logm('import from Poche completed');
1042 Tools::redirect();
1043 }
1044 1015
1045 /**
1046 * import datas into your poche
1047 * @param string $from name of the service to import : pocket, instapaper or readability
1048 * @todo add the return value
1049 * @return boolean
1050 */
1051 public function import($from)
1052 {
1053 $providers = array(
1054 'pocket' => 'importFromPocket',
1055 'readability' => 'importFromReadability',
1056 'instapaper' => 'importFromInstapaper',
1057 'poche' => 'importFromPoche',
1058 );
1059
1060 if (! isset($providers[$from])) {
1061 $this->messages->add('e', _('Unknown import provider.'));
1062 Tools::redirect();
1063 }
1064
1065 $targetDefinition = 'IMPORT_' . strtoupper($from) . '_FILE';
1066 $targetFile = constant($targetDefinition);
1067
1068 if (! defined($targetDefinition)) {
1069 $this->messages->add('e', _('Incomplete inc/poche/define.inc.php file, please define "' . $targetDefinition . '".'));
1070 Tools::redirect();
1071 }
1072
1073 if (! file_exists($targetFile)) {
1074 $this->messages->add('e', _('Could not find required "' . $targetFile . '" import file.'));
1075 Tools::redirect();
1076 } 1016 }
1077 1017 }
1078 $this->$providers[$from]($targetFile); 1018
1019 return array('includeImport'=>true, 'import'=>array('recordsDownloadRequired'=>$recordsDownloadRequired, 'recordsUnderDownload'=> IMPORT_LIMIT, 'delay'=> IMPORT_DELAY * 1000) );
1079 } 1020 }
1080 1021
1081 /** 1022 /**
1082 * export poche entries in json 1023 * export poche entries in json
1083 * @return json all poche entries 1024 * @return json all poche entries
1084 */ 1025 */
1085 public function export() 1026 public function export() {
1086 { 1027 $filename = "wallabag-export-".$this->user->getId()."-".date("Y-m-d").".json";
1087 $entries = $this->store->retrieveAll($this->user->getId()); 1028 header('Content-Disposition: attachment; filename='.$filename);
1088 echo $this->tpl->render('export.twig', array( 1029
1089 'export' => Tools::renderJson($entries), 1030 $entries = $this->store->retrieveAll($this->user->getId());
1090 )); 1031 echo $this->tpl->render('export.twig', array(
1091 Tools::logm('export view'); 1032 'export' => Tools::renderJson($entries),
1033 ));
1034 Tools::logm('export view');
1092 } 1035 }
1093 1036
1094 /** 1037 /**
@@ -1096,35 +1039,42 @@ class Poche
1096 * @param string $which 'prod' or 'dev' 1039 * @param string $which 'prod' or 'dev'
1097 * @return string latest $which version 1040 * @return string latest $which version
1098 */ 1041 */
1099 private function getPocheVersion($which = 'prod') 1042 private function getPocheVersion($which = 'prod') {
1100 { 1043 $cache_file = CACHE . '/' . $which;
1101 $cache_file = CACHE . '/' . $which; 1044 $check_time = time();
1102 1045
1103 # checks if the cached version file exists 1046 # checks if the cached version file exists
1104 if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) { 1047 if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) {
1105 $version = file_get_contents($cache_file); 1048 $version = file_get_contents($cache_file);
1106 } else { 1049 $check_time = filemtime($cache_file);
1107 $version = file_get_contents('http://static.wallabag.org/versions/' . $which); 1050 } else {
1108 file_put_contents($cache_file, $version, LOCK_EX); 1051 $version = file_get_contents('http://static.wallabag.org/versions/' . $which);
1109 } 1052 file_put_contents($cache_file, $version, LOCK_EX);
1110 return $version; 1053 }
1054 return array($version, $check_time);
1111 } 1055 }
1112 1056
1113 public function generateToken() 1057 public function generateToken()
1114 { 1058 {
1115 if (ini_get('open_basedir') === '') { 1059 if (ini_get('open_basedir') === '') {
1116 $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15); 1060 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1117 } 1061 echo 'This is a server using Windows!';
1118 else { 1062 // alternative to /dev/urandom for Windows
1119 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20); 1063 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
1120 } 1064 } else {
1121 1065 $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
1122 $token = str_replace('+', '', $token); 1066 }
1123 $this->store->updateUserConfig($this->user->getId(), 'token', $token); 1067 }
1124 $currentConfig = $_SESSION['poche_user']->config; 1068 else {
1125 $currentConfig['token'] = $token; 1069 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
1126 $_SESSION['poche_user']->setConfig($currentConfig); 1070 }
1127 Tools::redirect(); 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();
1128 } 1078 }
1129 1079
1130 public function generateFeeds($token, $user_id, $tag_id, $type = 'home') 1080 public function generateFeeds($token, $user_id, $tag_id, $type = 'home')
@@ -1132,8 +1082,11 @@ class Poche
1132 $allowed_types = array('home', 'fav', 'archive', 'tag'); 1082 $allowed_types = array('home', 'fav', 'archive', 'tag');
1133 $config = $this->store->getConfigUser($user_id); 1083 $config = $this->store->getConfigUser($user_id);
1134 1084
1135 if (!in_array($type, $allowed_types) || 1085 if ($config == null) {
1136 $token != $config['token']) { 1086 die(sprintf(_('User with this id (%d) does not exist.'), $user_id));
1087 }
1088
1089 if (!in_array($type, $allowed_types) || $token != $config['token']) {
1137 die(_('Uh, there is a problem while generating feeds.')); 1090 die(_('Uh, there is a problem while generating feeds.'));
1138 } 1091 }
1139 // Check the token 1092 // Check the token
@@ -1141,8 +1094,9 @@ class Poche
1141 $feed = new FeedWriter(RSS2); 1094 $feed = new FeedWriter(RSS2);
1142 $feed->setTitle('wallabag — ' . $type . ' feed'); 1095 $feed->setTitle('wallabag — ' . $type . ' feed');
1143 $feed->setLink(Tools::getPocheUrl()); 1096 $feed->setLink(Tools::getPocheUrl());
1144 $feed->setChannelElement('updated', date(DATE_RSS , time())); 1097 $feed->setChannelElement('pubDate', date(DATE_RSS , time()));
1145 $feed->setChannelElement('author', 'wallabag'); 1098 $feed->setChannelElement('generator', 'wallabag');
1099 $feed->setDescription('wallabag ' . $type . ' elements');
1146 1100
1147 if ($type == 'tag') { 1101 if ($type == 'tag') {
1148 $entries = $this->store->retrieveEntriesByTag($tag_id, $user_id); 1102 $entries = $this->store->retrieveEntriesByTag($tag_id, $user_id);
@@ -1155,7 +1109,8 @@ class Poche
1155 foreach ($entries as $entry) { 1109 foreach ($entries as $entry) {
1156 $newItem = $feed->createNewItem(); 1110 $newItem = $feed->createNewItem();
1157 $newItem->setTitle($entry['title']); 1111 $newItem->setTitle($entry['title']);
1158 $newItem->setLink(Tools::getPocheUrl() . '?view=view&amp;id=' . $entry['id']); 1112 $newItem->setSource(Tools::getPocheUrl() . '?view=view&amp;id=' . $entry['id']);
1113 $newItem->setLink($entry['url']);
1159 $newItem->setDate(time()); 1114 $newItem->setDate(time());
1160 $newItem->setDescription($entry['content']); 1115 $newItem->setDescription($entry['content']);
1161 $feed->addItem($newItem); 1116 $feed->addItem($newItem);
@@ -1181,4 +1136,132 @@ class Poche
1181 $this->messages->add('s', _('Cache deleted.')); 1136 $this->messages->add('s', _('Cache deleted.'));
1182 Tools::redirect(); 1137 Tools::redirect();
1183 } 1138 }
1139
1140 /**
1141 * return new purifier object with actual config
1142 */
1143 protected function getPurifier() {
1144 $config = HTMLPurifier_Config::createDefault();
1145 $config->set('Cache.SerializerPath', CACHE);
1146 $config->set('HTML.SafeIframe', true);
1147
1148 //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/)%');
1150
1151 return new HTMLPurifier($config);
1152 }
1153
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
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 }
1184} 1267}
diff --git a/inc/poche/Tools.class.php b/inc/poche/Tools.class.php
index 4ed28ed1..cc01f403 100644..100755
--- a/inc/poche/Tools.class.php
+++ b/inc/poche/Tools.class.php
@@ -7,7 +7,7 @@
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://www.wtfpl.net/ see COPYING file
9 */ 9 */
10 10
11class Tools 11class Tools
12{ 12{
13 public static function initPhp() 13 public static function initPhp()
@@ -18,8 +18,6 @@ class Tools
18 die(_('Oops, it seems you don\'t have PHP 5.')); 18 die(_('Oops, it seems you don\'t have PHP 5.'));
19 } 19 }
20 20
21 error_reporting(E_ALL);
22
23 function stripslashesDeep($value) { 21 function stripslashesDeep($value) {
24 return is_array($value) 22 return is_array($value)
25 ? array_map('stripslashesDeep', $value) 23 ? array_map('stripslashesDeep', $value)
@@ -42,7 +40,7 @@ class Tools
42 && (strtolower($_SERVER['HTTPS']) == 'on')) 40 && (strtolower($_SERVER['HTTPS']) == 'on'))
43 || (isset($_SERVER["SERVER_PORT"]) 41 || (isset($_SERVER["SERVER_PORT"])
44 && $_SERVER["SERVER_PORT"] == '443') // HTTPS detection. 42 && $_SERVER["SERVER_PORT"] == '443') // HTTPS detection.
45 || (isset($_SERVER["SERVER_PORT"]) //Custom HTTPS port detection 43 || (isset($_SERVER["SERVER_PORT"]) //Custom HTTPS port detection
46 && $_SERVER["SERVER_PORT"] == SSL_PORT) 44 && $_SERVER["SERVER_PORT"] == SSL_PORT)
47 || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) 45 || (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
48 && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https'); 46 && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https');
@@ -59,8 +57,14 @@ class Tools
59 return $scriptname; 57 return $scriptname;
60 } 58 }
61 59
60 $host = (isset($_SERVER['HTTP_X_FORWARDED_HOST']) ? $_SERVER['HTTP_X_FORWARDED_HOST'] : (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']));
61
62 if (strpos($host, ':') !== false) {
63 $serverport = '';
64 }
65
62 return 'http' . ($https ? 's' : '') . '://' 66 return 'http' . ($https ? 's' : '') . '://'
63 . $_SERVER["HTTP_HOST"] . $serverport . $scriptname; 67 . $host . $serverport . $scriptname;
64 } 68 }
65 69
66 public static function redirect($url = '') 70 public static function redirect($url = '')
@@ -148,7 +152,7 @@ class Tools
148 ); 152 );
149 153
150 # only download page lesser than 4MB 154 # only download page lesser than 4MB
151 $data = @file_get_contents($url, false, $context, -1, 4000000); 155 $data = @file_get_contents($url, false, $context, -1, 4000000);
152 156
153 if (isset($http_response_header) and isset($http_response_header[0])) { 157 if (isset($http_response_header) and isset($http_response_header[0])) {
154 $httpcodeOK = isset($http_response_header) and isset($http_response_header[0]) and ((strpos($http_response_header[0], '200 OK') !== FALSE) or (strpos($http_response_header[0], '301 Moved Permanently') !== FALSE)); 158 $httpcodeOK = isset($http_response_header) and isset($http_response_header[0]) and ((strpos($http_response_header[0], '200 OK') !== FALSE) or (strpos($http_response_header[0], '301 Moved Permanently') !== FALSE));
@@ -193,14 +197,14 @@ class Tools
193 197
194 public static function logm($message) 198 public static function logm($message)
195 { 199 {
196 if (DEBUG_POCHE) { 200 if (DEBUG_POCHE && php_sapi_name() != 'cli') {
197 $t = strval(date('Y/m/d_H:i:s')) . ' - ' . $_SERVER["REMOTE_ADDR"] . ' - ' . strval($message) . "\n"; 201 $t = strval(date('Y/m/d_H:i:s')) . ' - ' . $_SERVER["REMOTE_ADDR"] . ' - ' . strval($message) . "\n";
198 file_put_contents(CACHE . '/log.txt', $t, FILE_APPEND); 202 file_put_contents(CACHE . '/log.txt', $t, FILE_APPEND);
199 error_log('DEBUG POCHE : ' . $message); 203 error_log('DEBUG POCHE : ' . $message);
200 } 204 }
201 } 205 }
202 206
203 public static function encodeString($string) 207 public static function encodeString($string)
204 { 208 {
205 return sha1($string . SALT); 209 return sha1($string . SALT);
206 } 210 }
@@ -212,7 +216,7 @@ class Tools
212 216
213 public static function getDomain($url) 217 public static function getDomain($url)
214 { 218 {
215 return parse_url($url, PHP_URL_HOST); 219 return parse_url($url, PHP_URL_HOST);
216 } 220 }
217 221
218 public static function getReadingTime($text) { 222 public static function getReadingTime($text) {
@@ -241,7 +245,6 @@ class Tools
241 } 245 }
242 } 246 }
243 247
244
245 public static function download_db() { 248 public static function download_db() {
246 header('Content-Disposition: attachment; filename="poche.sqlite.gz"'); 249 header('Content-Disposition: attachment; filename="poche.sqlite.gz"');
247 self::status(200); 250 self::status(200);
@@ -252,4 +255,74 @@ class Tools
252 255
253 exit; 256 exit;
254 } 257 }
258
259 public static function getPageContent(Url $url)
260 {
261 // Saving and clearing context
262 $REAL = array();
263 foreach( $GLOBALS as $key => $value ) {
264 if( $key != 'GLOBALS' && $key != '_SESSION' && $key != 'HTTP_SESSION_VARS' ) {
265 $GLOBALS[$key] = array();
266 $REAL[$key] = $value;
267 }
268 }
269 // Saving and clearing session
270 if ( isset($_SESSION) ) {
271 $REAL_SESSION = array();
272 foreach( $_SESSION as $key => $value ) {
273 $REAL_SESSION[$key] = $value;
274 unset($_SESSION[$key]);
275 }
276 }
277
278 // Running code in different context
279 $scope = function() {
280 extract( func_get_arg(1) );
281 $_GET = $_REQUEST = array(
282 "url" => $url->getUrl(),
283 "max" => 5,
284 "links" => "preserve",
285 "exc" => "",
286 "format" => "json",
287 "submit" => "Create Feed"
288 );
289 ob_start();
290 require func_get_arg(0);
291 $json = ob_get_contents();
292 ob_end_clean();
293 return $json;
294 };
295 $json = $scope( "inc/3rdparty/makefulltextfeed.php", array("url" => $url) );
296
297 // Clearing and restoring context
298 foreach( $GLOBALS as $key => $value ) {
299 if( $key != "GLOBALS" && $key != "_SESSION" ) {
300 unset($GLOBALS[$key]);
301 }
302 }
303 foreach( $REAL as $key => $value ) {
304 $GLOBALS[$key] = $value;
305 }
306 // Clearing and restoring session
307 if ( isset($REAL_SESSION) ) {
308 foreach( $_SESSION as $key => $value ) {
309 unset($_SESSION[$key]);
310 }
311 foreach( $REAL_SESSION as $key => $value ) {
312 $_SESSION[$key] = $value;
313 }
314 }
315
316 return json_decode($json, true);
317 }
318
319 /**
320 * Returns whether we handle an AJAX (XMLHttpRequest) request.
321 * @return boolean whether we handle an AJAX (XMLHttpRequest) request.
322 */
323 public static function isAjaxRequest()
324 {
325 return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest';
326 }
327
255} 328}
diff --git a/inc/poche/config.inc.default.php b/inc/poche/config.inc.default.php
new file mode 100755
index 00000000..95f727c6
--- /dev/null
+++ b/inc/poche/config.inc.default.php
@@ -0,0 +1,68 @@
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://www.wtfpl.net/ see COPYING file
9 */
10
11@define ('SALT', ''); # put a strong string here
12@define ('LANG', 'en_EN.utf8');
13
14@define ('STORAGE', 'sqlite'); # postgres, mysql or sqlite
15
16@define ('STORAGE_SQLITE', ROOT . '/db/poche.sqlite'); # if you are using sqlite, where the database file is located
17
18# only for postgres & mysql
19@define ('STORAGE_SERVER', 'localhost');
20@define ('STORAGE_DB', 'poche');
21@define ('STORAGE_USER', 'poche');
22@define ('STORAGE_PASSWORD', 'poche');
23
24#################################################################################
25# Do not trespass unless you know what you are doing
26#################################################################################
27
28// Change this if not using the standart port for SSL - i.e you server is behind sslh
29@define ('SSL_PORT', 443);
30
31@define ('MODE_DEMO', FALSE);
32@define ('DEBUG_POCHE', FALSE);
33
34//default level of error reporting in application. Developers should override it in their config.inc.php: set to E_ALL.
35@define ('ERROR_REPORTING', E_ALL & ~E_NOTICE);
36
37@define ('DOWNLOAD_PICTURES', FALSE); # This can slow down the process of adding articles
38@define ('REGENERATE_PICTURES_QUALITY', 75);
39@define ('CONVERT_LINKS_FOOTNOTES', FALSE);
40@define ('REVERT_FORCED_PARAGRAPH_ELEMENTS', FALSE);
41@define ('SHARE_TWITTER', TRUE);
42@define ('SHARE_MAIL', TRUE);
43@define ('SHARE_SHAARLI', FALSE);
44@define ('SHAARLI_URL', 'http://myshaarliurl.com');
45@define ('FLATTR', TRUE);
46@define ('FLATTR_API', 'https://api.flattr.com/rest/v2/things/lookup/?url=');
47@define ('NOT_FLATTRABLE', '0');
48@define ('FLATTRABLE', '1');
49@define ('FLATTRED', '2');
50// display or not print link in article view
51@define ('SHOW_PRINTLINK', '1');
52// display or not percent of read in article view. Affects only default theme.
53@define ('SHOW_READPERCENT', '1');
54@define ('ABS_PATH', 'assets/');
55
56@define ('DEFAULT_THEME', 'baggy');
57
58@define ('THEME', ROOT . '/themes');
59@define ('LOCALE', ROOT . '/locale');
60@define ('CACHE', ROOT . '/cache');
61
62@define ('PAGINATION', '10');
63
64//limit for download of articles during import
65@define ('IMPORT_LIMIT', 5);
66//delay between downloads (in sec)
67@define ('IMPORT_DELAY', 5);
68
diff --git a/inc/poche/config.inc.php.new b/inc/poche/config.inc.php.new
deleted file mode 100755
index 8d52497b..00000000
--- a/inc/poche/config.inc.php.new
+++ /dev/null
@@ -1,63 +0,0 @@
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://www.wtfpl.net/ see COPYING file
9 */
10
11define ('SALT', ''); # put a strong string here
12define ('LANG', 'en_EN.utf8');
13
14define ('STORAGE', 'sqlite'); # postgres, mysql or sqlite
15
16define ('STORAGE_SQLITE', ROOT . '/db/poche.sqlite'); # if you are using sqlite, where the database file is located
17
18# only for postgres & mysql
19define ('STORAGE_SERVER', 'localhost');
20define ('STORAGE_DB', 'poche');
21define ('STORAGE_USER', 'poche');
22define ('STORAGE_PASSWORD', 'poche');
23
24#################################################################################
25# Do not trespass unless you know what you are doing
26#################################################################################
27
28// Change this if not using the standart port for SSL - i.e you server is behind sslh
29define ('SSL_PORT', 443);
30
31define ('MODE_DEMO', FALSE);
32define ('DEBUG_POCHE', FALSE);
33define ('DOWNLOAD_PICTURES', FALSE);
34define ('CONVERT_LINKS_FOOTNOTES', FALSE);
35define ('REVERT_FORCED_PARAGRAPH_ELEMENTS', FALSE);
36define ('SHARE_TWITTER', TRUE);
37define ('SHARE_MAIL', TRUE);
38define ('SHARE_SHAARLI', FALSE);
39define ('SHAARLI_URL', 'http://myshaarliurl.com');
40define ('FLATTR', TRUE);
41define ('FLATTR_API', 'https://api.flattr.com/rest/v2/things/lookup/?url=');
42define ('NOT_FLATTRABLE', '0');
43define ('FLATTRABLE', '1');
44define ('FLATTRED', '2');
45define ('ABS_PATH', 'assets/');
46
47define ('DEFAULT_THEME', 'baggy');
48
49define ('THEME', ROOT . '/themes');
50define ('LOCALE', ROOT . '/locale');
51define ('CACHE', ROOT . '/cache');
52
53define ('PAGINATION', '10');
54
55define ('POCKET_FILE', '/ril_export.html');
56define ('READABILITY_FILE', '/readability');
57define ('INSTAPAPER_FILE', '/instapaper-export.html');
58define ('POCHE_FILE', '/poche-export');
59
60define ('IMPORT_POCKET_FILE', ROOT . POCKET_FILE);
61define ('IMPORT_READABILITY_FILE', ROOT . READABILITY_FILE);
62define ('IMPORT_INSTAPAPER_FILE', ROOT . INSTAPAPER_FILE);
63define ('IMPORT_POCHE_FILE', ROOT . POCHE_FILE); \ No newline at end of file
diff --git a/inc/poche/global.inc.php b/inc/poche/global.inc.php
index d22b0588..8cf86d03 100644..100755
--- a/inc/poche/global.inc.php
+++ b/inc/poche/global.inc.php
@@ -31,6 +31,11 @@ require_once INCLUDES . '/3rdparty/FlattrItem.class.php';
31 31
32require_once INCLUDES . '/3rdparty/htmlpurifier/HTMLPurifier.auto.php'; 32require_once INCLUDES . '/3rdparty/htmlpurifier/HTMLPurifier.auto.php';
33 33
34# epub library
35require_once INCLUDES . '/3rdparty/libraries/PHPePub/Logger.php';
36require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPub.php';
37require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPubChapterSplitter.php';
38
34# Composer its autoloader for automatically loading Twig 39# Composer its autoloader for automatically loading Twig
35if (! file_exists(ROOT . '/vendor/autoload.php')) { 40if (! file_exists(ROOT . '/vendor/autoload.php')) {
36 Poche::$canRenderTemplates = false; 41 Poche::$canRenderTemplates = false;
@@ -38,11 +43,12 @@ if (! file_exists(ROOT . '/vendor/autoload.php')) {
38 require_once ROOT . '/vendor/autoload.php'; 43 require_once ROOT . '/vendor/autoload.php';
39} 44}
40 45
41# system configuration; database credentials et cetera 46# system configuration; database credentials et caetera
42if (! file_exists(INCLUDES . '/poche/config.inc.php')) { 47if (! file_exists(INCLUDES . '/poche/config.inc.php')) {
43 Poche::$configFileAvailable = false; 48 Poche::$configFileAvailable = false;
44} else { 49} else {
45 require_once INCLUDES . '/poche/config.inc.php'; 50 require_once INCLUDES . '/poche/config.inc.php';
51 require_once INCLUDES . '/poche/config.inc.default.php';
46} 52}
47 53
48if (Poche::$configFileAvailable && DOWNLOAD_PICTURES) { 54if (Poche::$configFileAvailable && DOWNLOAD_PICTURES) {
diff --git a/inc/poche/pochePictures.php b/inc/poche/pochePictures.php
index e4b0b160..7c319a85 100644
--- a/inc/poche/pochePictures.php
+++ b/inc/poche/pochePictures.php
@@ -14,6 +14,7 @@
14function filtre_picture($content, $url, $id) 14function filtre_picture($content, $url, $id)
15{ 15{
16 $matches = array(); 16 $matches = array();
17 $processing_pictures = array(); // list of processing image to avoid processing the same pictures twice
17 preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER); 18 preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER);
18 foreach($matches as $i => $link) { 19 foreach($matches as $i => $link) {
19 $link[1] = trim($link[1]); 20 $link[1] = trim($link[1]);
@@ -22,8 +23,17 @@ function filtre_picture($content, $url, $id)
22 $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); 23 $filename = basename(parse_url($absolute_path, PHP_URL_PATH));
23 $directory = create_assets_directory($id); 24 $directory = create_assets_directory($id);
24 $fullpath = $directory . '/' . $filename; 25 $fullpath = $directory . '/' . $filename;
25 download_pictures($absolute_path, $fullpath); 26
26 $content = str_replace($matches[$i][2], $fullpath, $content); 27 if (in_array($absolute_path, $processing_pictures) === true) {
28 // replace picture's URL only if processing is OK : already processing -> go to next picture
29 continue;
30 }
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;
27 } 37 }
28 38
29 } 39 }
@@ -64,17 +74,55 @@ function get_absolute_link($relative_link, $url) {
64 74
65/** 75/**
66 * Téléchargement des images 76 * Téléchargement des images
77 *
78 * @return bool true if the download and processing is OK, false else
67 */ 79 */
68function download_pictures($absolute_path, $fullpath) 80function download_pictures($absolute_path, $fullpath)
69{ 81{
70 $rawdata = Tools::getFile($absolute_path); 82 $rawdata = Tools::getFile($absolute_path);
83 $fullpath = urldecode($fullpath);
71 84
72 if(file_exists($fullpath)) { 85 if(file_exists($fullpath)) {
73 unlink($fullpath); 86 unlink($fullpath);
74 } 87 }
75 $fp = fopen($fullpath, 'x'); 88
76 fwrite($fp, $rawdata); 89 // check extension
77 fclose($fp); 90 $file_ext = strrchr($fullpath, '.');
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;
78} 126}
79 127
80/** 128/**