diff --git a/COPYING.md b/COPYING.md index ee7d6a5..c43f619 100644 --- a/COPYING.md +++ b/COPYING.md @@ -1,14 +1,19 @@ - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - Version 2, December 2004 +Copyright (c) 2013-2014 Nicolas Lœuillet - Copyright (C) 2004 Sam Hocevar +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: - Everyone is permitted to copy and distribute verbatim or modified - copies of this license document, and changing it is allowed as long - as the name is changed. - - DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. You just DO WHAT THE FUCK YOU WANT TO. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/CREDITS.md b/CREDITS.md index c892336..7ec3cbb 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,7 +1,6 @@ wallabag is based on : * PHP Readability https://bitbucket.org/fivefilters/php-readability * Full Text RSS http://code.fivefilters.org/full-text-rss/src -* Encoding https://github.com/neitanod/forceutf8 * logo by Maylis Agniel https://github.com/wallabag/logo * icons http://icomoon.io * PHP Simple HTML DOM Parser (for Pocket import) http://simplehtmldom.sourceforge.net/ @@ -10,6 +9,6 @@ wallabag is based on : * Flash messages https://github.com/plasticbrain/PHP-Flash-Messages * Pagination https://github.com/daveismyname/pagination -wallabag is developed by Nicolas Lœuillet under the Do What the Fuck You Want to Public License +wallabag is mainly developed by Nicolas Lœuillet under the MIT License Contributors : https://github.com/wallabag/wallabag/graphs/contributors \ No newline at end of file diff --git a/README.md b/README.md index 0b54dff..38866f7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ wallabag is a self hostable application allowing you to not miss any content any More informations on our website: [wallabag.org](http://wallabag.org) ## License -Copyright © 2010-2014 Nicolas Lœuillet +Copyright © 2013-2014 Nicolas Lœuillet This work is free. You can redistribute it and/or modify it under the -terms of the Do What The Fuck You Want To Public License, Version 2, -as published by Sam Hocevar. See the COPYING file for more details. +terms of the MIT License. See the COPYING file for more details. diff --git a/check_setup.php b/check_setup.php index 2b84a74..eee5d24 100644 --- a/check_setup.php +++ b/check_setup.php @@ -1,5 +1,4 @@ + * @copyright 2013 + * @license http://opensource.org/licenses/MIT see COPYING file + */ +class FlattrItem +{ public $status; public $urltoflattr; public $flattrItemURL; public $numflattrs; - public function checkItem($urltoflattr,$id) { - $this->cacheflattrfile($urltoflattr, $id); + public function checkItem($urltoflattr, $id) + { + $this->_cacheflattrfile($urltoflattr, $id); $flattrResponse = file_get_contents(CACHE . "/flattr/".$id.".cache"); if($flattrResponse != FALSE) { $result = json_decode($flattrResponse); - if (isset($result->message)){ + if (isset($result->message)) { if ($result->message == "flattrable") { $this->status = FLATTRABLE; } } - elseif (is_object($result) && $result->link) { + elseif (is_object($result) && $result->link) { $this->status = FLATTRED; $this->flattrItemURL = $result->link; $this->numflattrs = $result->flattrs; @@ -33,7 +40,8 @@ class FlattrItem { } } - private function cacheflattrfile($urltoflattr, $id) { + private function _cacheflattrfile($urltoflattr, $id) + { if (!is_dir(CACHE . '/flattr')) { mkdir(CACHE . '/flattr', 0777); } diff --git a/inc/3rdparty/Session.class.php b/inc/3rdparty/Session.class.php index 59dfbe6..b56e4c5 100644 --- a/inc/3rdparty/Session.class.php +++ b/inc/3rdparty/Session.class.php @@ -309,4 +309,38 @@ class Session return true; // User is not banned. } + + + /** + * Tells if a param exists in session + * + * @param $name name of the param to test + * @return bool + */ + public static function isInSession($name) + { + return (isset($_SESSION[$name]) ? : FALSE); + } + + /** + * Returns param in session + * + * @param $name name of the param to return + * @return mixed param or null + */ + public static function getParam($name) + { + return (self::isInSession($name) ? $_SESSION[$name] : NULL); + } + + /** + * Store value in session + * + * @param $name name of the variable to store + * @param $value value to store + */ + public static function setParam($name, $value) + { + $_SESSION[$name] = $value; + } } diff --git a/inc/poche/Database.class.php b/inc/poche/Database.class.php index 11cccb7..9c1c028 100755 --- a/inc/poche/Database.class.php +++ b/inc/poche/Database.class.php @@ -5,7 +5,7 @@ * @category wallabag * @author Nicolas Lœuillet * @copyright 2013 - * @license http://www.wtfpl.net/ see COPYING file + * @license http://opensource.org/licenses/MIT see COPYING file */ class Database { @@ -38,6 +38,7 @@ class Database { } $this->handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->_checkTags(); Tools::logm('storage type ' . STORAGE); } @@ -45,21 +46,7 @@ class Database { return $this->handle; } - public function isInstalled() { - $sql = "SELECT username FROM users"; - $query = $this->executeQuery($sql, array()); - if ($query == false) { - die(STORAGE . ' database looks empty. You have to create it (you can find database structure in install folder).'); - } - $hasAdmin = count($query->fetchAll()); - - if ($hasAdmin == 0) - return false; - - return true; - } - - public function checkTags() { + private function _checkTags() { if (STORAGE == 'sqlite') { $sql = ' diff --git a/inc/poche/Language.class.php b/inc/poche/Language.class.php new file mode 100644 index 0000000..790cc19 --- /dev/null +++ b/inc/poche/Language.class.php @@ -0,0 +1,114 @@ + + * @copyright 2013 + * @license http://opensource.org/licenses/MIT see COPYING file + */ + +class Language +{ + protected $wallabag; + + private $currentLanguage; + + private $languageNames = array( + 'cs_CZ.utf8' => 'čeština', + 'de_DE.utf8' => 'German', + 'en_EN.utf8' => 'English', + 'es_ES.utf8' => 'Español', + 'fa_IR.utf8' => 'فارسی', + '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' => 'Українська', + ); + + public function __construct(Poche $wallabag) + { + $this->wallabag = $wallabag; + $pocheUser = Session::getParam('poche_user'); + $language = (is_null($pocheUser) ? LANG : $pocheUser->getConfigValue('language')); + + @putenv('LC_ALL=' . $language); + setlocale(LC_ALL, $language); + bindtextdomain($language, LOCALE); + textdomain($language); + + $this->currentLanguage = $language; + } + + public function getLanguage() { + return $this->currentLanguage; + } + + 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('..', '.', 'tools'))) { + continue; + } + + $current = false; + + if ($language === $this->getLanguage()) { + $current = true; + } + + $languages[] = array('name' => (isset($this->languageNames[$language]) ? $this->languageNames[$language] : $language), 'value' => $language, 'current' => $current); + } + + return $languages; + } + + + /** + * Update language for current user + * + * @param $newLanguage + */ + public function updateLanguage($newLanguage) + { + # we are not going to change it to the current language + if ($newLanguage == $this->getLanguage()) { + $this->wallabag->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'] == $newLanguage) { + $actualLanguage = true; + break; + } + } + + if (!$actualLanguage) { + $this->wallabag->messages->add('e', _('that language does not seem to be installed')); + Tools::redirect('?view=config'); + } + + $this->wallabag->store->updateUserConfig($this->wallabag->user->getId(), 'language', $newLanguage); + $this->wallabag->messages->add('s', _('you have changed your language preferences')); + + $currentConfig = $_SESSION['poche_user']->config; + $currentConfig['language'] = $newLanguage; + + $_SESSION['poche_user']->setConfig($currentConfig); + + $this->wallabag->emptyCache(); + + Tools::redirect('?view=config'); + } +} \ No newline at end of file diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php index 09a9f5f..bc4320b 100755 --- a/inc/poche/Poche.class.php +++ b/inc/poche/Poche.class.php @@ -5,244 +5,77 @@ * @category wallabag * @author Nicolas Lœuillet * @copyright 2013 - * @license http://www.wtfpl.net/ see COPYING file + * @license http://opensource.org/licenses/MIT see COPYING file */ class Poche { - public static $canRenderTemplates = true; - public static $configFileAvailable = true; - + /** + * @var User + */ public $user; + /** + * @var Database + */ public $store; + /** + * @var Template + */ public $tpl; + /** + * @var Language + */ + public $language; + /** + * @var Routing + */ + public $routing; + /** + * @var Messages + */ public $messages; + /** + * @var Paginator + */ public $pagination; - private $currentTheme = ''; - private $currentLanguage = ''; - private $notInstalledMessage = array(); - - private $language_names = array( - 'cs_CZ.utf8' => 'čeština', - 'de_DE.utf8' => 'German', - 'en_EN.utf8' => 'English', - 'es_ES.utf8' => 'Español', - 'fa_IR.utf8' => 'فارسی', - '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' => 'Українська', - ); public function __construct() { - if ($this->configFileIsAvailable()) { - $this->init(); - } - - if ($this->themeIsInstalled()) { - $this->initTpl(); - } - - if ($this->systemIsInstalled()) { - $this->store = new Database(); - $this->messages = new Messages(); - # installation - if (! $this->store->isInstalled()) { - $this->install(); - } - $this->store->checkTags(); - } + $this->init(); } private function init() { Tools::initPhp(); - if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) { - $this->user = $_SESSION['poche_user']; + $pocheUser = Session::getParam('poche_user'); + + if ($pocheUser && $pocheUser != array()) { + $this->user = $pocheUser; } else { - # fake user, just for install & login screens + // fake user, just for install & login screens $this->user = new User(); $this->user->setConfig($this->getDefaultConfig()); } - # l10n - $language = $this->user->getConfigValue('language'); - @putenv('LC_ALL=' . $language); - setlocale(LC_ALL, $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; + $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p'); + $this->language = new Language($this); + $this->tpl = new Template($this); + $this->store = new Database(); + $this->messages = new Messages(); + $this->routing = new Routing($this); } - public function configFileIsAvailable() { - if (! self::$configFileAvailable) { - $this->notInstalledMessage[] = 'You have to copy (don\'t just rename!) inc/poche/config.inc.default.php to inc/poche/config.inc.php.'; - - return false; - } - - 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 - if (! self::$canRenderTemplates) { - $this->notInstalledMessage[] = 'Twig does not seem to be installed. Please initialize the Composer installation to automatically fetch dependencies. You can also download vendor.zip and extract it in your wallabag folder.'; - $passTheme = FALSE; - } - - if (! is_writable(CACHE)) { - $this->notInstalledMessage[] = 'You don\'t have write access on cache directory.'; - - 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; - } - } - } - - if (!$passTheme) { - return FALSE; - } - - - return true; + public function run() + { + $this->routing->run(); } /** - * all checks before installation. - * @todo move HTML to template - * @return boolean + * Creates a new user */ - public function systemIsInstalled() + public function createNewUser() { - $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; - } - if (STORAGE == 'sqlite' && ! file_exists(STORAGE_SQLITE)) { - Tools::logm('sqlite file doesn\'t exist'); - $this->notInstalledMessage[] = 'sqlite file doesn\'t exist, you can find it in install folder. Copy it in /db folder.'; - $msg = FALSE; - } - if (is_dir(ROOT . '/install') && ! DEBUG_POCHE) { - $this->notInstalledMessage[] = 'you have to delete the /install folder before using poche.'; - $msg = FALSE; - } - if (STORAGE == 'sqlite' && ! is_writable(STORAGE_SQLITE)) { - Tools::logm('you don\'t have write access on sqlite file'); - $this->notInstalledMessage[] = 'You don\'t have write access on sqlite file.'; - $msg = FALSE; - } - - if (! $msg) { - return false; - } - - return true; - } - - public function getNotInstalledMessage() { - return $this->notInstalledMessage; - } - - private function initTpl() - { - $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)); - } catch (Twig_Error_Loader $e) { - # @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'])) { - foreach ($themeInfo['requirements'] as $requiredTheme) { - try { - $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $requiredTheme)); - } catch (Twig_Error_Loader $e) { - # @todo isInstalled() should catch this, inject Twig later - die('The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')'); - } - } - } - - 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); - - # filter for reading time - $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); @@ -266,7 +99,11 @@ class Poche } } - public function deleteUser(){ + /** + * Delete an existing user + */ + 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())) { @@ -294,115 +131,6 @@ class Poche } } - private function install() - { - Tools::logm('poche still not installed'); - echo $this->tpl->render('install.twig', array( - 'token' => Session::getToken(), - 'theme' => $this->getTheme(), - 'poche_url' => Tools::getPocheUrl() - )); - if (isset($_GET['install'])) { - 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']))) - { - Session::logout(); - Tools::logm('poche is now installed'); - Tools::redirect(); - } - } - else { - Tools::logm('error during installation'); - Tools::redirect(); - } - } - exit(); - } - - public function getTheme() { - return $this->currentTheme; - } - - /** - * Provides theme information by parsing theme.ini file if present in the theme's root directory. - * In all cases, the following data will be returned: - * - name: theme's name, or key if the theme is unnamed, - * - current: boolean informing if the theme is the current user theme. - * - * @param string $theme Theme key (directory name) - * @return array|boolean Theme information, or false if the theme doesn't exist. - */ - public function getThemeInfo($theme) { - if (!is_dir(THEME . '/' . $theme)) { - return false; - } - - $themeIniFile = THEME . '/' . $theme . '/theme.ini'; - $themeInfo = array(); - - if (is_file($themeIniFile) && is_readable($themeIniFile)) { - $themeInfo = parse_ini_file($themeIniFile); - } - - if ($themeInfo === false) { - $themeInfo = array(); - } - if (!isset($themeInfo['name'])) { - $themeInfo['name'] = $theme; - } - $themeInfo['current'] = ($theme === $this->getTheme()); - - return $themeInfo; - } - - public function getInstalledThemes() { - $handle = opendir(THEME); - $themes = array(); - - while (($theme = readdir($handle)) !== false) { - # Themes are stored in a directory, so all directory names are themes - # @todo move theme installation data to database - if (!is_dir(THEME . '/' . $theme) || in_array($theme, array('.', '..'))) { - continue; - } - - $themes[$theme] = $this->getThemeInfo($theme); - } - - ksort($themes); - - return $themes; - } - - public function getLanguage() { - return $this->currentLanguage; - } - - 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('..', '.', 'tools'))) { - continue; - } - - $current = false; - - if ($language === $this->getLanguage()) { - $current = true; - } - - $languages[] = array('name' => (isset($this->language_names[$language]) ? $this->language_names[$language] : $language), 'value' => $language, 'current' => $current); - } - - return $languages; - } - public function getDefaultConfig() { return array( @@ -437,7 +165,7 @@ class Poche if ( $last_id ) { Tools::logm('add link ' . $url->getUrl()); if (DOWNLOAD_PICTURES) { - $content = filtre_picture($body, $url->getUrl(), $last_id); + $content = Picture::filterPicture($body, $url->getUrl(), $last_id); Tools::logm('updating content article'); $this->store->updateContent($last_id, $content, $this->user->getId()); } @@ -472,7 +200,7 @@ class Poche $msg = 'delete link #' . $id; if ($this->store->deleteById($id, $this->user->getId())) { if (DOWNLOAD_PICTURES) { - remove_directory(ABS_PATH . $id); + Picture::removeDirectory(ABS_PATH . $id); } $this->messages->add('s', _('the link has been deleted successfully')); } @@ -598,8 +326,8 @@ class Poche $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(); + $themes = $this->tpl->getInstalledThemes(); + $languages = $this->language->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; @@ -703,7 +431,7 @@ class Poche 'listmode' => (isset($_COOKIE['listmode']) ? true : false), ); - //if id is given - we retrive entries by tag: id is tag id + //if id is given - we retrieve entries by tag: id is tag id if ($id) { $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId()); $tpl_vars['id'] = intval($id); @@ -757,85 +485,6 @@ 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'); - } - - public function updateLanguage() - { - # 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 diff --git a/inc/poche/Routing.class.php b/inc/poche/Routing.class.php new file mode 100644 index 0000000..7e259c2 --- /dev/null +++ b/inc/poche/Routing.class.php @@ -0,0 +1,149 @@ + + * @copyright 2013 + * @license http://opensource.org/licenses/MIT see COPYING file + */ + +class Routing +{ + protected $wallabag; + protected $referer; + protected $view; + protected $action; + protected $id; + protected $url; + protected $file; + protected $defaultVars = array(); + protected $vars = array(); + + public function __construct(Poche $wallabag) + { + $this->wallabag = $wallabag; + $this->_init(); + } + + private function _init() + { + # Parse GET & REFERER vars + $this->referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']; + $this->view = Tools::checkVar('view', 'home'); + $this->action = Tools::checkVar('action'); + $this->id = Tools::checkVar('id'); + $_SESSION['sort'] = Tools::checkVar('sort', 'id'); + $this->url = new Url((isset ($_GET['url'])) ? $_GET['url'] : ''); + } + + public function run() + { + # vars to _always_ send to templates + $this->defaultVars = array( + 'referer' => $this->referer, + 'view' => $this->view, + 'poche_url' => Tools::getPocheUrl(), + 'title' => _('wallabag, a read it later open source system'), + 'token' => \Session::getToken(), + 'theme' => $this->wallabag->tpl->getTheme() + ); + + $this->_launchAction(); + $this->_defineTplInformation(); + + # because messages can be added in $poche->action(), we have to add this entry now (we can add it before) + $this->vars = array_merge($this->vars, array('messages' => $this->wallabag->messages->display('all', FALSE))); + + $this->_render($this->file, $this->vars); + } + + private function _defineTplInformation() + { + $tplFile = array(); + $tplVars = array(); + + if (\Session::isLogged()) { + $this->wallabag->action($this->action, $this->url, $this->id); + $tplFile = Tools::getTplFile($this->view); + $tplVars = array_merge($this->vars, $this->wallabag->displayView($this->view, $this->id)); + } elseif(isset($_SERVER['PHP_AUTH_USER'])) { + if($this->wallabag->store->userExists($_SERVER['PHP_AUTH_USER'])) { + $this->wallabag->login($this->referer); + } else { + $this->wallabag->messages->add('e', _('login failed: user doesn\'t exist')); + Tools::logm('user doesn\'t exist'); + $tplFile = Tools::getTplFile('login'); + $tplVars['http_auth'] = 1; + } + } elseif(isset($_SERVER['REMOTE_USER'])) { + if($this->wallabag->store->userExists($_SERVER['REMOTE_USER'])) { + $this->wallabag->login($this->referer); + } else { + $this->wallabag->messages->add('e', _('login failed: user doesn\'t exist')); + Tools::logm('user doesn\'t exist'); + $tplFile = Tools::getTplFile('login'); + $tplVars['http_auth'] = 1; + } + } else { + $tplFile = Tools::getTplFile('login'); + $tplVars['http_auth'] = 0; + \Session::logout(); + } + + $this->file = $tplFile; + $this->vars = array_merge($this->defaultVars, $tplVars); + } + + private function _launchAction() + { + if (isset($_GET['login'])) { + // hello you + $this->wallabag->login($this->referer); + } elseif (isset($_GET['logout'])) { + // see you soon ! + $this->wallabag->logout(); + } elseif (isset($_GET['config'])) { + // update password + $this->wallabag->updatePassword(); + } elseif (isset($_GET['newuser'])) { + $this->wallabag->createNewUser(); + } elseif (isset($_GET['deluser'])) { + $this->wallabag->deleteUser(); + } elseif (isset($_GET['epub'])) { + $this->wallabag->createEpub(); + } elseif (isset($_GET['import'])) { + $import = $this->wallabag->import(); + $tplVars = array_merge($this->vars, $import); + } elseif (isset($_GET['download'])) { + Tools::downloadDb(); + } elseif (isset($_GET['empty-cache'])) { + $this->wallabag->emptyCache(); + } elseif (isset($_GET['export'])) { + $this->wallabag->export(); + } elseif (isset($_GET['updatetheme'])) { + $this->wallabag->tpl->updateTheme($_POST['theme']); + } elseif (isset($_GET['updatelanguage'])) { + $this->wallabag->language->updateLanguage($_POST['language']); + } elseif (isset($_GET['uploadfile'])) { + $this->wallabag->uploadFile(); + } elseif (isset($_GET['feed'])) { + if (isset($_GET['action']) && $_GET['action'] == 'generate') { + $this->wallabag->generateToken(); + } + else { + $tag_id = (isset($_GET['tag_id']) ? intval($_GET['tag_id']) : 0); + $this->wallabag->generateFeeds($_GET['token'], filter_var($_GET['user_id'],FILTER_SANITIZE_NUMBER_INT), $tag_id, $_GET['type']); + } + } + elseif (isset($_GET['plainurl']) && !empty($_GET['plainurl'])) { + $plainUrl = new Url(base64_encode($_GET['plainurl'])); + $this->wallabag->action('add', $plainUrl); + } + } + + private function _render($file, $vars) + { + echo $this->wallabag->tpl->render($file, $vars); + } +} \ No newline at end of file diff --git a/inc/poche/Template.class.php b/inc/poche/Template.class.php new file mode 100644 index 0000000..0e09ad6 --- /dev/null +++ b/inc/poche/Template.class.php @@ -0,0 +1,236 @@ + + * @copyright 2013 + * @license http://opensource.org/licenses/MIT see COPYING file + */ + +class Template extends Twig_Environment +{ + protected $wallabag; + + private $canRenderTemplates = TRUE; + private $currentTheme = ''; + + public function __construct(Poche $wallabag) + { + $this->wallabag = $wallabag; + + // Set up theme + $pocheUser = Session::getParam('poche_user'); + + $themeDirectory = (is_null($pocheUser) ? DEFAULT_THEME : $pocheUser->getConfigValue('theme')); + + if ($themeDirectory === false) { + $themeDirectory = DEFAULT_THEME; + } + + $this->currentTheme = $themeDirectory; + + if ($this->_themeIsInstalled() === array()) { + $this->_init(); + } + } + + /** + * Returns true if selected theme is installed + * + * @return bool + */ + private function _themeIsInstalled() + { + $errors = array(); + + // Twig is an absolute requirement for wallabag to function. + // Abort immediately if the Composer installer hasn't been run yet + if (!$this->canRenderTemplates) { + $errors[] = 'Twig does not seem to be installed. Please initialize the Composer installation to automatically fetch dependencies. You can also download vendor.zip and extract it in your wallabag folder.'; + } + + // Check if the selected theme and its requirements are present + $theme = $this->getTheme(); + if ($theme != '' && !is_dir(THEME . '/' . $theme)) { + $errors[] = 'The currently selected theme (' . $theme . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $theme . ')'; + $this->canRenderTemplates = FALSE; + } + + $themeInfo = $this->getThemeInfo($theme); + if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) { + foreach ($themeInfo['requirements'] as $requiredTheme) { + if (! is_dir(THEME . '/' . $requiredTheme)) { + $errors[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')'; + $this->canRenderTemplates = FALSE; + } + } + } + + $currentErrors = (is_null(Session::getParam('errors'))? array() : Session::getParam('errors')); + Session::setParam('errors', array_merge($errors, $currentErrors)); + + return $errors; + } + + /** + * Initialization for templates + */ + private function _init() + { + $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)); + } catch (Twig_Error_Loader $e) { + # @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'])) { + foreach ($themeInfo['requirements'] as $requiredTheme) { + try { + $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $requiredTheme)); + } catch (Twig_Error_Loader $e) { + # @todo isInstalled() should catch this, inject Twig later + die('The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')'); + } + } + } + + if (DEBUG_POCHE) { + $twigParams = array(); + } else { + $twigParams = array('cache' => CACHE); + } + + parent::__construct($loaderChain, $twigParams); + + //$tpl = new Twig_Environment($loaderChain, $twigParams); + $this->addExtension(new Twig_Extensions_Extension_I18n()); + + # filter to display domain name of an url + $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain'); + $this->addFilter($filter); + + # filter for reading time + $filter = new Twig_SimpleFilter('getReadingTime', 'Tools::getReadingTime'); + $this->addFilter($filter); + } + + /** + * Returns current theme + * + * @return string + */ + public function getTheme() + { + return $this->currentTheme; + } + + /** + * Provides theme information by parsing theme.ini file if present in the theme's root directory. + * In all cases, the following data will be returned: + * - name: theme's name, or key if the theme is unnamed, + * - current: boolean informing if the theme is the current user theme. + * + * @param string $theme Theme key (directory name) + * @return array|boolean Theme information, or false if the theme doesn't exist. + */ + public function getThemeInfo($theme) + { + if (!is_dir(THEME . '/' . $theme)) { + return false; + } + + $themeIniFile = THEME . '/' . $theme . '/theme.ini'; + $themeInfo = array(); + + if (is_file($themeIniFile) && is_readable($themeIniFile)) { + $themeInfo = parse_ini_file($themeIniFile); + } + + if ($themeInfo === false) { + $themeInfo = array(); + } + + if (!isset($themeInfo['name'])) { + $themeInfo['name'] = $theme; + } + + $themeInfo['current'] = ($theme === $this->getTheme()); + + return $themeInfo; + } + + /** + * Returns an array with installed themes + * + * @return array + */ + public function getInstalledThemes() + { + $handle = opendir(THEME); + $themes = array(); + + while (($theme = readdir($handle)) !== false) { + # Themes are stored in a directory, so all directory names are themes + # @todo move theme installation data to database + if (!is_dir(THEME . '/' . $theme) || in_array($theme, array('.', '..'))) { + continue; + } + + $themes[$theme] = $this->getThemeInfo($theme); + } + + ksort($themes); + + return $themes; + } + + /** + * Update theme for the current user + * + * @param $newTheme + */ + public function updateTheme($newTheme) + { + # we are not going to change it to the current theme... + if ($newTheme == $this->getTheme()) { + $this->wallabag->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 == $newTheme) { + $actualTheme = true; + break; + } + } + + if (!$actualTheme) { + $this->wallabag->messages->add('e', _('that theme does not seem to be installed')); + Tools::redirect('?view=config'); + } + + $this->wallabag->store->updateUserConfig($this->wallabag->user->getId(), 'theme', $newTheme); + $this->wallabag->messages->add('s', _('you have changed your theme preferences')); + + $currentConfig = $_SESSION['poche_user']->config; + $currentConfig['theme'] = $newTheme; + + $_SESSION['poche_user']->setConfig($currentConfig); + + $this->wallabag->emptyCache(); + + Tools::redirect('?view=config'); + } +} \ No newline at end of file diff --git a/inc/poche/Tools.class.php b/inc/poche/Tools.class.php index cc01f40..762e444 100755 --- a/inc/poche/Tools.class.php +++ b/inc/poche/Tools.class.php @@ -5,19 +5,23 @@ * @category wallabag * @author Nicolas Lœuillet * @copyright 2013 - * @license http://www.wtfpl.net/ see COPYING file + * @license http://opensource.org/licenses/MIT see COPYING file */ -class Tools +final class Tools { + private function __construct() + { + + } + + /** + * Initialize PHP environment + */ public static function initPhp() { define('START_TIME', microtime(true)); - if (phpversion() < 5) { - die(_('Oops, it seems you don\'t have PHP 5.')); - } - function stripslashesDeep($value) { return is_array($value) ? array_map('stripslashesDeep', $value) @@ -34,6 +38,11 @@ class Tools register_shutdown_function('ob_end_flush'); } + /** + * Get wallabag instance URL + * + * @return string + */ public static function getPocheUrl() { $https = (!empty($_SERVER['HTTPS']) @@ -67,6 +76,11 @@ class Tools . $host . $serverport . $scriptname; } + /** + * Redirects to a URL + * + * @param string $url + */ public static function redirect($url = '') { if ($url === '') { @@ -87,11 +101,18 @@ class Tools $url = $ref; } } + self::logm('redirect to ' . $url); header('Location: '.$url); exit(); } + /** + * Returns name of the template file to display + * + * @param $view + * @return string + */ public static function getTplFile($view) { $views = array( @@ -99,13 +120,15 @@ class Tools 'edit-tags', 'view', 'login', 'error' ); - if (in_array($view, $views)) { - return $view . '.twig'; - } - - return 'home.twig'; + return (in_array($view, $views) ? $view . '.twig' : 'home.twig'); } + /** + * Download a file (typically, for downloading pictures on web server) + * + * @param $url + * @return bool|mixed|string + */ public static function getFile($url) { $timeout = 15; @@ -186,6 +209,11 @@ class Tools } } + /** + * Headers for JSON export + * + * @param $data + */ public static function renderJson($data) { header('Cache-Control: no-cache, must-revalidate'); @@ -195,6 +223,11 @@ class Tools exit(); } + /** + * Create new line in log file + * + * @param $message + */ public static function logm($message) { if (DEBUG_POCHE && php_sapi_name() != 'cli') { @@ -204,36 +237,57 @@ class Tools } } + /** + * Encode a URL by using a salt + * + * @param $string + * @return string + */ public static function encodeString($string) { return sha1($string . SALT); } + /** + * Cleans a variable + * + * @param $var + * @param string $default + * @return string + */ public static function checkVar($var, $default = '') { - return ((isset ($_REQUEST["$var"])) ? htmlentities($_REQUEST["$var"]) : $default); + return ((isset($_REQUEST["$var"])) ? htmlentities($_REQUEST["$var"]) : $default); } + /** + * Returns the domain name for a URL + * + * @param $url + * @return string + */ public static function getDomain($url) { return parse_url($url, PHP_URL_HOST); } - public static function getReadingTime($text) { - $word = str_word_count(strip_tags($text)); - $minutes = floor($word / 200); - $seconds = floor($word % 200 / (200 / 60)); - $time = array('minutes' => $minutes, 'seconds' => $seconds); - - return $minutes; + /** + * For a given text, we calculate reading time for an article + * + * @param $text + * @return float + */ + public static function getReadingTime($text) + { + return floor(str_word_count(strip_tags($text)) / 200); } - public static function getDocLanguage($userlanguage) { - $lang = explode('.', $userlanguage); - return str_replace('_', '-', $lang[0]); - } - - public static function status($status_code) + /** + * Returns the correct header for a status code + * + * @param $status_code + */ + private static function _status($status_code) { if (strpos(php_sapi_name(), 'apache') !== false) { @@ -245,9 +299,13 @@ class Tools } } - public static function download_db() { + /** + * Download the sqlite database + */ + public static function downloadDb() + { header('Content-Disposition: attachment; filename="poche.sqlite.gz"'); - self::status(200); + self::_status(200); header('Content-Transfer-Encoding: binary'); header('Content-Type: application/octet-stream'); @@ -256,18 +314,24 @@ class Tools exit; } + /** + * Get the content for a given URL (by a call to FullTextFeed) + * + * @param Url $url + * @return mixed + */ public static function getPageContent(Url $url) { // Saving and clearing context $REAL = array(); foreach( $GLOBALS as $key => $value ) { if( $key != 'GLOBALS' && $key != '_SESSION' && $key != 'HTTP_SESSION_VARS' ) { - $GLOBALS[$key] = array(); - $REAL[$key] = $value; + $GLOBALS[$key] = array(); + $REAL[$key] = $value; } } // Saving and clearing session - if ( isset($_SESSION) ) { + if (isset($_SESSION)) { $REAL_SESSION = array(); foreach( $_SESSION as $key => $value ) { $REAL_SESSION[$key] = $value; @@ -279,12 +343,12 @@ class Tools $scope = function() { extract( func_get_arg(1) ); $_GET = $_REQUEST = array( - "url" => $url->getUrl(), - "max" => 5, - "links" => "preserve", - "exc" => "", - "format" => "json", - "submit" => "Create Feed" + "url" => $url->getUrl(), + "max" => 5, + "links" => "preserve", + "exc" => "", + "format" => "json", + "submit" => "Create Feed" ); ob_start(); require func_get_arg(0); @@ -292,23 +356,26 @@ class Tools ob_end_clean(); return $json; }; - $json = $scope( "inc/3rdparty/makefulltextfeed.php", array("url" => $url) ); + + $json = $scope("inc/3rdparty/makefulltextfeed.php", array("url" => $url)); // Clearing and restoring context - foreach( $GLOBALS as $key => $value ) { - if( $key != "GLOBALS" && $key != "_SESSION" ) { + foreach ($GLOBALS as $key => $value) { + if($key != "GLOBALS" && $key != "_SESSION" ) { unset($GLOBALS[$key]); } } - foreach( $REAL as $key => $value ) { + foreach ($REAL as $key => $value) { $GLOBALS[$key] = $value; } + // Clearing and restoring session - if ( isset($REAL_SESSION) ) { - foreach( $_SESSION as $key => $value ) { + if (isset($REAL_SESSION)) { + foreach($_SESSION as $key => $value) { unset($_SESSION[$key]); } - foreach( $REAL_SESSION as $key => $value ) { + + foreach($REAL_SESSION as $key => $value) { $_SESSION[$key] = $value; } } @@ -318,11 +385,12 @@ class Tools /** * Returns whether we handle an AJAX (XMLHttpRequest) request. + * * @return boolean whether we handle an AJAX (XMLHttpRequest) request. */ public static function isAjaxRequest() { - return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest'; + return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest'; } } diff --git a/inc/poche/Url.class.php b/inc/poche/Url.class.php index aba236f..d9172b7 100644 --- a/inc/poche/Url.class.php +++ b/inc/poche/Url.class.php @@ -5,7 +5,7 @@ * @category wallabag * @author Nicolas Lœuillet * @copyright 2013 - * @license http://www.wtfpl.net/ see COPYING file + * @license http://opensource.org/licenses/MIT see COPYING file */ class Url diff --git a/inc/poche/User.class.php b/inc/poche/User.class.php index cc8bec6..eaadd3e 100644 --- a/inc/poche/User.class.php +++ b/inc/poche/User.class.php @@ -5,7 +5,7 @@ * @category wallabag * @author Nicolas Lœuillet * @copyright 2013 - * @license http://www.wtfpl.net/ see COPYING file + * @license http://opensource.org/licenses/MIT see COPYING file */ class User @@ -44,7 +44,14 @@ class User $this->config = $config; } - public function getConfigValue($name) { + /** + * Returns configuration entry for a user + * + * @param $name + * @return bool + */ + public function getConfigValue($name) + { return (isset($this->config[$name])) ? $this->config[$name] : FALSE; } } \ No newline at end of file diff --git a/inc/poche/config.inc.default.php b/inc/poche/config.inc.default.php index 95f727c..6f03af1 100755 --- a/inc/poche/config.inc.default.php +++ b/inc/poche/config.inc.default.php @@ -5,7 +5,7 @@ * @category wallabag * @author Nicolas Lœuillet * @copyright 2013 - * @license http://www.wtfpl.net/ see COPYING file + * @license http://opensource.org/licenses/MIT see COPYING file */ @define ('SALT', ''); # put a strong string here diff --git a/inc/poche/global.inc.php b/inc/poche/global.inc.php index 8cf86d0..2c22c01 100755 --- a/inc/poche/global.inc.php +++ b/inc/poche/global.inc.php @@ -5,7 +5,7 @@ * @category wallabag * @author Nicolas Lœuillet * @copyright 2013 - * @license http://www.wtfpl.net/ see COPYING file + * @license http://opensource.org/licenses/MIT see COPYING file */ # the poche system root directory (/inc) @@ -18,6 +18,10 @@ require_once INCLUDES . '/poche/Tools.class.php'; require_once INCLUDES . '/poche/User.class.php'; require_once INCLUDES . '/poche/Url.class.php'; require_once INCLUDES . '/3rdparty/class.messages.php'; +require_once ROOT . '/vendor/autoload.php'; +require_once INCLUDES . '/poche/Template.class.php'; +require_once INCLUDES . '/poche/Language.class.php'; +require_once INCLUDES . '/poche/Routing.class.php'; require_once INCLUDES . '/poche/Poche.class.php'; require_once INCLUDES . '/poche/Database.class.php'; @@ -36,22 +40,11 @@ require_once INCLUDES . '/3rdparty/libraries/PHPePub/Logger.php'; require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPub.php'; require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPubChapterSplitter.php'; -# Composer its autoloader for automatically loading Twig -if (! file_exists(ROOT . '/vendor/autoload.php')) { - Poche::$canRenderTemplates = false; -} else { - require_once ROOT . '/vendor/autoload.php'; -} - # system configuration; database credentials et caetera -if (! file_exists(INCLUDES . '/poche/config.inc.php')) { - Poche::$configFileAvailable = false; -} else { - require_once INCLUDES . '/poche/config.inc.php'; - require_once INCLUDES . '/poche/config.inc.default.php'; -} +require_once INCLUDES . '/poche/config.inc.php'; +require_once INCLUDES . '/poche/config.inc.default.php'; -if (Poche::$configFileAvailable && DOWNLOAD_PICTURES) { +if (DOWNLOAD_PICTURES) { require_once INCLUDES . '/poche/pochePictures.php'; } diff --git a/inc/poche/pochePictures.php b/inc/poche/pochePictures.php index 7c319a8..26bf070 100644 --- a/inc/poche/pochePictures.php +++ b/inc/poche/pochePictures.php @@ -5,154 +5,169 @@ * @category wallabag * @author Nicolas Lœuillet * @copyright 2013 - * @license http://www.wtfpl.net/ see COPYING file + * @license http://opensource.org/licenses/MIT see COPYING file */ -/** - * On modifie les URLS des images dans le corps de l'article - */ -function filtre_picture($content, $url, $id) + +final class Picture { - $matches = array(); - $processing_pictures = array(); // list of processing image to avoid processing the same pictures twice - preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER); - foreach($matches as $i => $link) { - $link[1] = trim($link[1]); - if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1])) { - $absolute_path = get_absolute_link($link[2],$url); - $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); - $directory = create_assets_directory($id); - $fullpath = $directory . '/' . $filename; - - if (in_array($absolute_path, $processing_pictures) === true) { - // replace picture's URL only if processing is OK : already processing -> go to next picture - continue; + private function __construct() + { + + } + + /** + * Changing pictures URL in article content + */ + public static function filterPicture($content, $url, $id) + { + $matches = array(); + $processing_pictures = array(); // list of processing image to avoid processing the same pictures twice + preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER); + foreach($matches as $i => $link) { + $link[1] = trim($link[1]); + if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1])) { + $absolute_path = self::_getAbsoluteLink($link[2], $url); + $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); + $directory = self::_createAssetsDirectory($id); + $fullpath = $directory . '/' . $filename; + + if (in_array($absolute_path, $processing_pictures) === true) { + // replace picture's URL only if processing is OK : already processing -> go to next picture + continue; + } + + if (self::_downloadPictures($absolute_path, $fullpath) === true) { + $content = str_replace($matches[$i][2], $fullpath, $content); + } + + $processing_pictures[] = $absolute_path; } - - if (download_pictures($absolute_path, $fullpath) === true) { - $content = str_replace($matches[$i][2], $fullpath, $content); - } - - $processing_pictures[] = $absolute_path; } + return $content; } - return $content; -} + /** + * Get absolute URL + */ + private static function _getAbsoluteLink($relativeLink, $url) + { + /* return if already absolute URL */ + if (parse_url($relativeLink, PHP_URL_SCHEME) != '') return $relativeLink; -/** - * Retourne le lien absolu - */ -function get_absolute_link($relative_link, $url) { - /* return if already absolute URL */ - if (parse_url($relative_link, PHP_URL_SCHEME) != '') return $relative_link; + /* queries and anchors */ + if ($relativeLink[0]=='#' || $relativeLink[0]=='?') return $url . $relativeLink; - /* queries and anchors */ - if ($relative_link[0]=='#' || $relative_link[0]=='?') return $url . $relative_link; + /* parse base URL and convert to local variables: + $scheme, $host, $path */ + extract(parse_url($url)); - /* parse base URL and convert to local variables: - $scheme, $host, $path */ - extract(parse_url($url)); + /* remove non-directory element from path */ + $path = preg_replace('#/[^/]*$#', '', $path); - /* remove non-directory element from path */ - $path = preg_replace('#/[^/]*$#', '', $path); + /* destroy path if relative url points to root */ + if ($relativeLink[0] == '/') $path = ''; - /* destroy path if relative url points to root */ - if ($relative_link[0] == '/') $path = ''; + /* dirty absolute URL */ + $abs = $host . $path . '/' . $relativeLink; - /* dirty absolute URL */ - $abs = $host . $path . '/' . $relative_link; + /* replace '//' or '/./' or '/foo/../' with '/' */ + $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'); + for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {} - /* replace '//' or '/./' or '/foo/../' with '/' */ - $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'); - for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {} - - /* absolute URL is ready! */ - return $scheme.'://'.$abs; -} - -/** - * Téléchargement des images - * - * @return bool true if the download and processing is OK, false else - */ -function download_pictures($absolute_path, $fullpath) -{ - $rawdata = Tools::getFile($absolute_path); - $fullpath = urldecode($fullpath); - - if(file_exists($fullpath)) { - unlink($fullpath); - } - - // check extension - $file_ext = strrchr($fullpath, '.'); - $whitelist = array(".jpg",".jpeg",".gif",".png"); - if (!(in_array($file_ext, $whitelist))) { - Tools::logm('processed image with not allowed extension. Skipping ' . $fullpath); - return false; - } - - // check headers - $imageinfo = getimagesize($absolute_path); - if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg'&& $imageinfo['mime'] != 'image/jpg'&& $imageinfo['mime'] != 'image/png') { - Tools::logm('processed image with bad header. Skipping ' . $fullpath); - return false; - } - - // regenerate image - $im = imagecreatefromstring($rawdata); - if ($im === false) { - Tools::logm('error while regenerating image ' . $fullpath); - return false; - } - - switch ($imageinfo['mime']) { - case 'image/gif': - $result = imagegif($im, $fullpath); - break; - case 'image/jpeg': - case 'image/jpg': - $result = imagejpeg($im, $fullpath, REGENERATE_PICTURES_QUALITY); - break; - case 'image/png': - $result = imagepng($im, $fullpath, ceil(REGENERATE_PICTURES_QUALITY / 100 * 9)); - break; - } - imagedestroy($im); - - return $result; -} - -/** - * Crée un répertoire de médias pour l'article - */ -function create_assets_directory($id) -{ - $assets_path = ABS_PATH; - if(!is_dir($assets_path)) { - mkdir($assets_path, 0715); + /* absolute URL is ready! */ + return $scheme.'://'.$abs; } - $article_directory = $assets_path . $id; - if(!is_dir($article_directory)) { - mkdir($article_directory, 0715); - } + /** + * Downloading pictures + * + * @return bool true if the download and processing is OK, false else + */ + private static function _downloadPictures($absolute_path, $fullpath) + { + $rawdata = Tools::getFile($absolute_path); + $fullpath = urldecode($fullpath); - return $article_directory; -} - -/** - * Suppression du répertoire d'images - */ -function remove_directory($directory) -{ - if(is_dir($directory)) { - $files = array_diff(scandir($directory), array('.','..')); - foreach ($files as $file) { - (is_dir("$directory/$file")) ? remove_directory("$directory/$file") : unlink("$directory/$file"); + if(file_exists($fullpath)) { + unlink($fullpath); } - return rmdir($directory); + + // check extension + $file_ext = strrchr($fullpath, '.'); + $whitelist = array(".jpg",".jpeg",".gif",".png"); + if (!(in_array($file_ext, $whitelist))) { + Tools::logm('processed image with not allowed extension. Skipping ' . $fullpath); + return false; + } + + // check headers + $imageinfo = getimagesize($absolute_path); + if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg'&& $imageinfo['mime'] != 'image/jpg'&& $imageinfo['mime'] != 'image/png') { + Tools::logm('processed image with bad header. Skipping ' . $fullpath); + return false; + } + + // regenerate image + $im = imagecreatefromstring($rawdata); + if ($im === false) { + Tools::logm('error while regenerating image ' . $fullpath); + return false; + } + + switch ($imageinfo['mime']) { + case 'image/gif': + $result = imagegif($im, $fullpath); + break; + case 'image/jpeg': + case 'image/jpg': + $result = imagejpeg($im, $fullpath, REGENERATE_PICTURES_QUALITY); + break; + case 'image/png': + $result = imagepng($im, $fullpath, ceil(REGENERATE_PICTURES_QUALITY / 100 * 9)); + break; + } + imagedestroy($im); + + return $result; } -} + + /** + * Create a directory for an article + * + * @param $id ID of the article + * @return string + */ + private static function _createAssetsDirectory($id) + { + $assets_path = ABS_PATH; + if (!is_dir($assets_path)) { + mkdir($assets_path, 0715); + } + + $article_directory = $assets_path . $id; + if (!is_dir($article_directory)) { + mkdir($article_directory, 0715); + } + + return $article_directory; + } + + /** + * Remove the directory + * + * @param $directory + * @return bool + */ + public static function removeDirectory($directory) + { + if (is_dir($directory)) { + $files = array_diff(scandir($directory), array('.','..')); + foreach ($files as $file) { + (is_dir("$directory/$file")) ? self::removeDirectory("$directory/$file") : unlink("$directory/$file"); + } + return rmdir($directory); + } + } +} \ No newline at end of file diff --git a/index.php b/index.php index 481841e..825e9d5 100755 --- a/index.php +++ b/index.php @@ -5,139 +5,21 @@ * @category wallabag * @author Nicolas Lœuillet * @copyright 2013 - * @license http://www.wtfpl.net/ see COPYING file + * @license http://opensource.org/licenses/MIT see COPYING file */ -define ('POCHE', '1.7.1'); +define ('POCHE', '1.8.0'); require 'check_setup.php'; require_once 'inc/poche/global.inc.php'; -# Set error reporting level if (defined('ERROR_REPORTING')) { error_reporting(ERROR_REPORTING); } -# Start session -Session::$sessionName = 'poche'; +// Start session +Session::$sessionName = 'wallabag'; Session::init(); -# Start Poche -$poche = new Poche(); -$notInstalledMessage = $poche -> getNotInstalledMessage(); - -# Parse GET & REFERER vars -$referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']; -$view = Tools::checkVar('view', 'home'); -$action = Tools::checkVar('action'); -$id = Tools::checkVar('id'); -$_SESSION['sort'] = Tools::checkVar('sort', 'id'); -$url = new Url((isset ($_GET['url'])) ? $_GET['url'] : ''); - -# vars to _always_ send to templates -$tpl_vars = array( - 'referer' => $referer, - 'view' => $view, - 'poche_url' => Tools::getPocheUrl(), - 'title' => _('wallabag, a read it later open source system'), - 'token' => Session::getToken(), - 'theme' => $poche->getTheme() -); - -if (! empty($notInstalledMessage)) { - if (! Poche::$canRenderTemplates || ! Poche::$configFileAvailable) { - # We cannot use Twig to display the error message - echo '

