]> git.immae.eu Git - github/wallabag/wallabag.git/blobdiff - inc/poche/Poche.class.php
Fixed Multi-user system
[github/wallabag/wallabag.git] / inc / poche / Poche.class.php
index e852c7e9f8de58565849c78360e7e6b98fc47344..aa313c2530b49b053cd68224b690632ec8afb6dd 100755 (executable)
@@ -18,7 +18,7 @@ class Poche
     public $tpl;
     public $messages;
     public $pagination;
-    
+
     private $currentTheme = '';
     private $currentLanguage = '';
     private $notInstalledMessage = array();
@@ -32,20 +32,21 @@ class Poche
       'fr_FR.utf8' => 'Français',
       'it_IT.utf8' => 'Italiano',
       'pl_PL.utf8' => 'Polski',
+      'pt_BR.utf8' => 'Português (Brasil)',
       'ru_RU.utf8' => 'Pусский',
       'sl_SI.utf8' => 'Slovenščina',
-      'uk_UA.utf8' => 'УкÑ\80аÑ\97нÑ\81Ñ\8cкий',
+      'uk_UA.utf8' => 'УкÑ\80аÑ\97нÑ\81Ñ\8cка',
     );
     public function __construct()
     {
         if ($this->configFileIsAvailable()) {
             $this->init();
         }
-        
+
         if ($this->themeIsInstalled()) {
             $this->initTpl();
         }
-        
+
         if ($this->systemIsInstalled()) {
             $this->store = new Database();
             $this->messages = new Messages();
@@ -56,12 +57,10 @@ class Poche
             $this->store->checkTags();
         }
     }
-    
-    private function init() 
+
+    private function init()
     {
         Tools::initPhp();
-        Session::$sessionName = 'poche'; 
-        Session::init();
 
         if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) {
             $this->user = $_SESSION['poche_user'];
@@ -75,28 +74,28 @@ class Poche
         $language = $this->user->getConfigValue('language');
         putenv('LC_ALL=' . $language);
         setlocale(LC_ALL, $language);
-        bindtextdomain($language, LOCALE); 
-        textdomain($language); 
+        bindtextdomain($language, LOCALE);
+        textdomain($language);
 
         # Pagination
         $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p');
-        
+
         # Set up theme
         $themeDirectory = $this->user->getConfigValue('theme');
-        
+
         if ($themeDirectory === false) {
             $themeDirectory = DEFAULT_THEME;
         }
-        
+
         $this->currentTheme = $themeDirectory;
 
         # Set up language
         $languageDirectory = $this->user->getConfigValue('language');
-        
+
         if ($languageDirectory === false) {
             $languageDirectory = DEFAULT_THEME;
         }
-        
+
         $this->currentLanguage = $languageDirectory;
     }
 
@@ -109,7 +108,7 @@ class Poche
 
         return true;
     }
-    
+
     public function themeIsInstalled() {
         $passTheme = TRUE;
         # Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet
@@ -124,27 +123,27 @@ class Poche
             self::$canRenderTemplates = false;
 
             $passTheme = FALSE;
-        } 
-        
+        }
+
         # Check if the selected theme and its requirements are present
         $theme = $this->getTheme();
 
         if ($theme != '' && ! is_dir(THEME . '/' . $theme)) {
             $this->notInstalledMessage[] = 'The currently selected theme (' . $theme . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $theme . ')';
-            
+
             self::$canRenderTemplates = false;
-            
+
             $passTheme = FALSE;
         }
-        
+
         $themeInfo = $this->getThemeInfo($theme);
         if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
             foreach ($themeInfo['requirements'] as $requiredTheme) {
                 if (! is_dir(THEME . '/' . $requiredTheme)) {
                     $this->notInstalledMessage[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')';
-                
+
                     self::$canRenderTemplates = false;
-                
+
                     $passTheme = FALSE;
                 }
             }
@@ -154,21 +153,21 @@ class Poche
             return FALSE;
         }
 
-        
+
         return true;
     }
-    
+
     /**
      * all checks before installation.
      * @todo move HTML to template
-     * @return boolean 
+     * @return boolean
      */
     public function systemIsInstalled()
     {
         $msg = TRUE;
-        
+
         $configSalt = defined('SALT') ? constant('SALT') : '';
-        
+
         if (empty($configSalt)) {
             $this->notInstalledMessage[] = 'You have not yet filled in the SALT value in the config.inc.php file.';
             $msg = FALSE;
@@ -194,7 +193,7 @@ class Poche
 
         return true;
     }
-    
+
     public function getNotInstalledMessage() {
         return $this->notInstalledMessage;
     }
@@ -203,7 +202,7 @@ class Poche
     {
         $loaderChain = new Twig_Loader_Chain();
         $theme = $this->getTheme();
-       
+
         # add the current theme as first to the loader chain so Twig will look there first for overridden template files
         try {
             $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme));
@@ -211,7 +210,7 @@ class Poche
             # @todo isInstalled() should catch this, inject Twig later
             die('The currently selected theme (' . $theme . ') does not seem to be properly installed (' . THEME . '/' . $theme .' is missing)');
         }
-        
+
         # add all required themes to the loader chain
         $themeInfo = $this->getThemeInfo($theme);
         if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
