aboutsummaryrefslogtreecommitdiffhomepage
path: root/inc/poche
diff options
context:
space:
mode:
authorNicolas Lœuillet <nicolas.loeuillet@gmail.com>2013-08-07 10:41:26 -0700
committerNicolas Lœuillet <nicolas.loeuillet@gmail.com>2013-08-07 10:41:26 -0700
commit01c0e050ad8eca54f115dfa21db99e4f61ab7ca7 (patch)
treee1bdacb68b3a56644f4525974844dd954d6e3c6b /inc/poche
parentda2c5d6fc33587c775a7d8a738c2c18de41f83b2 (diff)
parent339d510fda0a43b08981309f7540acedf3a4976c (diff)
downloadwallabag-01c0e050ad8eca54f115dfa21db99e4f61ab7ca7.tar.gz
wallabag-01c0e050ad8eca54f115dfa21db99e4f61ab7ca7.tar.zst
wallabag-01c0e050ad8eca54f115dfa21db99e4f61ab7ca7.zip
Merge pull request #104 from inthepoche/twig
Twig version on dev branch
Diffstat (limited to 'inc/poche')
-rw-r--r--inc/poche/Database.class.php216
-rw-r--r--inc/poche/Poche.class.php429
-rw-r--r--inc/poche/Tools.class.php226
-rw-r--r--inc/poche/Url.class.php94
-rw-r--r--inc/poche/User.class.php50
-rw-r--r--inc/poche/config.inc.php61
-rw-r--r--inc/poche/pochePictures.php110
7 files changed, 1186 insertions, 0 deletions
diff --git a/inc/poche/Database.class.php b/inc/poche/Database.class.php
new file mode 100644
index 00000000..cd5a9a31
--- /dev/null
+++ b/inc/poche/Database.class.php
@@ -0,0 +1,216 @@
1<?php
2/**
3 * poche, a read it later open source system
4 *
5 * @category poche
6 * @author Nicolas Lœuillet <support@inthepoche.com>
7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file
9 */
10
11class Database {
12 var $handle;
13
14 function __construct()
15 {
16 switch (STORAGE) {
17 case 'sqlite':
18 $db_path = 'sqlite:' . STORAGE_SQLITE;
19 $this->handle = new PDO($db_path);
20 break;
21 case 'mysql':
22 $db_path = 'mysql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB;
23 $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD);
24 break;
25 case 'postgres':
26 $db_path = 'pgsql:host=' . STORAGE_SERVER . ';dbname=' . STORAGE_DB;
27 $this->handle = new PDO($db_path, STORAGE_USER, STORAGE_PASSWORD);
28 break;
29 }
30
31 $this->handle->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
32 Tools::logm('storage type ' . STORAGE);
33 }
34
35 private function getHandle() {
36 return $this->handle;
37 }
38
39 public function isInstalled() {
40 $sql = "SELECT username FROM users";
41 $query = $this->executeQuery($sql, array());
42 $hasAdmin = count($query->fetchAll());
43
44 if ($hasAdmin == 0)
45 return FALSE;
46
47 return TRUE;
48 }
49
50 public function install($login, $password) {
51 $sql = 'INSERT INTO users ( username, password, name, email) VALUES (?, ?, ?, ?)';
52 $params = array($login, $password, $login, ' ');
53 $query = $this->executeQuery($sql, $params);
54
55 $sequence = '';
56 if (STORAGE == 'postgres') {
57 $sequence = 'users_id_seq';
58 }
59
60 $id_user = intval($this->getLastId($sequence));
61
62 $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)';
63 $params = array($id_user, 'pager', '10');
64 $query = $this->executeQuery($sql, $params);
65
66 $sql = 'INSERT INTO users_config ( user_id, name, value ) VALUES (?, ?, ?)';
67 $params = array($id_user, 'language', 'en_EN.UTF8');
68 $query = $this->executeQuery($sql, $params);
69
70 return TRUE;
71 }
72
73 private function getConfigUser($id) {
74 $sql = "SELECT * FROM users_config WHERE user_id = ?";
75 $query = $this->executeQuery($sql, array($id));
76 $result = $query->fetchAll();
77 $user_config = array();
78
79 foreach ($result as $key => $value) {
80 $user_config[$value['name']] = $value['value'];
81 }
82
83 return $user_config;
84 }
85
86 public function login($username, $password) {
87 $sql = "SELECT * FROM users WHERE username=? AND password=?";
88 $query = $this->executeQuery($sql, array($username, $password));
89 $login = $query->fetchAll();
90
91 $user = array();
92 if (isset($login[0])) {
93 $user['id'] = $login[0]['id'];
94 $user['username'] = $login[0]['username'];
95 $user['password'] = $login[0]['password'];
96 $user['name'] = $login[0]['name'];
97 $user['email'] = $login[0]['email'];
98 $user['config'] = $this->getConfigUser($login[0]['id']);
99 }
100
101 return $user;
102 }
103
104 public function updatePassword($id, $password)
105 {
106 $sql_update = "UPDATE users SET password=? WHERE id=?";
107 $params_update = array($password, $id);
108 $query = $this->executeQuery($sql_update, $params_update);
109 }
110
111 private function executeQuery($sql, $params) {
112 try
113 {
114 $query = $this->getHandle()->prepare($sql);
115 $query->execute($params);
116 return $query;
117 }
118 catch (Exception $e)
119 {
120 Tools::logm('execute query error : '.$e->getMessage());
121 return FALSE;
122 }
123 }
124
125 public function retrieveAll($user_id) {
126 $sql = "SELECT * FROM entries WHERE user_id=? ORDER BY id";
127 $query = $this->executeQuery($sql, array($user_id));
128 $entries = $query->fetchAll();
129
130 return $entries;
131 }
132
133 public function retrieveOneById($id, $user_id) {
134 $entry = NULL;
135 $sql = "SELECT * FROM entries WHERE id=? AND user_id=?";
136 $params = array(intval($id), $user_id);
137 $query = $this->executeQuery($sql, $params);
138 $entry = $query->fetchAll();
139
140 return $entry[0];
141 }
142
143 public function getEntriesByView($view, $user_id, $limit = '') {
144 switch ($_SESSION['sort'])
145 {
146 case 'ia':
147 $order = 'ORDER BY id';
148 break;
149 case 'id':
150 $order = 'ORDER BY id DESC';
151 break;
152 case 'ta':
153 $order = 'ORDER BY lower(title)';
154 break;
155 case 'td':
156 $order = 'ORDER BY lower(title) DESC';
157 break;
158 default:
159 $order = 'ORDER BY id';
160 break;
161 }
162
163 switch ($view)
164 {
165 case 'archive':
166 $sql = "SELECT * FROM entries WHERE user_id=? AND is_read=? " . $order;
167 $params = array($user_id, 1);
168 break;
169 case 'fav' :
170 $sql = "SELECT * FROM entries WHERE user_id=? AND is_fav=? " . $order;
171 $params = array($user_id, 1);
172 break;
173 default:
174 $sql = "SELECT * FROM entries WHERE user_id=? AND is_read=? " . $order;
175 $params = array($user_id, 0);
176 break;
177 }
178
179 $sql .= ' ' . $limit;
180
181 $query = $this->executeQuery($sql, $params);
182 $entries = $query->fetchAll();
183
184 return $entries;
185 }
186
187 public function add($url, $title, $content, $user_id) {
188 $sql_action = 'INSERT INTO entries ( url, title, content, user_id ) VALUES (?, ?, ?, ?)';
189 $params_action = array($url, $title, $content, $user_id);
190 $query = $this->executeQuery($sql_action, $params_action);
191 return $query;
192 }
193
194 public function deleteById($id, $user_id) {
195 $sql_action = "DELETE FROM entries WHERE id=? AND user_id=?";
196 $params_action = array($id, $user_id);
197 $query = $this->executeQuery($sql_action, $params_action);
198 return $query;
199 }
200
201 public function favoriteById($id, $user_id) {
202 $sql_action = "UPDATE entries SET is_fav=NOT is_fav WHERE id=? AND user_id=?";
203 $params_action = array($id, $user_id);
204 $query = $this->executeQuery($sql_action, $params_action);
205 }
206
207 public function archiveById($id, $user_id) {
208 $sql_action = "UPDATE entries SET is_read=NOT is_read WHERE id=? AND user_id=?";
209 $params_action = array($id, $user_id);
210 $query = $this->executeQuery($sql_action, $params_action);
211 }
212
213 public function getLastId($column = '') {
214 return $this->getHandle()->lastInsertId($column);
215 }
216}
diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php
new file mode 100644
index 00000000..38b4a98e
--- /dev/null
+++ b/inc/poche/Poche.class.php
@@ -0,0 +1,429 @@
1<?php
2/**
3 * poche, a read it later open source system
4 *
5 * @category poche
6 * @author Nicolas Lœuillet <support@inthepoche.com>
7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file
9 */
10
11class Poche
12{
13 public $user;
14 public $store;
15 public $tpl;
16 public $messages;
17 public $pagination;
18
19 function __construct()
20 {
21 $this->store = new Database();
22 $this->init();
23 $this->messages = new Messages();
24
25 # installation
26 if(!$this->store->isInstalled())
27 {
28 $this->install();
29 }
30 }
31
32 private function init()
33 {
34 Tools::initPhp();
35 Session::init();
36
37 if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) {
38 $this->user = $_SESSION['poche_user'];
39 }
40 else {
41 # fake user, just for install & login screens
42 $this->user = new User();
43 $this->user->setConfig($this->getDefaultConfig());
44 }
45
46 # l10n
47 $language = $this->user->getConfigValue('language');
48 putenv('LC_ALL=' . $language);
49 setlocale(LC_ALL, $language);
50 bindtextdomain($language, LOCALE);
51 textdomain($language);
52
53 # template engine
54 $loader = new Twig_Loader_Filesystem(TPL);
55 if (DEBUG_POCHE) {
56 $twig_params = array();
57 }
58 else {
59 $twig_params = array('cache' => CACHE);
60 }
61 $this->tpl = new Twig_Environment($loader, $twig_params);
62 $this->tpl->addExtension(new Twig_Extensions_Extension_I18n());
63 # filter to display domain name of an url
64 $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain');
65 $this->tpl->addFilter($filter);
66
67 # Pagination
68 $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p');
69 }
70
71 private function install()
72 {
73 Tools::logm('poche still not installed');
74 echo $this->tpl->render('install.twig', array(
75 'token' => Session::getToken()
76 ));
77 if (isset($_GET['install'])) {
78 if (($_POST['password'] == $_POST['password_repeat'])
79 && $_POST['password'] != "" && $_POST['login'] != "") {
80 # let's rock, install poche baby !
81 $this->store->install($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login']));
82 Session::logout();
83 Tools::logm('poche is now installed');
84 Tools::redirect();
85 }
86 else {
87 Tools::logm('error during installation');
88 Tools::redirect();
89 }
90 }
91 exit();
92 }
93
94 public function getDefaultConfig()
95 {
96 return array(
97 'pager' => PAGINATION,
98 'language' => LANG,
99 );
100 }
101
102 /**
103 * Call action (mark as fav, archive, delete, etc.)
104 */
105 public function action($action, Url $url, $id = 0, $import = FALSE)
106 {
107 switch ($action)
108 {
109 case 'add':
110 if($parametres_url = $url->fetchContent()) {
111 if ($this->store->add($url->getUrl(), $parametres_url['title'], $parametres_url['content'], $this->user->getId())) {
112 Tools::logm('add link ' . $url->getUrl());
113 $sequence = '';
114 if (STORAGE == 'postgres') {
115 $sequence = 'entries_id_seq';
116 }
117 $last_id = $this->store->getLastId($sequence);
118 if (DOWNLOAD_PICTURES) {
119 $content = filtre_picture($parametres_url['content'], $url->getUrl(), $last_id);
120 }
121 if (!$import) {
122 $this->messages->add('s', _('the link has been added successfully'));
123 }
124 }
125 else {
126 if (!$import) {
127 $this->messages->add('e', _('error during insertion : the link wasn\'t added'));
128 Tools::logm('error during insertion : the link wasn\'t added ' . $url->getUrl());
129 }
130 }
131 }
132 else {
133 if (!$import) {
134 $this->messages->add('e', _('error during fetching content : the link wasn\'t added'));
135 Tools::logm('error during content fetch ' . $url->getUrl());
136 }
137 }
138 if (!$import) {
139 Tools::redirect();
140 }
141 break;
142 case 'delete':
143 $msg = 'delete link #' . $id;
144 if ($this->store->deleteById($id, $this->user->getId())) {
145 if (DOWNLOAD_PICTURES) {
146 remove_directory(ABS_PATH . $id);
147 }
148 $this->messages->add('s', _('the link has been deleted successfully'));
149 }
150 else {
151 $this->messages->add('e', _('the link wasn\'t deleted'));
152 $msg = 'error : can\'t delete link #' . $id;
153 }
154 Tools::logm($msg);
155 Tools::redirect('?');
156 break;
157 case 'toggle_fav' :
158 $this->store->favoriteById($id, $this->user->getId());
159 Tools::logm('mark as favorite link #' . $id);
160 if (!$import) {
161 Tools::redirect();
162 }
163 break;
164 case 'toggle_archive' :
165 $this->store->archiveById($id, $this->user->getId());
166 Tools::logm('archive link #' . $id);
167 if (!$import) {
168 Tools::redirect();
169 }
170 break;
171 default:
172 break;
173 }
174 }
175
176 function displayView($view, $id = 0)
177 {
178 $tpl_vars = array();
179
180 switch ($view)
181 {
182 case 'config':
183 $dev = $this->getPocheVersion('dev');
184 $prod = $this->getPocheVersion('prod');
185 $compare_dev = version_compare(POCHE_VERSION, $dev);
186 $compare_prod = version_compare(POCHE_VERSION, $prod);
187 $tpl_vars = array(
188 'dev' => $dev,
189 'prod' => $prod,
190 'compare_dev' => $compare_dev,
191 'compare_prod' => $compare_prod,
192 );
193 Tools::logm('config view');
194 break;
195 case 'view':
196 $entry = $this->store->retrieveOneById($id, $this->user->getId());
197 if ($entry != NULL) {
198 Tools::logm('view link #' . $id);
199 $content = $entry['content'];
200 if (function_exists('tidy_parse_string')) {
201 $tidy = tidy_parse_string($content, array('indent'=>true, 'show-body-only' => true), 'UTF8');
202 $tidy->cleanRepair();
203 $content = $tidy->value;
204 }
205 $tpl_vars = array(
206 'entry' => $entry,
207 'content' => $content,
208 );
209 }
210 else {
211 Tools::logm('error in view call : entry is NULL');
212 }
213 break;
214 default: # home view
215 $entries = $this->store->getEntriesByView($view, $this->user->getId());
216 $this->pagination->set_total(count($entries));
217 $page_links = $this->pagination->page_links('?view=' . $view . '&sort=' . $_SESSION['sort'] . '&');
218 $datas = $this->store->getEntriesByView($view, $this->user->getId(), $this->pagination->get_limit());
219 $tpl_vars = array(
220 'entries' => $datas,
221 'page_links' => $page_links,
222 );
223 Tools::logm('display ' . $view . ' view');
224 break;
225 }
226
227 return $tpl_vars;
228 }
229
230 public function updatePassword()
231 {
232 if (MODE_DEMO) {
233 $this->messages->add('i', _('in demo mode, you can\'t update your password'));
234 Tools::logm('in demo mode, you can\'t do this');
235 Tools::redirect('?view=config');
236 }
237 else {
238 if (isset($_POST['password']) && isset($_POST['password_repeat'])) {
239 if ($_POST['password'] == $_POST['password_repeat'] && $_POST['password'] != "") {
240 $this->messages->add('s', _('your password has been updated'));
241 $this->store->updatePassword($this->user->getId(), Tools::encodeString($_POST['password'] . $this->user->getUsername()));
242 Session::logout();
243 Tools::logm('password updated');
244 Tools::redirect();
245 }
246 else {
247 $this->messages->add('e', _('the two fields have to be filled & the password must be the same in the two fields'));
248 Tools::redirect('?view=config');
249 }
250 }
251 }
252 }
253
254 public function login($referer)
255 {
256 if (!empty($_POST['login']) && !empty($_POST['password'])) {
257 $user = $this->store->login($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login']));
258 if ($user != array()) {
259 # Save login into Session
260 Session::login($user['username'], $user['password'], $_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login']), array('poche_user' => new User($user)));
261
262 $this->messages->add('s', _('welcome to your poche'));
263 if (!empty($_POST['longlastingsession'])) {
264 $_SESSION['longlastingsession'] = 31536000;
265 $_SESSION['expires_on'] = time() + $_SESSION['longlastingsession'];
266 session_set_cookie_params($_SESSION['longlastingsession']);
267 } else {
268 session_set_cookie_params(0);
269 }
270 session_regenerate_id(true);
271 Tools::logm('login successful');
272 Tools::redirect($referer);
273 }
274 $this->messages->add('e', _('login failed: bad login or password'));
275 Tools::logm('login failed');
276 Tools::redirect();
277 } else {
278 $this->messages->add('e', _('login failed: you have to fill all fields'));
279 Tools::logm('login failed');
280 Tools::redirect();
281 }
282 }
283
284 public function logout()
285 {
286 $this->user = array();
287 Session::logout();
288 $this->messages->add('s', _('see you soon!'));
289 Tools::logm('logout');
290 Tools::redirect();
291 }
292
293 private function importFromInstapaper()
294 {
295 # TODO gestion des articles favs
296 $html = new simple_html_dom();
297 $html->load_file('./instapaper-export.html');
298 Tools::logm('starting import from instapaper');
299
300 $read = 0;
301 $errors = array();
302 foreach($html->find('ol') as $ul)
303 {
304 foreach($ul->find('li') as $li)
305 {
306 $a = $li->find('a');
307 $url = new Url(base64_encode($a[0]->href));
308 $this->action('add', $url, 0, TRUE);
309 if ($read == '1') {
310 $sequence = '';
311 if (STORAGE == 'postgres') {
312 $sequence = 'entries_id_seq';
313 }
314 $last_id = $this->store->getLastId($sequence);
315 $this->action('toggle_archive', $url, $last_id, TRUE);
316 }
317 }
318
319 # the second <ol> is for read links
320 $read = 1;
321 }
322 $this->messages->add('s', _('import from instapaper completed'));
323 Tools::logm('import from instapaper completed');
324 Tools::redirect();
325 }
326
327 private function importFromPocket()
328 {
329 # TODO gestion des articles favs
330 $html = new simple_html_dom();
331 $html->load_file('./ril_export.html');
332 Tools::logm('starting import from pocket');
333
334 $read = 0;
335 $errors = array();
336 foreach($html->find('ul') as $ul)
337 {
338 foreach($ul->find('li') as $li)
339 {
340 $a = $li->find('a');
341 $url = new Url(base64_encode($a[0]->href));
342 $this->action('add', $url, 0, TRUE);
343 if ($read == '1') {
344 $sequence = '';
345 if (STORAGE == 'postgres') {
346 $sequence = 'entries_id_seq';
347 }
348 $last_id = $this->store->getLastId($sequence);
349 $this->action('toggle_archive', $url, $last_id, TRUE);
350 }
351 }
352
353 # the second <ul> is for read links
354 $read = 1;
355 }
356 $this->messages->add('s', _('import from pocket completed'));
357 Tools::logm('import from pocket completed');
358 Tools::redirect();
359 }
360
361 private function importFromReadability()
362 {
363 # TODO gestion des articles lus / favs
364 $str_data = file_get_contents("./readability");
365 $data = json_decode($str_data,true);
366 Tools::logm('starting import from Readability');
367
368 foreach ($data as $key => $value) {
369 $url = '';
370 foreach ($value as $attr => $attr_value) {
371 if ($attr == 'article__url') {
372 $url = new Url(base64_encode($attr_value));
373 }
374 $sequence = '';
375 if (STORAGE == 'postgres') {
376 $sequence = 'entries_id_seq';
377 }
378 // if ($attr_value == 'favorite' && $attr_value == 'true') {
379 // $last_id = $this->store->getLastId($sequence);
380 // $this->store->favoriteById($last_id);
381 // $this->action('toogle_fav', $url, $last_id, TRUE);
382 // }
383 if ($attr_value == 'archive' && $attr_value == 'true') {
384 $last_id = $this->store->getLastId($sequence);
385 $this->action('toggle_archive', $url, $last_id, TRUE);
386 }
387 }
388 if ($url->isCorrect())
389 $this->action('add', $url, 0, TRUE);
390 }
391 $this->messages->add('s', _('import from Readability completed'));
392 Tools::logm('import from Readability completed');
393 Tools::redirect();
394 }
395
396 public function import($from)
397 {
398 if ($from == 'pocket') {
399 $this->importFromPocket();
400 }
401 else if ($from == 'readability') {
402 $this->importFromReadability();
403 }
404 else if ($from == 'instapaper') {
405 $this->importFromInstapaper();
406 }
407 }
408
409 public function export()
410 {
411 $entries = $this->store->retrieveAll($this->user->getId());
412 echo $this->tpl->render('export.twig', array(
413 'export' => Tools::renderJson($entries),
414 ));
415 Tools::logm('export view');
416 }
417
418 private function getPocheVersion($which = 'prod')
419 {
420 $cache_file = CACHE . '/' . $which;
421 if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) {
422 $version = file_get_contents($cache_file);
423 } else {
424 $version = file_get_contents('http://static.inthepoche.com/versions/' . $which);
425 file_put_contents($cache_file, $version, LOCK_EX);
426 }
427 return $version;
428 }
429} \ No newline at end of file
diff --git a/inc/poche/Tools.class.php b/inc/poche/Tools.class.php
new file mode 100644
index 00000000..d0e43166
--- /dev/null
+++ b/inc/poche/Tools.class.php
@@ -0,0 +1,226 @@
1<?php
2/**
3 * poche, a read it later open source system
4 *
5 * @category poche
6 * @author Nicolas Lœuillet <support@inthepoche.com>
7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file
9 */
10
11class Tools
12{
13 public static function initPhp()
14 {
15 define('START_TIME', microtime(true));
16
17 if (phpversion() < 5) {
18 die(_('Oops, it seems you don\'t have PHP 5.'));
19 }
20
21 error_reporting(E_ALL);
22
23 function stripslashesDeep($value) {
24 return is_array($value)
25 ? array_map('stripslashesDeep', $value)
26 : stripslashes($value);
27 }
28
29 if (get_magic_quotes_gpc()) {
30 $_POST = array_map('stripslashesDeep', $_POST);
31 $_GET = array_map('stripslashesDeep', $_GET);
32 $_COOKIE = array_map('stripslashesDeep', $_COOKIE);
33 }
34
35 ob_start();
36 register_shutdown_function('ob_end_flush');
37 }
38
39 public static function getPocheUrl()
40 {
41 $https = (!empty($_SERVER['HTTPS'])
42 && (strtolower($_SERVER['HTTPS']) == 'on'))
43 || (isset($_SERVER["SERVER_PORT"])
44 && $_SERVER["SERVER_PORT"] == '443'); // HTTPS detection.
45 $serverport = (!isset($_SERVER["SERVER_PORT"])
46 || $_SERVER["SERVER_PORT"] == '80'
47 || ($https && $_SERVER["SERVER_PORT"] == '443')
48 ? '' : ':' . $_SERVER["SERVER_PORT"]);
49
50 $scriptname = str_replace('/index.php', '/', $_SERVER["SCRIPT_NAME"]);
51
52 if (!isset($_SERVER["SERVER_NAME"])) {
53 return $scriptname;
54 }
55
56 return 'http' . ($https ? 's' : '') . '://'
57 . $_SERVER["SERVER_NAME"] . $serverport . $scriptname;
58 }
59
60 public static function redirect($url = '')
61 {
62 if ($url === '') {
63 $url = (empty($_SERVER['HTTP_REFERER'])?'?':$_SERVER['HTTP_REFERER']);
64 if (isset($_POST['returnurl'])) {
65 $url = $_POST['returnurl'];
66 }
67 }
68
69 # prevent loop
70 if (empty($url) || parse_url($url, PHP_URL_QUERY) === $_SERVER['QUERY_STRING']) {
71 $url = Tools::getPocheUrl();
72 }
73
74 if (substr($url, 0, 1) !== '?') {
75 $ref = Tools::getPocheUrl();
76 if (substr($url, 0, strlen($ref)) !== $ref) {
77 $url = $ref;
78 }
79 }
80 self::logm('redirect to ' . $url);
81 header('Location: '.$url);
82 exit();
83 }
84
85 public static function getTplFile($view)
86 {
87 $tpl_file = 'home.twig';
88 switch ($view)
89 {
90 case 'install':
91 $tpl_file = 'install.twig';
92 break;
93 case 'import';
94 $tpl_file = 'import.twig';
95 break;
96 case 'export':
97 $tpl_file = 'export.twig';
98 break;
99 case 'config':
100 $tpl_file = 'config.twig';
101 break;
102 case 'view':
103 $tpl_file = 'view.twig';
104 break;
105 default:
106 break;
107 }
108 return $tpl_file;
109 }
110
111 public static function getFile($url)
112 {
113 $timeout = 15;
114 $useragent = "Mozilla/5.0 (Windows NT 5.1; rv:18.0) Gecko/20100101 Firefox/18.0";
115
116 if (in_array ('curl', get_loaded_extensions())) {
117 # Fetch feed from URL
118 $curl = curl_init();
119 curl_setopt($curl, CURLOPT_URL, $url);
120 curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
121 curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
122 curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
123 curl_setopt($curl, CURLOPT_HEADER, false);
124
125 # for ssl, do not verified certificate
126 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
127 curl_setopt($curl, CURLOPT_AUTOREFERER, TRUE );
128
129 # FeedBurner requires a proper USER-AGENT...
130 curl_setopt($curl, CURL_HTTP_VERSION_1_1, true);
131 curl_setopt($curl, CURLOPT_ENCODING, "gzip, deflate");
132 curl_setopt($curl, CURLOPT_USERAGENT, $useragent);
133
134 $data = curl_exec($curl);
135 $httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
136 $httpcodeOK = isset($httpcode) and ($httpcode == 200 or $httpcode == 301);
137 curl_close($curl);
138 } else {
139 # create http context and add timeout and user-agent
140 $context = stream_context_create(
141 array(
142 'http' => array(
143 'timeout' => $timeout,
144 'header' => "User-Agent: " . $useragent,
145 'follow_location' => true
146 ),
147 'ssl' => array(
148 'verify_peer' => false,
149 'allow_self_signed' => true
150 )
151 )
152 );
153
154 # only download page lesser than 4MB
155 $data = @file_get_contents($url, false, $context, -1, 4000000);
156
157 if (isset($http_response_header) and isset($http_response_header[0])) {
158 $httpcodeOK = isset($http_response_header) and isset($http_response_header[0]) and ((strpos($http_response_header[0], '200 OK') !== FALSE) or (strpos($http_response_header[0], '301 Moved Permanently') !== FALSE));
159 }
160 }
161
162 # if response is not empty and response is OK
163 if (isset($data) and isset($httpcodeOK) and $httpcodeOK) {
164
165 # take charset of page and get it
166 preg_match('#<meta .*charset=.*>#Usi', $data, $meta);
167
168 # if meta tag is found
169 if (!empty($meta[0])) {
170 preg_match('#charset="?(.*)"#si', $meta[0], $encoding);
171 # if charset is found set it otherwise, set it to utf-8
172 $html_charset = (!empty($encoding[1])) ? strtolower($encoding[1]) : 'utf-8';
173 } else {
174 $html_charset = 'utf-8';
175 $encoding[1] = '';
176 }
177
178 # replace charset of url to charset of page
179 $data = str_replace('charset=' . $encoding[1], 'charset=' . $html_charset, $data);
180
181 return $data;
182 }
183 else {
184 return FALSE;
185 }
186 }
187
188 public static function renderJson($data)
189 {
190 header('Cache-Control: no-cache, must-revalidate');
191 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
192 header('Content-type: application/json; charset=UTF-8');
193 echo json_encode($data);
194 exit();
195 }
196
197 public static function logm($message)
198 {
199 if (DEBUG_POCHE) {
200 $t = strval(date('Y/m/d_H:i:s')) . ' - ' . $_SERVER["REMOTE_ADDR"] . ' - ' . strval($message) . "\n";
201 file_put_contents(CACHE . '/log.txt', $t, FILE_APPEND);
202 error_log('DEBUG POCHE : ' . $message);
203 }
204 }
205
206 public static function encodeString($string)
207 {
208 return sha1($string . SALT);
209 }
210
211 public static function checkVar($var, $default = '')
212 {
213 return ((isset ($_REQUEST["$var"])) ? htmlentities($_REQUEST["$var"]) : $default);
214 }
215
216 public static function getDomain($url)
217 {
218 $pieces = parse_url($url);
219 $domain = isset($pieces['host']) ? $pieces['host'] : '';
220 if (preg_match('/(?P<domain>[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$/i', $domain, $regs)) {
221 return $regs['domain'];
222 }
223
224 return FALSE;
225 }
226} \ No newline at end of file
diff --git a/inc/poche/Url.class.php b/inc/poche/Url.class.php
new file mode 100644
index 00000000..f4a8f99e
--- /dev/null
+++ b/inc/poche/Url.class.php
@@ -0,0 +1,94 @@
1<?php
2/**
3 * poche, a read it later open source system
4 *
5 * @category poche
6 * @author Nicolas Lœuillet <support@inthepoche.com>
7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file
9 */
10
11class Url
12{
13 public $url;
14
15 function __construct($url)
16 {
17 $this->url = base64_decode($url);
18 }
19
20 public function getUrl() {
21 return $this->url;
22 }
23
24 public function setUrl($url) {
25 $this->url = $url;
26 }
27
28 public function isCorrect()
29 {
30 $pattern = '|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i';
31
32 return preg_match($pattern, $this->url);
33 }
34
35 public function clean()
36 {
37 $url = html_entity_decode(trim($this->url));
38
39 $stuff = strpos($url,'&utm_source=');
40 if ($stuff !== FALSE)
41 $url = substr($url, 0, $stuff);
42 $stuff = strpos($url,'?utm_source=');
43 if ($stuff !== FALSE)
44 $url = substr($url, 0, $stuff);
45 $stuff = strpos($url,'#xtor=RSS-');
46 if ($stuff !== FALSE)
47 $url = substr($url, 0, $stuff);
48
49 $this->url = $url;
50 }
51
52 public function fetchContent()
53 {
54 if ($this->isCorrect()) {
55 $this->clean();
56 $html = Encoding::toUTF8(Tools::getFile($this->getUrl()));
57
58 # if Tools::getFile() if not able to retrieve HTTPS content, try the same URL with HTTP protocol
59 if (!preg_match('!^https?://!i', $this->getUrl()) && (!isset($html) || strlen($html) <= 0)) {
60 $this->setUrl('http://' . $this->getUrl());
61 $html = Encoding::toUTF8(Tools::getFile($this->getUrl()));
62 }
63
64 if (function_exists('tidy_parse_string')) {
65 $tidy = tidy_parse_string($html, array(), 'UTF8');
66 $tidy->cleanRepair();
67 $html = $tidy->value;
68 }
69
70 $parameters = array();
71 if (isset($html) and strlen($html) > 0)
72 {
73 $readability = new Readability($html, $this->getUrl());
74 $readability->convertLinksToFootnotes = CONVERT_LINKS_FOOTNOTES;
75 $readability->revertForcedParagraphElements = REVERT_FORCED_PARAGRAPH_ELEMENTS;
76
77 if($readability->init())
78 {
79 $content = $readability->articleContent->innerHTML;
80 $parameters['title'] = $readability->articleTitle->innerHTML;
81 $parameters['content'] = $content;
82
83 return $parameters;
84 }
85 }
86 }
87 else {
88 #$msg->add('e', _('error during url preparation : the link is not valid'));
89 Tools::logm($this->getUrl() . ' is not a valid url');
90 }
91
92 return FALSE;
93 }
94} \ No newline at end of file
diff --git a/inc/poche/User.class.php b/inc/poche/User.class.php
new file mode 100644
index 00000000..6dac7839
--- /dev/null
+++ b/inc/poche/User.class.php
@@ -0,0 +1,50 @@
1<?php
2/**
3 * poche, a read it later open source system
4 *
5 * @category poche
6 * @author Nicolas Lœuillet <support@inthepoche.com>
7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file
9 */
10
11class User
12{
13 public $id;
14 public $username;
15 public $name;
16 public $password;
17 public $email;
18 public $config;
19
20 function __construct($user = array())
21 {
22 if ($user != array()) {
23 $this->id = $user['id'];
24 $this->username = $user['username'];
25 $this->name = $user['name'];
26 $this->password = $user['password'];
27 $this->email = $user['email'];
28 $this->config = $user['config'];
29 }
30 }
31
32 public function getId()
33 {
34 return $this->id;
35 }
36
37 public function getUsername()
38 {
39 return $this->username;
40 }
41
42 public function setConfig($config)
43 {
44 $this->config = $config;
45 }
46
47 public function getConfigValue($name) {
48 return (isset($this->config[$name])) ? $this->config[$name] : FALSE;
49 }
50} \ No newline at end of file
diff --git a/inc/poche/config.inc.php b/inc/poche/config.inc.php
new file mode 100644
index 00000000..930b31e5
--- /dev/null
+++ b/inc/poche/config.inc.php
@@ -0,0 +1,61 @@
1<?php
2/**
3 * poche, a read it later open source system
4 *
5 * @category poche
6 * @author Nicolas Lœuillet <nicolas@loeuillet.org>
7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file
9 */
10
11# storage
12define ('STORAGE','sqlite'); # postgres, mysql, sqlite
13define ('STORAGE_SERVER', 'localhost'); # leave blank for sqlite
14define ('STORAGE_DB', 'poche'); # only for postgres & mysql
15define ('STORAGE_SQLITE', './db/poche.sqlite');
16define ('STORAGE_USER', 'postgres'); # leave blank for sqlite
17define ('STORAGE_PASSWORD', 'postgres'); # leave blank for sqlite
18
19define ('POCHE_VERSION', '1.0-beta');
20define ('MODE_DEMO', FALSE);
21define ('DEBUG_POCHE', FALSE);
22define ('CONVERT_LINKS_FOOTNOTES', FALSE);
23define ('REVERT_FORCED_PARAGRAPH_ELEMENTS', FALSE);
24define ('DOWNLOAD_PICTURES', FALSE);
25define ('SHARE_TWITTER', TRUE);
26define ('SHARE_MAIL', TRUE);
27define ('SALT', '464v54gLLw928uz4zUBqkRJeiPY68zCX');
28define ('ABS_PATH', 'assets/');
29define ('TPL', './tpl');
30define ('LOCALE', './locale');
31define ('CACHE', './cache');
32define ('LANG', 'en_EN.UTF8');
33define ('PAGINATION', '10');
34define ('THEME', 'light');
35
36# /!\ Be careful if you change the lines below /!\
37require_once './inc/poche/User.class.php';
38require_once './inc/poche/Tools.class.php';
39require_once './inc/poche/Url.class.php';
40require_once './inc/3rdparty/class.messages.php';
41require_once './inc/poche/Poche.class.php';
42require_once './inc/3rdparty/Readability.php';
43require_once './inc/3rdparty/Encoding.php';
44require_once './inc/poche/Database.class.php';
45require_once './vendor/autoload.php';
46require_once './inc/3rdparty/simple_html_dom.php';
47require_once './inc/3rdparty/paginator.php';
48require_once './inc/3rdparty/Session.class.php';
49
50if (DOWNLOAD_PICTURES) {
51 require_once './inc/poche/pochePictures.php';
52}
53
54$poche = new Poche();
55#XSRF protection with token
56// if (!empty($_POST)) {
57// if (!Session::isToken($_POST['token'])) {
58// die(_('Wrong token'));
59// }
60// unset($_SESSION['tokens']);
61// } \ No newline at end of file
diff --git a/inc/poche/pochePictures.php b/inc/poche/pochePictures.php
new file mode 100644
index 00000000..4e4a0b08
--- /dev/null
+++ b/inc/poche/pochePictures.php
@@ -0,0 +1,110 @@
1<?php
2/**
3 * poche, a read it later open source system
4 *
5 * @category poche
6 * @author Nicolas Lœuillet <support@inthepoche.com>
7 * @copyright 2013
8 * @license http://www.wtfpl.net/ see COPYING file
9 */
10
11/**
12 * On modifie les URLS des images dans le corps de l'article
13 */
14function filtre_picture($content, $url, $id)
15{
16 $matches = array();
17 preg_match_all('#<\s*(img)[^>]+src="([^"]*)"[^>]*>#Si', $content, $matches, PREG_SET_ORDER);
18 foreach($matches as $i => $link) {
19 $link[1] = trim($link[1]);
20 if (!preg_match('#^(([a-z]+://)|(\#))#', $link[1])) {
21 $absolute_path = get_absolute_link($link[2],$url);
22 $filename = basename(parse_url($absolute_path, PHP_URL_PATH));
23 $directory = create_assets_directory($id);
24 $fullpath = $directory . '/' . $filename;
25 download_pictures($absolute_path, $fullpath);
26 $content = str_replace($matches[$i][2], $fullpath, $content);
27 }
28
29 }
30
31 return $content;
32}
33
34/**
35 * Retourne le lien absolu
36 */
37function get_absolute_link($relative_link, $url) {
38 /* return if already absolute URL */
39 if (parse_url($relative_link, PHP_URL_SCHEME) != '') return $relative_link;
40
41 /* queries and anchors */
42 if ($relative_link[0]=='#' || $relative_link[0]=='?') return $url . $relative_link;
43
44 /* parse base URL and convert to local variables:
45 $scheme, $host, $path */
46 extract(parse_url($url));
47
48 /* remove non-directory element from path */
49 $path = preg_replace('#/[^/]*$#', '', $path);
50
51 /* destroy path if relative url points to root */
52 if ($relative_link[0] == '/') $path = '';
53
54 /* dirty absolute URL */
55 $abs = $host . $path . '/' . $relative_link;
56
57 /* replace '//' or '/./' or '/foo/../' with '/' */
58 $re = array('#(/\.?/)#', '#/(?!\.\.)[^/]+/\.\./#');
59 for($n=1; $n>0; $abs=preg_replace($re, '/', $abs, -1, $n)) {}
60
61 /* absolute URL is ready! */
62 return $scheme.'://'.$abs;
63}
64
65/**
66 * Téléchargement des images
67 */
68function download_pictures($absolute_path, $fullpath)
69{
70 $rawdata = Tools::getFile($absolute_path);
71
72 if(file_exists($fullpath)) {
73 unlink($fullpath);
74 }
75 $fp = fopen($fullpath, 'x');
76 fwrite($fp, $rawdata);
77 fclose($fp);
78}
79
80/**
81 * Crée un répertoire de médias pour l'article
82 */
83function create_assets_directory($id)
84{
85 $assets_path = ABS_PATH;
86 if(!is_dir($assets_path)) {
87 mkdir($assets_path, 0705);
88 }
89
90 $article_directory = $assets_path . $id;
91 if(!is_dir($article_directory)) {
92 mkdir($article_directory, 0705);
93 }
94
95 return $article_directory;
96}
97
98/**
99 * Suppression du répertoire d'images
100 */
101function remove_directory($directory)
102{
103 if(is_dir($directory)) {
104 $files = array_diff(scandir($directory), array('.','..'));
105 foreach ($files as $file) {
106 (is_dir("$directory/$file")) ? remove_directory("$directory/$file") : unlink("$directory/$file");
107 }
108 return rmdir($directory);
109 }
110} \ No newline at end of file