Errors

    '; - foreach ($notInstalledMessage as $message) { - echo '
  1. ' . $message . '
  2. '; - } - echo '
'; - die(); - } else { - # Twig is installed, put the error message in the template - $tpl_file = Tools::getTplFile('error'); - $tpl_vars = array_merge($tpl_vars, array('msg' => $poche->getNotInstalledMessage())); - echo $poche->tpl->render($tpl_file, $tpl_vars); - exit; - } -} - -# poche actions -if (isset($_GET['login'])) { - # hello you - $poche->login($referer); -} elseif (isset($_GET['logout'])) { - # see you soon ! - $poche->logout(); -} elseif (isset($_GET['config'])) { - # Update password - $poche->updatePassword(); -} elseif (isset($_GET['newuser'])) { - $poche->createNewUser(); -} elseif (isset($_GET['deluser'])) { - $poche->deleteUser(); -} elseif (isset($_GET['epub'])) { - $poche->createEpub(); -} elseif (isset($_GET['import'])) { - $import = $poche->import(); - $tpl_vars = array_merge($tpl_vars, $import); -} elseif (isset($_GET['download'])) { - Tools::download_db(); -} elseif (isset($_GET['empty-cache'])) { - $poche->emptyCache(); -} elseif (isset($_GET['export'])) { - $poche->export(); -} elseif (isset($_GET['updatetheme'])) { - $poche->updateTheme(); -} elseif (isset($_GET['updatelanguage'])) { - $poche->updateLanguage(); -} elseif (isset($_GET['uploadfile'])) { - $poche->uploadFile(); -} elseif (isset($_GET['feed'])) { - if (isset($_GET['action']) && $_GET['action'] == 'generate') { - $poche->generateToken(); - } - else { - $tag_id = (isset($_GET['tag_id']) ? intval($_GET['tag_id']) : 0); - $poche->generateFeeds($_GET['token'], filter_var($_GET['user_id'],FILTER_SANITIZE_NUMBER_INT), $tag_id, $_GET['type']); - } -} - -elseif (isset($_GET['plainurl']) && !empty($_GET['plainurl'])) { - $plain_url = new Url(base64_encode($_GET['plainurl'])); - $poche->action('add', $plain_url); -} - -if (Session::isLogged()) { - $poche->action($action, $url, $id); - $tpl_file = Tools::getTplFile($view); - $tpl_vars = array_merge($tpl_vars, $poche->displayView($view, $id)); -} elseif(isset($_SERVER['PHP_AUTH_USER'])) { - if($poche->store->userExists($_SERVER['PHP_AUTH_USER'])) { - $poche->login($referer); - } else { - $poche->messages->add('e', _('login failed: user doesn\'t exist')); - Tools::logm('user doesn\'t exist'); - $tpl_file = Tools::getTplFile('login'); - $tpl_vars['http_auth'] = 1; - } -} elseif(isset($_SERVER['REMOTE_USER'])) { - if($poche->store->userExists($_SERVER['REMOTE_USER'])) { - $poche->login($referer); - } else { - $poche->messages->add('e', _('login failed: user doesn\'t exist')); - Tools::logm('user doesn\'t exist'); - $tpl_file = Tools::getTplFile('login'); - $tpl_vars['http_auth'] = 1; - } -} else { - $tpl_file = Tools::getTplFile('login'); - $tpl_vars['http_auth'] = 0; - Session::logout(); -} - -# because messages can be added in $poche->action(), we have to add this entry now (we can add it before) -$messages = $poche->messages->display('all', FALSE); -$tpl_vars = array_merge($tpl_vars, array('messages' => $messages)); - -# display poche -echo $poche->tpl->render($tpl_file, $tpl_vars); +// Let's rock ! +$wallabag = new Poche(); +$wallabag->run(); diff --git a/install/index.php b/install/index.php index e702891..1ae782a 100755 --- a/install/index.php +++ b/install/index.php @@ -1,4 +1,13 @@ + * @copyright 2013 + * @license http://opensource.org/licenses/MIT see COPYING file + */ + $errors = array(); $successes = array(); diff --git a/wallabag_compatibility_test.php b/wallabag_compatibility_test.php index d6f2215..da07862 100644 --- a/wallabag_compatibility_test.php +++ b/wallabag_compatibility_test.php @@ -1,4 +1,13 @@ + * @copyright 2013 + * @license http://opensource.org/licenses/MIT see COPYING file + */ + $app_name = 'wallabag'; $php_ok = (function_exists('version_compare') && version_compare(phpversion(), '5.3.3', '>='));