]>
git.immae.eu Git - github/wallabag/wallabag.git/blob - inc/poche/Poche.class.php
3 * poche, a read it later open source system
6 * @author Nicolas LÅ“uillet <support@inthepoche.com>
8 * @license http://www.wtfpl.net/ see COPYING file
13 public static $canRenderTemplates = true ;
14 public static $configFileAvailable = true ;
22 private $currentTheme = '' ;
23 private $notInstalledMessage = '' ;
25 # @todo make this dynamic (actually install themes and save them in the database including author information et cetera)
26 private $installedThemes = array (
27 'default' => array ( 'requires' => array ()),
28 'dark' => array ( 'requires' => array ( 'default' )),
29 'dmagenta' => array ( 'requires' => array ( 'default' )),
30 'solarized' => array ( 'requires' => array ( 'default' )),
31 'solarized-dark' => array ( 'requires' => array ( 'default' ))
34 public function __construct ()
36 if (! $this- > configFileIsAvailable ()) {
42 if (! $this- > themeIsInstalled ()) {
48 if (! $this- > systemIsInstalled ()) {
52 $this- > store
= new Database ();
53 $this- > messages
= new Messages ();
56 if (! $this- > store
-> isInstalled ()) {
61 private function init ()
64 Session
:: $sessionName = 'poche' ;
67 if ( isset ( $_SESSION [ 'poche_user' ]) && $_SESSION [ 'poche_user' ] != array ()) {
68 $this- > user
= $_SESSION [ 'poche_user' ];
70 # fake user, just for install & login screens
71 $this- > user
= new User ();
72 $this- > user
-> setConfig ( $this- > getDefaultConfig ());
76 $language = $this- > user
-> getConfigValue ( 'language' );
77 putenv ( 'LC_ALL=' . $language );
78 setlocale ( LC_ALL
, $language );
79 bindtextdomain ( $language , LOCALE
);
80 textdomain ( $language );
83 $this- > pagination
= new Paginator ( $this- > user
-> getConfigValue ( 'pager' ), 'p' );
86 $themeDirectory = $this- > user
-> getConfigValue ( 'theme' );
88 if ( $themeDirectory === false ) {
89 $themeDirectory = DEFAULT_THEME
;
92 $this- > currentTheme
= $themeDirectory ;
95 public function configFileIsAvailable () {
96 if (! self
:: $configFileAvailable ) {
97 $this- > notInstalledMessage
= 'You have to rename <strong>inc/poche/config.inc.php.new</strong> to <strong>inc/poche/config.inc.php</strong>.' ;
105 public function themeIsInstalled () {
106 # Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet
107 if (! self
:: $canRenderTemplates ) {
108 $this- > notInstalledMessage
= 'Twig does not seem to be installed. Please initialize the Composer installation to automatically fetch dependencies. Have a look at <a href="http://inthepoche.com/?pages/Documentation">the documentation.</a>' ;
113 # Check if the selected theme and its requirements are present
114 if (! is_dir ( THEME
. '/' . $this- > getTheme ())) {
115 $this- > notInstalledMessage
= 'The currently selected theme (' . $this- > getTheme () . ') does not seem to be properly installed (Missing directory: ' . THEME
. '/' . $this- > getTheme () . ')' ;
117 self
:: $canRenderTemplates = false ;
122 foreach ( $this- > installedThemes
[ $this- > getTheme ()][ 'requires' ] as $requiredTheme ) {
123 if (! is_dir ( THEME
. '/' . $requiredTheme )) {
124 $this- > notInstalledMessage
= 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $this- > getTheme () . ')' ;
126 self
:: $canRenderTemplates = false ;
136 * all checks before installation.
137 * @todo move HTML to template
140 public function systemIsInstalled ()
144 $configSalt = defined ( 'SALT' ) ? constant ( 'SALT' ) : '' ;
146 if ( empty ( $configSalt )) {
147 $msg = '<h1>error</h1><p>You have not yet filled in the SALT value in the config.inc.php file.</p>' ;
148 } else if (! is_writable ( CACHE
)) {
149 Tools
:: logm ( 'you don \' t have write access on cache directory' );
150 $msg = '<h1>error</h1><p>You don \' t have write access on cache directory.</p>' ;
151 } else if ( STORAGE
== 'sqlite' && ! file_exists ( STORAGE_SQLITE
)) {
152 Tools
:: logm ( 'sqlite file doesn \' t exist' );
153 $msg = '<h1>error</h1><p>sqlite file doesn \' t exist, you can find it in install folder. Copy it in /db folder.</p>' ;
154 } else if ( file_exists ( ROOT
. '/install/update.php' ) && ! DEBUG_POCHE
) {
155 $msg = '<h1>setup</h1><p><strong>It \' s your first time here?</strong> Please copy /install/poche.sqlite in db folder. Then, delete install folder.<br /><strong>If you have already installed poche</strong>, an update is needed <a href="install/update.php">by clicking here</a>.</p>' ;
156 } else if ( is_dir ( ROOT
. '/install' ) && ! DEBUG_POCHE
) {
157 $msg = '<h1>setup</h1><p><strong>If you want to update your poche</strong>, you just have to delete /install folder. <br /><strong>To install your poche with sqlite</strong>, copy /install/poche.sqlite in /db and delete the folder /install. you have to delete the /install folder before using poche.</p>' ;
158 } else if ( STORAGE
== 'sqlite' && ! is_writable ( STORAGE_SQLITE
)) {
159 Tools
:: logm ( 'you don \' t have write access on sqlite file' );
160 $msg = '<h1>error</h1><p>You don \' t have write access on sqlite file.</p>' ;
164 $this- > notInstalledMessage
= $msg ;
172 public function getNotInstalledMessage () {
173 return $this- > notInstalledMessage
;
176 private function initTpl ()
178 $loaderChain = new Twig_Loader_Chain ();
180 # add the current theme as first to the loader chain so Twig will look there first for overridden template files
182 $loaderChain- > addLoader ( new Twig_Loader_Filesystem ( THEME
. '/' . $this- > getTheme ()));
183 } catch ( Twig_Error_Loader
$e ) {
184 # @todo isInstalled() should catch this, inject Twig later
185 die ( 'The currently selected theme (' . $this- > getTheme () . ') does not seem to be properly installed (' . THEME
. '/' . $this- > getTheme () . ' is missing)' );
188 # add all required themes to the loader chain
189 foreach ( $this- > installedThemes
[ $this- > getTheme ()][ 'requires' ] as $requiredTheme ) {
191 $loaderChain- > addLoader ( new Twig_Loader_Filesystem ( THEME
. '/' . DEFAULT_THEME
));
192 } catch ( Twig_Error_Loader
$e ) {
193 # @todo isInstalled() should catch this, inject Twig later
194 die ( 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $this- > getTheme () . ')' );
199 $twig_params = array ();
201 $twig_params = array ( 'cache' => CACHE
);
204 $this- > tpl
= new Twig_Environment ( $loaderChain , $twig_params );
205 $this- > tpl
-> addExtension ( new Twig_Extensions_Extension_I18n ());
207 # filter to display domain name of an url
208 $filter = new Twig_SimpleFilter ( 'getDomain' , 'Tools::getDomain' );
209 $this- > tpl
-> addFilter ( $filter );
211 # filter for reading time
212 $filter = new Twig_SimpleFilter ( 'getReadingTime' , 'Tools::getReadingTime' );
213 $this- > tpl
-> addFilter ( $filter );
215 # filter for simple filenames in config view
216 $filter = new Twig_SimpleFilter ( 'getPrettyFilename' , function ( $string ) { return str_replace ( ROOT
, '' , $
string ); });
217 $this- > tpl
-> addFilter ( $filter );
220 private function install ()
222 Tools
:: logm ( 'poche still not installed' );
223 echo $this- > tpl
-> render ( 'install.twig' , array (
224 'token' => Session
:: getToken (),
225 'theme' => $this- > getTheme (),
226 'poche_url' => Tools
:: getPocheUrl ()
228 if ( isset ( $_GET [ 'install' ])) {
229 if (( $_POST [ 'password' ] == $_POST [ 'password_repeat' ])
230 && $_POST [ 'password' ] != "" && $_POST [ 'login' ] != "" ) {
231 # let's rock, install poche baby !
232 if ( $this- > store
-> install ( $_POST [ 'login' ], Tools
:: encodeString ( $_POST [ 'password' ] . $_POST [ 'login' ])))
235 Tools
:: logm ( 'poche is now installed' );
240 Tools
:: logm ( 'error during installation' );
247 public function getTheme () {
248 return $this- > currentTheme
;
251 public function getInstalledThemes () {
252 $handle = opendir ( THEME
);
255 while (( $theme = readdir ( $handle )) !== false ) {
256 # Themes are stored in a directory, so all directory names are themes
257 # @todo move theme installation data to database
258 if (! is_dir ( THEME
. '/' . $theme ) || in_array ( $theme , array ( '..' , '.' ))) {
264 if ( $theme === $this- > getTheme ()) {
268 $themes [] = array ( 'name' => $theme , 'current' => $current );
274 public function getDefaultConfig ()
277 'pager' => PAGINATION
,
279 'theme' => DEFAULT_THEME
284 * Call action (mark as fav, archive, delete, etc.)
286 public function action ( $action , Url
$url , $id = 0 , $import = FALSE )
291 $content = $url- > extract ();
293 if ( $this- > store
-> add ( $url- > getUrl (), $content [ 'title' ], $content [ 'body' ], $this- > user
-> getId ())) {
294 Tools
:: logm ( 'add link ' . $url- > getUrl ());
296 if ( STORAGE
== 'postgres' ) {
297 $sequence = 'entries_id_seq' ;
299 $last_id = $this- > store
-> getLastId ( $sequence );
300 if ( DOWNLOAD_PICTURES
) {
301 $content = filtre_picture ( $content [ 'body' ], $url- > getUrl (), $last_id );
302 Tools
:: logm ( 'updating content article' );
303 $this- > store
-> updateContent ( $last_id , $content , $this- > user
-> getId ());
306 $this- > messages
-> add ( 's' , _ ( 'the link has been added successfully' ));
311 $this- > messages
-> add ( 'e' , _ ( 'error during insertion : the link wasn \' t added' ));
312 Tools
:: logm ( 'error during insertion : the link wasn \' t added ' . $url- > getUrl ());
317 Tools
:: redirect ( '?view=home' );
321 $msg = 'delete link #' . $id ;
322 if ( $this- > store
-> deleteById ( $id , $this- > user
-> getId ())) {
323 if ( DOWNLOAD_PICTURES
) {
324 remove_directory ( ABS_PATH
. $id );
326 $this- > messages
-> add ( 's' , _ ( 'the link has been deleted successfully' ));
329 $this- > messages
-> add ( 'e' , _ ( 'the link wasn \' t deleted' ));
330 $msg = 'error : can \' t delete link #' . $id ;
333 Tools
:: redirect ( '?' );
336 $this- > store
-> favoriteById ( $id , $this- > user
-> getId ());
337 Tools
:: logm ( 'mark as favorite link #' . $id );
342 case 'toggle_archive' :
343 $this- > store
-> archiveById ( $id , $this- > user
-> getId ());
344 Tools
:: logm ( 'archive link #' . $id );
354 function displayView ( $view , $id = 0 )
361 $dev = $this- > getPocheVersion ( 'dev' );
362 $prod = $this- > getPocheVersion ( 'prod' );
363 $compare_dev = version_compare ( POCHE_VERSION
, $dev );
364 $compare_prod = version_compare ( POCHE_VERSION
, $prod );
365 $themes = $this- > getInstalledThemes ();
370 'compare_dev' => $compare_dev ,
371 'compare_prod' => $compare_prod ,
373 Tools
:: logm ( 'config view' );
376 $entry = $this- > store
-> retrieveOneById ( $id , $this- > user
-> getId ());
377 if ( $entry != NULL ) {
378 Tools
:: logm ( 'view link #' . $id );
379 $content = $entry [ 'content' ];
380 if ( function_exists ( 'tidy_parse_string' )) {
381 $tidy = tidy_parse_string ( $content , array ( 'indent' => true , 'show-body-only' => true ), 'UTF8' );
382 $tidy- > cleanRepair ();
383 $content = $tidy- > value
;
387 $flattr = new FlattrItem ();
388 $flattr- > checkItem ( $entry [ 'url' ]);
392 'content' => $content ,
397 Tools
:: logm ( 'error in view call : entry is null' );
400 default : # home, favorites and archive views
401 $entries = $this- > store
-> getEntriesByView ( $view , $this- > user
-> getId ());
407 if ( count ( $entries ) > 0 ) {
408 $this- > pagination
-> set_total ( count ( $entries ));
409 $page_links = $this- > pagination
-> page_links ( '?view=' . $view . '&sort=' . $_SESSION [ 'sort' ] . '&' );
410 $datas = $this- > store
-> getEntriesByView ( $view , $this- > user
-> getId (), $this- > pagination
-> get_limit ());
411 $tpl_vars [ 'entries' ] = $datas ;
412 $tpl_vars [ 'page_links' ] = $page_links ;
413 $tpl_vars [ 'nb_results' ] = count ( $entries );
415 Tools
:: logm ( 'display ' . $view . ' view' );
423 * update the password of the current user.
424 * if MODE_DEMO is TRUE, the password can't be updated.
425 * @todo add the return value
426 * @todo set the new password in function header like this updatePassword($newPassword)
429 public function updatePassword ()
432 $this- > messages
-> add ( 'i' , _ ( 'in demo mode, you can \' t update your password' ));
433 Tools
:: logm ( 'in demo mode, you can \' t do this' );
434 Tools
:: redirect ( '?view=config' );
437 if ( isset ( $_POST [ 'password' ]) && isset ( $_POST [ 'password_repeat' ])) {
438 if ( $_POST [ 'password' ] == $_POST [ 'password_repeat' ] && $_POST [ 'password' ] != "" ) {
439 $this- > messages
-> add ( 's' , _ ( 'your password has been updated' ));
440 $this- > store
-> updatePassword ( $this- > user
-> getId (), Tools
:: encodeString ( $_POST [ 'password' ] . $this- > user
-> getUsername ()));
442 Tools
:: logm ( 'password updated' );
446 $this- > messages
-> add ( 'e' , _ ( 'the two fields have to be filled & the password must be the same in the two fields' ));
447 Tools
:: redirect ( '?view=config' );
453 public function updateTheme ()
456 if ( empty ( $_POST [ 'theme' ])) {
459 # we are not going to change it to the current theme...
460 if ( $_POST [ 'theme' ] == $this- > getTheme ()) {
461 $this- > messages
-> add ( 'w' , _ ( 'still using the "' . $this- > getTheme () . '" theme!' ));
462 Tools
:: redirect ( '?view=config' );
465 $themes = $this- > getInstalledThemes ();
466 $actualTheme = false ;
468 foreach ( $themes as $theme ) {
469 if ( $theme [ 'name' ] == $_POST [ 'theme' ]) {
475 if (! $actualTheme ) {
476 $this- > messages
-> add ( 'e' , _ ( 'that theme does not seem to be installed' ));
477 Tools
:: redirect ( '?view=config' );
480 $this- > store
-> updateUserConfig ( $this- > user
-> getId (), 'theme' , $_POST [ 'theme' ]);
481 $this- > messages
-> add ( 's' , _ ( 'you have changed your theme preferences' ));
483 $currentConfig = $_SESSION [ 'poche_user' ]-> config
;
484 $currentConfig [ 'theme' ] = $_POST [ 'theme' ];
486 $_SESSION [ 'poche_user' ]-> setConfig ( $currentConfig );
488 Tools
:: redirect ( '?view=config' );
492 * checks if login & password are correct and save the user in session.
493 * it redirects the user to the $referer link
494 * @param string $referer the url to redirect after login
495 * @todo add the return value
498 public function login ( $referer )
500 if (! empty ( $_POST [ 'login' ]) && ! empty ( $_POST [ 'password' ])) {
501 $user = $this- > store
-> login ( $_POST [ 'login' ], Tools
:: encodeString ( $_POST [ 'password' ] . $_POST [ 'login' ]));
502 if ( $user != array ()) {
503 # Save login into Session
504 Session
:: login ( $user [ 'username' ], $user [ 'password' ], $_POST [ 'login' ], Tools
:: encodeString ( $_POST [ 'password' ] . $_POST [ 'login' ]), array ( 'poche_user' => new User ( $user )));
505 $this- > messages
-> add ( 's' , _ ( 'welcome to your poche' ));
506 Tools
:: logm ( 'login successful' );
507 Tools
:: redirect ( $referer );
509 $this- > messages
-> add ( 'e' , _ ( 'login failed: bad login or password' ));
510 Tools
:: logm ( 'login failed' );
513 $this- > messages
-> add ( 'e' , _ ( 'login failed: you have to fill all fields' ));
514 Tools
:: logm ( 'login failed' );
520 * log out the poche user. It cleans the session.
521 * @todo add the return value
524 public function logout ()
526 $this- > user
= array ();
528 $this- > messages
-> add ( 's' , _ ( 'see you soon!' ));
529 Tools
:: logm ( 'logout' );
534 * import from Instapaper. poche needs a ./instapaper-export.html file
535 * @todo add the return value
536 * @param string $targetFile the file used for importing
539 private function importFromInstapaper ( $targetFile )
541 # TODO gestion des articles favs
542 $html = new simple_html_dom ();
543 $html- > load_file ( $targetFile );
544 Tools
:: logm ( 'starting import from instapaper' );
548 foreach ( $html- > find ( 'ol' ) as $ul )
550 foreach ( $ul- > find ( 'li' ) as $li )
553 $url = new Url ( base64_encode ( $a [ 0 ]-> href
));
554 $this- > action ( 'add' , $url , 0 , TRUE );
557 if ( STORAGE
== 'postgres' ) {
558 $sequence = 'entries_id_seq' ;
560 $last_id = $this- > store
-> getLastId ( $sequence );
561 $this- > action ( 'toggle_archive' , $url , $last_id , TRUE );
565 # the second <ol> is for read links
568 $this- > messages
-> add ( 's' , _ ( 'import from instapaper completed' ));
569 Tools
:: logm ( 'import from instapaper completed' );
574 * import from Pocket. poche needs a ./ril_export.html file
575 * @todo add the return value
576 * @param string $targetFile the file used for importing
579 private function importFromPocket ( $targetFile )
581 # TODO gestion des articles favs
582 $html = new simple_html_dom ();
583 $html- > load_file ( $targetFile );
584 Tools
:: logm ( 'starting import from pocket' );
588 foreach ( $html- > find ( 'ul' ) as $ul )
590 foreach ( $ul- > find ( 'li' ) as $li )
593 $url = new Url ( base64_encode ( $a [ 0 ]-> href
));
594 $this- > action ( 'add' , $url , 0 , TRUE );
597 if ( STORAGE
== 'postgres' ) {
598 $sequence = 'entries_id_seq' ;
600 $last_id = $this- > store
-> getLastId ( $sequence );
601 $this- > action ( 'toggle_archive' , $url , $last_id , TRUE );
605 # the second <ul> is for read links
608 $this- > messages
-> add ( 's' , _ ( 'import from pocket completed' ));
609 Tools
:: logm ( 'import from pocket completed' );
614 * import from Readability. poche needs a ./readability file
615 * @todo add the return value
616 * @param string $targetFile the file used for importing
619 private function importFromReadability ( $targetFile )
621 # TODO gestion des articles lus / favs
622 $str_data = file_get_contents ( $targetFile );
623 $data = json_decode ( $str_data , true );
624 Tools
:: logm ( 'starting import from Readability' );
626 foreach ( $data as $key => $value ) {
630 foreach ( $value as $attr => $attr_value ) {
631 if ( $attr == 'article__url' ) {
632 $url = new Url ( base64_encode ( $attr_value ));
635 if ( STORAGE
== 'postgres' ) {
636 $sequence = 'entries_id_seq' ;
638 if ( $attr_value == 'true' ) {
639 if ( $attr == 'favorite' ) {
642 if ( $attr == 'archive' ) {
648 if (! is_null ( $url ) && $url- > isCorrect ()) {
649 $this- > action ( 'add' , $url , 0 , TRUE );
652 $last_id = $this- > store
-> getLastId ( $sequence );
653 $this- > action ( 'toggle_fav' , $url , $last_id , TRUE );
656 $last_id = $this- > store
-> getLastId ( $sequence );
657 $this- > action ( 'toggle_archive' , $url , $last_id , TRUE );
661 $this- > messages
-> add ( 's' , _ ( 'import from Readability completed. ' . $count . ' new links.' ));
662 Tools
:: logm ( 'import from Readability completed' );
667 * import datas into your poche
668 * @param string $from name of the service to import : pocket, instapaper or readability
669 * @todo add the return value
672 public function import ( $from )
675 'pocket' => 'importFromPocket' ,
676 'readability' => 'importFromReadability' ,
677 'instapaper' => 'importFromInstapaper'
680 if (! isset ( $providers [ $from ])) {
681 $this- > messages
-> add ( 'e' , _ ( 'Unknown import provider.' ));
685 $targetDefinition = 'IMPORT_' . strtoupper ( $from ) . '_FILE' ;
686 $targetFile = constant ( $targetDefinition );
688 if (! defined ( $targetDefinition )) {
689 $this- > messages
-> add ( 'e' , _ ( 'Incomplete inc/poche/define.inc.php file, please define "' . $targetDefinition . '".' ));
693 if (! file_exists ( $targetFile )) {
694 $this- > messages
-> add ( 'e' , _ ( 'Could not find required "' . $targetFile . '" import file.' ));
698 $this- > $providers [ $from ]( $targetFile );
702 * export poche entries in json
703 * @return json all poche entries
705 public function export ()
707 $entries = $this- > store
-> retrieveAll ( $this- > user
-> getId ());
708 echo $this- > tpl
-> render ( 'export.twig' , array (
709 'export' => Tools
:: renderJson ( $entries ),
711 Tools
:: logm ( 'export view' );
715 * Checks online the latest version of poche and cache it
716 * @param string $which 'prod' or 'dev'
717 * @return string latest $which version
719 private function getPocheVersion ( $which = 'prod' )
721 $cache_file = CACHE
. '/' . $which ;
723 # checks if the cached version file exists
724 if ( file_exists ( $cache_file ) && ( filemtime ( $cache_file ) > ( time () - 86400 ))) {
725 $version = file_get_contents ( $cache_file );
727 $version = file_get_contents ( 'http://static.inthepoche.com/versions/' . $which );
728 file_put_contents ( $cache_file , $version , LOCK_EX
);