@@ -224,16 +223,16 @@ class Poche
                 }
             }
         }
-        
+
         if (DEBUG_POCHE) {
             $twigParams = array();
         } else {
             $twigParams = array('cache' => CACHE);
         }
-        
+
         $this->tpl = new Twig_Environment($loaderChain, $twigParams);
         $this->tpl->addExtension(new Twig_Extensions_Extension_I18n());
-        
+
         # filter to display domain name of an url
         $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain');
         $this->tpl->addFilter($filter);
@@ -242,6 +241,58 @@ class Poche
         $filter = new Twig_SimpleFilter('getReadingTime', 'Tools::getReadingTime');
         $this->tpl->addFilter($filter);
     }
+    
+    public function createNewUser() {
+        if (isset($_GET['newuser'])){
+            if ($_POST['newusername'] != "" && $_POST['password4newuser'] != ""){
+                $newusername = filter_var($_POST['newusername'], FILTER_SANITIZE_STRING);
+                if (!$this->store->userExists($newusername)){  
+                    if ($this->store->install($newusername, Tools::encodeString($_POST['password4newuser'] . $newusername))) {
+                        Tools::logm('The new user '.$newusername.' has been installed');
+                        $this->messages->add('s', sprintf(_('The new user %s has been installed. Do you want to <a href="?logout">logout ?</a>'),$newusername));
+                        Tools::redirect();
+                    }
+                    else {
+                        Tools::logm('error during adding new user');
+                        Tools::redirect();
+                    }
+                }
+                else {
+                    $this->messages->add('e', sprintf(_('Error : An user with the name %s already exists !'),$newusername));
+                    Tools::logm('An user with the name '.$newusername.' already exists !');
+                    Tools::redirect();
+                }
+            }
+        }
+    }
+   
+    public function deleteUser(){
+        if (isset($_GET['deluser'])){
+            if ($this->store->listUsers() > 1) {
+                if (Tools::encodeString($_POST['password4deletinguser'].$this->user->getUsername()) == $this->store->getUserPassword($this->user->getId())) {
+                    $username = $this->user->getUsername();
+                    $this->store->deleteUserConfig($this->user->getId());
+                    Tools::logm('The configuration for user '. $username .' has been deleted !');
+                    $this->store->deleteTagsEntriesAndEntries($this->user->getId());
+                    Tools::logm('The entries for user '. $username .' has been deleted !');
+                    $this->store->deleteUser($this->user->getId());
+                    Tools::logm('User '. $username .' has been completely deleted !');
+                    Session::logout();
+                    Tools::logm('logout');
+                    Tools::redirect();
+                    $this->messages->add('s', sprintf(_('User %s has been successfully deleted !'),$newusername));
+                }
+                else {
+                    Tools::logm('Bad password !');
+                    $this->messages->add('e', _('Error : The password is wrong !'));
+                }
+            }
+            else {
+                Tools::logm('Only user !');
+                $this->messages->add('e', _('Error : You are the only user, you cannot delete your account !'));
+            }
+        }
+    }
 
     private function install()
     {
@@ -252,7 +303,7 @@ class Poche
             'poche_url' => Tools::getPocheUrl()
         ));
         if (isset($_GET['install'])) {
-            if (($_POST['password'] == $_POST['password_repeat']) 
+            if (($_POST['password'] == $_POST['password_repeat'])
                 && $_POST['password'] != "" && $_POST['login'] != "") {
                 # let's rock, install poche baby !
                 if ($this->store->install($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login'])))
@@ -269,7 +320,7 @@ class Poche
         }
         exit();
     }
-    
+
     public function getTheme() {
         return $this->currentTheme;
     }
@@ -294,7 +345,7 @@ class Poche
         if (is_file($themeIniFile) && is_readable($themeIniFile)) {
             $themeInfo = parse_ini_file($themeIniFile);
         }
-        
+
         if ($themeInfo === false) {
             $themeInfo = array();
         }
@@ -305,7 +356,7 @@ class Poche
 
         return $themeInfo;
     }
-    
+
     public function getInstalledThemes() {
         $handle = opendir(THEME);
         $themes = array();
@@ -332,28 +383,28 @@ class Poche
     public function getInstalledLanguages() {
         $handle = opendir(LOCALE);
         $languages = array();
-        
+
         while (($language = readdir($handle)) !== false) {
             # Languages are stored in a directory, so all directory names are languages
             # @todo move language installation data to database
-            if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.'))) {
+            if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.', 'tools'))) {
                 continue;
             }
-            
+
             $current = false;
-            
+
             if ($language === $this->getLanguage()) {
                 $current = true;
             }
-            
-            $languages[] = array('name' => $this->language_names[$language], 'value' => $language, 'current' => $current);
+
+            $languages[] = array('name' => (isset($this->language_names[$language]) ? $this->language_names[$language] : $language), 'value' => $language, 'current' => $current);
         }
-        
+
         return $languages;
     }
 
     public function getDefaultConfig()
-    {   
+    {
         return array(
             'pager' => PAGINATION,
             'language' => LANG,
@@ -361,60 +412,6 @@ class Poche
         );
     }
 
-    protected function getPageContent(Url $url)
-    {
-        // Saving and clearing context
-        $REAL = array();
-        foreach( $GLOBALS as $key => $value ) {
-            if( $key != "GLOBALS" && $key != "_SESSION" ) {
-                $GLOBALS[$key] = array();
-                $REAL[$key] = $value;
-            }
-        }
-        // Saving and clearing session
-        $REAL_SESSION = array();
-        foreach( $_SESSION as $key => $value ) {
-            $REAL_SESSION[$key] = $value;
-            unset($_SESSION[$key]);
-        }
-
-        // Running code in different context
-        $scope = function() {
-            extract( func_get_arg(1) );
-            $_GET = $_REQUEST = array(
-                        "url" => $url->getUrl(),
-                        "max" => 5,
-                        "links" => "preserve",
-                        "exc" => "",
-                        "format" => "json",
-                        "submit" => "Create Feed"
-            );
-            ob_start();
-            require func_get_arg(0);
-            $json = ob_get_flush();
-            return $json;
-        };
-        $json = $scope( "inc/3rdparty/makefulltextfeed.php", array("url" => $url) );
-
-        // Clearing and restoring context
-        foreach( $GLOBALS as $key => $value ) {
-            if( $key != "GLOBALS" && $key != "_SESSION" ) {
-                unset($GLOBALS[$key]);
-            }
-        }
-        foreach( $REAL as $key => $value ) {
-            $GLOBALS[$key] = $value;
-        }
-        // Clearing and restoring session
-        foreach( $_SESSION as $key => $value ) {
-            unset($_SESSION[$key]);
-        }
-        foreach( $REAL_SESSION as $key => $value ) {
-            $_SESSION[$key] = $value;
-        }
-        return json_decode($json, true);
-    }
-
     /**
      * Call action (mark as fav, archive, delete, etc.)
      */
@@ -423,22 +420,22 @@ class Poche
         switch ($action)
         {
             case 'add':
-                $content = $this->getPageContent($url);
+                $content = Tools::getPageContent($url);
                 $title = ($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled');
                 $body = $content['rss']['channel']['item']['description'];
 
-                //search for possible duplicate if not in import mode
-                if (!$import) {
-                    $duplicate = $this->store->retrieveOneByURL($url->getUrl(), $this->user->getId());
-                }
+                // clean content from prevent xss attack
+                $purifier = $this->getPurifier();
+                $title = $purifier->purify($title);
+                $body = $purifier->purify($body);
 
-                if ($this->store->add($url->getUrl(), $title, $body, $this->user->getId())) {
+                //search for possible duplicate
+                $duplicate = NULL;
+                $duplicate = $this->store->retrieveOneByURL($url->getUrl(), $this->user->getId());
+
+                $last_id = $this->store->add($url->getUrl(), $title, $body, $this->user->getId());
+                if ( $last_id ) {
                     Tools::logm('add link ' . $url->getUrl());
-                    $sequence = '';
-                    if (STORAGE == 'postgres') {
-                        $sequence = 'entries_id_seq';
-                    }
-                    $last_id = $this->store->getLastId($sequence);
                     if (DOWNLOAD_PICTURES) {
                         $content = filtre_picture($body, $url->getUrl(), $last_id);
                         Tools::logm('updating content article');
@@ -458,23 +455,17 @@ class Poche
                         }
                     }
 
-                    if (!$import) {
-                        $this->messages->add('s', _('the link has been added successfully'));
-                    }
+                    $this->messages->add('s', _('the link has been added successfully'));
                 }
                 else {
-                    if (!$import) {
-                        $this->messages->add('e', _('error during insertion : the link wasn\'t added'));
-                        Tools::logm('error during insertion : the link wasn\'t added ' . $url->getUrl());
-                    }
+                    $this->messages->add('e', _('error during insertion : the link wasn\'t added'));
+                    Tools::logm('error during insertion : the link wasn\'t added ' . $url->getUrl());
                 }
 
-                if (!$import) {
-                    if ($autoclose == TRUE) {
-                      Tools::redirect('?view=home');
-                    } else {
-                      Tools::redirect('?view=home&closewin=true');
-                    }
+                if ($autoclose == TRUE) {
+                  Tools::redirect('?view=home');
+                } else {
+                  Tools::redirect('?view=home&closewin=true');
                 }
                 break;
             case 'delete':
@@ -495,62 +486,56 @@ class Poche
             case 'toggle_fav' :
                 $this->store->favoriteById($id, $this->user->getId());
                 Tools::logm('mark as favorite link #' . $id);
-                if (!$import) {
-                    Tools::redirect();
-                }
+                Tools::redirect();
                 break;
             case 'toggle_archive' :
                 $this->store->archiveById($id, $this->user->getId());
                 Tools::logm('archive link #' . $id);
-                if (!$import) {
-                    Tools::redirect();
-                }
+                Tools::redirect();
                 break;
             case 'archive_all' :
                 $this->store->archiveAll($this->user->getId());
                 Tools::logm('archive all links');
-                if (!$import) {
-                    Tools::redirect();
-                }
+                Tools::redirect();
                 break;
             case 'add_tag' :
-                if($import){
-                    $entry_id = $id;
-                    $tags = explode(',', $tags);
-                }
-                else{
-                    $tags = explode(',', $_POST['value']);
-                    $entry_id = $_POST['entry_id'];
-                }
+                $tags = explode(',', $_POST['value']);
+                $entry_id = $_POST['entry_id'];
                 $entry = $this->store->retrieveOneById($entry_id, $this->user->getId());
                 if (!$entry) {
                     $this->messages->add('e', _('Article not found!'));
                     Tools::logm('error : article not found');
                     Tools::redirect();
                 }
+                //get all already set tags to preven duplicates
+                $already_set_tags = array();
+                $entry_tags = $this->store->retrieveTagsByEntry($entry_id);
+                foreach ($entry_tags as $tag) {
+                  $already_set_tags[] = $tag['value'];
+                }
                 foreach($tags as $key => $tag_value) {
                     $value = trim($tag_value);
-                    $tag = $this->store->retrieveTagByValue($value);
-
-                    if (is_null($tag)) {
-                        # we create the tag
-                        $tag = $this->store->createTag($value);
-                        $sequence = '';
-                        if (STORAGE == 'postgres') {
-                            $sequence = 'tags_id_seq';
-                        }
-                        $tag_id = $this->store->getLastId($sequence);
-                    }
-                    else {
-                        $tag_id = $tag['id'];
+                    if ($value && !in_array($value, $already_set_tags)) {
+                      $tag = $this->store->retrieveTagByValue($value);
+
+                      if (is_null($tag)) {
+                          # we create the tag
+                          $tag = $this->store->createTag($value);
+                          $sequence = '';
+                          if (STORAGE == 'postgres') {
+                              $sequence = 'tags_id_seq';
+                          }
+                          $tag_id = $this->store->getLastId($sequence);
+                      }
+                      else {
+                          $tag_id = $tag['id'];
+                      }
+
+                      # we assign the tag to the article
+                      $this->store->setTagToEntry($tag_id, $entry_id);
                     }
-
-                    # we assign the tag to the article
-                    $this->store->setTagToEntry($tag_id, $entry_id);
-                }
-                if(!$import) {
-                    Tools::redirect();
                 }
+                Tools::redirect();
                 break;
             case 'remove_tag' :
                 $tag_id = $_GET['tag_id'];
@@ -575,24 +560,32 @@ class Poche
         switch ($view)
         {
             case 'config':
-                $dev = trim($this->getPocheVersion('dev'));
-                $prod = trim($this->getPocheVersion('prod'));
+                $dev_infos = $this->getPocheVersion('dev');
+                $dev = trim($dev_infos[0]);
+                $check_time_dev = date('d-M-Y H:i', $dev_infos[1]);
+                $prod_infos = $this->getPocheVersion('prod');
+                $prod = trim($prod_infos[0]);
+                $check_time_prod = date('d-M-Y H:i', $prod_infos[1]);
                 $compare_dev = version_compare(POCHE, $dev);
                 $compare_prod = version_compare(POCHE, $prod);
                 $themes = $this->getInstalledThemes();
                 $languages = $this->getInstalledLanguages();
                 $token = $this->user->getConfigValue('token');
                 $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false;
+                $only_user = ($this->store->listUsers() > 1) ? false : true;
                 $tpl_vars = array(
                     'themes' => $themes,
                     'languages' => $languages,
                     'dev' => $dev,
                     'prod' => $prod,
+                    'check_time_dev' => $check_time_dev,
+                    'check_time_prod' => $check_time_prod,
                     'compare_dev' => $compare_dev,
                     'compare_prod' => $compare_prod,
                     'token' => $token,
                     'user_id' => $this->user->getId(),
                     'http_auth' => $http_auth,
+                    'only_user' => $only_user
                 );
                 Tools::logm('config view');
                 break;
@@ -613,13 +606,36 @@ class Poche
                 break;
             case 'tags':
                 $token = $this->user->getConfigValue('token');
-                $tags = $this->store->retrieveAllTags($this->user->getId());
+                //if term is set - search tags for this term
+                $term = Tools::checkVar('term');
+                $tags = $this->store->retrieveAllTags($this->user->getId(), $term);
+                if (Tools::isAjaxRequest()) {
+                  $result = array();
+                  foreach ($tags as $tag) {
+                    $result[] = $tag['value'];
+                  }
+                  echo json_encode($result);
+                  exit;
+                }
                 $tpl_vars = array(
                     'token' => $token,
                     'user_id' => $this->user->getId(),
                     'tags' => $tags,
                 );
                 break;
+            case 'search':
+                if (isset($_GET['search'])) {
+                   $search = filter_var($_GET['search'], FILTER_SANITIZE_STRING);
+                   $tpl_vars['entries'] = $this->store->search($search, $this->user->getId());
+                   $count = count($tpl_vars['entries']);
+                   $this->pagination->set_total($count);
+                   $page_links = str_replace(array('previous', 'next'), array(_('previous'), _('next')),
+                            $this->pagination->page_links('?view=' . $view . '?search=' . $search . '&sort=' . $_SESSION['sort'] . '&' ));
+                   $tpl_vars['page_links'] = $page_links;
+                   $tpl_vars['nb_results'] = $count;
+                   $tpl_vars['search_term'] = $search;
+                }
+                break;
             case 'view':
                 $entry = $this->store->retrieveOneById($id, $this->user->getId());
                 if ($entry != NULL) {
@@ -654,8 +670,9 @@ class Poche
                     'entries' => '',
                     'page_links' => '',
                     'nb_results' => '',
+                    'listmode' => (isset($_COOKIE['listmode']) ? true : false),
                 );
-                
+
                 //if id is given - we retrive entries by tag: id is tag id
                 if ($id) {
                   $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId());
@@ -680,8 +697,8 @@ class Poche
     }
 
     /**
-     * update the password of the current user. 
-     * if MODE_DEMO is TRUE, the password can't be updated. 
+     * update the password of the current user.
+     * if MODE_DEMO is TRUE, the password can't be updated.
      * @todo add the return value
      * @todo set the new password in function header like this updatePassword($newPassword)
      * @return boolean
@@ -709,42 +726,44 @@ class Poche
             }
         }
     }
-    
+
     public function updateTheme()
     {
         # no data
         if (empty($_POST['theme'])) {
         }
-        
+
         # we are not going to change it to the current theme...
         if ($_POST['theme'] == $this->getTheme()) {
             $this->messages->add('w', _('still using the "' . $this->getTheme() . '" theme!'));
             Tools::redirect('?view=config');
         }
-        
+
         $themes = $this->getInstalledThemes();
         $actualTheme = false;
-        
+
         foreach (array_keys($themes) as $theme) {
             if ($theme == $_POST['theme']) {
                 $actualTheme = true;
                 break;
             }
         }
-        
+
         if (! $actualTheme) {
             $this->messages->add('e', _('that theme does not seem to be installed'));
             Tools::redirect('?view=config');
         }
-        
+
         $this->store->updateUserConfig($this->user->getId(), 'theme', $_POST['theme']);
         $this->messages->add('s', _('you have changed your theme preferences'));
-        
+
         $currentConfig = $_SESSION['poche_user']->config;
         $currentConfig['theme'] = $_POST['theme'];
-        
+
         $_SESSION['poche_user']->setConfig($currentConfig);
-        
+
+        $this->emptyCache();
+
         Tools::redirect('?view=config');
     }
 
@@ -753,39 +772,40 @@ class Poche
         # no data
         if (empty($_POST['language'])) {
         }
-        
+
         # we are not going to change it to the current language...
         if ($_POST['language'] == $this->getLanguage()) {
             $this->messages->add('w', _('still using the "' . $this->getLanguage() . '" language!'));
             Tools::redirect('?view=config');
         }
-        
+
         $languages = $this->getInstalledLanguages();
         $actualLanguage = false;
-        
+
         foreach ($languages as $language) {
             if ($language['value'] == $_POST['language']) {
                 $actualLanguage = true;
                 break;
             }
         }
-        
+
         if (! $actualLanguage) {
             $this->messages->add('e', _('that language does not seem to be installed'));
             Tools::redirect('?view=config');
         }
-        
+
         $this->store->updateUserConfig($this->user->getId(), 'language', $_POST['language']);
         $this->messages->add('s', _('you have changed your language preferences'));
-        
+
         $currentConfig = $_SESSION['poche_user']->config;
         $currentConfig['language'] = $_POST['language'];
-        
+
         $_SESSION['poche_user']->setConfig($currentConfig);
-        
+
+        $this->emptyCache();
+
         Tools::redirect('?view=config');
     }
-
     /**
      * get credentials from differents sources
      * it redirects the user to the $referer link
@@ -840,7 +860,7 @@ class Poche
     /**
      * log out the poche user. It cleans the session.
      * @todo add the return value
-     * @return boolean 
+     * @return boolean
      */
     public function logout()
     {
@@ -851,238 +871,144 @@ class Poche
     }
 
     /**
-     * import from Instapaper. poche needs a ./instapaper-export.html file
-     * @todo add the return value
-     * @param string $targetFile the file used for importing
+     * import datas into your poche
      * @return boolean
      */
-    private function importFromInstapaper($targetFile)
-    {
-        # TODO gestion des articles favs
-        $html = new simple_html_dom();
-        $html->load_file($targetFile);
-        Tools::logm('starting import from instapaper');
-
-        $read = 0;
-        $errors = array();
-        foreach($html->find('ol') as $ul)
-        {
-            foreach($ul->find('li') as $li)
-            {
-                $a = $li->find('a');
-                $url = new Url(base64_encode($a[0]->href));
-                $this->action('add', $url, 0, TRUE);
-                if ($read == '1') {
-                    $sequence = '';
-                    if (STORAGE == 'postgres') {
-                        $sequence = 'entries_id_seq';
-                    }
-                    $last_id = $this->store->getLastId($sequence);
-                    $this->action('toggle_archive', $url, $last_id, TRUE);
-                }
+    public function import() {
+
+      if (!defined('IMPORT_LIMIT')) {
+        define('IMPORT_LIMIT', 5);
+      }
+      if (!defined('IMPORT_DELAY')) {
+        define('IMPORT_DELAY', 5);
+      }
+
+      if ( isset($_FILES['file']) ) {
+        Tools::logm('Import stated: parsing file');
+
+        // assume, that file is in json format
+        $str_data = file_get_contents($_FILES['file']['tmp_name']);
+        $data = json_decode($str_data, true);
+
+        if ( $data === null ) {
+          //not json - assume html
+          $html = new simple_html_dom();
+          $html->load_file($_FILES['file']['tmp_name']);
+          $data = array();
+          $read = 0;
+          foreach (array('ol','ul') as $list) {
+            foreach ($html->find($list) as $ul) {
+              foreach ($ul->find('li') as $li) {
+                $tmpEntry = array();
+                  $a = $li->find('a');
+                  $tmpEntry['url'] = $a[0]->href;
+                  $tmpEntry['tags'] = $a[0]->tags;
+                  $tmpEntry['is_read'] = $read;
+                  if ($tmpEntry['url']) {
+                    $data[] = $tmpEntry;
+                  }
+              }
+              # the second <ol/ul> is for read links
+              $read = ((sizeof($data) && $read)?0:1);
             }
-
-            # the second <ol> is for read links
-            $read = 1;
+          }
         }
-        $this->messages->add('s', _('import from instapaper completed'));
-        Tools::logm('import from instapaper completed');
-        Tools::redirect();
-    }
 
-    /**
-     * import from Pocket. poche needs a ./ril_export.html file
-     * @todo add the return value
-     * @param string $targetFile the file used for importing
-     * @return boolean 
-     */
-    private function importFromPocket($targetFile)
-    {
-        # TODO gestion des articles favs
-        $html = new simple_html_dom();
-        $html->load_file($targetFile);
-        Tools::logm('starting import from pocket');
-
-        $read = 0;
-        $errors = array();
-        foreach($html->find('ul') as $ul)
-        {
-            foreach($ul->find('li') as $li)
-            {
-                $a = $li->find('a');
-                $url = new Url(base64_encode($a[0]->href));
-                $this->action('add', $url, 0, TRUE);
-                $sequence = '';
-                if (STORAGE == 'postgres') {
-                    $sequence = 'entries_id_seq';
-                }
-                $last_id = $this->store->getLastId($sequence);
-                if ($read == '1') {
-                    $this->action('toggle_archive', $url, $last_id, TRUE);
-                }
-                $tags = $a[0]->tags;
-                if(!empty($tags)) {
-                    $this->action('add_tag',$url,$last_id,true,false,$tags);
-                }
+        //for readability structure
+        foreach ($data as $record) {
+          if (is_array($record)) {
+            $data[] = $record;
+            foreach ($record as $record2) {
+              if (is_array($record2)) {
+                $data[] = $record2;
+              }
             }
-            
-            # the second <ul> is for read links
-            $read = 1;
+          }
+        }
+
+        $urlsInserted = array(); //urls of articles inserted
+        foreach ($data as $record) {
+          $url = trim( isset($record['article__url']) ? $record['article__url'] : (isset($record['url']) ? $record['url'] : '') );
+          if ( $url and !in_array($url, $urlsInserted) ) {
+            $title = (isset($record['title']) ? $record['title'] :  _('Untitled - Import - ').'</a> <a href="./?import">'._('click to finish import').'</a><a>');
+            $body = (isset($record['content']) ? $record['content'] : '');
+            $isRead = (isset($record['is_read']) ? intval($record['is_read']) : (isset($record['archive'])?intval($record['archive']):0));
+            $isFavorite = (isset($record['is_fav']) ? intval($record['is_fav']) : (isset($record['favorite'])?intval($record['favorite']):0) );
+            //insert new record
+            $id = $this->store->add($url, $title, $body, $this->user->getId(), $isFavorite, $isRead);
+            if ( $id ) {
+              $urlsInserted[] = $url; //add
+
+              if ( isset($record['tags']) && trim($record['tags']) ) {
+                //@TODO: set tags
+
+              }
+            }
+          }
         }
-        $this->messages->add('s', _('import from pocket completed'));
-        Tools::logm('import from pocket completed');
-        Tools::redirect();
-    }
-
-    /**
-     * import from Readability. poche needs a ./readability file
-     * @todo add the return value
-     * @param string $targetFile the file used for importing
-     * @return boolean 
-     */
-    private function importFromReadability($targetFile)
-    {
-        # TODO gestion des articles lus / favs
-        $str_data = file_get_contents($targetFile);
-        $data = json_decode($str_data,true);
-        Tools::logm('starting import from Readability');
-        $count = 0;
-        foreach ($data as $key => $value) {
-            $url = NULL;
-            $favorite = FALSE;
-            $archive = FALSE;
-            foreach ($value as $item) {
-                foreach ($item as $attr => $value) {
-                    if ($attr == 'article__url') {
-                        $url = new Url(base64_encode($value));
-                    }
-                    $sequence = '';
-                    if (STORAGE == 'postgres') {
-                        $sequence = 'entries_id_seq';
-                    }
-                    if ($value == 'true') {
-                        if ($attr == 'favorite') {
-                            $favorite = TRUE;
-                        }
-                        if ($attr == 'archive') {
-                            $archive = TRUE;
-                        }
-                    }
-                }
 
-                # we can add the url
-                if (!is_null($url) && $url->isCorrect()) {
-                    $this->action('add', $url, 0, TRUE);
-                    $count++;
-                    if ($favorite) {
-                        $last_id = $this->store->getLastId($sequence);
-                        $this->action('toggle_fav', $url, $last_id, TRUE);
-                    }
-                    if ($archive) {
-                        $last_id = $this->store->getLastId($sequence);
-                        $this->action('toggle_archive', $url, $last_id, TRUE);
-                    }
-                }
-            }
+        $i = sizeof($urlsInserted);
+        if ( $i > 0 ) {
+          $this->messages->add('s', _('Articles inserted: ').$i._('. Please note, that some may be marked as "read".'));
         }
-        $this->messages->add('s', _('import from Readability completed. ' . $count . ' new links.'));
-        Tools::logm('import from Readability completed');
+        Tools::logm('Import of articles finished: '.$i.' articles added (w/o content if not provided).');
+      }
+      //file parsing finished here
+
+      //now download article contents if any
+
+      //check if we need to download any content
+      $recordsDownloadRequired = $this->store->retrieveUnfetchedEntriesCount($this->user->getId());
+      if ( $recordsDownloadRequired == 0 ) {
+        //nothing to download
+        $this->messages->add('s', _('Import finished.'));
+        Tools::logm('Import finished completely');
         Tools::redirect();
-    }
+      }
+      else {
+        //if just inserted - don't download anything, download will start in next reload
+        if ( !isset($_FILES['file']) ) {
+          //download next batch
+          Tools::logm('Fetching next batch of articles...');
+          $items = $this->store->retrieveUnfetchedEntries($this->user->getId(), IMPORT_LIMIT);
 
-    /**
-     * import from Poche exported file
-     * @param string $targetFile the file used for importing
-     * @return boolean 
-     */
-    private function importFromPoche($targetFile)
-    {
-        $str_data = file_get_contents($targetFile);
-        $data = json_decode($str_data,true);
-        Tools::logm('starting import from Poche');
+          $purifier = $this->getPurifier();
 
+          foreach ($items as $item) {
+            $url = new Url(base64_encode($item['url']));
+            Tools::logm('Fetching article '.$item['id']);
+            $content = Tools::getPageContent($url);
 
-        $sequence = '';
-        if (STORAGE == 'postgres') {
-            $sequence = 'entries_id_seq';
-        }
+            $title = (($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled'));
+            $body = (($content['rss']['channel']['item']['description'] != '') ? $content['rss']['channel']['item']['description'] : _('Undefined'));
 
-        $count = 0;
-        foreach ($data as $value) {
+            //clean content to prevent xss attack
+            $title = $purifier->purify($title);
+            $body = $purifier->purify($body);
 
-            $url = new Url(base64_encode($value['url']));
-            $favorite = ($value['is_fav'] == -1);
-            $archive = ($value['is_read'] == -1);
-    
-            # we can add the url
-            if (!is_null($url) && $url->isCorrect()) {
-                
-                $this->action('add', $url, 0, TRUE);
-                
-                $count++;
-                if ($favorite) {
-                    $last_id = $this->store->getLastId($sequence);
-                    $this->action('toggle_fav', $url, $last_id, TRUE);
-                }
-                if ($archive) {
-                    $last_id = $this->store->getLastId($sequence);
-                    $this->action('toggle_archive', $url, $last_id, TRUE);
-                }
-            }
-            
-        }
-        $this->messages->add('s', _('import from Poche completed. ' . $count . ' new links.'));
-        Tools::logm('import from Poche completed');
-        Tools::redirect();
-    }
+            $this->store->updateContentAndTitle($item['id'], $title, $body, $this->user->getId());
+            Tools::logm('Article '.$item['id'].' updated.');
+          }
 
-    /**
-     * import datas into your poche
-     * @param  string $from name of the service to import : pocket, instapaper or readability
-     * @todo add the return value
-     * @return boolean       
-     */
-    public function import($from)
-    {
-        $providers = array(
-            'pocket' => 'importFromPocket',
-            'readability' => 'importFromReadability',
-            'instapaper' => 'importFromInstapaper',
-            'poche' => 'importFromPoche',
-        );
-        
-        if (! isset($providers[$from])) {
-            $this->messages->add('e', _('Unknown import provider.'));
-            Tools::redirect();
-        }
-        
-        $targetDefinition = 'IMPORT_' . strtoupper($from) . '_FILE';
-        $targetFile = constant($targetDefinition);
-        
-        if (! defined($targetDefinition)) {
-            $this->messages->add('e', _('Incomplete inc/poche/define.inc.php file, please define "' . $targetDefinition . '".'));
-            Tools::redirect();
         }
-        
-        if (! file_exists($targetFile)) {
-            $this->messages->add('e', _('Could not find required "' . $targetFile . '" import file.'));
-            Tools::redirect();
-        }
-        
-        $this->$providers[$from]($targetFile);
+      }
+
+      return array('includeImport'=>true, 'import'=>array('recordsDownloadRequired'=>$recordsDownloadRequired, 'recordsUnderDownload'=> IMPORT_LIMIT, 'delay'=> IMPORT_DELAY * 1000) );
     }
 
     /**
      * export poche entries in json
      * @return json all poche entries
      */
-    public function export()
-    {
-        $entries = $this->store->retrieveAll($this->user->getId());
-        echo $this->tpl->render('export.twig', array(
-            'export' => Tools::renderJson($entries),
-        ));
-        Tools::logm('export view');
+    public function export() {
+      $filename = "wallabag-export-".$this->user->getId()."-".date("Y-m-d").".json";
+      header('Content-Disposition: attachment; filename='.$filename);
+
+      $entries = $this->store->retrieveAll($this->user->getId());
+      echo $this->tpl->render('export.twig', array(
+          'export' => Tools::renderJson($entries),
+      ));
+      Tools::logm('export view');
     }
 
     /**
@@ -1090,34 +1016,42 @@ class Poche
      * @param  string $which 'prod' or 'dev'
      * @return string        latest $which version
      */
-    private function getPocheVersion($which = 'prod')
-    {
-        $cache_file = CACHE . '/' . $which;
-
-        # checks if the cached version file exists
-        if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) {
-           $version = file_get_contents($cache_file);
-        } else {
-           $version = file_get_contents('http://static.wallabag.org/versions/' . $which);
-           file_put_contents($cache_file, $version, LOCK_EX);
-        }
-        return $version;
+    private function getPocheVersion($which = 'prod') {
+      $cache_file = CACHE . '/' . $which;
+      $check_time = time();
+
+      # checks if the cached version file exists
+      if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) {
+         $version = file_get_contents($cache_file);
+         $check_time = filemtime($cache_file);
+      } else {
+         $version = file_get_contents('http://static.wallabag.org/versions/' . $which);
+         file_put_contents($cache_file, $version, LOCK_EX);
+      }
+      return array($version, $check_time);
     }
 
     public function generateToken()
     {
-        if (ini_get('open_basedir') === '') {
-            $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
-        }
-        else {
-            $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
-        }
-
-        $token = str_replace('+', '', $token);
-        $this->store->updateUserConfig($this->user->getId(), 'token', $token);
-        $currentConfig = $_SESSION['poche_user']->config;
-        $currentConfig['token'] = $token;
-        $_SESSION['poche_user']->setConfig($currentConfig);
+      if (ini_get('open_basedir') === '') {
+        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+          echo 'This is a server using Windows!';
+          // alternative to /dev/urandom for Windows
+          $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
+        } else {
+          $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
+        }
+      }
+      else {
+        $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
+      }
+
+      $token = str_replace('+', '', $token);
+      $this->store->updateUserConfig($this->user->getId(), 'token', $token);
+      $currentConfig = $_SESSION['poche_user']->config;
+      $currentConfig['token'] = $token;
+      $_SESSION['poche_user']->setConfig($currentConfig);
+      Tools::redirect();
     }
 
     public function generateFeeds($token, $user_id, $tag_id, $type = 'home')
@@ -1125,6 +1059,10 @@ class Poche
         $allowed_types = array('home', 'fav', 'archive', 'tag');
         $config = $this->store->getConfigUser($user_id);
 
+        if ($config == null) {
+            die(_('User with this id (' . $user_id . ') does not exist.'));
+        }
+
         if (!in_array($type, $allowed_types) ||
             $token != $config['token']) {
             die(_('Uh, there is a problem while generating feeds.'));
@@ -1134,8 +1072,9 @@ class Poche
         $feed = new FeedWriter(RSS2);
         $feed->setTitle('wallabag — ' . $type . ' feed');
         $feed->setLink(Tools::getPocheUrl());
-        $feed->setChannelElement('updated', date(DATE_RSS , time()));
-        $feed->setChannelElement('author', 'wallabag');
+        $feed->setChannelElement('pubDate', date(DATE_RSS , time()));
+        $feed->setChannelElement('generator', 'wallabag');
+        $feed->setDescription('wallabag ' . $type . ' elements');
 
         if ($type == 'tag') {
             $entries = $this->store->retrieveEntriesByTag($tag_id, $user_id);
@@ -1148,7 +1087,8 @@ class Poche
             foreach ($entries as $entry) {
                 $newItem = $feed->createNewItem();
                 $newItem->setTitle($entry['title']);
-                $newItem->setLink(Tools::getPocheUrl() . '?view=view&amp;id=' . $entry['id']);
+                $newItem->setSource(Tools::getPocheUrl() . '?view=view&amp;id=' . $entry['id']);
+                $newItem->setLink($entry['url']);
                 $newItem->setDate(time());
                 $newItem->setDescription($entry['content']);
                 $feed->addItem($newItem);
@@ -1174,4 +1114,16 @@ class Poche
         $this->messages->add('s', _('Cache deleted.'));
         Tools::redirect();
     }
+
+    /**
+     * return new purifier object with actual config
+     */
+    protected function getPurifier() {
+      $config = HTMLPurifier_Config::createDefault();\r
+      $config->set('Cache.SerializerPath', CACHE);\r
+      $config->set('HTML.SafeIframe', true);\r
+      $config->set('URI.SafeIframeRegexp', '%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/)%'); //allow YouTube and Vimeo$purifier = new HTMLPurifier($config);
+\r
+      return new HTMLPurifier($config);
+    }
 }