aboutsummaryrefslogtreecommitdiffhomepage
path: root/inc/poche/Poche.class.php
diff options
context:
space:
mode:
Diffstat (limited to 'inc/poche/Poche.class.php')
-rwxr-xr-xinc/poche/Poche.class.php882
1 files changed, 208 insertions, 674 deletions
diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php
index bcf2ddeb..64a5c4f7 100755
--- a/inc/poche/Poche.class.php
+++ b/inc/poche/Poche.class.php
@@ -5,402 +5,126 @@
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 }
101
102 public function configFileIsAvailable() {
103 if (! self::$configFileAvailable) {
104 $this->notInstalledMessage[] = 'You have to copy (don\'t just rename!) inc/poche/config.inc.default.php to inc/poche/config.inc.php.';
105
106 return false;
107 }
108
109 return true;
110 } 67 }
111 68
112 public function themeIsInstalled() { 69 public function run()
113 $passTheme = TRUE; 70 {
114 # Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet 71 $this->routing->run();
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)
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 if (!$this->store->userExists($newUsername)){
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->install($newUsername, Tools::encodeString($password . $newUsername))) {
207 try { 83 Tools::logm('The new user ' . $newUsername . ' has been installed');
208 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme)); 84 $this->messages->add('s', sprintf(_('The new user %s has been installed. Do you want to <a href="?logout">logout ?</a>'), $newUsername));
209 } catch (Twig_Error_Loader $e) {
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(); 85 Tools::redirect();
264 } 86 }
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 { 87 else {
286 Tools::logm('Bad password !'); 88 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(); 89 Tools::redirect();
314 } 90 }
315 } 91 }
316 else { 92 else {
317 Tools::logm('error during installation'); 93 $this->messages->add('e', sprintf(_('Error : An user with the name %s already exists !'), $newUsername));
94 Tools::logm('An user with the name ' . $newUsername . ' already exists !');
318 Tools::redirect(); 95 Tools::redirect();
319 } 96 }
320 } 97 }
321 exit();
322 }
323
324 public function getTheme() {
325 return $this->currentTheme;
326 } 98 }
327 99
328 /** 100 /**
329 * Provides theme information by parsing theme.ini file if present in the theme's root directory. 101 * 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 */ 102 */
337 public function getThemeInfo($theme) { 103 public function deleteUser($password)
338 if (!is_dir(THEME . '/' . $theme)) { 104 {
339 return false; 105 if ($this->store->listUsers() > 1) {
340 } 106 if (Tools::encodeString($password . $this->user->getUsername()) == $this->store->getUserPassword($this->user->getId())) {
341 107 $username = $this->user->getUsername();
342 $themeIniFile = THEME . '/' . $theme . '/theme.ini'; 108 $this->store->deleteUserConfig($this->user->getId());
343 $themeInfo = array(); 109 Tools::logm('The configuration for user '. $username .' has been deleted !');
344 110 $this->store->deleteTagsEntriesAndEntries($this->user->getId());
345 if (is_file($themeIniFile) && is_readable($themeIniFile)) { 111 Tools::logm('The entries for user '. $username .' has been deleted !');
346 $themeInfo = parse_ini_file($themeIniFile); 112 $this->store->deleteUser($this->user->getId());
347 } 113 Tools::logm('User '. $username .' has been completely deleted !');
348 114 Session::logout();
349 if ($themeInfo === false) { 115 Tools::logm('logout');
350 $themeInfo = array(); 116 Tools::redirect();
351 } 117 $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 } 118 }
393 119 else {
394 $current = false; 120 Tools::logm('Bad password !');
395 121 $this->messages->add('e', _('Error : The password is wrong !'));
396 if ($language === $this->getLanguage()) {
397 $current = true;
398 } 122 }
399
400 $languages[] = array('name' => (isset($this->language_names[$language]) ? $this->language_names[$language] : $language), 'value' => $language, 'current' => $current);
401 } 123 }
402 124 else {
403 return $languages; 125 Tools::logm('Only user !');
126 $this->messages->add('e', _('Error : You are the only user, you cannot delete your account !'));
127 }
404 } 128 }
405 129
406 public function getDefaultConfig() 130 public function getDefaultConfig()
@@ -425,7 +149,7 @@ class Poche
425 $body = $content['rss']['channel']['item']['description']; 149 $body = $content['rss']['channel']['item']['description'];
426 150
427 // clean content from prevent xss attack 151 // clean content from prevent xss attack
428 $purifier = $this->getPurifier(); 152 $purifier = $this->_getPurifier();
429 $title = $purifier->purify($title); 153 $title = $purifier->purify($title);
430 $body = $purifier->purify($body); 154 $body = $purifier->purify($body);
431 155
@@ -437,7 +161,7 @@ class Poche
437 if ( $last_id ) { 161 if ( $last_id ) {
438 Tools::logm('add link ' . $url->getUrl()); 162 Tools::logm('add link ' . $url->getUrl());
439 if (DOWNLOAD_PICTURES) { 163 if (DOWNLOAD_PICTURES) {
440 $content = filtre_picture($body, $url->getUrl(), $last_id); 164 $content = Picture::filterPicture($body, $url->getUrl(), $last_id);
441 Tools::logm('updating content article'); 165 Tools::logm('updating content article');
442 $this->store->updateContent($last_id, $content, $this->user->getId()); 166 $this->store->updateContent($last_id, $content, $this->user->getId());
443 } 167 }
@@ -472,7 +196,7 @@ class Poche
472 $msg = 'delete link #' . $id; 196 $msg = 'delete link #' . $id;
473 if ($this->store->deleteById($id, $this->user->getId())) { 197 if ($this->store->deleteById($id, $this->user->getId())) {
474 if (DOWNLOAD_PICTURES) { 198 if (DOWNLOAD_PICTURES) {
475 remove_directory(ABS_PATH . $id); 199 Picture::removeDirectory(ABS_PATH . $id);
476 } 200 }
477 $this->messages->add('s', _('the link has been deleted successfully')); 201 $this->messages->add('s', _('the link has been deleted successfully'));
478 } 202 }
@@ -590,16 +314,16 @@ class Poche
590 switch ($view) 314 switch ($view)
591 { 315 {
592 case 'config': 316 case 'config':
593 $dev_infos = $this->getPocheVersion('dev'); 317 $dev_infos = $this->_getPocheVersion('dev');
594 $dev = trim($dev_infos[0]); 318 $dev = trim($dev_infos[0]);
595 $check_time_dev = date('d-M-Y H:i', $dev_infos[1]); 319 $check_time_dev = date('d-M-Y H:i', $dev_infos[1]);
596 $prod_infos = $this->getPocheVersion('prod'); 320 $prod_infos = $this->_getPocheVersion('prod');
597 $prod = trim($prod_infos[0]); 321 $prod = trim($prod_infos[0]);
598 $check_time_prod = date('d-M-Y H:i', $prod_infos[1]); 322 $check_time_prod = date('d-M-Y H:i', $prod_infos[1]);
599 $compare_dev = version_compare(POCHE, $dev); 323 $compare_dev = version_compare(POCHE, $dev);
600 $compare_prod = version_compare(POCHE, $prod); 324 $compare_prod = version_compare(POCHE, $prod);
601 $themes = $this->getInstalledThemes(); 325 $themes = $this->tpl->getInstalledThemes();
602 $languages = $this->getInstalledLanguages(); 326 $languages = $this->language->getInstalledLanguages();
603 $token = $this->user->getConfigValue('token'); 327 $token = $this->user->getConfigValue('token');
604 $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false; 328 $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false;
605 $only_user = ($this->store->listUsers() > 1) ? false : true; 329 $only_user = ($this->store->listUsers() > 1) ? false : true;
@@ -703,7 +427,7 @@ class Poche
703 'listmode' => (isset($_COOKIE['listmode']) ? true : false), 427 'listmode' => (isset($_COOKIE['listmode']) ? true : false),
704 ); 428 );
705 429
706 //if id is given - we retrive entries by tag: id is tag id 430 //if id is given - we retrieve entries by tag: id is tag id
707 if ($id) { 431 if ($id) {
708 $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId()); 432 $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId());
709 $tpl_vars['id'] = intval($id); 433 $tpl_vars['id'] = intval($id);
@@ -733,7 +457,7 @@ class Poche
733 * @todo set the new password in function header like this updatePassword($newPassword) 457 * @todo set the new password in function header like this updatePassword($newPassword)
734 * @return boolean 458 * @return boolean
735 */ 459 */
736 public function updatePassword() 460 public function updatePassword($password, $confirmPassword)
737 { 461 {
738 if (MODE_DEMO) { 462 if (MODE_DEMO) {
739 $this->messages->add('i', _('in demo mode, you can\'t update your password')); 463 $this->messages->add('i', _('in demo mode, you can\'t update your password'));
@@ -741,10 +465,10 @@ class Poche
741 Tools::redirect('?view=config'); 465 Tools::redirect('?view=config');
742 } 466 }
743 else { 467 else {
744 if (isset($_POST['password']) && isset($_POST['password_repeat'])) { 468 if (isset($password) && isset($confirmPassword)) {
745 if ($_POST['password'] == $_POST['password_repeat'] && $_POST['password'] != "") { 469 if ($password == $confirmPassword && !empty($password)) {
746 $this->messages->add('s', _('your password has been updated')); 470 $this->messages->add('s', _('your password has been updated'));
747 $this->store->updatePassword($this->user->getId(), Tools::encodeString($_POST['password'] . $this->user->getUsername())); 471 $this->store->updatePassword($this->user->getId(), Tools::encodeString($password . $this->user->getUsername()));
748 Session::logout(); 472 Session::logout();
749 Tools::logm('password updated'); 473 Tools::logm('password updated');
750 Tools::redirect(); 474 Tools::redirect();
@@ -757,102 +481,25 @@ class Poche
757 } 481 }
758 } 482 }
759 483
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 /** 484 /**
840 * get credentials from differents sources 485 * Get credentials from differents sources
841 * it redirects the user to the $referer link 486 * It redirects the user to the $referer link
487 *
842 * @return array 488 * @return array
843 */ 489 */
844 private function credentials() { 490 private function credentials()
845 if(isset($_SERVER['PHP_AUTH_USER'])) { 491 {
846 return array($_SERVER['PHP_AUTH_USER'],'php_auth',true); 492 if (isset($_SERVER['PHP_AUTH_USER'])) {
493 return array($_SERVER['PHP_AUTH_USER'], 'php_auth', true);
847 } 494 }
848 if(!empty($_POST['login']) && !empty($_POST['password'])) { 495 if (!empty($_POST['login']) && !empty($_POST['password'])) {
849 return array($_POST['login'],$_POST['password'],false); 496 return array($_POST['login'], $_POST['password'], false);
850 } 497 }
851 if(isset($_SERVER['REMOTE_USER'])) { 498 if (isset($_SERVER['REMOTE_USER'])) {
852 return array($_SERVER['REMOTE_USER'],'http_auth',true); 499 return array($_SERVER['REMOTE_USER'], 'http_auth', true);
853 } 500 }
854 501
855 return array(false,false,false); 502 return array(false, false, false);
856 } 503 }
857 504
858 /** 505 /**
@@ -901,9 +548,10 @@ class Poche
901 } 548 }
902 549
903 /** 550 /**
904 * import datas into your poche 551 * import datas into your wallabag
905 * @return boolean 552 * @return boolean
906 */ 553 */
554
907 public function import() { 555 public function import() {
908 556
909 if ( isset($_FILES['file']) && $_FILES['file']['tmp_name'] ) { 557 if ( isset($_FILES['file']) && $_FILES['file']['tmp_name'] ) {
@@ -935,98 +583,112 @@ class Poche
935 $read = ((sizeof($data) && $read)?0:1); 583 $read = ((sizeof($data) && $read)?0:1);
936 } 584 }
937 } 585 }
938 } 586 }
939 587
940 //for readability structure 588 // for readability structure
941 foreach ($data as $record) { 589
942 if (is_array($record)) { 590 foreach($data as $record) {
943 $data[] = $record; 591 if (is_array($record)) {
944 foreach ($record as $record2) { 592 $data[] = $record;
945 if (is_array($record2)) { 593 foreach($record as $record2) {
946 $data[] = $record2; 594 if (is_array($record2)) {
947 } 595 $data[] = $record2;
596 }
597 }
598 }
948 } 599 }
949 }
950 }
951 600
952 $urlsInserted = array(); //urls of articles inserted 601 $urlsInserted = array(); //urls of articles inserted
953 foreach ($data as $record) { 602 foreach($data as $record) {
954 $url = trim( isset($record['article__url']) ? $record['article__url'] : (isset($record['url']) ? $record['url'] : '') ); 603 $url = trim(isset($record['article__url']) ? $record['article__url'] : (isset($record['url']) ? $record['url'] : ''));
955 if ( $url and !in_array($url, $urlsInserted) ) { 604 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>'); 605 $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'] : ''); 606 $body = (isset($record['content']) ? $record['content'] : '');
958 $isRead = (isset($record['is_read']) ? intval($record['is_read']) : (isset($record['archive'])?intval($record['archive']):0)); 607 $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) ); 608 $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 609
968 } 610 // insert new record
611
612 $id = $this->store->add($url, $title, $body, $this->user->getId() , $isFavorite, $isRead);
613 if ($id) {
614 $urlsInserted[] = $url; //add
615 if (isset($record['tags']) && trim($record['tags'])) {
616
617 // @TODO: set tags
618
619 }
620 }
621 }
622 }
623
624 $i = sizeof($urlsInserted);
625 if ($i > 0) {
626 $this->messages->add('s', _('Articles inserted: ') . $i . _('. Please note, that some may be marked as "read".'));
969 } 627 }
970 }
971 }
972 628
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).'); 629 Tools::logm('Import of articles finished: '.$i.' articles added (w/o content if not provided).');
978 } 630 }
979 else { 631 else {
980 $this->messages->add('s', _('Did you forget to select a file?')); 632 $this->messages->add('s', _('Did you forget to select a file?'));
981 } 633 }
982 //file parsing finished here 634 // file parsing finished here
635 // now download article contents if any
636 // check if we need to download any content
983 637
984 //now download article contents if any 638 $recordsDownloadRequired = $this->store->retrieveUnfetchedEntriesCount($this->user->getId());
985 639
986 //check if we need to download any content 640 if ($recordsDownloadRequired == 0) {
987 $recordsDownloadRequired = $this->store->retrieveUnfetchedEntriesCount($this->user->getId());
988 if ( $recordsDownloadRequired == 0 ) {
989 //nothing to download
990 $this->messages->add('s', _('Import finished.'));
991 Tools::logm('Import finished completely');
992 Tools::redirect();
993 }
994 else {
995 //if just inserted - don't download anything, download will start in next reload
996 if ( !isset($_FILES['file']) ) {
997 //download next batch
998 Tools::logm('Fetching next batch of articles...');
999 $items = $this->store->retrieveUnfetchedEntries($this->user->getId(), IMPORT_LIMIT);
1000 641
1001 $purifier = $this->getPurifier(); 642 // nothing to download
1002 643
1003 foreach ($items as $item) { 644 $this->messages->add('s', _('Import finished.'));
1004 $url = new Url(base64_encode($item['url'])); 645 Tools::logm('Import finished completely');
1005 Tools::logm('Fetching article '.$item['id']); 646 Tools::redirect();
1006 $content = Tools::getPageContent($url); 647 }
648 else {
1007 649
1008 $title = (($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled')); 650 // if just inserted - don't download anything, download will start in next reload
1009 $body = (($content['rss']['channel']['item']['description'] != '') ? $content['rss']['channel']['item']['description'] : _('Undefined'));
1010 651
1011 //clean content to prevent xss attack 652 if (!isset($_FILES['file'])) {
1012 $title = $purifier->purify($title);
1013 $body = $purifier->purify($body);
1014 653
1015 $this->store->updateContentAndTitle($item['id'], $title, $body, $this->user->getId()); 654 // download next batch
1016 Tools::logm('Article '.$item['id'].' updated.'); 655
1017 } 656 Tools::logm('Fetching next batch of articles...');
657 $items = $this->store->retrieveUnfetchedEntries($this->user->getId() , IMPORT_LIMIT);
658 $purifier = $this->_getPurifier();
659 foreach($items as $item) {
660 $url = new Url(base64_encode($item['url']));
661 Tools::logm('Fetching article ' . $item['id']);
662 $content = Tools::getPageContent($url);
663 $title = (($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled'));
664 $body = (($content['rss']['channel']['item']['description'] != '') ? $content['rss']['channel']['item']['description'] : _('Undefined'));
1018 665
666 // clean content to prevent xss attack
667
668 $title = $purifier->purify($title);
669 $body = $purifier->purify($body);
670 $this->store->updateContentAndTitle($item['id'], $title, $body, $this->user->getId());
671 Tools::logm('Article ' . $item['id'] . ' updated.');
672 }
673 }
1019 } 674 }
1020 }
1021 675
1022 return array('includeImport'=>true, 'import'=>array('recordsDownloadRequired'=>$recordsDownloadRequired, 'recordsUnderDownload'=> IMPORT_LIMIT, 'delay'=> IMPORT_DELAY * 1000) ); 676 return array(
677 'includeImport' => true,
678 'import' => array(
679 'recordsDownloadRequired' => $recordsDownloadRequired,
680 'recordsUnderDownload' => IMPORT_LIMIT,
681 'delay' => IMPORT_DELAY * 1000
682 )
683 );
1023 } 684 }
1024 685
1025 /** 686 /**
1026 * export poche entries in json 687 * export poche entries in json
1027 * @return json all poche entries 688 * @return json all poche entries
1028 */ 689 */
1029 public function export() { 690 public function export()
691 {
1030 $filename = "wallabag-export-".$this->user->getId()."-".date("Y-m-d").".json"; 692 $filename = "wallabag-export-".$this->user->getId()."-".date("Y-m-d").".json";
1031 header('Content-Disposition: attachment; filename='.$filename); 693 header('Content-Disposition: attachment; filename='.$filename);
1032 694
@@ -1042,7 +704,7 @@ class Poche
1042 * @param string $which 'prod' or 'dev' 704 * @param string $which 'prod' or 'dev'
1043 * @return string latest $which version 705 * @return string latest $which version
1044 */ 706 */
1045 private function getPocheVersion($which = 'prod') { 707 private function _getPocheVersion($which = 'prod') {
1046 $cache_file = CACHE . '/' . $which; 708 $cache_file = CACHE . '/' . $which;
1047 $check_time = time(); 709 $check_time = time();
1048 710
@@ -1057,29 +719,27 @@ class Poche
1057 return array($version, $check_time); 719 return array($version, $check_time);
1058 } 720 }
1059 721
1060 public function generateToken() 722 /**
723 * Update token for current user
724 */
725 public function updateToken()
1061 { 726 {
1062 if (ini_get('open_basedir') === '') { 727 $token = Tools::generateToken();
1063 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { 728 $this->store->updateUserConfig($this->user->getId(), 'token', $token);
1064 echo 'This is a server using Windows!'; 729 $currentConfig = $_SESSION['poche_user']->config;
1065 // alternative to /dev/urandom for Windows 730 $currentConfig['token'] = $token;
1066 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20); 731 $_SESSION['poche_user']->setConfig($currentConfig);
1067 } else { 732 Tools::redirect();
1068 $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
1069 }
1070 }
1071 else {
1072 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
1073 }
1074
1075 $token = str_replace('+', '', $token);
1076 $this->store->updateUserConfig($this->user->getId(), 'token', $token);
1077 $currentConfig = $_SESSION['poche_user']->config;
1078 $currentConfig['token'] = $token;
1079 $_SESSION['poche_user']->setConfig($currentConfig);
1080 Tools::redirect();
1081 } 733 }
1082 734
735 /**
736 * Generate RSS feeds for current user
737 *
738 * @param $token
739 * @param $user_id
740 * @param $tag_id
741 * @param string $type
742 */
1083 public function generateFeeds($token, $user_id, $tag_id, $type = 'home') 743 public function generateFeeds($token, $user_id, $tag_id, $type = 'home')
1084 { 744 {
1085 $allowed_types = array('home', 'fav', 'archive', 'tag'); 745 $allowed_types = array('home', 'fav', 'archive', 'tag');
@@ -1089,10 +749,9 @@ class Poche
1089 die(sprintf(_('User with this id (%d) does not exist.'), $user_id)); 749 die(sprintf(_('User with this id (%d) does not exist.'), $user_id));
1090 } 750 }
1091 751
1092 if (!in_array($type, $allowed_types) || $token != $config['token']) { 752 if (!in_array($type, $allowed_types) || !isset($config['token']) || $token != $config['token']) {
1093 die(_('Uh, there is a problem while generating feeds.')); 753 die(_('Uh, there is a problem while generating feed. Wrong token used?'));
1094 } 754 }
1095 // Check the token
1096 755
1097 $feed = new FeedWriter(RSS2); 756 $feed = new FeedWriter(RSS2);
1098 $feed->setTitle('wallabag — ' . $type . ' feed'); 757 $feed->setTitle('wallabag — ' . $type . ' feed');
@@ -1124,147 +783,22 @@ class Poche
1124 exit; 783 exit;
1125 } 784 }
1126 785
1127 public function emptyCache() {
1128 $files = new RecursiveIteratorIterator(
1129 new RecursiveDirectoryIterator(CACHE, RecursiveDirectoryIterator::SKIP_DOTS),
1130 RecursiveIteratorIterator::CHILD_FIRST
1131 );
1132 786
1133 foreach ($files as $fileinfo) {
1134 $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
1135 $todo($fileinfo->getRealPath());
1136 }
1137
1138 Tools::logm('empty cache');
1139 $this->messages->add('s', _('Cache deleted.'));
1140 Tools::redirect();
1141 }
1142 787
1143 /** 788 /**
1144 * return new purifier object with actual config 789 * Returns new purifier object with actual config
1145 */ 790 */
1146 protected function getPurifier() { 791 private function _getPurifier()
1147 $config = HTMLPurifier_Config::createDefault(); 792 {
1148 $config->set('Cache.SerializerPath', CACHE); 793 $config = HTMLPurifier_Config::createDefault();
1149 $config->set('HTML.SafeIframe', true); 794 $config->set('Cache.SerializerPath', CACHE);
795 $config->set('HTML.SafeIframe', true);
1150 796
1151 //allow YouTube, Vimeo and dailymotion videos 797 //allow YouTube, Vimeo and dailymotion videos
1152 $config->set('URI.SafeIframeRegexp', '%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/|www\.dailymotion\.com/embed/video/)%'); 798 $config->set('URI.SafeIframeRegexp', '%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/|www\.dailymotion\.com/embed/video/)%');
1153 799
1154 return new HTMLPurifier($config); 800 return new HTMLPurifier($config);
1155 } 801 }
1156 802
1157 /**
1158 * handle epub
1159 */
1160 public function createEpub() {
1161
1162 switch ($_GET['method']) {
1163 case 'id':
1164 $entryID = filter_var($_GET['id'],FILTER_SANITIZE_NUMBER_INT);
1165 $entry = $this->store->retrieveOneById($entryID, $this->user->getId());
1166 $entries = array($entry);
1167 $bookTitle = $entry['title'];
1168 $bookFileName = substr($bookTitle, 0, 200);
1169 break;
1170 case 'all':
1171 $entries = $this->store->retrieveAll($this->user->getId());
1172 $bookTitle = sprintf(_('All my articles on '), date(_('d.m.y'))); #translatable because each country has it's own date format system
1173 $bookFileName = _('Allarticles') . date(_('dmY'));
1174 break;
1175 case 'tag':
1176 $tag = filter_var($_GET['tag'],FILTER_SANITIZE_STRING);
1177 $tags_id = $this->store->retrieveAllTags($this->user->getId(),$tag);
1178 $tag_id = $tags_id[0]["id"]; // we take the first result, which is supposed to match perfectly. There must be a workaround.
1179 $entries = $this->store->retrieveEntriesByTag($tag_id,$this->user->getId());
1180 $bookTitle = sprintf(_('Articles tagged %s'),$tag);
1181 $bookFileName = substr(sprintf(_('Tag %s'),$tag), 0, 200);
1182 break;
1183 case 'category':
1184 $category = filter_var($_GET['category'],FILTER_SANITIZE_STRING);
1185 $entries = $this->store->getEntriesByView($category,$this->user->getId());
1186 $bookTitle = sprintf(_('All articles in category %s'), $category);
1187 $bookFileName = substr(sprintf(_('Category %s'),$category), 0, 200);
1188 break;
1189 case 'search':
1190 $search = filter_var($_GET['search'],FILTER_SANITIZE_STRING);
1191 $entries = $this->store->search($search,$this->user->getId());
1192 $bookTitle = sprintf(_('All articles for search %s'), $search);
1193 $bookFileName = substr(sprintf(_('Search %s'), $search), 0, 200);
1194 break;
1195 case 'default':
1196 die(_('Uh, there is a problem while generating epub.'));
1197
1198 }
1199
1200 $content_start =
1201 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1202 . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n"
1203 . "<head>"
1204 . "<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n"
1205 . "<title>wallabag articles book</title>\n"
1206 . "</head>\n"
1207 . "<body>\n";
1208
1209 $bookEnd = "</body>\n</html>\n";
1210
1211 $log = new Logger("wallabag", TRUE);
1212 $fileDir = CACHE;
1213
1214 $book = new EPub(EPub::BOOK_VERSION_EPUB3, DEBUG_POCHE);
1215 $log->logLine("new EPub()");
1216 $log->logLine("EPub class version: " . EPub::VERSION);
1217 $log->logLine("EPub Req. Zip version: " . EPub::REQ_ZIP_VERSION);
1218 $log->logLine("Zip version: " . Zip::VERSION);
1219 $log->logLine("getCurrentServerURL: " . $book->getCurrentServerURL());
1220 $log->logLine("getCurrentPageURL..: " . $book->getCurrentPageURL());
1221 803
1222 $book->setTitle(_('wallabag\'s articles'));
1223 $book->setIdentifier("http://$_SERVER[HTTP_HOST]", EPub::IDENTIFIER_URI); // Could also be the ISBN number, prefered for published books, or a UUID.
1224 //$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.
1225 $book->setDescription(_("Some articles saved on my wallabag"));
1226 $book->setAuthor("wallabag","wallabag");
1227 $book->setPublisher("wallabag","wallabag"); // I hope this is a non existant address :)
1228 $book->setDate(time()); // Strictly not needed as the book date defaults to time().
1229 //$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.
1230 $book->setSourceURL("http://$_SERVER[HTTP_HOST]");
1231
1232 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "PHP");
1233 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "wallabag");
1234
1235 $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";
1236
1237 $log->logLine("Add Cover");
1238
1239 $fullTitle = "<h1> " . $bookTitle . "</h1>\n";
1240
1241 $book->setCoverImage("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png", $fullTitle);
1242
1243 $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;
1244
1245 //$book->addChapter("Table of Contents", "TOC.xhtml", NULL, false, EPub::EXTERNAL_REF_IGNORE);
1246 $book->addChapter("Notices", "Cover2.html", $cover);
1247
1248 $book->buildTOC();
1249
1250 foreach ($entries as $entry) { //set tags as subjects
1251 $tags = $this->store->retrieveTagsByEntry($entry['id']);
1252 foreach ($tags as $tag) {
1253 $book->setSubject($tag['value']);
1254 }
1255
1256 $log->logLine("Set up parameters");
1257
1258 $chapter = $content_start . $entry['content'] . $bookEnd;
1259 $book->addChapter($entry['title'], htmlspecialchars($entry['title']) . ".html", $chapter, true, EPub::EXTERNAL_REF_ADD);
1260 $log->logLine("Added chapter " . $entry['title']);
1261 }
1262
1263 if (DEBUG_POCHE) {
1264 $epuplog = $book->getLog();
1265 $book->addChapter("Log", "Log.html", $content_start . $log->getLog() . "\n</pre>" . $bookEnd); // log generation
1266 }
1267 $book->finalize();
1268 $zipData = $book->sendBook($bookFileName);
1269 }
1270} 804}