]> git.immae.eu Git - github/wallabag/wallabag.git/blame - inc/poche/Poche.class.php
Fix bad character encoding when downloading images
[github/wallabag/wallabag.git] / inc / poche / Poche.class.php
CommitLineData
eb1af592
NL
1<?php
2/**
c95b78a8 3 * wallabag, self hostable application allowing you to not miss any content anymore
eb1af592 4 *
c95b78a8
NL
5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
eb1af592
NL
7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file
9 */
10
11class Poche
12{
00dbaf90
NL
13 public static $canRenderTemplates = true;
14 public static $configFileAvailable = true;
15
7ce7ec4c 16 public $user;
eb1af592
NL
17 public $store;
18 public $tpl;
55821e04 19 public $messages;
6a361945 20 public $pagination;
182faf26 21
00dbaf90 22 private $currentTheme = '';
5011388f 23 private $currentLanguage = '';
9d3b88b3 24 private $notInstalledMessage = array();
eb1af592 25
c9bd17a1
NL
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',
5805ac45 35 'pt_BR.utf8' => 'Português (Brasil)',
c9bd17a1
NL
36 'ru_RU.utf8' => 'Pусский',
37 'sl_SI.utf8' => 'Slovenščina',
cbcae403 38 'uk_UA.utf8' => 'Українська',
c9bd17a1 39 );
00dbaf90 40 public function __construct()
eb1af592 41 {
9d3b88b3
NL
42 if ($this->configFileIsAvailable()) {
43 $this->init();
00dbaf90 44 }
182faf26 45
9d3b88b3
NL
46 if ($this->themeIsInstalled()) {
47 $this->initTpl();
00dbaf90 48 }
182faf26 49
9d3b88b3
NL
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 }
5cfafc61 57 $this->store->checkTags();
eb1af592 58 }
eb1af592 59 }
182faf26
MR
60
61 private function init()
00dbaf90
NL
62 {
63 Tools::initPhp();
eb1af592 64
00dbaf90
NL
65 if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) {
66 $this->user = $_SESSION['poche_user'];
67 } else {
68 # fake user, just for install & login screens
69 $this->user = new User();
70 $this->user->setConfig($this->getDefaultConfig());
71 }
72
73 # l10n
74 $language = $this->user->getConfigValue('language');
b6413975 75 @putenv('LC_ALL=' . $language);
00dbaf90 76 setlocale(LC_ALL, $language);
182faf26
MR
77 bindtextdomain($language, LOCALE);
78 textdomain($language);
00dbaf90
NL
79
80 # Pagination
81 $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p');
182faf26 82
00dbaf90
NL
83 # Set up theme
84 $themeDirectory = $this->user->getConfigValue('theme');
182faf26 85
00dbaf90
NL
86 if ($themeDirectory === false) {
87 $themeDirectory = DEFAULT_THEME;
88 }
182faf26 89
00dbaf90 90 $this->currentTheme = $themeDirectory;
5011388f
NL
91
92 # Set up language
93 $languageDirectory = $this->user->getConfigValue('language');
182faf26 94
5011388f
NL
95 if ($languageDirectory === false) {
96 $languageDirectory = DEFAULT_THEME;
97 }
182faf26 98
5011388f 99 $this->currentLanguage = $languageDirectory;
00dbaf90
NL
100 }
101
102 public function configFileIsAvailable() {
103 if (! self::$configFileAvailable) {
43c7b978 104 $this->notInstalledMessage[] = 'You have to copy (don\'t just rename!) inc/poche/config.inc.default.php to inc/poche/config.inc.php.';
00dbaf90
NL
105
106 return false;
107 }
108
109 return true;
110 }
182faf26 111
00dbaf90 112 public function themeIsInstalled() {
9d3b88b3 113 $passTheme = TRUE;
00dbaf90
NL
114 # Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet
115 if (! self::$canRenderTemplates) {
41265e07 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.';
9d3b88b3 117 $passTheme = FALSE;
00dbaf90 118 }
7f17a38d
NL
119
120 if (! is_writable(CACHE)) {
9d3b88b3 121 $this->notInstalledMessage[] = 'You don\'t have write access on cache directory.';
7f17a38d
NL
122
123 self::$canRenderTemplates = false;
124
9d3b88b3 125 $passTheme = FALSE;
182faf26
MR
126 }
127
00dbaf90 128 # Check if the selected theme and its requirements are present
f4fbfaa7
NL
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 . ')';
182faf26 133
00dbaf90 134 self::$canRenderTemplates = false;
182faf26 135
9d3b88b3 136 $passTheme = FALSE;
00dbaf90 137 }
182faf26 138
f4fbfaa7
NL
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 . ')';
182faf26 144
f4fbfaa7 145 self::$canRenderTemplates = false;
182faf26 146
f4fbfaa7
NL
147 $passTheme = FALSE;
148 }
00dbaf90
NL
149 }
150 }
9d3b88b3
NL
151
152 if (!$passTheme) {
153 return FALSE;
154 }
155
182faf26 156
00dbaf90
NL
157 return true;
158 }
182faf26 159
4a291288
NL
160 /**
161 * all checks before installation.
00dbaf90 162 * @todo move HTML to template
182faf26 163 * @return boolean
4a291288 164 */
00dbaf90 165 public function systemIsInstalled()
eb1af592 166 {
9d3b88b3 167 $msg = TRUE;
182faf26 168
00dbaf90 169 $configSalt = defined('SALT') ? constant('SALT') : '';
182faf26 170
00dbaf90 171 if (empty($configSalt)) {
9d3b88b3
NL
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)) {
00dbaf90 176 Tools::logm('sqlite file doesn\'t exist');
9d3b88b3
NL
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)) {
bb5a7d9e 185 Tools::logm('you don\'t have write access on sqlite file');
9d3b88b3
NL
186 $this->notInstalledMessage[] = 'You don\'t have write access on sqlite file.';
187 $msg = FALSE;
bb5a7d9e 188 }
00dbaf90 189
9d3b88b3 190 if (! $msg) {
00dbaf90 191 return false;
8d3275be 192 }
7ce7ec4c 193
00dbaf90
NL
194 return true;
195 }
182faf26 196
00dbaf90
NL
197 public function getNotInstalledMessage() {
198 return $this->notInstalledMessage;
4a291288 199 }
eb1af592 200
4a291288
NL
201 private function initTpl()
202 {
00dbaf90 203 $loaderChain = new Twig_Loader_Chain();
f4fbfaa7 204 $theme = $this->getTheme();
182faf26 205
00dbaf90
NL
206 # add the current theme as first to the loader chain so Twig will look there first for overridden template files
207 try {
f4fbfaa7 208 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme));
00dbaf90
NL
209 } catch (Twig_Error_Loader $e) {
210 # @todo isInstalled() should catch this, inject Twig later
f4fbfaa7 211 die('The currently selected theme (' . $theme . ') does not seem to be properly installed (' . THEME . '/' . $theme .' is missing)');
00dbaf90 212 }
182faf26 213
00dbaf90 214 # add all required themes to the loader chain
f4fbfaa7
NL
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 }
00dbaf90
NL
224 }
225 }
182faf26 226
bc1ee852 227 if (DEBUG_POCHE) {
f4fbfaa7 228 $twigParams = array();
00dbaf90 229 } else {
f4fbfaa7 230 $twigParams = array('cache' => CACHE);
bc1ee852 231 }
182faf26 232
f4fbfaa7 233 $this->tpl = new Twig_Environment($loaderChain, $twigParams);
eb1af592 234 $this->tpl->addExtension(new Twig_Extensions_Extension_I18n());
182faf26 235
55821e04
NL
236 # filter to display domain name of an url
237 $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain');
238 $this->tpl->addFilter($filter);
eb1af592 239
d9178758
NL
240 # filter for reading time
241 $filter = new Twig_SimpleFilter('getReadingTime', 'Tools::getReadingTime');
242 $this->tpl->addFilter($filter);
eb1af592 243 }
b6413975 244
4d99bae8 245 public function createNewUser() {
246 if (isset($_GET['newuser'])){
247 if ($_POST['newusername'] != "" && $_POST['password4newuser'] != ""){
248 $newusername = filter_var($_POST['newusername'], FILTER_SANITIZE_STRING);
b6413975 249 if (!$this->store->userExists($newusername)){
4d99bae8 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 }
b6413975 268
4d99bae8 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 }
eb1af592 296
f4fbfaa7 297 private function install()
eb1af592
NL
298 {
299 Tools::logm('poche still not installed');
300 echo $this->tpl->render('install.twig', array(
00dbaf90
NL
301 'token' => Session::getToken(),
302 'theme' => $this->getTheme(),
303 'poche_url' => Tools::getPocheUrl()
eb1af592
NL
304 ));
305 if (isset($_GET['install'])) {
182faf26 306 if (($_POST['password'] == $_POST['password_repeat'])
eb1af592
NL
307 && $_POST['password'] != "" && $_POST['login'] != "") {
308 # let's rock, install poche baby !
bb5a7d9e
NL
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();
314 }
6a361945
NL
315 }
316 else {
317 Tools::logm('error during installation');
eb1af592
NL
318 Tools::redirect();
319 }
320 }
321 exit();
322 }
182faf26 323
00dbaf90
NL
324 public function getTheme() {
325 return $this->currentTheme;
326 }
5011388f 327
f4fbfaa7
NL
328 /**
329 * Provides theme information by parsing theme.ini file if present in the theme's root directory.
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 */
337 public function getThemeInfo($theme) {
338 if (!is_dir(THEME . '/' . $theme)) {
339 return false;
340 }
341
342 $themeIniFile = THEME . '/' . $theme . '/theme.ini';
343 $themeInfo = array();
344
345 if (is_file($themeIniFile) && is_readable($themeIniFile)) {
346 $themeInfo = parse_ini_file($themeIniFile);
347 }
182faf26 348
f4fbfaa7
NL
349 if ($themeInfo === false) {
350 $themeInfo = array();
351 }
352 if (!isset($themeInfo['name'])) {
353 $themeInfo['name'] = $theme;
354 }
355 $themeInfo['current'] = ($theme === $this->getTheme());
356
357 return $themeInfo;
5011388f 358 }
182faf26 359
00dbaf90
NL
360 public function getInstalledThemes() {
361 $handle = opendir(THEME);
362 $themes = array();
f4fbfaa7 363
00dbaf90
NL
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
f4fbfaa7 367 if (!is_dir(THEME . '/' . $theme) || in_array($theme, array('.', '..'))) {
00dbaf90
NL
368 continue;
369 }
f4fbfaa7
NL
370
371 $themes[$theme] = $this->getThemeInfo($theme);
00dbaf90 372 }
f4fbfaa7 373
3ade95a3
NL
374 ksort($themes);
375
00dbaf90
NL
376 return $themes;
377 }
eb1af592 378
f4fbfaa7
NL
379 public function getLanguage() {
380 return $this->currentLanguage;
381 }
382
5011388f
NL
383 public function getInstalledLanguages() {
384 $handle = opendir(LOCALE);
385 $languages = array();
182faf26 386
5011388f
NL
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
cbcae403 390 if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.', 'tools'))) {
5011388f
NL
391 continue;
392 }
182faf26 393
5011388f 394 $current = false;
182faf26 395
5011388f
NL
396 if ($language === $this->getLanguage()) {
397 $current = true;
398 }
182faf26 399
cbcae403 400 $languages[] = array('name' => (isset($this->language_names[$language]) ? $this->language_names[$language] : $language), 'value' => $language, 'current' => $current);
5011388f 401 }
182faf26 402
5011388f
NL
403 return $languages;
404 }
405
8d3275be 406 public function getDefaultConfig()
182faf26 407 {
8d3275be
NL
408 return array(
409 'pager' => PAGINATION,
410 'language' => LANG,
00dbaf90
NL
411 'theme' => DEFAULT_THEME
412 );
8d3275be
NL
413 }
414
eb1af592
NL
415 /**
416 * Call action (mark as fav, archive, delete, etc.)
417 */
926acd7b 418 public function action($action, Url $url, $id = 0, $import = FALSE, $autoclose = FALSE, $tags = null)
eb1af592
NL
419 {
420 switch ($action)
421 {
422 case 'add':
a297fb1e
MR
423 $content = Tools::getPageContent($url);
424 $title = ($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled');
425 $body = $content['rss']['channel']['item']['description'];
426
427 // clean content from prevent xss attack
0f859c6f 428 $purifier = $this->getPurifier();
a297fb1e
MR
429 $title = $purifier->purify($title);
430 $body = $purifier->purify($body);
1570a653 431
182faf26 432 //search for possible duplicate
8d7cd2cc 433 $duplicate = NULL;
a297fb1e 434 $duplicate = $this->store->retrieveOneByURL($url->getUrl(), $this->user->getId());
488fc63b 435
182faf26 436 $last_id = $this->store->add($url->getUrl(), $title, $body, $this->user->getId());
a297fb1e 437 if ( $last_id ) {
ec397236 438 Tools::logm('add link ' . $url->getUrl());
ec397236 439 if (DOWNLOAD_PICTURES) {
42c80841 440 $content = filtre_picture($body, $url->getUrl(), $last_id);
ec397236
NL
441 Tools::logm('updating content article');
442 $this->store->updateContent($last_id, $content, $this->user->getId());
443 }
488fc63b
MR
444
445 if ($duplicate != NULL) {
446 // duplicate exists, so, older entry needs to be deleted (as new entry should go to the top of list), BUT favorite mark and tags should be preserved
447 Tools::logm('link ' . $url->getUrl() . ' is a duplicate');
448 // 1) - preserve tags and favorite, then drop old entry
449 $this->store->reassignTags($duplicate['id'], $last_id);
450 if ($duplicate['is_fav']) {
451 $this->store->favoriteById($last_id, $this->user->getId());
452 }
453 if ($this->store->deleteById($duplicate['id'], $this->user->getId())) {
454 Tools::logm('previous link ' . $url->getUrl() .' entry deleted');
455 }
456 }
457
182faf26 458 $this->messages->add('s', _('the link has been added successfully'));
eb1af592
NL
459 }
460 else {
a297fb1e
MR
461 $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());
b916bcfc 463 }
ec397236 464
a297fb1e
MR
465 if ($autoclose == TRUE) {
466 Tools::redirect('?view=home');
467 } else {
468 Tools::redirect('?view=home&closewin=true');
eb1af592
NL
469 }
470 break;
471 case 'delete':
bc1ee852 472 $msg = 'delete link #' . $id;
8d3275be 473 if ($this->store->deleteById($id, $this->user->getId())) {
eb1af592
NL
474 if (DOWNLOAD_PICTURES) {
475 remove_directory(ABS_PATH . $id);
476 }
6a361945 477 $this->messages->add('s', _('the link has been deleted successfully'));
eb1af592
NL
478 }
479 else {
6a361945 480 $this->messages->add('e', _('the link wasn\'t deleted'));
bc1ee852 481 $msg = 'error : can\'t delete link #' . $id;
eb1af592 482 }
bc1ee852 483 Tools::logm($msg);
985ce3ec 484 Tools::redirect('?');
eb1af592
NL
485 break;
486 case 'toggle_fav' :
8d3275be 487 $this->store->favoriteById($id, $this->user->getId());
eb1af592 488 Tools::logm('mark as favorite link #' . $id);
c2cf7075
MR
489 if ( Tools::isAjaxRequest() ) {
490 echo 1;
491 exit;
492 }
493 else {
494 Tools::redirect();
495 }
eb1af592
NL
496 break;
497 case 'toggle_archive' :
8d3275be 498 $this->store->archiveById($id, $this->user->getId());
eb1af592 499 Tools::logm('archive link #' . $id);
c2cf7075
MR
500 if ( Tools::isAjaxRequest() ) {
501 echo 1;
502 exit;
503 }
504 else {
505 Tools::redirect();
506 }
eb1af592 507 break;
f14807de
NL
508 case 'archive_all' :
509 $this->store->archiveAll($this->user->getId());
510 Tools::logm('archive all links');
a297fb1e 511 Tools::redirect();
f14807de 512 break;
c432fa16 513 case 'add_tag' :
decc23aa 514 if (isset($_GET['search'])) {
515 //when we want to apply a tag to a search
decc23aa 516 $tags = array($_GET['search']);
517 $allentry_ids = $this->store->search($tags[0], $this->user->getId());
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
523 $tags = explode(',', $_POST['value']);
524 $entry_ids = array($_POST['entry_id']);
fb26cc93 525 }
decc23aa 526 foreach($entry_ids as $entry_id) {
527 $entry = $this->store->retrieveOneById($entry_id, $this->user->getId());
528 if (!$entry) {
529 $this->messages->add('e', _('Article not found!'));
530 Tools::logm('error : article not found');
531 Tools::redirect();
532 }
533 //get all already set tags to preven duplicates
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);
fb26cc93 551 }
decc23aa 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 }
c432fa16 559 }
c432fa16 560 }
9c743ab9 561 $this->messages->add('s', _('The tag has been applied successfully'));
24696800 562 Tools::logm('The tag has been applied successfully');
a297fb1e 563 Tools::redirect();
c432fa16
NL
564 break;
565 case 'remove_tag' :
566 $tag_id = $_GET['tag_id'];
b89d5a2b
NL
567 $entry = $this->store->retrieveOneById($id, $this->user->getId());
568 if (!$entry) {
569 $this->messages->add('e', _('Article not found!'));
570 Tools::logm('error : article not found');
571 Tools::redirect();
572 }
c432fa16 573 $this->store->removeTagForEntry($id, $tag_id);
9c743ab9 574 Tools::logm('tag entry deleted');
24696800 575 if ($this->store->cleanUnusedTag($tag_id)) {
576 Tools::logm('tag deleted');
577 }
9c743ab9 578 $this->messages->add('s', _('The tag has been successfully deleted'));
c432fa16
NL
579 Tools::redirect();
580 break;
eb1af592
NL
581 default:
582 break;
583 }
584 }
585
586 function displayView($view, $id = 0)
587 {
588 $tpl_vars = array();
589
590 switch ($view)
591 {
eb1af592 592 case 'config':
11c680f9
NL
593 $dev_infos = $this->getPocheVersion('dev');
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]);
031df528
NL
599 $compare_dev = version_compare(POCHE, $dev);
600 $compare_prod = version_compare(POCHE, $prod);
00dbaf90 601 $themes = $this->getInstalledThemes();
5011388f 602 $languages = $this->getInstalledLanguages();
72c20a52 603 $token = $this->user->getConfigValue('token');
1810c13b 604 $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false;
4d99bae8 605 $only_user = ($this->store->listUsers() > 1) ? false : true;
32520785 606 $tpl_vars = array(
00dbaf90 607 'themes' => $themes,
5011388f 608 'languages' => $languages,
32520785
NL
609 'dev' => $dev,
610 'prod' => $prod,
11c680f9
NL
611 'check_time_dev' => $check_time_dev,
612 'check_time_prod' => $check_time_prod,
32520785
NL
613 'compare_dev' => $compare_dev,
614 'compare_prod' => $compare_prod,
72c20a52
NL
615 'token' => $token,
616 'user_id' => $this->user->getId(),
df6afaf0 617 'http_auth' => $http_auth,
4d99bae8 618 'only_user' => $only_user
32520785 619 );
eb1af592
NL
620 Tools::logm('config view');
621 break;
6cab59c3
NL
622 case 'edit-tags':
623 # tags
b89d5a2b
NL
624 $entry = $this->store->retrieveOneById($id, $this->user->getId());
625 if (!$entry) {
626 $this->messages->add('e', _('Article not found!'));
627 Tools::logm('error : article not found');
628 Tools::redirect();
629 }
6cab59c3
NL
630 $tags = $this->store->retrieveTagsByEntry($id);
631 $tpl_vars = array(
c432fa16 632 'entry_id' => $id,
6cab59c3 633 'tags' => $tags,
032e0ca1 634 'entry' => $entry,
4886ed6d
NL
635 );
636 break;
2e2ebe5e 637 case 'tags':
f778e472 638 $token = $this->user->getConfigValue('token');
fb26cc93
MR
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 }
2e2ebe5e 650 $tpl_vars = array(
f778e472
NL
651 'token' => $token,
652 'user_id' => $this->user->getId(),
2e2ebe5e
NL
653 'tags' => $tags,
654 );
655 break;
a4585f7e
MR
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;
eb1af592 669 case 'view':
8d3275be 670 $entry = $this->store->retrieveOneById($id, $this->user->getId());
eb1af592
NL
671 if ($entry != NULL) {
672 Tools::logm('view link #' . $id);
673 $content = $entry['content'];
674 if (function_exists('tidy_parse_string')) {
675 $tidy = tidy_parse_string($content, array('indent'=>true, 'show-body-only' => true), 'UTF8');
676 $tidy->cleanRepair();
677 $content = $tidy->value;
3408ed48 678 }
a3223127 679
3408ed48
NL
680 # flattr checking
681 $flattr = new FlattrItem();
7b171c73
NL
682 $flattr->checkItem($entry['url'], $entry['id']);
683
684 # tags
685 $tags = $this->store->retrieveTagsByEntry($entry['id']);
a3223127 686
3408ed48 687 $tpl_vars = array(
7b171c73
NL
688 'entry' => $entry,
689 'content' => $content,
690 'flattr' => $flattr,
691 'tags' => $tags
3408ed48 692 );
eb1af592
NL
693 }
694 else {
d8d1542e 695 Tools::logm('error in view call : entry is null');
eb1af592
NL
696 }
697 break;
032e0ca1 698 default: # home, favorites, archive and tag views
eb1af592 699 $tpl_vars = array(
3eb04903
N
700 'entries' => '',
701 'page_links' => '',
7f9f5281 702 'nb_results' => '',
6065553c 703 'listmode' => (isset($_COOKIE['listmode']) ? true : false),
eb1af592 704 );
182faf26 705
032e0ca1
MR
706 //if id is given - we retrive entries by tag: id is tag id
707 if ($id) {
708 $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId());
709 $tpl_vars['id'] = intval($id);
710 }
711
712 $count = $this->store->getEntriesByViewCount($view, $this->user->getId(), $id);
713
714 if ($count > 0) {
715 $this->pagination->set_total($count);
c515ffec 716 $page_links = str_replace(array('previous', 'next'), array(_('previous'), _('next')),
032e0ca1
MR
717 $this->pagination->page_links('?view=' . $view . '&sort=' . $_SESSION['sort'] . (($id)?'&id='.$id:'') . '&' ));
718 $tpl_vars['entries'] = $this->store->getEntriesByView($view, $this->user->getId(), $this->pagination->get_limit(), $id);
3eb04903 719 $tpl_vars['page_links'] = $page_links;
032e0ca1 720 $tpl_vars['nb_results'] = $count;
3eb04903 721 }
6a361945 722 Tools::logm('display ' . $view . ' view');
eb1af592
NL
723 break;
724 }
725
726 return $tpl_vars;
727 }
c765c367 728
07ee09f4 729 /**
182faf26
MR
730 * update the password of the current user.
731 * if MODE_DEMO is TRUE, the password can't be updated.
07ee09f4
NL
732 * @todo add the return value
733 * @todo set the new password in function header like this updatePassword($newPassword)
734 * @return boolean
735 */
c765c367
NL
736 public function updatePassword()
737 {
55821e04 738 if (MODE_DEMO) {
8d3275be 739 $this->messages->add('i', _('in demo mode, you can\'t update your password'));
55821e04 740 Tools::logm('in demo mode, you can\'t do this');
6a361945 741 Tools::redirect('?view=config');
55821e04
NL
742 }
743 else {
744 if (isset($_POST['password']) && isset($_POST['password_repeat'])) {
745 if ($_POST['password'] == $_POST['password_repeat'] && $_POST['password'] != "") {
8d3275be
NL
746 $this->messages->add('s', _('your password has been updated'));
747 $this->store->updatePassword($this->user->getId(), Tools::encodeString($_POST['password'] . $this->user->getUsername()));
c765c367 748 Session::logout();
8d3275be 749 Tools::logm('password updated');
c765c367
NL
750 Tools::redirect();
751 }
752 else {
8d3275be 753 $this->messages->add('e', _('the two fields have to be filled & the password must be the same in the two fields'));
6a361945 754 Tools::redirect('?view=config');
c765c367
NL
755 }
756 }
757 }
758 }
182faf26 759
00dbaf90
NL
760 public function updateTheme()
761 {
762 # no data
763 if (empty($_POST['theme'])) {
764 }
182faf26 765
00dbaf90
NL
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 }
182faf26 771
00dbaf90
NL
772 $themes = $this->getInstalledThemes();
773 $actualTheme = false;
182faf26 774
f4fbfaa7
NL
775 foreach (array_keys($themes) as $theme) {
776 if ($theme == $_POST['theme']) {
00dbaf90
NL
777 $actualTheme = true;
778 break;
779 }
780 }
182faf26 781
00dbaf90
NL
782 if (! $actualTheme) {
783 $this->messages->add('e', _('that theme does not seem to be installed'));
784 Tools::redirect('?view=config');
785 }
182faf26 786
00dbaf90
NL
787 $this->store->updateUserConfig($this->user->getId(), 'theme', $_POST['theme']);
788 $this->messages->add('s', _('you have changed your theme preferences'));
182faf26 789
00dbaf90
NL
790 $currentConfig = $_SESSION['poche_user']->config;
791 $currentConfig['theme'] = $_POST['theme'];
182faf26 792
00dbaf90 793 $_SESSION['poche_user']->setConfig($currentConfig);
56532c4e
NL
794
795 $this->emptyCache();
182faf26 796
00dbaf90
NL
797 Tools::redirect('?view=config');
798 }
c765c367 799
5011388f
NL
800 public function updateLanguage()
801 {
802 # no data
803 if (empty($_POST['language'])) {
804 }
182faf26 805
5011388f
NL
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 }
182faf26 811
5011388f
NL
812 $languages = $this->getInstalledLanguages();
813 $actualLanguage = false;
182faf26 814
5011388f 815 foreach ($languages as $language) {
c9bd17a1 816 if ($language['value'] == $_POST['language']) {
5011388f
NL
817 $actualLanguage = true;
818 break;
819 }
820 }
182faf26 821
5011388f
NL
822 if (! $actualLanguage) {
823 $this->messages->add('e', _('that language does not seem to be installed'));
824 Tools::redirect('?view=config');
825 }
182faf26 826
5011388f
NL
827 $this->store->updateUserConfig($this->user->getId(), 'language', $_POST['language']);
828 $this->messages->add('s', _('you have changed your language preferences'));
182faf26 829
5011388f
NL
830 $currentConfig = $_SESSION['poche_user']->config;
831 $currentConfig['language'] = $_POST['language'];
182faf26 832
5011388f 833 $_SESSION['poche_user']->setConfig($currentConfig);
e145f767
NL
834
835 $this->emptyCache();
182faf26 836
5011388f 837 Tools::redirect('?view=config');
182faf26 838 }
df6afaf0
DS
839 /**
840 * get credentials from differents sources
841 * it redirects the user to the $referer link
842 * @return array
843 */
1810c13b
NL
844 private function credentials() {
845 if(isset($_SERVER['PHP_AUTH_USER'])) {
6af66b11 846 return array($_SERVER['PHP_AUTH_USER'],'php_auth',true);
1810c13b
NL
847 }
848 if(!empty($_POST['login']) && !empty($_POST['password'])) {
6af66b11 849 return array($_POST['login'],$_POST['password'],false);
1810c13b
NL
850 }
851 if(isset($_SERVER['REMOTE_USER'])) {
6af66b11 852 return array($_SERVER['REMOTE_USER'],'http_auth',true);
1810c13b 853 }
5cfafc61 854
6af66b11
MR
855 return array(false,false,false);
856 }
df6afaf0 857
07ee09f4
NL
858 /**
859 * checks if login & password are correct and save the user in session.
860 * it redirects the user to the $referer link
861 * @param string $referer the url to redirect after login
862 * @todo add the return value
863 * @return boolean
864 */
c765c367
NL
865 public function login($referer)
866 {
6af66b11 867 list($login,$password,$isauthenticated)=$this->credentials();
df6afaf0
DS
868 if($login === false || $password === false) {
869 $this->messages->add('e', _('login failed: you have to fill all fields'));
870 Tools::logm('login failed');
871 Tools::redirect();
872 }
873 if (!empty($login) && !empty($password)) {
6af66b11 874 $user = $this->store->login($login, Tools::encodeString($password . $login), $isauthenticated);
7ce7ec4c
NL
875 if ($user != array()) {
876 # Save login into Session
6af66b11
MR
877 $longlastingsession = isset($_POST['longlastingsession']);
878 $passwordTest = ($isauthenticated) ? $user['password'] : Tools::encodeString($password . $login);
879 Session::login($user['username'], $user['password'], $login, $passwordTest, $longlastingsession, array('poche_user' => new User($user)));
26929c08 880 $this->messages->add('s', _('welcome to your wallabag'));
8d3275be 881 Tools::logm('login successful');
c765c367
NL
882 Tools::redirect($referer);
883 }
8d3275be 884 $this->messages->add('e', _('login failed: bad login or password'));
c765c367
NL
885 Tools::logm('login failed');
886 Tools::redirect();
c765c367
NL
887 }
888 }
889
07ee09f4
NL
890 /**
891 * log out the poche user. It cleans the session.
892 * @todo add the return value
182faf26 893 * @return boolean
07ee09f4 894 */
c765c367
NL
895 public function logout()
896 {
7ce7ec4c 897 $this->user = array();
c765c367 898 Session::logout();
b916bcfc 899 Tools::logm('logout');
c765c367
NL
900 Tools::redirect();
901 }
902
07ee09f4
NL
903 /**
904 * import datas into your poche
182faf26 905 * @return boolean
07ee09f4 906 */
182faf26
MR
907 public function import() {
908
909 if ( isset($_FILES['file']) ) {
5ce39784
MR
910 Tools::logm('Import stated: parsing file');
911
182faf26
MR
912 // assume, that file is in json format
913 $str_data = file_get_contents($_FILES['file']['tmp_name']);
914 $data = json_decode($str_data, true);
915
916 if ( $data === null ) {
917 //not json - assume html
918 $html = new simple_html_dom();
919 $html->load_file($_FILES['file']['tmp_name']);
920 $data = array();
921 $read = 0;
922 foreach (array('ol','ul') as $list) {
923 foreach ($html->find($list) as $ul) {
86da3988
MR
924 foreach ($ul->find('li') as $li) {
925 $tmpEntry = array();
a8ef1f3f
MR
926 $a = $li->find('a');
927 $tmpEntry['url'] = $a[0]->href;
928 $tmpEntry['tags'] = $a[0]->tags;
929 $tmpEntry['is_read'] = $read;
930 if ($tmpEntry['url']) {
931 $data[] = $tmpEntry;
932 }
86da3988
MR
933 }
934 # the second <ol/ul> is for read links
935 $read = ((sizeof($data) && $read)?0:1);
182faf26
MR
936 }
937 }
63c35580 938 }
182faf26 939
a297fb1e
MR
940 //for readability structure
941 foreach ($data as $record) {
942 if (is_array($record)) {
943 $data[] = $record;
944 foreach ($record as $record2) {
945 if (is_array($record2)) {
86da3988 946 $data[] = $record2;
a297fb1e
MR
947 }
948 }
949 }
950 }
951
86da3988 952 $urlsInserted = array(); //urls of articles inserted
182faf26 953 foreach ($data as $record) {
a297fb1e 954 $url = trim( isset($record['article__url']) ? $record['article__url'] : (isset($record['url']) ? $record['url'] : '') );
86da3988 955 if ( $url and !in_array($url, $urlsInserted) ) {
182faf26
MR
956 $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'] : '');
a297fb1e
MR
958 $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) );
182faf26
MR
960 //insert new record
961 $id = $this->store->add($url, $title, $body, $this->user->getId(), $isFavorite, $isRead);
962 if ( $id ) {
86da3988
MR
963 $urlsInserted[] = $url; //add
964
182faf26 965 if ( isset($record['tags']) && trim($record['tags']) ) {
86da3988 966 //@TODO: set tags
182faf26
MR
967
968 }
969 }
970 }
971 }
972
86da3988 973 $i = sizeof($urlsInserted);
182faf26
MR
974 if ( $i > 0 ) {
975 $this->messages->add('s', _('Articles inserted: ').$i._('. Please note, that some may be marked as "read".'));
976 }
5ce39784 977 Tools::logm('Import of articles finished: '.$i.' articles added (w/o content if not provided).');
182faf26
MR
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.'));
5ce39784 988 Tools::logm('Import finished completely');
182faf26
MR
989 Tools::redirect();
990 }
991 else {
992 //if just inserted - don't download anything, download will start in next reload
993 if ( !isset($_FILES['file']) ) {
994 //download next batch
5ce39784 995 Tools::logm('Fetching next batch of articles...');
182faf26
MR
996 $items = $this->store->retrieveUnfetchedEntries($this->user->getId(), IMPORT_LIMIT);
997
0f859c6f 998 $purifier = $this->getPurifier();
182faf26
MR
999
1000 foreach ($items as $item) {
86da3988 1001 $url = new Url(base64_encode($item['url']));
5ce39784 1002 Tools::logm('Fetching article '.$item['id']);
86da3988 1003 $content = Tools::getPageContent($url);
182faf26 1004
86da3988
MR
1005 $title = (($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled'));
1006 $body = (($content['rss']['channel']['item']['description'] != '') ? $content['rss']['channel']['item']['description'] : _('Undefined'));
182faf26 1007
86da3988
MR
1008 //clean content to prevent xss attack
1009 $title = $purifier->purify($title);
1010 $body = $purifier->purify($body);
182faf26 1011
86da3988 1012 $this->store->updateContentAndTitle($item['id'], $title, $body, $this->user->getId());
5ce39784 1013 Tools::logm('Article '.$item['id'].' updated.');
182faf26
MR
1014 }
1015
63c35580 1016 }
182faf26
MR
1017 }
1018
1019 return array('includeImport'=>true, 'import'=>array('recordsDownloadRequired'=>$recordsDownloadRequired, 'recordsUnderDownload'=> IMPORT_LIMIT, 'delay'=> IMPORT_DELAY * 1000) );
63c35580 1020 }
c765c367 1021
07ee09f4
NL
1022 /**
1023 * export poche entries in json
1024 * @return json all poche entries
1025 */
a8ef1f3f
MR
1026 public function export() {
1027 $filename = "wallabag-export-".$this->user->getId()."-".date("Y-m-d").".json";
1028 header('Content-Disposition: attachment; filename='.$filename);
1029
1030 $entries = $this->store->retrieveAll($this->user->getId());
1031 echo $this->tpl->render('export.twig', array(
1032 'export' => Tools::renderJson($entries),
1033 ));
1034 Tools::logm('export view');
c765c367 1035 }
32520785 1036
07ee09f4 1037 /**
a3436d4c 1038 * Checks online the latest version of poche and cache it
07ee09f4
NL
1039 * @param string $which 'prod' or 'dev'
1040 * @return string latest $which version
1041 */
a8ef1f3f
MR
1042 private function getPocheVersion($which = 'prod') {
1043 $cache_file = CACHE . '/' . $which;
1044 $check_time = time();
1045
1046 # checks if the cached version file exists
1047 if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) {
1048 $version = file_get_contents($cache_file);
1049 $check_time = filemtime($cache_file);
1050 } else {
1051 $version = file_get_contents('http://static.wallabag.org/versions/' . $which);
1052 file_put_contents($cache_file, $version, LOCK_EX);
1053 }
1054 return array($version, $check_time);
32520785 1055 }
72c20a52
NL
1056
1057 public function generateToken()
1058 {
a8ef1f3f
MR
1059 if (ini_get('open_basedir') === '') {
1060 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1061 echo 'This is a server using Windows!';
1062 // alternative to /dev/urandom for Windows
1063 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
1064 } else {
1065 $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
72c20a52 1066 }
a8ef1f3f
MR
1067 }
1068 else {
1069 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
1070 }
72c20a52 1071
a8ef1f3f
MR
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();
72c20a52
NL
1078 }
1079
f778e472 1080 public function generateFeeds($token, $user_id, $tag_id, $type = 'home')
72c20a52 1081 {
f778e472 1082 $allowed_types = array('home', 'fav', 'archive', 'tag');
72c20a52
NL
1083 $config = $this->store->getConfigUser($user_id);
1084
17b2afef
NL
1085 if ($config == null) {
1086 die(_('User with this id (' . $user_id . ') does not exist.'));
1087 }
1088
72c20a52
NL
1089 if (!in_array($type, $allowed_types) ||
1090 $token != $config['token']) {
1091 die(_('Uh, there is a problem while generating feeds.'));
1092 }
1093 // Check the token
1094
9e7c840b 1095 $feed = new FeedWriter(RSS2);
2e4440c3 1096 $feed->setTitle('wallabag — ' . $type . ' feed');
72c20a52 1097 $feed->setLink(Tools::getPocheUrl());
223268c2
NL
1098 $feed->setChannelElement('pubDate', date(DATE_RSS , time()));
1099 $feed->setChannelElement('generator', 'wallabag');
1100 $feed->setDescription('wallabag ' . $type . ' elements');
72c20a52 1101
f778e472 1102 if ($type == 'tag') {
b89d5a2b 1103 $entries = $this->store->retrieveEntriesByTag($tag_id, $user_id);
f778e472
NL
1104 }
1105 else {
1106 $entries = $this->store->getEntriesByView($type, $user_id);
1107 }
1108
72c20a52
NL
1109 if (count($entries) > 0) {
1110 foreach ($entries as $entry) {
1111 $newItem = $feed->createNewItem();
0b57c682 1112 $newItem->setTitle($entry['title']);
f86784c2 1113 $newItem->setSource(Tools::getPocheUrl() . '?view=view&amp;id=' . $entry['id']);
ed02e38e 1114 $newItem->setLink($entry['url']);
72c20a52
NL
1115 $newItem->setDate(time());
1116 $newItem->setDescription($entry['content']);
1117 $feed->addItem($newItem);
1118 }
1119 }
1120
1121 $feed->genarateFeed();
1122 exit;
1123 }
6285e57c
NL
1124
1125 public function emptyCache() {
1126 $files = new RecursiveIteratorIterator(
1127 new RecursiveDirectoryIterator(CACHE, RecursiveDirectoryIterator::SKIP_DOTS),
1128 RecursiveIteratorIterator::CHILD_FIRST
1129 );
1130
1131 foreach ($files as $fileinfo) {
1132 $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
1133 $todo($fileinfo->getRealPath());
1134 }
1135
1136 Tools::logm('empty cache');
1137 $this->messages->add('s', _('Cache deleted.'));
1138 Tools::redirect();
1139 }
0f859c6f
MR
1140
1141 /**
1142 * return new purifier object with actual config
1143 */
1144 protected function getPurifier() {
1145 $config = HTMLPurifier_Config::createDefault();\r
1146 $config->set('Cache.SerializerPath', CACHE);\r
1147 $config->set('HTML.SafeIframe', true);\r
1148 $config->set('URI.SafeIframeRegexp', '%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/)%'); //allow YouTube and Vimeo$purifier = new HTMLPurifier($config);
1149\r
1150 return new HTMLPurifier($config);
1151 }
87090d8a 1152
1153 /**
1154 * handle epub
1155 */
1156 public function createEpub() {
7ec445b0 1157
1158 switch ($_GET['method']) {
1159 case 'id':
87090d8a 1160 $entryID = filter_var($_GET['id'],FILTER_SANITIZE_NUMBER_INT);
1161 $entry = $this->store->retrieveOneById($entryID, $this->user->getId());
1162 $entries = array($entry);
f2b6b4e2 1163 $bookTitle = $entry['title'];
f3f0b113 1164 $bookFileName = substr($bookTitle, 0, 200);
7ec445b0 1165 break;
1166 case 'all':
1167 $entries = $this->store->retrieveAll($this->user->getId());
f3f0b113 1168 $bookTitle = sprintf(_('All my articles on '), date(_('d.m.y'))); #translatable because each country has it's own date format system
1169 $bookFileName = _('Allarticles') . date(_('dmY'));
7ec445b0 1170 break;
1171 case 'tag':
1172 $tag = filter_var($_GET['tag'],FILTER_SANITIZE_STRING);
1173 $tags_id = $this->store->retrieveAllTags($this->user->getId(),$tag);
1174 $tag_id = $tags_id[0]["id"]; // we take the first result, which is supposed to match perfectly. There must be a workaround.
1175 $entries = $this->store->retrieveEntriesByTag($tag_id,$this->user->getId());
f3f0b113 1176 $bookTitle = sprintf(_('Articles tagged %s'),$tag);
1177 $bookFileName = substr(sprintf(_('Tag %s'),$tag), 0, 200);
7ec445b0 1178 break;
1179 case 'category':
1180 $category = filter_var($_GET['category'],FILTER_SANITIZE_STRING);
1181 $entries = $this->store->getEntriesByView($category,$this->user->getId());
f3f0b113 1182 $bookTitle = sprintf(_('All articles in category %s'), $category);
1183 $bookFileName = substr(sprintf(_('Category %s'),$category), 0, 200);
7ec445b0 1184 break;
1185 case 'search':
1186 $search = filter_var($_GET['search'],FILTER_SANITIZE_STRING);
1187 $entries = $this->store->search($search,$this->user->getId());
f3f0b113 1188 $bookTitle = sprintf(_('All articles for search %s'), $search);
1189 $bookFileName = substr(sprintf(_('Search %s'), $search), 0, 200);
7ec445b0 1190 break;
1191 case 'default':
1192 die(_('Uh, there is a problem while generating epub.'));
1193
87090d8a 1194 }
7ec445b0 1195
87090d8a 1196 $content_start =
f2b6b4e2 1197 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1198 . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n"
1199 . "<head>"
1200 . "<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n"
1201 . "<title>wallabag articles book</title>\n"
87090d8a 1202 . "</head>\n"
1203 . "<body>\n";
1204
1205 $bookEnd = "</body>\n</html>\n";
1206
7ec445b0 1207 $log = new Logger("wallabag", TRUE);
87090d8a 1208 $fileDir = CACHE;
1209
1210
7ec445b0 1211 $book = new EPub(EPub::BOOK_VERSION_EPUB3);
87090d8a 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
34acb02c 1219 $book->setTitle(_('wallabag\'s articles'));
87090d8a 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.
34acb02c 1222 $book->setDescription(_("Some articles saved on my wallabag"));
87090d8a 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");
4877836b 1230 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "wallabag");
87090d8a 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";
7ec445b0 1233
4877836b 1234 $log->logLine("Add Cover");
4877836b 1235
f2b6b4e2 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);
7ec445b0 1239
e212e6b1 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;
f2b6b4e2 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();
87090d8a 1246
e212e6b1 1247 foreach ($entries as $entry) { //set tags as subjects
87090d8a 1248 $tags = $this->store->retrieveTagsByEntry($entry['id']);
1249 foreach ($tags as $tag) {
f2b6b4e2 1250 $book->setSubject($tag['value']);
87090d8a 1251 }
1252
1253 $log->logLine("Set up parameters");
1254
87090d8a 1255 $chapter = $content_start . $entry['content'] . $bookEnd;
7ec445b0 1256 $book->addChapter($entry['title'], htmlspecialchars($entry['title']) . ".html", $chapter, true, EPub::EXTERNAL_REF_ADD);
4877836b 1257 $log->logLine("Added chapter " . $entry['title']);
f2b6b4e2 1258 }
87090d8a 1259
7ec445b0 1260 if (DEBUG_POCHE) {
e212e6b1 1261 $epuplog = $book->getLog();
1262 $book->addChapter("Log", "Log.html", $content_start . $log->getLog() . "\n</pre>" . $bookEnd); // log generation
87090d8a 1263 }
1264 $book->finalize();
f3f0b113 1265 $zipData = $book->sendBook($bookFileName);
87090d8a 1266 }
df6afaf0 1267}