]> git.immae.eu Git - github/wallabag/wallabag.git/blob - inc/poche/Poche.class.php
WHAT. A. BIG. REFACTOR. + new license (we moved to MIT one)
[github/wallabag/wallabag.git] / inc / poche / Poche.class.php
1 <?php
2 /**
3 * wallabag, self hostable application allowing you to not miss any content anymore
4 *
5 * @category wallabag
6 * @author Nicolas LÅ“uillet <nicolas@loeuillet.org>
7 * @copyright 2013
8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */
10
11 class Poche
12 {
13 /**
14 * @var User
15 */
16 public $user;
17 /**
18 * @var Database
19 */
20 public $store;
21 /**
22 * @var Template
23 */
24 public $tpl;
25 /**
26 * @var Language
27 */
28 public $language;
29 /**
30 * @var Routing
31 */
32 public $routing;
33 /**
34 * @var Messages
35 */
36 public $messages;
37 /**
38 * @var Paginator
39 */
40 public $pagination;
41
42 public function __construct()
43 {
44 $this->init();
45 }
46
47 private function init()
48 {
49 Tools::initPhp();
50
51 $pocheUser = Session::getParam('poche_user');
52
53 if ($pocheUser && $pocheUser != array()) {
54 $this->user = $pocheUser;
55 } else {
56 // fake user, just for install & login screens
57 $this->user = new User();
58 $this->user->setConfig($this->getDefaultConfig());
59 }
60
61 $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p');
62 $this->language = new Language($this);
63 $this->tpl = new Template($this);
64 $this->store = new Database();
65 $this->messages = new Messages();
66 $this->routing = new Routing($this);
67 }
68
69 public function run()
70 {
71 $this->routing->run();
72 }
73
74 /**
75 * Creates a new user
76 */
77 public function createNewUser()
78 {
79 if (isset($_GET['newuser'])){
80 if ($_POST['newusername'] != "" && $_POST['password4newuser'] != ""){
81 $newusername = filter_var($_POST['newusername'], FILTER_SANITIZE_STRING);
82 if (!$this->store->userExists($newusername)){
83 if ($this->store->install($newusername, Tools::encodeString($_POST['password4newuser'] . $newusername))) {
84 Tools::logm('The new user '.$newusername.' has been installed');
85 $this->messages->add('s', sprintf(_('The new user %s has been installed. Do you want to <a href="?logout">logout ?</a>'),$newusername));
86 Tools::redirect();
87 }
88 else {
89 Tools::logm('error during adding new user');
90 Tools::redirect();
91 }
92 }
93 else {
94 $this->messages->add('e', sprintf(_('Error : An user with the name %s already exists !'),$newusername));
95 Tools::logm('An user with the name '.$newusername.' already exists !');
96 Tools::redirect();
97 }
98 }
99 }
100 }
101
102 /**
103 * Delete an existing user
104 */
105 public function deleteUser()
106 {
107 if (isset($_GET['deluser'])){
108 if ($this->store->listUsers() > 1) {
109 if (Tools::encodeString($_POST['password4deletinguser'].$this->user->getUsername()) == $this->store->getUserPassword($this->user->getId())) {
110 $username = $this->user->getUsername();
111 $this->store->deleteUserConfig($this->user->getId());
112 Tools::logm('The configuration for user '. $username .' has been deleted !');
113 $this->store->deleteTagsEntriesAndEntries($this->user->getId());
114 Tools::logm('The entries for user '. $username .' has been deleted !');
115 $this->store->deleteUser($this->user->getId());
116 Tools::logm('User '. $username .' has been completely deleted !');
117 Session::logout();
118 Tools::logm('logout');
119 Tools::redirect();
120 $this->messages->add('s', sprintf(_('User %s has been successfully deleted !'),$newusername));
121 }
122 else {
123 Tools::logm('Bad password !');
124 $this->messages->add('e', _('Error : The password is wrong !'));
125 }
126 }
127 else {
128 Tools::logm('Only user !');
129 $this->messages->add('e', _('Error : You are the only user, you cannot delete your account !'));
130 }
131 }
132 }
133
134 public function getDefaultConfig()
135 {
136 return array(
137 'pager' => PAGINATION,
138 'language' => LANG,
139 'theme' => DEFAULT_THEME
140 );
141 }
142
143 /**
144 * Call action (mark as fav, archive, delete, etc.)
145 */
146 public function action($action, Url $url, $id = 0, $import = FALSE, $autoclose = FALSE, $tags = null)
147 {
148 switch ($action)
149 {
150 case 'add':
151 $content = Tools::getPageContent($url);
152 $title = ($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled');
153 $body = $content['rss']['channel']['item']['description'];
154
155 // clean content from prevent xss attack
156 $purifier = $this->getPurifier();
157 $title = $purifier->purify($title);
158 $body = $purifier->purify($body);
159
160 //search for possible duplicate
161 $duplicate = NULL;
162 $duplicate = $this->store->retrieveOneByURL($url->getUrl(), $this->user->getId());
163
164 $last_id = $this->store->add($url->getUrl(), $title, $body, $this->user->getId());
165 if ( $last_id ) {
166 Tools::logm('add link ' . $url->getUrl());
167 if (DOWNLOAD_PICTURES) {
168 $content = Picture::filterPicture($body, $url->getUrl(), $last_id);
169 Tools::logm('updating content article');
170 $this->store->updateContent($last_id, $content, $this->user->getId());
171 }
172
173 if ($duplicate != NULL) {
174 // 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
175 Tools::logm('link ' . $url->getUrl() . ' is a duplicate');
176 // 1) - preserve tags and favorite, then drop old entry
177 $this->store->reassignTags($duplicate['id'], $last_id);
178 if ($duplicate['is_fav']) {
179 $this->store->favoriteById($last_id, $this->user->getId());
180 }
181 if ($this->store->deleteById($duplicate['id'], $this->user->getId())) {
182 Tools::logm('previous link ' . $url->getUrl() .' entry deleted');
183 }
184 }
185
186 $this->messages->add('s', _('the link has been added successfully'));
187 }
188 else {
189 $this->messages->add('e', _('error during insertion : the link wasn\'t added'));
190 Tools::logm('error during insertion : the link wasn\'t added ' . $url->getUrl());
191 }
192
193 if ($autoclose == TRUE) {
194 Tools::redirect('?view=home');
195 } else {
196 Tools::redirect('?view=home&closewin=true');
197 }
198 break;
199 case 'delete':
200 $msg = 'delete link #' . $id;
201 if ($this->store->deleteById($id, $this->user->getId())) {
202 if (DOWNLOAD_PICTURES) {
203 Picture::removeDirectory(ABS_PATH . $id);
204 }
205 $this->messages->add('s', _('the link has been deleted successfully'));
206 }
207 else {
208 $this->messages->add('e', _('the link wasn\'t deleted'));
209 $msg = 'error : can\'t delete link #' . $id;
210 }
211 Tools::logm($msg);
212 Tools::redirect('?');
213 break;
214 case 'toggle_fav' :
215 $this->store->favoriteById($id, $this->user->getId());
216 Tools::logm('mark as favorite link #' . $id);
217 if ( Tools::isAjaxRequest() ) {
218 echo 1;
219 exit;
220 }
221 else {
222 Tools::redirect();
223 }
224 break;
225 case 'toggle_archive' :
226 $this->store->archiveById($id, $this->user->getId());
227 Tools::logm('archive link #' . $id);
228 if ( Tools::isAjaxRequest() ) {
229 echo 1;
230 exit;
231 }
232 else {
233 Tools::redirect();
234 }
235 break;
236 case 'archive_all' :
237 $this->store->archiveAll($this->user->getId());
238 Tools::logm('archive all links');
239 Tools::redirect();
240 break;
241 case 'add_tag' :
242 if (isset($_GET['search'])) {
243 //when we want to apply a tag to a search
244 $tags = array($_GET['search']);
245 $allentry_ids = $this->store->search($tags[0], $this->user->getId());
246 $entry_ids = array();
247 foreach ($allentry_ids as $eachentry) {
248 $entry_ids[] = $eachentry[0];
249 }
250 } else { //add a tag to a single article
251 $tags = explode(',', $_POST['value']);
252 $entry_ids = array($_POST['entry_id']);
253 }
254 foreach($entry_ids as $entry_id) {
255 $entry = $this->store->retrieveOneById($entry_id, $this->user->getId());
256 if (!$entry) {
257 $this->messages->add('e', _('Article not found!'));
258 Tools::logm('error : article not found');
259 Tools::redirect();
260 }
261 //get all already set tags to preven duplicates
262 $already_set_tags = array();
263 $entry_tags = $this->store->retrieveTagsByEntry($entry_id);
264 foreach ($entry_tags as $tag) {
265 $already_set_tags[] = $tag['value'];
266 }
267 foreach($tags as $key => $tag_value) {
268 $value = trim($tag_value);
269 if ($value && !in_array($value, $already_set_tags)) {
270 $tag = $this->store->retrieveTagByValue($value);
271 if (is_null($tag)) {
272 # we create the tag
273 $tag = $this->store->createTag($value);
274 $sequence = '';
275 if (STORAGE == 'postgres') {
276 $sequence = 'tags_id_seq';
277 }
278 $tag_id = $this->store->getLastId($sequence);
279 }
280 else {
281 $tag_id = $tag['id'];
282 }
283
284 # we assign the tag to the article
285 $this->store->setTagToEntry($tag_id, $entry_id);
286 }
287 }
288 }
289 $this->messages->add('s', _('The tag has been applied successfully'));
290 Tools::logm('The tag has been applied successfully');
291 Tools::redirect();
292 break;
293 case 'remove_tag' :
294 $tag_id = $_GET['tag_id'];
295 $entry = $this->store->retrieveOneById($id, $this->user->getId());
296 if (!$entry) {
297 $this->messages->add('e', _('Article not found!'));
298 Tools::logm('error : article not found');
299 Tools::redirect();
300 }
301 $this->store->removeTagForEntry($id, $tag_id);
302 Tools::logm('tag entry deleted');
303 if ($this->store->cleanUnusedTag($tag_id)) {
304 Tools::logm('tag deleted');
305 }
306 $this->messages->add('s', _('The tag has been successfully deleted'));
307 Tools::redirect();
308 break;
309 default:
310 break;
311 }
312 }
313
314 function displayView($view, $id = 0)
315 {
316 $tpl_vars = array();
317
318 switch ($view)
319 {
320 case 'config':
321 $dev_infos = $this->getPocheVersion('dev');
322 $dev = trim($dev_infos[0]);
323 $check_time_dev = date('d-M-Y H:i', $dev_infos[1]);
324 $prod_infos = $this->getPocheVersion('prod');
325 $prod = trim($prod_infos[0]);
326 $check_time_prod = date('d-M-Y H:i', $prod_infos[1]);
327 $compare_dev = version_compare(POCHE, $dev);
328 $compare_prod = version_compare(POCHE, $prod);
329 $themes = $this->tpl->getInstalledThemes();
330 $languages = $this->language->getInstalledLanguages();
331 $token = $this->user->getConfigValue('token');
332 $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false;
333 $only_user = ($this->store->listUsers() > 1) ? false : true;
334 $tpl_vars = array(
335 'themes' => $themes,
336 'languages' => $languages,
337 'dev' => $dev,
338 'prod' => $prod,
339 'check_time_dev' => $check_time_dev,
340 'check_time_prod' => $check_time_prod,
341 'compare_dev' => $compare_dev,
342 'compare_prod' => $compare_prod,
343 'token' => $token,
344 'user_id' => $this->user->getId(),
345 'http_auth' => $http_auth,
346 'only_user' => $only_user
347 );
348 Tools::logm('config view');
349 break;
350 case 'edit-tags':
351 # tags
352 $entry = $this->store->retrieveOneById($id, $this->user->getId());
353 if (!$entry) {
354 $this->messages->add('e', _('Article not found!'));
355 Tools::logm('error : article not found');
356 Tools::redirect();
357 }
358 $tags = $this->store->retrieveTagsByEntry($id);
359 $tpl_vars = array(
360 'entry_id' => $id,
361 'tags' => $tags,
362 'entry' => $entry,
363 );
364 break;
365 case 'tags':
366 $token = $this->user->getConfigValue('token');
367 //if term is set - search tags for this term
368 $term = Tools::checkVar('term');
369 $tags = $this->store->retrieveAllTags($this->user->getId(), $term);
370 if (Tools::isAjaxRequest()) {
371 $result = array();
372 foreach ($tags as $tag) {
373 $result[] = $tag['value'];
374 }
375 echo json_encode($result);
376 exit;
377 }
378 $tpl_vars = array(
379 'token' => $token,
380 'user_id' => $this->user->getId(),
381 'tags' => $tags,
382 );
383 break;
384 case 'search':
385 if (isset($_GET['search'])) {
386 $search = filter_var($_GET['search'], FILTER_SANITIZE_STRING);
387 $tpl_vars['entries'] = $this->store->search($search, $this->user->getId());
388 $count = count($tpl_vars['entries']);
389 $this->pagination->set_total($count);
390 $page_links = str_replace(array('previous', 'next'), array(_('previous'), _('next')),
391 $this->pagination->page_links('?view=' . $view . '?search=' . $search . '&sort=' . $_SESSION['sort'] . '&' ));
392 $tpl_vars['page_links'] = $page_links;
393 $tpl_vars['nb_results'] = $count;
394 $tpl_vars['search_term'] = $search;
395 }
396 break;
397 case 'view':
398 $entry = $this->store->retrieveOneById($id, $this->user->getId());
399 if ($entry != NULL) {
400 Tools::logm('view link #' . $id);
401 $content = $entry['content'];
402 if (function_exists('tidy_parse_string')) {
403 $tidy = tidy_parse_string($content, array('indent'=>true, 'show-body-only' => true), 'UTF8');
404 $tidy->cleanRepair();
405 $content = $tidy->value;
406 }
407
408 # flattr checking
409 $flattr = new FlattrItem();
410 $flattr->checkItem($entry['url'], $entry['id']);
411
412 # tags
413 $tags = $this->store->retrieveTagsByEntry($entry['id']);
414
415 $tpl_vars = array(
416 'entry' => $entry,
417 'content' => $content,
418 'flattr' => $flattr,
419 'tags' => $tags
420 );
421 }
422 else {
423 Tools::logm('error in view call : entry is null');
424 }
425 break;
426 default: # home, favorites, archive and tag views
427 $tpl_vars = array(
428 'entries' => '',
429 'page_links' => '',
430 'nb_results' => '',
431 'listmode' => (isset($_COOKIE['listmode']) ? true : false),
432 );
433
434 //if id is given - we retrieve entries by tag: id is tag id
435 if ($id) {
436 $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId());
437 $tpl_vars['id'] = intval($id);
438 }
439
440 $count = $this->store->getEntriesByViewCount($view, $this->user->getId(), $id);
441
442 if ($count > 0) {
443 $this->pagination->set_total($count);
444 $page_links = str_replace(array('previous', 'next'), array(_('previous'), _('next')),
445 $this->pagination->page_links('?view=' . $view . '&sort=' . $_SESSION['sort'] . (($id)?'&id='.$id:'') . '&' ));
446 $tpl_vars['entries'] = $this->store->getEntriesByView($view, $this->user->getId(), $this->pagination->get_limit(), $id);
447 $tpl_vars['page_links'] = $page_links;
448 $tpl_vars['nb_results'] = $count;
449 }
450 Tools::logm('display ' . $view . ' view');
451 break;
452 }
453
454 return $tpl_vars;
455 }
456
457 /**
458 * update the password of the current user.
459 * if MODE_DEMO is TRUE, the password can't be updated.
460 * @todo add the return value
461 * @todo set the new password in function header like this updatePassword($newPassword)
462 * @return boolean
463 */
464 public function updatePassword()
465 {
466 if (MODE_DEMO) {
467 $this->messages->add('i', _('in demo mode, you can\'t update your password'));
468 Tools::logm('in demo mode, you can\'t do this');
469 Tools::redirect('?view=config');
470 }
471 else {
472 if (isset($_POST['password']) && isset($_POST['password_repeat'])) {
473 if ($_POST['password'] == $_POST['password_repeat'] && $_POST['password'] != "") {
474 $this->messages->add('s', _('your password has been updated'));
475 $this->store->updatePassword($this->user->getId(), Tools::encodeString($_POST['password'] . $this->user->getUsername()));
476 Session::logout();
477 Tools::logm('password updated');
478 Tools::redirect();
479 }
480 else {
481 $this->messages->add('e', _('the two fields have to be filled & the password must be the same in the two fields'));
482 Tools::redirect('?view=config');
483 }
484 }
485 }
486 }
487
488 /**
489 * get credentials from differents sources
490 * it redirects the user to the $referer link
491 * @return array
492 */
493 private function credentials() {
494 if(isset($_SERVER['PHP_AUTH_USER'])) {
495 return array($_SERVER['PHP_AUTH_USER'],'php_auth',true);
496 }
497 if(!empty($_POST['login']) && !empty($_POST['password'])) {
498 return array($_POST['login'],$_POST['password'],false);
499 }
500 if(isset($_SERVER['REMOTE_USER'])) {
501 return array($_SERVER['REMOTE_USER'],'http_auth',true);
502 }
503
504 return array(false,false,false);
505 }
506
507 /**
508 * checks if login & password are correct and save the user in session.
509 * it redirects the user to the $referer link
510 * @param string $referer the url to redirect after login
511 * @todo add the return value
512 * @return boolean
513 */
514 public function login($referer)
515 {
516 list($login,$password,$isauthenticated)=$this->credentials();
517 if($login === false || $password === false) {
518 $this->messages->add('e', _('login failed: you have to fill all fields'));
519 Tools::logm('login failed');
520 Tools::redirect();
521 }
522 if (!empty($login) && !empty($password)) {
523 $user = $this->store->login($login, Tools::encodeString($password . $login), $isauthenticated);
524 if ($user != array()) {
525 # Save login into Session
526 $longlastingsession = isset($_POST['longlastingsession']);
527 $passwordTest = ($isauthenticated) ? $user['password'] : Tools::encodeString($password . $login);
528 Session::login($user['username'], $user['password'], $login, $passwordTest, $longlastingsession, array('poche_user' => new User($user)));
529 $this->messages->add('s', _('welcome to your wallabag'));
530 Tools::logm('login successful');
531 Tools::redirect($referer);
532 }
533 $this->messages->add('e', _('login failed: bad login or password'));
534 Tools::logm('login failed');
535 Tools::redirect();
536 }
537 }
538
539 /**
540 * log out the poche user. It cleans the session.
541 * @todo add the return value
542 * @return boolean
543 */
544 public function logout()
545 {
546 $this->user = array();
547 Session::logout();
548 Tools::logm('logout');
549 Tools::redirect();
550 }
551
552 /**
553 * import datas into your poche
554 * @return boolean
555 */
556 public function import() {
557
558 if ( isset($_FILES['file']) ) {
559 Tools::logm('Import stated: parsing file');
560
561 // assume, that file is in json format
562 $str_data = file_get_contents($_FILES['file']['tmp_name']);
563 $data = json_decode($str_data, true);
564
565 if ( $data === null ) {
566 //not json - assume html
567 $html = new simple_html_dom();
568 $html->load_file($_FILES['file']['tmp_name']);
569 $data = array();
570 $read = 0;
571 foreach (array('ol','ul') as $list) {
572 foreach ($html->find($list) as $ul) {
573 foreach ($ul->find('li') as $li) {
574 $tmpEntry = array();
575 $a = $li->find('a');
576 $tmpEntry['url'] = $a[0]->href;
577 $tmpEntry['tags'] = $a[0]->tags;
578 $tmpEntry['is_read'] = $read;
579 if ($tmpEntry['url']) {
580 $data[] = $tmpEntry;
581 }
582 }
583 # the second <ol/ul> is for read links
584 $read = ((sizeof($data) && $read)?0:1);
585 }
586 }
587 }
588
589 //for readability structure
590 foreach ($data as $record) {
591 if (is_array($record)) {
592 $data[] = $record;
593 foreach ($record as $record2) {
594 if (is_array($record2)) {
595 $data[] = $record2;
596 }
597 }
598 }
599 }
600
601 $urlsInserted = array(); //urls of articles inserted
602 foreach ($data as $record) {
603 $url = trim( isset($record['article__url']) ? $record['article__url'] : (isset($record['url']) ? $record['url'] : '') );
604 if ( $url and !in_array($url, $urlsInserted) ) {
605 $title = (isset($record['title']) ? $record['title'] : _('Untitled - Import - ').'</a> <a href="./?import">'._('click to finish import').'</a><a>');
606 $body = (isset($record['content']) ? $record['content'] : '');
607 $isRead = (isset($record['is_read']) ? intval($record['is_read']) : (isset($record['archive'])?intval($record['archive']):0));
608 $isFavorite = (isset($record['is_fav']) ? intval($record['is_fav']) : (isset($record['favorite'])?intval($record['favorite']):0) );
609 //insert new record
610 $id = $this->store->add($url, $title, $body, $this->user->getId(), $isFavorite, $isRead);
611 if ( $id ) {
612 $urlsInserted[] = $url; //add
613
614 if ( isset($record['tags']) && trim($record['tags']) ) {
615 //@TODO: set tags
616
617 }
618 }
619 }
620 }
621
622 $i = sizeof($urlsInserted);
623 if ( $i > 0 ) {
624 $this->messages->add('s', _('Articles inserted: ').$i._('. Please note, that some may be marked as "read".'));
625 }
626 Tools::logm('Import of articles finished: '.$i.' articles added (w/o content if not provided).');
627 }
628 //file parsing finished here
629
630 //now download article contents if any
631
632 //check if we need to download any content
633 $recordsDownloadRequired = $this->store->retrieveUnfetchedEntriesCount($this->user->getId());
634 if ( $recordsDownloadRequired == 0 ) {
635 //nothing to download
636 $this->messages->add('s', _('Import finished.'));
637 Tools::logm('Import finished completely');
638 Tools::redirect();
639 }
640 else {
641 //if just inserted - don't download anything, download will start in next reload
642 if ( !isset($_FILES['file']) ) {
643 //download next batch
644 Tools::logm('Fetching next batch of articles...');
645 $items = $this->store->retrieveUnfetchedEntries($this->user->getId(), IMPORT_LIMIT);
646
647 $purifier = $this->getPurifier();
648
649 foreach ($items as $item) {
650 $url = new Url(base64_encode($item['url']));
651 Tools::logm('Fetching article '.$item['id']);
652 $content = Tools::getPageContent($url);
653
654 $title = (($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled'));
655 $body = (($content['rss']['channel']['item']['description'] != '') ? $content['rss']['channel']['item']['description'] : _('Undefined'));
656
657 //clean content to prevent xss attack
658 $title = $purifier->purify($title);
659 $body = $purifier->purify($body);
660
661 $this->store->updateContentAndTitle($item['id'], $title, $body, $this->user->getId());
662 Tools::logm('Article '.$item['id'].' updated.');
663 }
664
665 }
666 }
667
668 return array('includeImport'=>true, 'import'=>array('recordsDownloadRequired'=>$recordsDownloadRequired, 'recordsUnderDownload'=> IMPORT_LIMIT, 'delay'=> IMPORT_DELAY * 1000) );
669 }
670
671 /**
672 * export poche entries in json
673 * @return json all poche entries
674 */
675 public function export() {
676 $filename = "wallabag-export-".$this->user->getId()."-".date("Y-m-d").".json";
677 header('Content-Disposition: attachment; filename='.$filename);
678
679 $entries = $this->store->retrieveAll($this->user->getId());
680 echo $this->tpl->render('export.twig', array(
681 'export' => Tools::renderJson($entries),
682 ));
683 Tools::logm('export view');
684 }
685
686 /**
687 * Checks online the latest version of poche and cache it
688 * @param string $which 'prod' or 'dev'
689 * @return string latest $which version
690 */
691 private function getPocheVersion($which = 'prod') {
692 $cache_file = CACHE . '/' . $which;
693 $check_time = time();
694
695 # checks if the cached version file exists
696 if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) {
697 $version = file_get_contents($cache_file);
698 $check_time = filemtime($cache_file);
699 } else {
700 $version = file_get_contents('http://static.wallabag.org/versions/' . $which);
701 file_put_contents($cache_file, $version, LOCK_EX);
702 }
703 return array($version, $check_time);
704 }
705
706 public function generateToken()
707 {
708 if (ini_get('open_basedir') === '') {
709 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
710 echo 'This is a server using Windows!';
711 // alternative to /dev/urandom for Windows
712 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
713 } else {
714 $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
715 }
716 }
717 else {
718 $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
719 }
720
721 $token = str_replace('+', '', $token);
722 $this->store->updateUserConfig($this->user->getId(), 'token', $token);
723 $currentConfig = $_SESSION['poche_user']->config;
724 $currentConfig['token'] = $token;
725 $_SESSION['poche_user']->setConfig($currentConfig);
726 Tools::redirect();
727 }
728
729 public function generateFeeds($token, $user_id, $tag_id, $type = 'home')
730 {
731 $allowed_types = array('home', 'fav', 'archive', 'tag');
732 $config = $this->store->getConfigUser($user_id);
733
734 if ($config == null) {
735 die(sprintf(_('User with this id (%d) does not exist.'), $user_id));
736 }
737
738 if (!in_array($type, $allowed_types) || $token != $config['token']) {
739 die(_('Uh, there is a problem while generating feeds.'));
740 }
741 // Check the token
742
743 $feed = new FeedWriter(RSS2);
744 $feed->setTitle('wallabag — ' . $type . ' feed');
745 $feed->setLink(Tools::getPocheUrl());
746 $feed->setChannelElement('pubDate', date(DATE_RSS , time()));
747 $feed->setChannelElement('generator', 'wallabag');
748 $feed->setDescription('wallabag ' . $type . ' elements');
749
750 if ($type == 'tag') {
751 $entries = $this->store->retrieveEntriesByTag($tag_id, $user_id);
752 }
753 else {
754 $entries = $this->store->getEntriesByView($type, $user_id);
755 }
756
757 if (count($entries) > 0) {
758 foreach ($entries as $entry) {
759 $newItem = $feed->createNewItem();
760 $newItem->setTitle($entry['title']);
761 $newItem->setSource(Tools::getPocheUrl() . '?view=view&amp;id=' . $entry['id']);
762 $newItem->setLink($entry['url']);
763 $newItem->setDate(time());
764 $newItem->setDescription($entry['content']);
765 $feed->addItem($newItem);
766 }
767 }
768
769 $feed->genarateFeed();
770 exit;
771 }
772
773 public function emptyCache() {
774 $files = new RecursiveIteratorIterator(
775 new RecursiveDirectoryIterator(CACHE, RecursiveDirectoryIterator::SKIP_DOTS),
776 RecursiveIteratorIterator::CHILD_FIRST
777 );
778
779 foreach ($files as $fileinfo) {
780 $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
781 $todo($fileinfo->getRealPath());
782 }
783
784 Tools::logm('empty cache');
785 $this->messages->add('s', _('Cache deleted.'));
786 Tools::redirect();
787 }
788
789 /**
790 * return new purifier object with actual config
791 */
792 protected function getPurifier() {
793 $config = HTMLPurifier_Config::createDefault();
794 $config->set('Cache.SerializerPath', CACHE);
795 $config->set('HTML.SafeIframe', true);
796
797 //allow YouTube, Vimeo and dailymotion videos
798 $config->set('URI.SafeIframeRegexp', '%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/|www\.dailymotion\.com/embed/video/)%');
799
800 return new HTMLPurifier($config);
801 }
802
803 /**
804 * handle epub
805 */
806 public function createEpub() {
807
808 switch ($_GET['method']) {
809 case 'id':
810 $entryID = filter_var($_GET['id'],FILTER_SANITIZE_NUMBER_INT);
811 $entry = $this->store->retrieveOneById($entryID, $this->user->getId());
812 $entries = array($entry);
813 $bookTitle = $entry['title'];
814 $bookFileName = substr($bookTitle, 0, 200);
815 break;
816 case 'all':
817 $entries = $this->store->retrieveAll($this->user->getId());
818 $bookTitle = sprintf(_('All my articles on '), date(_('d.m.y'))); #translatable because each country has it's own date format system
819 $bookFileName = _('Allarticles') . date(_('dmY'));
820 break;
821 case 'tag':
822 $tag = filter_var($_GET['tag'],FILTER_SANITIZE_STRING);
823 $tags_id = $this->store->retrieveAllTags($this->user->getId(),$tag);
824 $tag_id = $tags_id[0]["id"]; // we take the first result, which is supposed to match perfectly. There must be a workaround.
825 $entries = $this->store->retrieveEntriesByTag($tag_id,$this->user->getId());
826 $bookTitle = sprintf(_('Articles tagged %s'),$tag);
827 $bookFileName = substr(sprintf(_('Tag %s'),$tag), 0, 200);
828 break;
829 case 'category':
830 $category = filter_var($_GET['category'],FILTER_SANITIZE_STRING);
831 $entries = $this->store->getEntriesByView($category,$this->user->getId());
832 $bookTitle = sprintf(_('All articles in category %s'), $category);
833 $bookFileName = substr(sprintf(_('Category %s'),$category), 0, 200);
834 break;
835 case 'search':
836 $search = filter_var($_GET['search'],FILTER_SANITIZE_STRING);
837 $entries = $this->store->search($search,$this->user->getId());
838 $bookTitle = sprintf(_('All articles for search %s'), $search);
839 $bookFileName = substr(sprintf(_('Search %s'), $search), 0, 200);
840 break;
841 case 'default':
842 die(_('Uh, there is a problem while generating epub.'));
843
844 }
845
846 $content_start =
847 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
848 . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n"
849 . "<head>"
850 . "<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n"
851 . "<title>wallabag articles book</title>\n"
852 . "</head>\n"
853 . "<body>\n";
854
855 $bookEnd = "</body>\n</html>\n";
856
857 $log = new Logger("wallabag", TRUE);
858 $fileDir = CACHE;
859
860 $book = new EPub(EPub::BOOK_VERSION_EPUB3, DEBUG_POCHE);
861 $log->logLine("new EPub()");
862 $log->logLine("EPub class version: " . EPub::VERSION);
863 $log->logLine("EPub Req. Zip version: " . EPub::REQ_ZIP_VERSION);
864 $log->logLine("Zip version: " . Zip::VERSION);
865 $log->logLine("getCurrentServerURL: " . $book->getCurrentServerURL());
866 $log->logLine("getCurrentPageURL..: " . $book->getCurrentPageURL());
867
868 $book->setTitle(_('wallabag\'s articles'));
869 $book->setIdentifier("http://$_SERVER[HTTP_HOST]", EPub::IDENTIFIER_URI); // Could also be the ISBN number, prefered for published books, or a UUID.
870 //$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.
871 $book->setDescription(_("Some articles saved on my wallabag"));
872 $book->setAuthor("wallabag","wallabag");
873 $book->setPublisher("wallabag","wallabag"); // I hope this is a non existant address :)
874 $book->setDate(time()); // Strictly not needed as the book date defaults to time().
875 //$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.
876 $book->setSourceURL("http://$_SERVER[HTTP_HOST]");
877
878 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "PHP");
879 $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "wallabag");
880
881 $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";
882
883 $log->logLine("Add Cover");
884
885 $fullTitle = "<h1> " . $bookTitle . "</h1>\n";
886
887 $book->setCoverImage("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png", $fullTitle);
888
889 $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;
890
891 //$book->addChapter("Table of Contents", "TOC.xhtml", NULL, false, EPub::EXTERNAL_REF_IGNORE);
892 $book->addChapter("Notices", "Cover2.html", $cover);
893
894 $book->buildTOC();
895
896 foreach ($entries as $entry) { //set tags as subjects
897 $tags = $this->store->retrieveTagsByEntry($entry['id']);
898 foreach ($tags as $tag) {
899 $book->setSubject($tag['value']);
900 }
901
902 $log->logLine("Set up parameters");
903
904 $chapter = $content_start . $entry['content'] . $bookEnd;
905 $book->addChapter($entry['title'], htmlspecialchars($entry['title']) . ".html", $chapter, true, EPub::EXTERNAL_REF_ADD);
906 $log->logLine("Added chapter " . $entry['title']);
907 }
908
909 if (DEBUG_POCHE) {
910 $epuplog = $book->getLog();
911 $book->addChapter("Log", "Log.html", $content_start . $log->getLog() . "\n</pre>" . $bookEnd); // log generation
912 }
913 $book->finalize();
914 $zipData = $book->sendBook($bookFileName);
915 }
916 }