aboutsummaryrefslogtreecommitdiffhomepage
path: root/inc/poche
diff options
context:
space:
mode:
Diffstat (limited to 'inc/poche')
-rw-r--r--inc/poche/Database.class.php216
-rw-r--r--inc/poche/Poche.class.php485
-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, 1242 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..56910bc0
--- /dev/null
+++ b/inc/poche/Poche.class.php
@@ -0,0 +1,485 @@
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 if (file_exists('./install') && !DEBUG_POCHE) {
35 Tools::logm('folder /install exists');
36 die('the folder /install exists, you have to delete it before using poche.');
37 }
38
39 Tools::initPhp();
40 Session::init();
41
42 if (isset($_SESSION['poche_user']) && $_SESSION['poche_user'] != array()) {
43 $this->user = $_SESSION['poche_user'];
44 }
45 else {
46 # fake user, just for install & login screens
47 $this->user = new User();
48 $this->user->setConfig($this->getDefaultConfig());
49 }
50
51 # l10n
52 $language = $this->user->getConfigValue('language');
53 putenv('LC_ALL=' . $language);
54 setlocale(LC_ALL, $language);
55 bindtextdomain($language, LOCALE);
56 textdomain($language);
57
58 # template engine
59 $loader = new Twig_Loader_Filesystem(TPL);
60 if (DEBUG_POCHE) {
61 $twig_params = array();
62 }
63 else {
64 $twig_params = array('cache' => CACHE);
65 }
66 $this->tpl = new Twig_Environment($loader, $twig_params);
67 $this->tpl->addExtension(new Twig_Extensions_Extension_I18n());
68 # filter to display domain name of an url
69 $filter = new Twig_SimpleFilter('getDomain', 'Tools::getDomain');
70 $this->tpl->addFilter($filter);
71
72 # Pagination
73 $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p');
74 }
75
76 private function install()
77 {
78 Tools::logm('poche still not installed');
79 echo $this->tpl->render('install.twig', array(
80 'token' => Session::getToken()
81 ));
82 if (isset($_GET['install'])) {
83 if (($_POST['password'] == $_POST['password_repeat'])
84 && $_POST['password'] != "" && $_POST['login'] != "") {
85 # let's rock, install poche baby !
86 $this->store->install($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login']));
87 Session::logout();
88 Tools::logm('poche is now installed');
89 Tools::redirect();
90 }
91 else {
92 Tools::logm('error during installation');
93 Tools::redirect();
94 }
95 }
96 exit();
97 }
98
99 public function getDefaultConfig()
100 {
101 return array(
102 'pager' => PAGINATION,
103 'language' => LANG,
104 );
105 }
106
107 /**
108 * Call action (mark as fav, archive, delete, etc.)
109 */
110 public function action($action, Url $url, $id = 0, $import = FALSE)
111 {
112 switch ($action)
113 {
114 case 'add':
115 if($parametres_url = $url->fetchContent()) {
116 if ($this->store->add($url->getUrl(), $parametres_url['title'], $parametres_url['content'], $this->user->getId())) {
117 Tools::logm('add link ' . $url->getUrl());
118 $sequence = '';
119 if (STORAGE == 'postgres') {
120 $sequence = 'entries_id_seq';
121 }
122 $last_id = $this->store->getLastId($sequence);
123 if (DOWNLOAD_PICTURES) {
124 $content = filtre_picture($parametres_url['content'], $url->getUrl(), $last_id);
125 }
126 if (!$import) {
127 $this->messages->add('s', _('the link has been added successfully'));
128 }
129 }
130 else {
131 if (!$import) {
132 $this->messages->add('e', _('error during insertion : the link wasn\'t added'));
133 Tools::logm('error during insertion : the link wasn\'t added ' . $url->getUrl());
134 }
135 }
136 }
137 else {
138 if (!$import) {
139 $this->messages->add('e', _('error during fetching content : the link wasn\'t added'));
140 Tools::logm('error during content fetch ' . $url->getUrl());
141 }
142 }
143 if (!$import) {
144 Tools::redirect();
145 }
146 break;
147 case 'delete':
148 $msg = 'delete link #' . $id;
149 if ($this->store->deleteById($id, $this->user->getId())) {
150 if (DOWNLOAD_PICTURES) {
151 remove_directory(ABS_PATH . $id);
152 }
153 $this->messages->add('s', _('the link has been deleted successfully'));
154 }
155 else {
156 $this->messages->add('e', _('the link wasn\'t deleted'));
157 $msg = 'error : can\'t delete link #' . $id;
158 }
159 Tools::logm($msg);
160 Tools::redirect('?');
161 break;
162 case 'toggle_fav' :
163 $this->store->favoriteById($id, $this->user->getId());
164 Tools::logm('mark as favorite link #' . $id);
165 if (!$import) {
166 Tools::redirect();
167 }
168 break;
169 case 'toggle_archive' :
170 $this->store->archiveById($id, $this->user->getId());
171 Tools::logm('archive link #' . $id);
172 if (!$import) {
173 Tools::redirect();
174 }
175 break;
176 default:
177 break;
178 }
179 }
180
181 function displayView($view, $id = 0)
182 {
183 $tpl_vars = array();
184
185 switch ($view)
186 {
187 case 'config':
188 $dev = $this->getPocheVersion('dev');
189 $prod = $this->getPocheVersion('prod');
190 $compare_dev = version_compare(POCHE_VERSION, $dev);
191 $compare_prod = version_compare(POCHE_VERSION, $prod);
192 $tpl_vars = array(
193 'dev' => $dev,
194 'prod' => $prod,
195 'compare_dev' => $compare_dev,
196 'compare_prod' => $compare_prod,
197 );
198 Tools::logm('config view');
199 break;
200 case 'view':
201 $entry = $this->store->retrieveOneById($id, $this->user->getId());
202 if ($entry != NULL) {
203 Tools::logm('view link #' . $id);
204 $content = $entry['content'];
205 if (function_exists('tidy_parse_string')) {
206 $tidy = tidy_parse_string($content, array('indent'=>true, 'show-body-only' => true), 'UTF8');
207 $tidy->cleanRepair();
208 $content = $tidy->value;
209 }
210 $tpl_vars = array(
211 'entry' => $entry,
212 'content' => $content,
213 );
214 }
215 else {
216 Tools::logm('error in view call : entry is NULL');
217 }
218 break;
219 default: # home view
220 $entries = $this->store->getEntriesByView($view, $this->user->getId());
221 $this->pagination->set_total(count($entries));
222 $page_links = $this->pagination->page_links('?view=' . $view . '&sort=' . $_SESSION['sort'] . '&');
223 $datas = $this->store->getEntriesByView($view, $this->user->getId(), $this->pagination->get_limit());
224 $tpl_vars = array(
225 'entries' => $datas,
226 'page_links' => $page_links,
227 );
228 Tools::logm('display ' . $view . ' view');
229 break;
230 }
231
232 return $tpl_vars;
233 }
234
235 /**
236 * update the password of the current user.
237 * if MODE_DEMO is TRUE, the password can't be updated.
238 * @todo add the return value
239 * @todo set the new password in function header like this updatePassword($newPassword)
240 * @return boolean
241 */
242 public function updatePassword()
243 {
244 if (MODE_DEMO) {
245 $this->messages->add('i', _('in demo mode, you can\'t update your password'));
246 Tools::logm('in demo mode, you can\'t do this');
247 Tools::redirect('?view=config');
248 }
249 else {
250 if (isset($_POST['password']) && isset($_POST['password_repeat'])) {
251 if ($_POST['password'] == $_POST['password_repeat'] && $_POST['password'] != "") {
252 $this->messages->add('s', _('your password has been updated'));
253 $this->store->updatePassword($this->user->getId(), Tools::encodeString($_POST['password'] . $this->user->getUsername()));
254 Session::logout();
255 Tools::logm('password updated');
256 Tools::redirect();
257 }
258 else {
259 $this->messages->add('e', _('the two fields have to be filled & the password must be the same in the two fields'));
260 Tools::redirect('?view=config');
261 }
262 }
263 }
264 }
265
266 /**
267 * checks if login & password are correct and save the user in session.
268 * it redirects the user to the $referer link
269 * @param string $referer the url to redirect after login
270 * @todo add the return value
271 * @return boolean
272 */
273 public function login($referer)
274 {
275 if (!empty($_POST['login']) && !empty($_POST['password'])) {
276 $user = $this->store->login($_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login']));
277 if ($user != array()) {
278 # Save login into Session
279 Session::login($user['username'], $user['password'], $_POST['login'], Tools::encodeString($_POST['password'] . $_POST['login']), array('poche_user' => new User($user)));
280
281 $this->messages->add('s', _('welcome to your poche'));
282 if (!empty($_POST['longlastingsession'])) {
283 $_SESSION['longlastingsession'] = 31536000;
284 $_SESSION['expires_on'] = time() + $_SESSION['longlastingsession'];
285 session_set_cookie_params($_SESSION['longlastingsession']);
286 } else {
287 session_set_cookie_params(0);
288 }
289 session_regenerate_id(true);
290 Tools::logm('login successful');
291 Tools::redirect($referer);
292 }
293 $this->messages->add('e', _('login failed: bad login or password'));
294 Tools::logm('login failed');
295 Tools::redirect();
296 } else {
297 $this->messages->add('e', _('login failed: you have to fill all fields'));
298 Tools::logm('login failed');
299 Tools::redirect();
300 }
301 }
302
303 /**
304 * log out the poche user. It cleans the session.
305 * @todo add the return value
306 * @return boolean
307 */
308 public function logout()
309 {
310 $this->user = array();
311 Session::logout();
312 $this->messages->add('s', _('see you soon!'));
313 Tools::logm('logout');
314 Tools::redirect();
315 }
316
317 /**
318 * import from Instapaper. poche needs a ./instapaper-export.html file
319 * @todo add the return value
320 * @return boolean
321 */
322 private function importFromInstapaper()
323 {
324 # TODO gestion des articles favs
325 $html = new simple_html_dom();
326 $html->load_file('./instapaper-export.html');
327 Tools::logm('starting import from instapaper');
328
329 $read = 0;
330 $errors = array();
331 foreach($html->find('ol') as $ul)
332 {
333 foreach($ul->find('li') as $li)
334 {
335 $a = $li->find('a');
336 $url = new Url(base64_encode($a[0]->href));
337 $this->action('add', $url, 0, TRUE);
338 if ($read == '1') {
339 $sequence = '';
340 if (STORAGE == 'postgres') {
341 $sequence = 'entries_id_seq';
342 }
343 $last_id = $this->store->getLastId($sequence);
344 $this->action('toggle_archive', $url, $last_id, TRUE);
345 }
346 }
347
348 # the second <ol> is for read links
349 $read = 1;
350 }
351 $this->messages->add('s', _('import from instapaper completed'));
352 Tools::logm('import from instapaper completed');
353 Tools::redirect();
354 }
355
356 /**
357 * import from Pocket. poche needs a ./ril_export.html file
358 * @todo add the return value
359 * @return boolean
360 */
361 private function importFromPocket()
362 {
363 # TODO gestion des articles favs
364 $html = new simple_html_dom();
365 $html->load_file('./ril_export.html');
366 Tools::logm('starting import from pocket');
367
368 $read = 0;
369 $errors = array();
370 foreach($html->find('ul') as $ul)
371 {
372 foreach($ul->find('li') as $li)
373 {
374 $a = $li->find('a');
375 $url = new Url(base64_encode($a[0]->href));
376 $this->action('add', $url, 0, TRUE);
377 if ($read == '1') {
378 $sequence = '';
379 if (STORAGE == 'postgres') {
380 $sequence = 'entries_id_seq';
381 }
382 $last_id = $this->store->getLastId($sequence);
383 $this->action('toggle_archive', $url, $last_id, TRUE);
384 }
385 }
386
387 # the second <ul> is for read links
388 $read = 1;
389 }
390 $this->messages->add('s', _('import from pocket completed'));
391 Tools::logm('import from pocket completed');
392 Tools::redirect();
393 }
394
395 /**
396 * import from Readability. poche needs a ./readability file
397 * @todo add the return value
398 * @return boolean
399 */
400 private function importFromReadability()
401 {
402 # TODO gestion des articles lus / favs
403 $str_data = file_get_contents("./readability");
404 $data = json_decode($str_data,true);
405 Tools::logm('starting import from Readability');
406
407 foreach ($data as $key => $value) {
408 $url = '';
409 foreach ($value as $attr => $attr_value) {
410 if ($attr == 'article__url') {
411 $url = new Url(base64_encode($attr_value));
412 }
413 $sequence = '';
414 if (STORAGE == 'postgres') {
415 $sequence = 'entries_id_seq';
416 }
417 // if ($attr_value == 'favorite' && $attr_value == 'true') {
418 // $last_id = $this->store->getLastId($sequence);
419 // $this->store->favoriteById($last_id);
420 // $this->action('toogle_fav', $url, $last_id, TRUE);
421 // }
422 if ($attr_value == 'archive' && $attr_value == 'true') {
423 $last_id = $this->store->getLastId($sequence);
424 $this->action('toggle_archive', $url, $last_id, TRUE);
425 }
426 }
427 if ($url->isCorrect())
428 $this->action('add', $url, 0, TRUE);
429 }
430 $this->messages->add('s', _('import from Readability completed'));
431 Tools::logm('import from Readability completed');
432 Tools::redirect();
433 }
434
435 /**
436 * import datas into your poche
437 * @param string $from name of the service to import : pocket, instapaper or readability
438 * @todo add the return value
439 * @return boolean
440 */
441 public function import($from)
442 {
443 if ($from == 'pocket') {
444 return $this->importFromPocket();
445 }
446 else if ($from == 'readability') {
447 return $this->importFromReadability();
448 }
449 else if ($from == 'instapaper') {
450 return $this->importFromInstapaper();
451 }
452 }
453
454 /**
455 * export poche entries in json
456 * @return json all poche entries
457 */
458 public function export()
459 {
460 $entries = $this->store->retrieveAll($this->user->getId());
461 echo $this->tpl->render('export.twig', array(
462 'export' => Tools::renderJson($entries),
463 ));
464 Tools::logm('export view');
465 }
466
467 /**
468 * Checks online the latest version of poche and cache it
469 * @param string $which 'prod' or 'dev'
470 * @return string latest $which version
471 */
472 private function getPocheVersion($which = 'prod')
473 {
474 $cache_file = CACHE . '/' . $which;
475
476 # checks if the cached version file exists
477 if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) {
478 $version = file_get_contents($cache_file);
479 } else {
480 $version = file_get_contents('http://static.inthepoche.com/versions/' . $which);
481 file_put_contents($cache_file, $version, LOCK_EX);
482 }
483 return $version;
484 }
485} \ 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..0958600f
--- /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-beta1');
20define ('MODE_DEMO', FALSE);
21define ('DEBUG_POCHE', TRUE);
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