aboutsummaryrefslogtreecommitdiffhomepage
path: root/inc
diff options
context:
space:
mode:
authorNicolas Lœuillet <nicolas@loeuillet.org>2014-07-11 16:03:59 +0200
committerNicolas Lœuillet <nicolas@loeuillet.org>2014-07-11 16:03:59 +0200
commit3602405ec0dbc576fce09ff9e865ba2404622080 (patch)
tree45f9de611f4936091b0ed29a6a21fa4fef9b330b /inc
parent6400371ff93782d25cdbd50aa224c70145b3890a (diff)
downloadwallabag-3602405ec0dbc576fce09ff9e865ba2404622080.tar.gz
wallabag-3602405ec0dbc576fce09ff9e865ba2404622080.tar.zst
wallabag-3602405ec0dbc576fce09ff9e865ba2404622080.zip
WHAT. A. BIG. REFACTOR. + new license (we moved to MIT one)
Diffstat (limited to 'inc')
-rw-r--r--inc/3rdparty/FlattrItem.class.php26
-rw-r--r--inc/3rdparty/Session.class.php34
-rwxr-xr-xinc/poche/Database.class.php19
-rw-r--r--inc/poche/Language.class.php114
-rwxr-xr-xinc/poche/Poche.class.php453
-rw-r--r--inc/poche/Routing.class.php149
-rw-r--r--inc/poche/Template.class.php236
-rwxr-xr-xinc/poche/Tools.class.php156
-rw-r--r--inc/poche/Url.class.php2
-rw-r--r--inc/poche/User.class.php11
-rwxr-xr-xinc/poche/config.inc.default.php2
-rwxr-xr-xinc/poche/global.inc.php23
-rw-r--r--inc/poche/pochePictures.php267
13 files changed, 876 insertions, 616 deletions
diff --git a/inc/3rdparty/FlattrItem.class.php b/inc/3rdparty/FlattrItem.class.php
index 711b4ee0..5cf9e5fc 100644
--- a/inc/3rdparty/FlattrItem.class.php
+++ b/inc/3rdparty/FlattrItem.class.php
@@ -1,25 +1,32 @@
1<?php 1<?php
2/* 2/**
3* Class for Flattr querying 3 * wallabag, self hostable application allowing you to not miss any content anymore
4*/ 4 *
5class FlattrItem { 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013
8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */
6 10
11class FlattrItem
12{
7 public $status; 13 public $status;
8 public $urltoflattr; 14 public $urltoflattr;
9 public $flattrItemURL; 15 public $flattrItemURL;
10 public $numflattrs; 16 public $numflattrs;
11 17
12 public function checkItem($urltoflattr,$id) { 18 public function checkItem($urltoflattr, $id)
13 $this->cacheflattrfile($urltoflattr, $id); 19 {
20 $this->_cacheflattrfile($urltoflattr, $id);
14 $flattrResponse = file_get_contents(CACHE . "/flattr/".$id.".cache"); 21 $flattrResponse = file_get_contents(CACHE . "/flattr/".$id.".cache");
15 if($flattrResponse != FALSE) { 22 if($flattrResponse != FALSE) {
16 $result = json_decode($flattrResponse); 23 $result = json_decode($flattrResponse);
17 if (isset($result->message)){ 24 if (isset($result->message)) {
18 if ($result->message == "flattrable") { 25 if ($result->message == "flattrable") {
19 $this->status = FLATTRABLE; 26 $this->status = FLATTRABLE;
20 } 27 }
21 } 28 }
22 elseif (is_object($result) && $result->link) { 29 elseif (is_object($result) && $result->link) {
23 $this->status = FLATTRED; 30 $this->status = FLATTRED;
24 $this->flattrItemURL = $result->link; 31 $this->flattrItemURL = $result->link;
25 $this->numflattrs = $result->flattrs; 32 $this->numflattrs = $result->flattrs;
@@ -33,7 +40,8 @@ class FlattrItem {
33 } 40 }
34 } 41 }
35 42
36 private function cacheflattrfile($urltoflattr, $id) { 43 private function _cacheflattrfile($urltoflattr, $id)
44 {
37 if (!is_dir(CACHE . '/flattr')) { 45 if (!is_dir(CACHE . '/flattr')) {
38 mkdir(CACHE . '/flattr', 0777); 46 mkdir(CACHE . '/flattr', 0777);
39 } 47 }
diff --git a/inc/3rdparty/Session.class.php b/inc/3rdparty/Session.class.php
index 59dfbe67..b56e4c54 100644
--- a/inc/3rdparty/Session.class.php
+++ b/inc/3rdparty/Session.class.php
@@ -309,4 +309,38 @@ class Session
309 309
310 return true; // User is not banned. 310 return true; // User is not banned.
311 } 311 }
312
313
314 /**
315 * Tells if a param exists in session
316 *
317 * @param $name name of the param to test
318 * @return bool
319 */
320 public static function isInSession($name)
321 {
322 return (isset($_SESSION[$name]) ? : FALSE);
323 }
324
325 /**
326 * Returns param in session
327 *
328 * @param $name name of the param to return
329 * @return mixed param or null
330 */
331 public static function getParam($name)
332 {
333 return (self::isInSession($name) ? $_SESSION[$name] : NULL);
334 }
335
336 /**
337 * Store value in session
338 *
339 * @param $name name of the variable to store
340 * @param $value value to store
341 */
342 public static function setParam($name, $value)
343 {
344 $_SESSION[$name] = $value;
345 }
312} 346}
diff --git a/inc/poche/Database.class.php b/inc/poche/Database.class.php
index 11cccb72..9c1c0286 100755
--- a/inc/poche/Database.class.php
+++ b/inc/poche/Database.class.php
@@ -5,7 +5,7 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11class Database { 11class Database {
@@ -38,6 +38,7 @@ class Database {
38 } 38 }
39 39
40 $this->handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 40 $this->handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
41 $this->_checkTags();
41 Tools::logm('storage type ' . STORAGE); 42 Tools::logm('storage type ' . STORAGE);
42 } 43 }
43 44
@@ -45,21 +46,7 @@ class Database {
45 return $this->handle; 46 return $this->handle;
46 } 47 }
47 48
48 public function isInstalled() { 49 private function _checkTags() {
49 $sql = "SELECT username FROM users";
50 $query = $this->executeQuery($sql, array());
51 if ($query == false) {
52 die(STORAGE . ' database looks empty. You have to create it (you can find database structure in install folder).');
53 }
54 $hasAdmin = count($query->fetchAll());
55
56 if ($hasAdmin == 0)
57 return false;
58
59 return true;
60 }
61
62 public function checkTags() {
63 50
64 if (STORAGE == 'sqlite') { 51 if (STORAGE == 'sqlite') {
65 $sql = ' 52 $sql = '
diff --git a/inc/poche/Language.class.php b/inc/poche/Language.class.php
new file mode 100644
index 00000000..790cc197
--- /dev/null
+++ b/inc/poche/Language.class.php
@@ -0,0 +1,114 @@
1<?php
2/**
3 * wallabag, self hostable application allowing you to not miss any content anymore
4 *
5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013
8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */
10
11class Language
12{
13 protected $wallabag;
14
15 private $currentLanguage;
16
17 private $languageNames = array(
18 'cs_CZ.utf8' => 'čeština',
19 'de_DE.utf8' => 'German',
20 'en_EN.utf8' => 'English',
21 'es_ES.utf8' => 'Español',
22 'fa_IR.utf8' => 'فارسی',
23 'fr_FR.utf8' => 'Français',
24 'it_IT.utf8' => 'Italiano',
25 'pl_PL.utf8' => 'Polski',
26 'pt_BR.utf8' => 'Português (Brasil)',
27 'ru_RU.utf8' => 'Pусский',
28 'sl_SI.utf8' => 'Slovenščina',
29 'uk_UA.utf8' => 'Українська',
30 );
31
32 public function __construct(Poche $wallabag)
33 {
34 $this->wallabag = $wallabag;
35 $pocheUser = Session::getParam('poche_user');
36 $language = (is_null($pocheUser) ? LANG : $pocheUser->getConfigValue('language'));
37
38 @putenv('LC_ALL=' . $language);
39 setlocale(LC_ALL, $language);
40 bindtextdomain($language, LOCALE);
41 textdomain($language);
42
43 $this->currentLanguage = $language;
44 }
45
46 public function getLanguage() {
47 return $this->currentLanguage;
48 }
49
50 public function getInstalledLanguages() {
51 $handle = opendir(LOCALE);
52 $languages = array();
53
54 while (($language = readdir($handle)) !== false) {
55 # Languages are stored in a directory, so all directory names are languages
56 # @todo move language installation data to database
57 if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.', 'tools'))) {
58 continue;
59 }
60
61 $current = false;
62
63 if ($language === $this->getLanguage()) {
64 $current = true;
65 }
66
67 $languages[] = array('name' => (isset($this->languageNames[$language]) ? $this->languageNames[$language] : $language), 'value' => $language, 'current' => $current);
68 }
69
70 return $languages;
71 }
72
73
74 /**
75 * Update language for current user
76 *
77 * @param $newLanguage
78 */
79 public function updateLanguage($newLanguage)
80 {
81 # we are not going to change it to the current language
82 if ($newLanguage == $this->getLanguage()) {
83 $this->wallabag->messages->add('w', _('still using the "' . $this->getLanguage() . '" language!'));
84 Tools::redirect('?view=config');
85 }
86
87 $languages = $this->getInstalledLanguages();
88 $actualLanguage = false;
89
90 foreach ($languages as $language) {
91 if ($language['value'] == $newLanguage) {
92 $actualLanguage = true;
93 break;
94 }
95 }
96
97 if (!$actualLanguage) {
98 $this->wallabag->messages->add('e', _('that language does not seem to be installed'));
99 Tools::redirect('?view=config');
100 }
101
102 $this->wallabag->store->updateUserConfig($this->wallabag->user->getId(), 'language', $newLanguage);
103 $this->wallabag->messages->add('s', _('you have changed your language preferences'));
104
105 $currentConfig = $_SESSION['poche_user']->config;
106 $currentConfig['language'] = $newLanguage;
107
108 $_SESSION['poche_user']->setConfig($currentConfig);
109
110 $this->wallabag->emptyCache();
111
112 Tools::redirect('?view=config');
113 }
114} \ No newline at end of file
diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php
index 09a9f5ff..bc4320b8 100755
--- a/inc/poche/Poche.class.php
+++ b/inc/poche/Poche.class.php
@@ -5,244 +5,77 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11class Poche 11class Poche
12{ 12{
13 public static $canRenderTemplates = true; 13 /**
14 public static $configFileAvailable = true; 14 * @var User
15 15 */
16 public $user; 16 public $user;
17 /**
18 * @var Database
19 */
17 public $store; 20 public $store;
21 /**
22 * @var Template
23 */
18 public $tpl; 24 public $tpl;
25 /**
26 * @var Language
27 */
28 public $language;
29 /**
30 * @var Routing
31 */
32 public $routing;
33 /**
34 * @var Messages
35 */
19 public $messages; 36 public $messages;
37 /**
38 * @var Paginator
39 */
20 public $pagination; 40 public $pagination;
21 41
22 private $currentTheme = '';
23 private $currentLanguage = '';
24 private $notInstalledMessage = array();
25
26 private $language_names = array(
27 'cs_CZ.utf8' => 'čeština',
28 'de_DE.utf8' => 'German',
29 'en_EN.utf8' => 'English',
30 'es_ES.utf8' => 'Español',
31 'fa_IR.utf8' => 'فارسی',
32 'fr_FR.utf8' => 'Français',
33 'it_IT.utf8' => 'Italiano',
34 'pl_PL.utf8' => 'Polski',
35 'pt_BR.utf8' => 'Português (Brasil)',
36 'ru_RU.utf8' => 'Pусский',
37 'sl_SI.utf8' => 'Slovenščina',
38 'uk_UA.utf8' => 'Українська',
39 );
40 public function __construct() 42 public function __construct()
41 { 43 {
42 if ($this->configFileIsAvailable()) { 44 $this->init();
43 $this->init();
44 }
45
46 if ($this->themeIsInstalled()) {
47 $this->initTpl();
48 }
49
50 if ($this->systemIsInstalled()) {
51 $this->store = new Database();
52 $this->messages = new Messages();
53 # installation
54 if (! $this->store->isInstalled()) {
55 $this->install();
56 }
57 $this->store->checkTags();
58 }
59 } 45 }
60 46
61 private function init() 47 private function init()
62 { 48 {
63 Tools::initPhp(); 49 Tools::initPhp();
64 50
65 if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) { 51 $pocheUser = Session::getParam('poche_user');
66 $this->user = $_SESSION['poche_user']; 52
53 if ($pocheUser && $pocheUser != array()) {
54 $this->user = $pocheUser;
67 } else { 55 } else {
68 # fake user, just for install & login screens 56 // fake user, just for install & login screens
69 $this->user = new User(); 57 $this->user = new User();
70 $this->user->setConfig($this->getDefaultConfig()); 58 $this->user->setConfig($this->getDefaultConfig());
71 } 59 }
72 60
73 # l10n 61 $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p');
74 $language = $this->user->getConfigValue('language'); 62 $this->language = new Language($this);
75 @putenv('LC_ALL=' . $language); 63 $this->tpl = new Template($this);
76 setlocale(LC_ALL, $language); 64 $this->store = new Database();
77 bindtextdomain($language, LOCALE); 65 $this->messages = new Messages();
78 textdomain($language); 66 $this->routing = new Routing($this);
79
80 # Pagination
81 $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p');
82
83 # Set up theme
84 $themeDirectory = $this->user->getConfigValue('theme');
85
86 if ($themeDirectory === false) {
87 $themeDirectory = DEFAULT_THEME;
88 }
89
90 $this->currentTheme = $themeDirectory;
91
92 # Set up language
93 $languageDirectory = $this->user->getConfigValue('language');
94
95 if ($languageDirectory === false) {
96 $languageDirectory = DEFAULT_THEME;
97 }
98
99 $this->currentLanguage = $languageDirectory;
100 }
101
102 public function configFileIsAvailable() {
103 if (! self::$configFileAvailable) {
104 $this->notInstalledMessage[] = 'You have to copy (don\'t just rename!) inc/poche/config.inc.default.php to inc/poche/config.inc.php.';
105
106 return false;
107 }
108
109 return true;
110 } 67 }
111 68
112 public function themeIsInstalled() { 69 public function run()
113 $passTheme = TRUE; 70 {
114 # Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet 71 $this->routing->run();
115 if (! self::$canRenderTemplates) {
116 $this->notInstalledMessage[] = 'Twig does not seem to be installed. Please initialize the Composer installation to automatically fetch dependencies. You can also download <a href="http://wllbg.org/vendor">vendor.zip</a> and extract it in your wallabag folder.';
117 $passTheme = FALSE;
118 }
119
120 if (! is_writable(CACHE)) {
121 $this->notInstalledMessage[] = 'You don\'t have write access on cache directory.';
122
123 self::$canRenderTemplates = false;
124
125 $passTheme = FALSE;
126 }
127
128 # Check if the selected theme and its requirements are present
129 $theme = $this->getTheme();
130
131 if ($theme != '' && ! is_dir(THEME . '/' . $theme)) {
132 $this->notInstalledMessage[] = 'The currently selected theme (' . $theme . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $theme . ')';
133
134 self::$canRenderTemplates = false;
135
136 $passTheme = FALSE;
137 }
138
139 $themeInfo = $this->getThemeInfo($theme);
140 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
141 foreach ($themeInfo['requirements'] as $requiredTheme) {
142 if (! is_dir(THEME . '/' . $requiredTheme)) {
143 $this->notInstalledMessage[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')';
144
145 self::$canRenderTemplates = false;
146
147 $passTheme = FALSE;
148 }
149 }
150 }
151
152 if (!$passTheme) {
153 return FALSE;
154 }
155
156
157 return true;
158 } 72 }
159 73
160 /** 74 /**
161 * all checks before installation. 75 * Creates a new user
162 * @todo move HTML to template
163 * @return boolean
164 */ 76 */
165 public function systemIsInstalled() 77 public function createNewUser()
166 { 78 {
167 $msg = TRUE;
168
169 $configSalt = defined('SALT') ? constant('SALT') : '';
170
171 if (empty($configSalt)) {
172 $this->notInstalledMessage[] = 'You have not yet filled in the SALT value in the config.inc.php file.';
173 $msg = FALSE;
174 }
175 if (STORAGE == 'sqlite' && ! file_exists(STORAGE_SQLITE)) {
176 Tools::logm('sqlite file doesn\'t exist');
177 $this->notInstalledMessage[] = 'sqlite file doesn\'t exist, you can find it in install folder. Copy it in /db folder.';
178 $msg = FALSE;
179 }
180 if (is_dir(ROOT . '/install') && ! DEBUG_POCHE) {
181 $this->notInstalledMessage[] = 'you have to delete the /install folder before using poche.';
182 $msg = FALSE;
183 }
184 if (STORAGE == 'sqlite' && ! is_writable(STORAGE_SQLITE)) {
185 Tools::logm('you don\'t have write access on sqlite file');
186 $this->notInstalledMessage[] = 'You don\'t have write access on sqlite file.';
187 $msg = FALSE;
188 }
189
190 if (! $msg) {
191 return false;
192 }
193
194 return true;
195 }
196
197 public function getNotInstalledMessage() {
198 return $this->notInstalledMessage;
199 }
200
201 private function initTpl()
202 {
203 $loaderChain = new Twig_Loader_Chain();
204 $theme = $this->getTheme();
205
206 # add the current theme as first to the loader chain so Twig will look there first for overridden template files
207 try {
208 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme));
209 } catch (Twig_Error_Loader $e) {
210 # @todo isInstalled() should catch this, inject Twig later
211 die('The currently selected theme (' . $theme . ') does not seem to be properly installed (' . THEME . '/' . $theme .' is missing)');
212 }
213
214 # add all required themes to the loader chain
215 $themeInfo = $this->getThemeInfo($theme);
216 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
217 foreach ($themeInfo['requirements'] as $requiredTheme) {
218 try {
219 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $requiredTheme));
220 } catch (Twig_Error_Loader $e) {
221 # @todo isInstalled() should catch this, inject Twig later
222 die('The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')');
223 }
224 }
225 }
226
227 if (DEBUG_POCHE) {
228 $twigParams = array();
229 } else {
230 $twigParams = array('cache' => CACHE);
231 }
232
233 $this->tpl = new Twig_Environment($loaderChain, $twigParams);
234 $this->tpl->addExtension(new Twig_Extensions_Extension_I18n());
235
236 # filter to display domain name of an url
237 $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain');
238 $this->tpl->addFilter($filter);
239
240 # filter for reading time
241 $filter = new Twig_SimpleFilter('getReadingTime', 'Tools::getReadingTime');
242 $this->tpl->addFilter($filter);
243 }
244
245 public function createNewUser() {
246 if (isset($_GET['newuser'])){ 79 if (isset($_GET['newuser'])){
247 if ($_POST['newusername'] != "" && $_POST['password4newuser'] != ""){ 80 if ($_POST['newusername'] != "" && $_POST['password4newuser'] != ""){
248 $newusername = filter_var($_POST['newusername'], FILTER_SANITIZE_STRING); 81 $newusername = filter_var($_POST['newusername'], FILTER_SANITIZE_STRING);
@@ -266,7 +99,11 @@ class Poche
266 } 99 }
267 } 100 }
268 101
269 public function deleteUser(){ 102 /**
103 * Delete an existing user
104 */
105 public function deleteUser()
106 {
270 if (isset($_GET['deluser'])){ 107 if (isset($_GET['deluser'])){
271 if ($this->store->listUsers() > 1) { 108 if ($this->store->listUsers() > 1) {
272 if (Tools::encodeString($_POST['password4deletinguser'].$this->user->getUsername()) == $this->store->getUserPassword($this->user->getId())) { 109 if (Tools::encodeString($_POST['password4deletinguser'].$this->user->getUsername()) == $this->store->getUserPassword($this->user->getId())) {
@@ -294,115 +131,6 @@ class Poche
294 } 131 }
295 } 132 }
296 133
297 private function install()
298 {
299 Tools::logm('poche still not installed');
300 echo $this->tpl->render('install.twig', array(
301 'token' => Session::getToken(),
302 'theme' => $this->getTheme(),
303 'poche_url' => Tools::getPocheUrl()
304 ));
305 if (isset($_GET['install'])) {
306 if (($_POST['password'] == $_POST['password_repeat'])
307 && $_POST['password'] != "" && $_POST['login'] != "") {
308 # let's rock, install poche baby !
309 if ($this->store->install($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login'])))
310 {
311 Session::logout();
312 Tools::logm('poche is now installed');
313 Tools::redirect();
314 }
315 }
316 else {
317 Tools::logm('error during installation');
318 Tools::redirect();
319 }
320 }
321 exit();
322 }
323
324 public function getTheme() {
325 return $this->currentTheme;
326 }
327
328 /**
329 * Provides theme information by parsing theme.ini file if present in the theme's root directory.
330 * In all cases, the following data will be returned:
331 * - name: theme's name, or key if the theme is unnamed,
332 * - current: boolean informing if the theme is the current user theme.
333 *
334 * @param string $theme Theme key (directory name)
335 * @return array|boolean Theme information, or false if the theme doesn't exist.
336 */
337 public function getThemeInfo($theme) {
338 if (!is_dir(THEME . '/' . $theme)) {
339 return false;
340 }
341
342 $themeIniFile = THEME . '/' . $theme . '/theme.ini';
343 $themeInfo = array();
344
345 if (is_file($themeIniFile) && is_readable($themeIniFile)) {
346 $themeInfo = parse_ini_file($themeIniFile);
347 }
348
349 if ($themeInfo === false) {
350 $themeInfo = array();
351 }
352 if (!isset($themeInfo['name'])) {
353 $themeInfo['name'] = $theme;
354 }
355 $themeInfo['current'] = ($theme === $this->getTheme());
356
357 return $themeInfo;
358 }
359
360 public function getInstalledThemes() {
361 $handle = opendir(THEME);
362 $themes = array();
363
364 while (($theme = readdir($handle)) !== false) {
365 # Themes are stored in a directory, so all directory names are themes
366 # @todo move theme installation data to database
367 if (!is_dir(THEME . '/' . $theme) || in_array($theme, array('.', '..'))) {
368 continue;
369 }
370
371 $themes[$theme] = $this->getThemeInfo($theme);
372 }
373
374 ksort($themes);
375
376 return $themes;
377 }
378
379 public function getLanguage() {
380 return $this->currentLanguage;
381 }
382
383 public function getInstalledLanguages() {
384 $handle = opendir(LOCALE);
385 $languages = array();
386
387 while (($language = readdir($handle)) !== false) {
388 # Languages are stored in a directory, so all directory names are languages
389 # @todo move language installation data to database
390 if (! is_dir(LOCALE . '/' . $language) || in_array($language, array('..', '.', 'tools'))) {
391 continue;
392 }
393
394 $current = false;
395
396 if ($language === $this->getLanguage()) {
397 $current = true;
398 }
399
400 $languages[] = array('name' => (isset($this->language_names[$language]) ? $this->language_names[$language] : $language), 'value' => $language, 'current' => $current);
401 }
402
403 return $languages;
404 }
405
406 public function getDefaultConfig() 134 public function getDefaultConfig()
407 { 135 {
408 return array( 136 return array(
@@ -437,7 +165,7 @@ class Poche
437 if ( $last_id ) { 165 if ( $last_id ) {
438 Tools::logm('add link ' . $url->getUrl()); 166 Tools::logm('add link ' . $url->getUrl());
439 if (DOWNLOAD_PICTURES) { 167 if (DOWNLOAD_PICTURES) {
440 $content = filtre_picture($body, $url->getUrl(), $last_id); 168 $content = Picture::filterPicture($body, $url->getUrl(), $last_id);
441 Tools::logm('updating content article'); 169 Tools::logm('updating content article');
442 $this->store->updateContent($last_id, $content, $this->user->getId()); 170 $this->store->updateContent($last_id, $content, $this->user->getId());
443 } 171 }
@@ -472,7 +200,7 @@ class Poche
472 $msg = 'delete link #' . $id; 200 $msg = 'delete link #' . $id;
473 if ($this->store->deleteById($id, $this->user->getId())) { 201 if ($this->store->deleteById($id, $this->user->getId())) {
474 if (DOWNLOAD_PICTURES) { 202 if (DOWNLOAD_PICTURES) {
475 remove_directory(ABS_PATH . $id); 203 Picture::removeDirectory(ABS_PATH . $id);
476 } 204 }
477 $this->messages->add('s', _('the link has been deleted successfully')); 205 $this->messages->add('s', _('the link has been deleted successfully'));
478 } 206 }
@@ -598,8 +326,8 @@ class Poche
598 $check_time_prod = date('d-M-Y H:i', $prod_infos[1]); 326 $check_time_prod = date('d-M-Y H:i', $prod_infos[1]);
599 $compare_dev = version_compare(POCHE, $dev); 327 $compare_dev = version_compare(POCHE, $dev);
600 $compare_prod = version_compare(POCHE, $prod); 328 $compare_prod = version_compare(POCHE, $prod);
601 $themes = $this->getInstalledThemes(); 329 $themes = $this->tpl->getInstalledThemes();
602 $languages = $this->getInstalledLanguages(); 330 $languages = $this->language->getInstalledLanguages();
603 $token = $this->user->getConfigValue('token'); 331 $token = $this->user->getConfigValue('token');
604 $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false; 332 $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false;
605 $only_user = ($this->store->listUsers() > 1) ? false : true; 333 $only_user = ($this->store->listUsers() > 1) ? false : true;
@@ -703,7 +431,7 @@ class Poche
703 'listmode' => (isset($_COOKIE['listmode']) ? true : false), 431 'listmode' => (isset($_COOKIE['listmode']) ? true : false),
704 ); 432 );
705 433
706 //if id is given - we retrive entries by tag: id is tag id 434 //if id is given - we retrieve entries by tag: id is tag id
707 if ($id) { 435 if ($id) {
708 $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId()); 436 $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId());
709 $tpl_vars['id'] = intval($id); 437 $tpl_vars['id'] = intval($id);
@@ -757,85 +485,6 @@ class Poche
757 } 485 }
758 } 486 }
759 487
760 public function updateTheme()
761 {
762 # no data
763 if (empty($_POST['theme'])) {
764 }
765
766 # we are not going to change it to the current theme...
767 if ($_POST['theme'] == $this->getTheme()) {
768 $this->messages->add('w', _('still using the "' . $this->getTheme() . '" theme!'));
769 Tools::redirect('?view=config');
770 }
771
772 $themes = $this->getInstalledThemes();
773 $actualTheme = false;
774
775 foreach (array_keys($themes) as $theme) {
776 if ($theme == $_POST['theme']) {
777 $actualTheme = true;
778 break;
779 }
780 }
781
782 if (! $actualTheme) {
783 $this->messages->add('e', _('that theme does not seem to be installed'));
784 Tools::redirect('?view=config');
785 }
786
787 $this->store->updateUserConfig($this->user->getId(), 'theme', $_POST['theme']);
788 $this->messages->add('s', _('you have changed your theme preferences'));
789
790 $currentConfig = $_SESSION['poche_user']->config;
791 $currentConfig['theme'] = $_POST['theme'];
792
793 $_SESSION['poche_user']->setConfig($currentConfig);
794
795 $this->emptyCache();
796
797 Tools::redirect('?view=config');
798 }
799
800 public function updateLanguage()
801 {
802 # no data
803 if (empty($_POST['language'])) {
804 }
805
806 # we are not going to change it to the current language...
807 if ($_POST['language'] == $this->getLanguage()) {
808 $this->messages->add('w', _('still using the "' . $this->getLanguage() . '" language!'));
809 Tools::redirect('?view=config');
810 }
811
812 $languages = $this->getInstalledLanguages();
813 $actualLanguage = false;
814
815 foreach ($languages as $language) {
816 if ($language['value'] == $_POST['language']) {
817 $actualLanguage = true;
818 break;
819 }
820 }
821
822 if (! $actualLanguage) {
823 $this->messages->add('e', _('that language does not seem to be installed'));
824 Tools::redirect('?view=config');
825 }
826
827 $this->store->updateUserConfig($this->user->getId(), 'language', $_POST['language']);
828 $this->messages->add('s', _('you have changed your language preferences'));
829
830 $currentConfig = $_SESSION['poche_user']->config;
831 $currentConfig['language'] = $_POST['language'];
832
833 $_SESSION['poche_user']->setConfig($currentConfig);
834
835 $this->emptyCache();
836
837 Tools::redirect('?view=config');
838 }
839 /** 488 /**
840 * get credentials from differents sources 489 * get credentials from differents sources
841 * it redirects the user to the $referer link 490 * 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 00000000..7e259c24
--- /dev/null
+++ b/inc/poche/Routing.class.php
@@ -0,0 +1,149 @@
1<?php
2/**
3 * wallabag, self hostable application allowing you to not miss any content anymore
4 *
5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013
8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */
10
11class Routing
12{
13 protected $wallabag;
14 protected $referer;
15 protected $view;
16 protected $action;
17 protected $id;
18 protected $url;
19 protected $file;
20 protected $defaultVars = array();
21 protected $vars = array();
22
23 public function __construct(Poche $wallabag)
24 {
25 $this->wallabag = $wallabag;
26 $this->_init();
27 }
28
29 private function _init()
30 {
31 # Parse GET & REFERER vars
32 $this->referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER'];
33 $this->view = Tools::checkVar('view', 'home');
34 $this->action = Tools::checkVar('action');
35 $this->id = Tools::checkVar('id');
36 $_SESSION['sort'] = Tools::checkVar('sort', 'id');
37 $this->url = new Url((isset ($_GET['url'])) ? $_GET['url'] : '');
38 }
39
40 public function run()
41 {
42 # vars to _always_ send to templates
43 $this->defaultVars = array(
44 'referer' => $this->referer,
45 'view' => $this->view,
46 'poche_url' => Tools::getPocheUrl(),
47 'title' => _('wallabag, a read it later open source system'),
48 'token' => \Session::getToken(),
49 'theme' => $this->wallabag->tpl->getTheme()
50 );
51
52 $this->_launchAction();
53 $this->_defineTplInformation();
54
55 # because messages can be added in $poche->action(), we have to add this entry now (we can add it before)
56 $this->vars = array_merge($this->vars, array('messages' => $this->wallabag->messages->display('all', FALSE)));
57
58 $this->_render($this->file, $this->vars);
59 }
60
61 private function _defineTplInformation()
62 {
63 $tplFile = array();
64 $tplVars = array();
65
66 if (\Session::isLogged()) {
67 $this->wallabag->action($this->action, $this->url, $this->id);
68 $tplFile = Tools::getTplFile($this->view);
69 $tplVars = array_merge($this->vars, $this->wallabag->displayView($this->view, $this->id));
70 } elseif(isset($_SERVER['PHP_AUTH_USER'])) {
71 if($this->wallabag->store->userExists($_SERVER['PHP_AUTH_USER'])) {
72 $this->wallabag->login($this->referer);
73 } else {
74 $this->wallabag->messages->add('e', _('login failed: user doesn\'t exist'));
75 Tools::logm('user doesn\'t exist');
76 $tplFile = Tools::getTplFile('login');
77 $tplVars['http_auth'] = 1;
78 }
79 } elseif(isset($_SERVER['REMOTE_USER'])) {
80 if($this->wallabag->store->userExists($_SERVER['REMOTE_USER'])) {
81 $this->wallabag->login($this->referer);
82 } else {
83 $this->wallabag->messages->add('e', _('login failed: user doesn\'t exist'));
84 Tools::logm('user doesn\'t exist');
85 $tplFile = Tools::getTplFile('login');
86 $tplVars['http_auth'] = 1;
87 }
88 } else {
89 $tplFile = Tools::getTplFile('login');
90 $tplVars['http_auth'] = 0;
91 \Session::logout();
92 }
93
94 $this->file = $tplFile;
95 $this->vars = array_merge($this->defaultVars, $tplVars);
96 }
97
98 private function _launchAction()
99 {
100 if (isset($_GET['login'])) {
101 // hello you
102 $this->wallabag->login($this->referer);
103 } elseif (isset($_GET['logout'])) {
104 // see you soon !
105 $this->wallabag->logout();
106 } elseif (isset($_GET['config'])) {
107 // update password
108 $this->wallabag->updatePassword();
109 } elseif (isset($_GET['newuser'])) {
110 $this->wallabag->createNewUser();
111 } elseif (isset($_GET['deluser'])) {
112 $this->wallabag->deleteUser();
113 } elseif (isset($_GET['epub'])) {
114 $this->wallabag->createEpub();
115 } elseif (isset($_GET['import'])) {
116 $import = $this->wallabag->import();
117 $tplVars = array_merge($this->vars, $import);
118 } elseif (isset($_GET['download'])) {
119 Tools::downloadDb();
120 } elseif (isset($_GET['empty-cache'])) {
121 $this->wallabag->emptyCache();
122 } elseif (isset($_GET['export'])) {
123 $this->wallabag->export();
124 } elseif (isset($_GET['updatetheme'])) {
125 $this->wallabag->tpl->updateTheme($_POST['theme']);
126 } elseif (isset($_GET['updatelanguage'])) {
127 $this->wallabag->language->updateLanguage($_POST['language']);
128 } elseif (isset($_GET['uploadfile'])) {
129 $this->wallabag->uploadFile();
130 } elseif (isset($_GET['feed'])) {
131 if (isset($_GET['action']) && $_GET['action'] == 'generate') {
132 $this->wallabag->generateToken();
133 }
134 else {
135 $tag_id = (isset($_GET['tag_id']) ? intval($_GET['tag_id']) : 0);
136 $this->wallabag->generateFeeds($_GET['token'], filter_var($_GET['user_id'],FILTER_SANITIZE_NUMBER_INT), $tag_id, $_GET['type']);
137 }
138 }
139 elseif (isset($_GET['plainurl']) && !empty($_GET['plainurl'])) {
140 $plainUrl = new Url(base64_encode($_GET['plainurl']));
141 $this->wallabag->action('add', $plainUrl);
142 }
143 }
144
145 private function _render($file, $vars)
146 {
147 echo $this->wallabag->tpl->render($file, $vars);
148 }
149} \ 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 00000000..0e09ad64
--- /dev/null
+++ b/inc/poche/Template.class.php
@@ -0,0 +1,236 @@
1<?php
2/**
3 * wallabag, self hostable application allowing you to not miss any content anymore
4 *
5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013
8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */
10
11class Template extends Twig_Environment
12{
13 protected $wallabag;
14
15 private $canRenderTemplates = TRUE;
16 private $currentTheme = '';
17
18 public function __construct(Poche $wallabag)
19 {
20 $this->wallabag = $wallabag;
21
22 // Set up theme
23 $pocheUser = Session::getParam('poche_user');
24
25 $themeDirectory = (is_null($pocheUser) ? DEFAULT_THEME : $pocheUser->getConfigValue('theme'));
26
27 if ($themeDirectory === false) {
28 $themeDirectory = DEFAULT_THEME;
29 }
30
31 $this->currentTheme = $themeDirectory;
32
33 if ($this->_themeIsInstalled() === array()) {
34 $this->_init();
35 }
36 }
37
38 /**
39 * Returns true if selected theme is installed
40 *
41 * @return bool
42 */
43 private function _themeIsInstalled()
44 {
45 $errors = array();
46
47 // Twig is an absolute requirement for wallabag to function.
48 // Abort immediately if the Composer installer hasn't been run yet
49 if (!$this->canRenderTemplates) {
50 $errors[] = 'Twig does not seem to be installed. Please initialize the Composer installation to automatically fetch dependencies. You can also download <a href="http://wllbg.org/vendor">vendor.zip</a> and extract it in your wallabag folder.';
51 }
52
53 // Check if the selected theme and its requirements are present
54 $theme = $this->getTheme();
55 if ($theme != '' && !is_dir(THEME . '/' . $theme)) {
56 $errors[] = 'The currently selected theme (' . $theme . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $theme . ')';
57 $this->canRenderTemplates = FALSE;
58 }
59
60 $themeInfo = $this->getThemeInfo($theme);
61 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
62 foreach ($themeInfo['requirements'] as $requiredTheme) {
63 if (! is_dir(THEME . '/' . $requiredTheme)) {
64 $errors[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')';
65 $this->canRenderTemplates = FALSE;
66 }
67 }
68 }
69
70 $currentErrors = (is_null(Session::getParam('errors'))? array() : Session::getParam('errors'));
71 Session::setParam('errors', array_merge($errors, $currentErrors));
72
73 return $errors;
74 }
75
76 /**
77 * Initialization for templates
78 */
79 private function _init()
80 {
81 $loaderChain = new Twig_Loader_Chain();
82 $theme = $this->getTheme();
83
84 // add the current theme as first to the loader chain
85 // so Twig will look there first for overridden template files
86 try {
87 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $theme));
88 } catch (Twig_Error_Loader $e) {
89 # @todo isInstalled() should catch this, inject Twig later
90 die('The currently selected theme (' . $theme . ') does not seem to be properly installed (' . THEME . '/' . $theme .' is missing)');
91 }
92
93 // add all required themes to the loader chain
94 $themeInfo = $this->getThemeInfo($theme);
95 if (isset($themeInfo['requirements']) && is_array($themeInfo['requirements'])) {
96 foreach ($themeInfo['requirements'] as $requiredTheme) {
97 try {
98 $loaderChain->addLoader(new Twig_Loader_Filesystem(THEME . '/' . $requiredTheme));
99 } catch (Twig_Error_Loader $e) {
100 # @todo isInstalled() should catch this, inject Twig later
101 die('The required "' . $requiredTheme . '" theme is missing for the current theme (' . $theme . ')');
102 }
103 }
104 }
105
106 if (DEBUG_POCHE) {
107 $twigParams = array();
108 } else {
109 $twigParams = array('cache' => CACHE);
110 }
111
112 parent::__construct($loaderChain, $twigParams);
113
114 //$tpl = new Twig_Environment($loaderChain, $twigParams);
115 $this->addExtension(new Twig_Extensions_Extension_I18n());
116
117 # filter to display domain name of an url
118 $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain');
119 $this->addFilter($filter);
120
121 # filter for reading time
122 $filter = new Twig_SimpleFilter('getReadingTime', 'Tools::getReadingTime');
123 $this->addFilter($filter);
124 }
125
126 /**
127 * Returns current theme
128 *
129 * @return string
130 */
131 public function getTheme()
132 {
133 return $this->currentTheme;
134 }
135
136 /**
137 * Provides theme information by parsing theme.ini file if present in the theme's root directory.
138 * In all cases, the following data will be returned:
139 * - name: theme's name, or key if the theme is unnamed,
140 * - current: boolean informing if the theme is the current user theme.
141 *
142 * @param string $theme Theme key (directory name)
143 * @return array|boolean Theme information, or false if the theme doesn't exist.
144 */
145 public function getThemeInfo($theme)
146 {
147 if (!is_dir(THEME . '/' . $theme)) {
148 return false;
149 }
150
151 $themeIniFile = THEME . '/' . $theme . '/theme.ini';
152 $themeInfo = array();
153
154 if (is_file($themeIniFile) && is_readable($themeIniFile)) {
155 $themeInfo = parse_ini_file($themeIniFile);
156 }
157
158 if ($themeInfo === false) {
159 $themeInfo = array();
160 }
161
162 if (!isset($themeInfo['name'])) {
163 $themeInfo['name'] = $theme;
164 }
165
166 $themeInfo['current'] = ($theme === $this->getTheme());
167
168 return $themeInfo;
169 }
170
171 /**
172 * Returns an array with installed themes
173 *
174 * @return array
175 */
176 public function getInstalledThemes()
177 {
178 $handle = opendir(THEME);
179 $themes = array();
180
181 while (($theme = readdir($handle)) !== false) {
182 # Themes are stored in a directory, so all directory names are themes
183 # @todo move theme installation data to database
184 if (!is_dir(THEME . '/' . $theme) || in_array($theme, array('.', '..'))) {
185 continue;
186 }
187
188 $themes[$theme] = $this->getThemeInfo($theme);
189 }
190
191 ksort($themes);
192
193 return $themes;
194 }
195
196 /**
197 * Update theme for the current user
198 *
199 * @param $newTheme
200 */
201 public function updateTheme($newTheme)
202 {
203 # we are not going to change it to the current theme...
204 if ($newTheme == $this->getTheme()) {
205 $this->wallabag->messages->add('w', _('still using the "' . $this->getTheme() . '" theme!'));
206 Tools::redirect('?view=config');
207 }
208
209 $themes = $this->getInstalledThemes();
210 $actualTheme = false;
211
212 foreach (array_keys($themes) as $theme) {
213 if ($theme == $newTheme) {
214 $actualTheme = true;
215 break;
216 }
217 }
218
219 if (!$actualTheme) {
220 $this->wallabag->messages->add('e', _('that theme does not seem to be installed'));
221 Tools::redirect('?view=config');
222 }
223
224 $this->wallabag->store->updateUserConfig($this->wallabag->user->getId(), 'theme', $newTheme);
225 $this->wallabag->messages->add('s', _('you have changed your theme preferences'));
226
227 $currentConfig = $_SESSION['poche_user']->config;
228 $currentConfig['theme'] = $newTheme;
229
230 $_SESSION['poche_user']->setConfig($currentConfig);
231
232 $this->wallabag->emptyCache();
233
234 Tools::redirect('?view=config');
235 }
236} \ No newline at end of file
diff --git a/inc/poche/Tools.class.php b/inc/poche/Tools.class.php
index cc01f403..762e4446 100755
--- a/inc/poche/Tools.class.php
+++ b/inc/poche/Tools.class.php
@@ -5,19 +5,23 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11class Tools 11final class Tools
12{ 12{
13 private function __construct()
14 {
15
16 }
17
18 /**
19 * Initialize PHP environment
20 */
13 public static function initPhp() 21 public static function initPhp()
14 { 22 {
15 define('START_TIME', microtime(true)); 23 define('START_TIME', microtime(true));
16 24
17 if (phpversion() < 5) {
18 die(_('Oops, it seems you don\'t have PHP 5.'));
19 }
20
21 function stripslashesDeep($value) { 25 function stripslashesDeep($value) {
22 return is_array($value) 26 return is_array($value)
23 ? array_map('stripslashesDeep', $value) 27 ? array_map('stripslashesDeep', $value)
@@ -34,6 +38,11 @@ class Tools
34 register_shutdown_function('ob_end_flush'); 38 register_shutdown_function('ob_end_flush');
35 } 39 }
36 40
41 /**
42 * Get wallabag instance URL
43 *
44 * @return string
45 */
37 public static function getPocheUrl() 46 public static function getPocheUrl()
38 { 47 {
39 $https = (!empty($_SERVER['HTTPS']) 48 $https = (!empty($_SERVER['HTTPS'])
@@ -67,6 +76,11 @@ class Tools
67 . $host . $serverport . $scriptname; 76 . $host . $serverport . $scriptname;
68 } 77 }
69 78
79 /**
80 * Redirects to a URL
81 *
82 * @param string $url
83 */
70 public static function redirect($url = '') 84 public static function redirect($url = '')
71 { 85 {
72 if ($url === '') { 86 if ($url === '') {
@@ -87,11 +101,18 @@ class Tools
87 $url = $ref; 101 $url = $ref;
88 } 102 }
89 } 103 }
104
90 self::logm('redirect to ' . $url); 105 self::logm('redirect to ' . $url);
91 header('Location: '.$url); 106 header('Location: '.$url);
92 exit(); 107 exit();
93 } 108 }
94 109
110 /**
111 * Returns name of the template file to display
112 *
113 * @param $view
114 * @return string
115 */
95 public static function getTplFile($view) 116 public static function getTplFile($view)
96 { 117 {
97 $views = array( 118 $views = array(
@@ -99,13 +120,15 @@ class Tools
99 'edit-tags', 'view', 'login', 'error' 120 'edit-tags', 'view', 'login', 'error'
100 ); 121 );
101 122
102 if (in_array($view, $views)) { 123 return (in_array($view, $views) ? $view . '.twig' : 'home.twig');
103 return $view . '.twig';
104 }
105
106 return 'home.twig';
107 } 124 }
108 125
126 /**
127 * Download a file (typically, for downloading pictures on web server)
128 *
129 * @param $url
130 * @return bool|mixed|string
131 */
109 public static function getFile($url) 132 public static function getFile($url)
110 { 133 {
111 $timeout = 15; 134 $timeout = 15;
@@ -186,6 +209,11 @@ class Tools
186 } 209 }
187 } 210 }
188 211
212 /**
213 * Headers for JSON export
214 *
215 * @param $data
216 */
189 public static function renderJson($data) 217 public static function renderJson($data)
190 { 218 {
191 header('Cache-Control: no-cache, must-revalidate'); 219 header('Cache-Control: no-cache, must-revalidate');
@@ -195,6 +223,11 @@ class Tools
195 exit(); 223 exit();
196 } 224 }
197 225
226 /**
227 * Create new line in log file
228 *
229 * @param $message
230 */
198 public static function logm($message) 231 public static function logm($message)
199 { 232 {
200 if (DEBUG_POCHE && php_sapi_name() != 'cli') { 233 if (DEBUG_POCHE && php_sapi_name() != 'cli') {
@@ -204,36 +237,57 @@ class Tools
204 } 237 }
205 } 238 }
206 239
240 /**
241 * Encode a URL by using a salt
242 *
243 * @param $string
244 * @return string
245 */
207 public static function encodeString($string) 246 public static function encodeString($string)
208 { 247 {
209 return sha1($string . SALT); 248 return sha1($string . SALT);
210 } 249 }
211 250
251 /**
252 * Cleans a variable
253 *
254 * @param $var
255 * @param string $default
256 * @return string
257 */
212 public static function checkVar($var, $default = '') 258 public static function checkVar($var, $default = '')
213 { 259 {
214 return ((isset ($_REQUEST["$var"])) ? htmlentities($_REQUEST["$var"]) : $default); 260 return ((isset($_REQUEST["$var"])) ? htmlentities($_REQUEST["$var"]) : $default);
215 } 261 }
216 262
263 /**
264 * Returns the domain name for a URL
265 *
266 * @param $url
267 * @return string
268 */
217 public static function getDomain($url) 269 public static function getDomain($url)
218 { 270 {
219 return parse_url($url, PHP_URL_HOST); 271 return parse_url($url, PHP_URL_HOST);
220 } 272 }
221 273
222 public static function getReadingTime($text) { 274 /**
223 $word = str_word_count(strip_tags($text)); 275 * For a given text, we calculate reading time for an article
224 $minutes = floor($word / 200); 276 *
225 $seconds = floor($word % 200 / (200 / 60)); 277 * @param $text
226 $time = array('minutes' => $minutes, 'seconds' => $seconds); 278 * @return float
227 279 */
228 return $minutes; 280 public static function getReadingTime($text)
229 } 281 {
230 282 return floor(str_word_count(strip_tags($text)) / 200);
231 public static function getDocLanguage($userlanguage) {
232 $lang = explode('.', $userlanguage);
233 return str_replace('_', '-', $lang[0]);
234 } 283 }
235 284
236 public static function status($status_code) 285 /**
286 * Returns the correct header for a status code
287 *
288 * @param $status_code
289 */
290 private static function _status($status_code)
237 { 291 {
238 if (strpos(php_sapi_name(), 'apache') !== false) { 292 if (strpos(php_sapi_name(), 'apache') !== false) {
239 293
@@ -245,9 +299,13 @@ class Tools
245 } 299 }
246 } 300 }
247 301
248 public static function download_db() { 302 /**
303 * Download the sqlite database
304 */
305 public static function downloadDb()
306 {
249 header('Content-Disposition: attachment; filename="poche.sqlite.gz"'); 307 header('Content-Disposition: attachment; filename="poche.sqlite.gz"');
250 self::status(200); 308 self::_status(200);
251 309
252 header('Content-Transfer-Encoding: binary'); 310 header('Content-Transfer-Encoding: binary');
253 header('Content-Type: application/octet-stream'); 311 header('Content-Type: application/octet-stream');
@@ -256,18 +314,24 @@ class Tools
256 exit; 314 exit;
257 } 315 }
258 316
317 /**
318 * Get the content for a given URL (by a call to FullTextFeed)
319 *
320 * @param Url $url
321 * @return mixed
322 */
259 public static function getPageContent(Url $url) 323 public static function getPageContent(Url $url)
260 { 324 {
261 // Saving and clearing context 325 // Saving and clearing context
262 $REAL = array(); 326 $REAL = array();
263 foreach( $GLOBALS as $key => $value ) { 327 foreach( $GLOBALS as $key => $value ) {
264 if( $key != 'GLOBALS' && $key != '_SESSION' && $key != 'HTTP_SESSION_VARS' ) { 328 if( $key != 'GLOBALS' && $key != '_SESSION' && $key != 'HTTP_SESSION_VARS' ) {
265 $GLOBALS[$key] = array(); 329 $GLOBALS[$key] = array();
266 $REAL[$key] = $value; 330 $REAL[$key] = $value;
267 } 331 }
268 } 332 }
269 // Saving and clearing session 333 // Saving and clearing session
270 if ( isset($_SESSION) ) { 334 if (isset($_SESSION)) {
271 $REAL_SESSION = array(); 335 $REAL_SESSION = array();
272 foreach( $_SESSION as $key => $value ) { 336 foreach( $_SESSION as $key => $value ) {
273 $REAL_SESSION[$key] = $value; 337 $REAL_SESSION[$key] = $value;
@@ -279,12 +343,12 @@ class Tools
279 $scope = function() { 343 $scope = function() {
280 extract( func_get_arg(1) ); 344 extract( func_get_arg(1) );
281 $_GET = $_REQUEST = array( 345 $_GET = $_REQUEST = array(
282 "url" => $url->getUrl(), 346 "url" => $url->getUrl(),
283 "max" => 5, 347 "max" => 5,
284 "links" => "preserve", 348 "links" => "preserve",
285 "exc" => "", 349 "exc" => "",
286 "format" => "json", 350 "format" => "json",
287 "submit" => "Create Feed" 351 "submit" => "Create Feed"
288 ); 352 );
289 ob_start(); 353 ob_start();
290 require func_get_arg(0); 354 require func_get_arg(0);
@@ -292,23 +356,26 @@ class Tools
292 ob_end_clean(); 356 ob_end_clean();
293 return $json; 357 return $json;
294 }; 358 };
295 $json = $scope( "inc/3rdparty/makefulltextfeed.php", array("url" => $url) ); 359
360 $json = $scope("inc/3rdparty/makefulltextfeed.php", array("url" => $url));
296 361
297 // Clearing and restoring context 362 // Clearing and restoring context
298 foreach( $GLOBALS as $key => $value ) { 363 foreach ($GLOBALS as $key => $value) {
299 if( $key != "GLOBALS" && $key != "_SESSION" ) { 364 if($key != "GLOBALS" && $key != "_SESSION" ) {
300 unset($GLOBALS[$key]); 365 unset($GLOBALS[$key]);
301 } 366 }
302 } 367 }
303 foreach( $REAL as $key => $value ) { 368 foreach ($REAL as $key => $value) {
304 $GLOBALS[$key] = $value; 369 $GLOBALS[$key] = $value;
305 } 370 }
371
306 // Clearing and restoring session 372 // Clearing and restoring session
307 if ( isset($REAL_SESSION) ) { 373 if (isset($REAL_SESSION)) {
308 foreach( $_SESSION as $key => $value ) { 374 foreach($_SESSION as $key => $value) {
309 unset($_SESSION[$key]); 375 unset($_SESSION[$key]);
310 } 376 }
311 foreach( $REAL_SESSION as $key => $value ) { 377
378 foreach($REAL_SESSION as $key => $value) {
312 $_SESSION[$key] = $value; 379 $_SESSION[$key] = $value;
313 } 380 }
314 } 381 }
@@ -318,11 +385,12 @@ class Tools
318 385
319 /** 386 /**
320 * Returns whether we handle an AJAX (XMLHttpRequest) request. 387 * Returns whether we handle an AJAX (XMLHttpRequest) request.
388 *
321 * @return boolean whether we handle an AJAX (XMLHttpRequest) request. 389 * @return boolean whether we handle an AJAX (XMLHttpRequest) request.
322 */ 390 */
323 public static function isAjaxRequest() 391 public static function isAjaxRequest()
324 { 392 {
325 return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest'; 393 return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest';
326 } 394 }
327 395
328} 396}
diff --git a/inc/poche/Url.class.php b/inc/poche/Url.class.php
index aba236fa..d9172b7d 100644
--- a/inc/poche/Url.class.php
+++ b/inc/poche/Url.class.php
@@ -5,7 +5,7 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11class Url 11class Url
diff --git a/inc/poche/User.class.php b/inc/poche/User.class.php
index cc8bec65..eaadd3e5 100644
--- a/inc/poche/User.class.php
+++ b/inc/poche/User.class.php
@@ -5,7 +5,7 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11class User 11class User
@@ -44,7 +44,14 @@ class User
44 $this->config = $config; 44 $this->config = $config;
45 } 45 }
46 46
47 public function getConfigValue($name) { 47 /**
48 * Returns configuration entry for a user
49 *
50 * @param $name
51 * @return bool
52 */
53 public function getConfigValue($name)
54 {
48 return (isset($this->config[$name])) ? $this->config[$name] : FALSE; 55 return (isset($this->config[$name])) ? $this->config[$name] : FALSE;
49 } 56 }
50} \ No newline at end of file 57} \ No newline at end of file
diff --git a/inc/poche/config.inc.default.php b/inc/poche/config.inc.default.php
index 95f727c6..6f03af18 100755
--- a/inc/poche/config.inc.default.php
+++ b/inc/poche/config.inc.default.php
@@ -5,7 +5,7 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11@define ('SALT', ''); # put a strong string here 11@define ('SALT', ''); # put a strong string here
diff --git a/inc/poche/global.inc.php b/inc/poche/global.inc.php
index 8cf86d03..2c22c014 100755
--- a/inc/poche/global.inc.php
+++ b/inc/poche/global.inc.php
@@ -5,7 +5,7 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11# the poche system root directory (/inc) 11# the poche system root directory (/inc)
@@ -18,6 +18,10 @@ require_once INCLUDES . '/poche/Tools.class.php';
18require_once INCLUDES . '/poche/User.class.php'; 18require_once INCLUDES . '/poche/User.class.php';
19require_once INCLUDES . '/poche/Url.class.php'; 19require_once INCLUDES . '/poche/Url.class.php';
20require_once INCLUDES . '/3rdparty/class.messages.php'; 20require_once INCLUDES . '/3rdparty/class.messages.php';
21require_once ROOT . '/vendor/autoload.php';
22require_once INCLUDES . '/poche/Template.class.php';
23require_once INCLUDES . '/poche/Language.class.php';
24require_once INCLUDES . '/poche/Routing.class.php';
21require_once INCLUDES . '/poche/Poche.class.php'; 25require_once INCLUDES . '/poche/Poche.class.php';
22 26
23require_once INCLUDES . '/poche/Database.class.php'; 27require_once INCLUDES . '/poche/Database.class.php';
@@ -36,22 +40,11 @@ require_once INCLUDES . '/3rdparty/libraries/PHPePub/Logger.php';
36require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPub.php'; 40require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPub.php';
37require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPubChapterSplitter.php'; 41require_once INCLUDES . '/3rdparty/libraries/PHPePub/EPubChapterSplitter.php';
38 42
39# Composer its autoloader for automatically loading Twig
40if (! file_exists(ROOT . '/vendor/autoload.php')) {
41 Poche::$canRenderTemplates = false;
42} else {
43 require_once ROOT . '/vendor/autoload.php';
44}
45
46# system configuration; database credentials et caetera 43# system configuration; database credentials et caetera
47if (! file_exists(INCLUDES . '/poche/config.inc.php')) { 44require_once INCLUDES . '/poche/config.inc.php';
48 Poche::$configFileAvailable = false; 45require_once INCLUDES . '/poche/config.inc.default.php';
49} else {
50 require_once INCLUDES . '/poche/config.inc.php';
51 require_once INCLUDES . '/poche/config.inc.default.php';
52}
53 46
54if (Poche::$configFileAvailable && DOWNLOAD_PICTURES) { 47if (DOWNLOAD_PICTURES) {
55 require_once INCLUDES . '/poche/pochePictures.php'; 48 require_once INCLUDES . '/poche/pochePictures.php';
56} 49}
57 50
diff --git a/inc/poche/pochePictures.php b/inc/poche/pochePictures.php
index 7c319a85..26bf0706 100644
--- a/inc/poche/pochePictures.php
+++ b/inc/poche/pochePictures.php
@@ -5,154 +5,169 @@
5 * @category wallabag 5 * @category wallabag
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org> 6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013 7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file 8 * @license http://opensource.org/licenses/MIT see COPYING file
9 */ 9 */
10 10
11/** 11
12 * On modifie les URLS des images dans le corps de l'article 12final class Picture
13 */
14function filtre_picture($content, $url, $id)
15{ 13{
16 $matches = array(); 14 private function __construct()
17 $processing_pictures = array(); // list of processing image to avoid processing the same pictures twice 15 {
18 preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER); 16
19 foreach($matches as $i => $link) { 17 }
20 $link[1] = trim($link[1]); 18
21 if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1])) { 19 /**
22 $absolute_path = get_absolute_link($link[2],$url); 20 * Changing pictures URL in article content
23 $filename = basename(parse_url($absolute_path, PHP_URL_PATH)); 21 */
24 $directory = create_assets_directory($id); 22 public static function filterPicture($content, $url, $id)
25 $fullpath = $directory . '/' . $filename; 23 {
26 24 $matches = array();
27 if (in_array($absolute_path, $processing_pictures) === true) { 25 $processing_pictures = array(); // list of processing image to avoid processing the same pictures twice
28 // replace picture's URL only if processing is OK : already processing -> go to next picture 26 preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER);
29 continue; 27 foreach($matches as $i => $link) {
30 } 28 $link[1] = trim($link[1]);
31 29 if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1])) {
32 if (download_pictures($absolute_path, $fullpath) === true) { 30 $absolute_path = self::_getAbsoluteLink($link[2], $url);
33 $content = str_replace($matches[$i][2], $fullpath, $content); 31 $filename = basename(parse_url($absolute_path, PHP_URL_PATH));
32 $directory = self::_createAssetsDirectory($id);
33 $fullpath = $directory . '/' . $filename;
34
35 if (in_array($absolute_path, $processing_pictures) === true) {
36 // replace picture's URL only if processing is OK : already processing -> go to next picture
37 continue;
38 }
39
40 if (self::_downloadPictures($absolute_path, $fullpath) === true) {
41 $content = str_replace($matches[$i][2], $fullpath, $content);
42 }
43
44 $processing_pictures[] = $absolute_path;
34 } 45 }
35
36 $processing_pictures[] = $absolute_path;
37 } 46 }
38 47
48 return $content;
39 } 49 }
40 50
41 return $content; 51 /**
42} 52 * Get absolute URL
53 */
54 private static function _getAbsoluteLink($relativeLink, $url)
55 {
56 /* return if already absolute URL */
57 if (parse_url($relativeLink, PHP_URL_SCHEME) != '') return $relativeLink;
43 58
44/** 59 /* queries and anchors */
45 * Retourne le lien absolu 60 if ($relativeLink[0]=='#' || $relativeLink[0]=='?') return $url . $relativeLink;
46 */
47function get_absolute_link($relative_link, $url) {
48 /* return if already absolute URL */
49 if (parse_url($relative_link, PHP_URL_SCHEME) != '') return $relative_link;
50 61
51 /* queries and anchors */ 62 /* parse base URL and convert to local variables:
52 if ($relative_link[0]=='#' || $relative_link[0]=='?') return $url . $relative_link; 63 $scheme, $host, $path */
64 extract(parse_url($url));
53 65
54 /* parse base URL and convert to local variables: 66 /* remove non-directory element from path */
55 $scheme, $host, $path */ 67 $path = preg_replace('#/[^/]*$#', '', $path);
56 extract(parse_url($url));
57 68
58 /* remove non-directory element from path */ 69 /* destroy path if relative url points to root */
59 $path = preg_replace('#/[^/]*$#', '', $path); 70 if ($relativeLink[0] == '/') $path = '';
60 71
61 /* destroy path if relative url points to root */ 72 /* dirty absolute URL */
62 if ($relative_link[0] == '/') $path = ''; 73 $abs = $host . $path . '/' . $relativeLink;
63 74
64 /* dirty absolute URL */ 75 /* replace '//' or '/./' or '/foo/../' with '/' */
65 $abs = $host . $path . '/' . $relative_link; 76 $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
77 for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {}
66 78
67 /* replace '//' or '/./' or '/foo/../' with '/' */ 79 /* absolute URL is ready! */
68 $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#'); 80 return $scheme.'://'.$abs;
69 for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {} 81 }
70 82
71 /* absolute URL is ready! */ 83 /**
72 return $scheme.'://'.$abs; 84 * Downloading pictures
73} 85 *
86 * @return bool true if the download and processing is OK, false else
87 */
88 private static function _downloadPictures($absolute_path, $fullpath)
89 {
90 $rawdata = Tools::getFile($absolute_path);
91 $fullpath = urldecode($fullpath);
92
93 if(file_exists($fullpath)) {
94 unlink($fullpath);
95 }
74 96
75/** 97 // check extension
76 * Téléchargement des images 98 $file_ext = strrchr($fullpath, '.');
77 * 99 $whitelist = array(".jpg",".jpeg",".gif",".png");
78 * @return bool true if the download and processing is OK, false else 100 if (!(in_array($file_ext, $whitelist))) {
79 */ 101 Tools::logm('processed image with not allowed extension. Skipping ' . $fullpath);
80function download_pictures($absolute_path, $fullpath) 102 return false;
81{ 103 }
82 $rawdata = Tools::getFile($absolute_path);
83 $fullpath = urldecode($fullpath);
84 104
85 if(file_exists($fullpath)) { 105 // check headers
86 unlink($fullpath); 106 $imageinfo = getimagesize($absolute_path);
87 } 107 if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg'&& $imageinfo['mime'] != 'image/jpg'&& $imageinfo['mime'] != 'image/png') {
88 108 Tools::logm('processed image with bad header. Skipping ' . $fullpath);
89 // check extension 109 return false;
90 $file_ext = strrchr($fullpath, '.'); 110 }
91 $whitelist = array(".jpg",".jpeg",".gif",".png");
92 if (!(in_array($file_ext, $whitelist))) {
93 Tools::logm('processed image with not allowed extension. Skipping ' . $fullpath);
94 return false;
95 }
96
97 // check headers
98 $imageinfo = getimagesize($absolute_path);
99 if ($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg'&& $imageinfo['mime'] != 'image/jpg'&& $imageinfo['mime'] != 'image/png') {
100 Tools::logm('processed image with bad header. Skipping ' . $fullpath);
101 return false;
102 }
103
104 // regenerate image
105 $im = imagecreatefromstring($rawdata);
106 if ($im === false) {
107 Tools::logm('error while regenerating image ' . $fullpath);
108 return false;
109 }
110
111 switch ($imageinfo['mime']) {
112 case 'image/gif':
113 $result = imagegif($im, $fullpath);
114 break;
115 case 'image/jpeg':
116 case 'image/jpg':
117 $result = imagejpeg($im, $fullpath, REGENERATE_PICTURES_QUALITY);
118 break;
119 case 'image/png':
120 $result = imagepng($im, $fullpath, ceil(REGENERATE_PICTURES_QUALITY / 100 * 9));
121 break;
122 }
123 imagedestroy($im);
124
125 return $result;
126}
127 111
128/** 112 // regenerate image
129 * Crée un répertoire de médias pour l'article 113 $im = imagecreatefromstring($rawdata);
130 */ 114 if ($im === false) {
131function create_assets_directory($id) 115 Tools::logm('error while regenerating image ' . $fullpath);
132{ 116 return false;
133 $assets_path = ABS_PATH; 117 }
134 if(!is_dir($assets_path)) { 118
135 mkdir($assets_path, 0715); 119 switch ($imageinfo['mime']) {
136 } 120 case 'image/gif':
121 $result = imagegif($im, $fullpath);
122 break;
123 case 'image/jpeg':
124 case 'image/jpg':
125 $result = imagejpeg($im, $fullpath, REGENERATE_PICTURES_QUALITY);
126 break;
127 case 'image/png':
128 $result = imagepng($im, $fullpath, ceil(REGENERATE_PICTURES_QUALITY / 100 * 9));
129 break;
130 }
131 imagedestroy($im);
137 132
138 $article_directory = $assets_path . $id; 133 return $result;
139 if(!is_dir($article_directory)) {
140 mkdir($article_directory, 0715);
141 } 134 }
142 135
143 return $article_directory; 136 /**
144} 137 * Create a directory for an article
138 *
139 * @param $id ID of the article
140 * @return string
141 */
142 private static function _createAssetsDirectory($id)
143 {
144 $assets_path = ABS_PATH;
145 if (!is_dir($assets_path)) {
146 mkdir($assets_path, 0715);
147 }
145 148
146/** 149 $article_directory = $assets_path . $id;
147 * Suppression du répertoire d'images 150 if (!is_dir($article_directory)) {
148 */ 151 mkdir($article_directory, 0715);
149function remove_directory($directory) 152 }
150{ 153
151 if(is_dir($directory)) { 154 return $article_directory;
152 $files = array_diff(scandir($directory), array('.','..')); 155 }
153 foreach ($files as $file) { 156
154 (is_dir("$directory/$file")) ? remove_directory("$directory/$file") : unlink("$directory/$file"); 157 /**
158 * Remove the directory
159 *
160 * @param $directory
161 * @return bool
162 */
163 public static function removeDirectory($directory)
164 {
165 if (is_dir($directory)) {
166 $files = array_diff(scandir($directory), array('.','..'));
167 foreach ($files as $file) {
168 (is_dir("$directory/$file")) ? self::removeDirectory("$directory/$file") : unlink("$directory/$file");
169 }
170 return rmdir($directory);
155 } 171 }
156 return rmdir($directory);
157 } 172 }
158} 173} \ No newline at end of file