]>
Commit | Line | Data |
---|---|---|
eb1af592 NL |
1 | <?php |
2 | /** | |
c95b78a8 | 3 | * wallabag, self hostable application allowing you to not miss any content anymore |
eb1af592 | 4 | * |
c95b78a8 NL |
5 | * @category wallabag |
6 | * @author Nicolas Lœuillet <nicolas@loeuillet.org> | |
eb1af592 | 7 | * @copyright 2013 |
3602405e | 8 | * @license http://opensource.org/licenses/MIT see COPYING file |
eb1af592 NL |
9 | */ |
10 | ||
11 | class Poche | |
12 | { | |
3602405e NL |
13 | /** |
14 | * @var User | |
15 | */ | |
7ce7ec4c | 16 | public $user; |
3602405e NL |
17 | /** |
18 | * @var Database | |
19 | */ | |
eb1af592 | 20 | public $store; |
3602405e NL |
21 | /** |
22 | * @var Template | |
23 | */ | |
eb1af592 | 24 | public $tpl; |
3602405e NL |
25 | /** |
26 | * @var Language | |
27 | */ | |
28 | public $language; | |
29 | /** | |
30 | * @var Routing | |
31 | */ | |
32 | public $routing; | |
33 | /** | |
34 | * @var Messages | |
35 | */ | |
55821e04 | 36 | public $messages; |
3602405e NL |
37 | /** |
38 | * @var Paginator | |
39 | */ | |
6a361945 | 40 | public $pagination; |
182faf26 | 41 | |
00dbaf90 | 42 | public function __construct() |
eb1af592 | 43 | { |
3602405e | 44 | $this->init(); |
eb1af592 | 45 | } |
182faf26 MR |
46 | |
47 | private function init() | |
00dbaf90 NL |
48 | { |
49 | Tools::initPhp(); | |
eb1af592 | 50 | |
3602405e NL |
51 | $pocheUser = Session::getParam('poche_user'); |
52 | ||
53 | if ($pocheUser && $pocheUser != array()) { | |
54 | $this->user = $pocheUser; | |
00dbaf90 | 55 | } else { |
3602405e | 56 | // fake user, just for install & login screens |
00dbaf90 NL |
57 | $this->user = new User(); |
58 | $this->user->setConfig($this->getDefaultConfig()); | |
59 | } | |
60 | ||
3602405e NL |
61 | $this->pagination = new Paginator($this->user->getConfigValue('pager'), 'p'); |
62 | $this->language = new Language($this); | |
63 | $this->tpl = new Template($this); | |
64 | $this->store = new Database(); | |
65 | $this->messages = new Messages(); | |
66 | $this->routing = new Routing($this); | |
00dbaf90 | 67 | } |
182faf26 | 68 | |
3602405e NL |
69 | public function run() |
70 | { | |
71 | $this->routing->run(); | |
00dbaf90 | 72 | } |
182faf26 | 73 | |
4a291288 | 74 | /** |
3602405e | 75 | * Creates a new user |
4a291288 | 76 | */ |
3602405e | 77 | public function createNewUser() |
eb1af592 | 78 | { |
4d99bae8 | 79 | if (isset($_GET['newuser'])){ |
80 | if ($_POST['newusername'] != "" && $_POST['password4newuser'] != ""){ | |
81 | $newusername = filter_var($_POST['newusername'], FILTER_SANITIZE_STRING); | |
b6413975 | 82 | if (!$this->store->userExists($newusername)){ |
4d99bae8 | 83 | if ($this->store->install($newusername, Tools::encodeString($_POST['password4newuser'] . $newusername))) { |
84 | Tools::logm('The new user '.$newusername.' has been installed'); | |
85 | $this->messages->add('s', sprintf(_('The new user %s has been installed. Do you want to <a href="?logout">logout ?</a>'),$newusername)); | |
86 | Tools::redirect(); | |
87 | } | |
88 | else { | |
89 | Tools::logm('error during adding new user'); | |
90 | Tools::redirect(); | |
91 | } | |
92 | } | |
93 | else { | |
94 | $this->messages->add('e', sprintf(_('Error : An user with the name %s already exists !'),$newusername)); | |
95 | Tools::logm('An user with the name '.$newusername.' already exists !'); | |
96 | Tools::redirect(); | |
97 | } | |
98 | } | |
99 | } | |
100 | } | |
b6413975 | 101 | |
3602405e NL |
102 | /** |
103 | * Delete an existing user | |
104 | */ | |
105 | public function deleteUser() | |
106 | { | |
4d99bae8 | 107 | if (isset($_GET['deluser'])){ |
108 | if ($this->store->listUsers() > 1) { | |
109 | if (Tools::encodeString($_POST['password4deletinguser'].$this->user->getUsername()) == $this->store->getUserPassword($this->user->getId())) { | |
110 | $username = $this->user->getUsername(); | |
111 | $this->store->deleteUserConfig($this->user->getId()); | |
112 | Tools::logm('The configuration for user '. $username .' has been deleted !'); | |
113 | $this->store->deleteTagsEntriesAndEntries($this->user->getId()); | |
114 | Tools::logm('The entries for user '. $username .' has been deleted !'); | |
115 | $this->store->deleteUser($this->user->getId()); | |
116 | Tools::logm('User '. $username .' has been completely deleted !'); | |
117 | Session::logout(); | |
118 | Tools::logm('logout'); | |
119 | Tools::redirect(); | |
120 | $this->messages->add('s', sprintf(_('User %s has been successfully deleted !'),$newusername)); | |
121 | } | |
122 | else { | |
123 | Tools::logm('Bad password !'); | |
124 | $this->messages->add('e', _('Error : The password is wrong !')); | |
125 | } | |
126 | } | |
127 | else { | |
128 | Tools::logm('Only user !'); | |
129 | $this->messages->add('e', _('Error : You are the only user, you cannot delete your account !')); | |
130 | } | |
131 | } | |
132 | } | |
eb1af592 | 133 | |
8d3275be | 134 | public function getDefaultConfig() |
182faf26 | 135 | { |
8d3275be NL |
136 | return array( |
137 | 'pager' => PAGINATION, | |
138 | 'language' => LANG, | |
00dbaf90 NL |
139 | 'theme' => DEFAULT_THEME |
140 | ); | |
8d3275be NL |
141 | } |
142 | ||
eb1af592 NL |
143 | /** |
144 | * Call action (mark as fav, archive, delete, etc.) | |
145 | */ | |
926acd7b | 146 | public function action($action, Url $url, $id = 0, $import = FALSE, $autoclose = FALSE, $tags = null) |
eb1af592 NL |
147 | { |
148 | switch ($action) | |
149 | { | |
150 | case 'add': | |
a297fb1e MR |
151 | $content = Tools::getPageContent($url); |
152 | $title = ($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled'); | |
153 | $body = $content['rss']['channel']['item']['description']; | |
154 | ||
155 | // clean content from prevent xss attack | |
0f859c6f | 156 | $purifier = $this->getPurifier(); |
a297fb1e MR |
157 | $title = $purifier->purify($title); |
158 | $body = $purifier->purify($body); | |
1570a653 | 159 | |
182faf26 | 160 | //search for possible duplicate |
8d7cd2cc | 161 | $duplicate = NULL; |
a297fb1e | 162 | $duplicate = $this->store->retrieveOneByURL($url->getUrl(), $this->user->getId()); |
488fc63b | 163 | |
182faf26 | 164 | $last_id = $this->store->add($url->getUrl(), $title, $body, $this->user->getId()); |
a297fb1e | 165 | if ( $last_id ) { |
ec397236 | 166 | Tools::logm('add link ' . $url->getUrl()); |
ec397236 | 167 | if (DOWNLOAD_PICTURES) { |
3602405e | 168 | $content = Picture::filterPicture($body, $url->getUrl(), $last_id); |
ec397236 NL |
169 | Tools::logm('updating content article'); |
170 | $this->store->updateContent($last_id, $content, $this->user->getId()); | |
171 | } | |
488fc63b MR |
172 | |
173 | if ($duplicate != NULL) { | |
174 | // duplicate exists, so, older entry needs to be deleted (as new entry should go to the top of list), BUT favorite mark and tags should be preserved | |
175 | Tools::logm('link ' . $url->getUrl() . ' is a duplicate'); | |
176 | // 1) - preserve tags and favorite, then drop old entry | |
177 | $this->store->reassignTags($duplicate['id'], $last_id); | |
178 | if ($duplicate['is_fav']) { | |
179 | $this->store->favoriteById($last_id, $this->user->getId()); | |
180 | } | |
181 | if ($this->store->deleteById($duplicate['id'], $this->user->getId())) { | |
182 | Tools::logm('previous link ' . $url->getUrl() .' entry deleted'); | |
183 | } | |
184 | } | |
185 | ||
182faf26 | 186 | $this->messages->add('s', _('the link has been added successfully')); |
eb1af592 NL |
187 | } |
188 | else { | |
a297fb1e MR |
189 | $this->messages->add('e', _('error during insertion : the link wasn\'t added')); |
190 | Tools::logm('error during insertion : the link wasn\'t added ' . $url->getUrl()); | |
b916bcfc | 191 | } |
ec397236 | 192 | |
a297fb1e MR |
193 | if ($autoclose == TRUE) { |
194 | Tools::redirect('?view=home'); | |
195 | } else { | |
196 | Tools::redirect('?view=home&closewin=true'); | |
eb1af592 NL |
197 | } |
198 | break; | |
199 | case 'delete': | |
bc1ee852 | 200 | $msg = 'delete link #' . $id; |
8d3275be | 201 | if ($this->store->deleteById($id, $this->user->getId())) { |
eb1af592 | 202 | if (DOWNLOAD_PICTURES) { |
3602405e | 203 | Picture::removeDirectory(ABS_PATH . $id); |
eb1af592 | 204 | } |
6a361945 | 205 | $this->messages->add('s', _('the link has been deleted successfully')); |
eb1af592 NL |
206 | } |
207 | else { | |
6a361945 | 208 | $this->messages->add('e', _('the link wasn\'t deleted')); |
bc1ee852 | 209 | $msg = 'error : can\'t delete link #' . $id; |
eb1af592 | 210 | } |
bc1ee852 | 211 | Tools::logm($msg); |
985ce3ec | 212 | Tools::redirect('?'); |
eb1af592 NL |
213 | break; |
214 | case 'toggle_fav' : | |
8d3275be | 215 | $this->store->favoriteById($id, $this->user->getId()); |
eb1af592 | 216 | Tools::logm('mark as favorite link #' . $id); |
c2cf7075 MR |
217 | if ( Tools::isAjaxRequest() ) { |
218 | echo 1; | |
219 | exit; | |
220 | } | |
221 | else { | |
222 | Tools::redirect(); | |
223 | } | |
eb1af592 NL |
224 | break; |
225 | case 'toggle_archive' : | |
8d3275be | 226 | $this->store->archiveById($id, $this->user->getId()); |
eb1af592 | 227 | Tools::logm('archive link #' . $id); |
c2cf7075 MR |
228 | if ( Tools::isAjaxRequest() ) { |
229 | echo 1; | |
230 | exit; | |
231 | } | |
232 | else { | |
233 | Tools::redirect(); | |
234 | } | |
eb1af592 | 235 | break; |
f14807de NL |
236 | case 'archive_all' : |
237 | $this->store->archiveAll($this->user->getId()); | |
238 | Tools::logm('archive all links'); | |
a297fb1e | 239 | Tools::redirect(); |
f14807de | 240 | break; |
c432fa16 | 241 | case 'add_tag' : |
decc23aa | 242 | if (isset($_GET['search'])) { |
243 | //when we want to apply a tag to a search | |
decc23aa | 244 | $tags = array($_GET['search']); |
245 | $allentry_ids = $this->store->search($tags[0], $this->user->getId()); | |
246 | $entry_ids = array(); | |
247 | foreach ($allentry_ids as $eachentry) { | |
248 | $entry_ids[] = $eachentry[0]; | |
249 | } | |
250 | } else { //add a tag to a single article | |
251 | $tags = explode(',', $_POST['value']); | |
252 | $entry_ids = array($_POST['entry_id']); | |
fb26cc93 | 253 | } |
decc23aa | 254 | foreach($entry_ids as $entry_id) { |
255 | $entry = $this->store->retrieveOneById($entry_id, $this->user->getId()); | |
256 | if (!$entry) { | |
257 | $this->messages->add('e', _('Article not found!')); | |
258 | Tools::logm('error : article not found'); | |
259 | Tools::redirect(); | |
260 | } | |
261 | //get all already set tags to preven duplicates | |
262 | $already_set_tags = array(); | |
263 | $entry_tags = $this->store->retrieveTagsByEntry($entry_id); | |
264 | foreach ($entry_tags as $tag) { | |
265 | $already_set_tags[] = $tag['value']; | |
266 | } | |
267 | foreach($tags as $key => $tag_value) { | |
268 | $value = trim($tag_value); | |
269 | if ($value && !in_array($value, $already_set_tags)) { | |
270 | $tag = $this->store->retrieveTagByValue($value); | |
271 | if (is_null($tag)) { | |
272 | # we create the tag | |
273 | $tag = $this->store->createTag($value); | |
274 | $sequence = ''; | |
275 | if (STORAGE == 'postgres') { | |
276 | $sequence = 'tags_id_seq'; | |
277 | } | |
278 | $tag_id = $this->store->getLastId($sequence); | |
fb26cc93 | 279 | } |
decc23aa | 280 | else { |
281 | $tag_id = $tag['id']; | |
282 | } | |
283 | ||
284 | # we assign the tag to the article | |
285 | $this->store->setTagToEntry($tag_id, $entry_id); | |
286 | } | |
c432fa16 | 287 | } |
c432fa16 | 288 | } |
9c743ab9 | 289 | $this->messages->add('s', _('The tag has been applied successfully')); |
24696800 | 290 | Tools::logm('The tag has been applied successfully'); |
a297fb1e | 291 | Tools::redirect(); |
c432fa16 NL |
292 | break; |
293 | case 'remove_tag' : | |
294 | $tag_id = $_GET['tag_id']; | |
b89d5a2b NL |
295 | $entry = $this->store->retrieveOneById($id, $this->user->getId()); |
296 | if (!$entry) { | |
297 | $this->messages->add('e', _('Article not found!')); | |
298 | Tools::logm('error : article not found'); | |
299 | Tools::redirect(); | |
300 | } | |
c432fa16 | 301 | $this->store->removeTagForEntry($id, $tag_id); |
9c743ab9 | 302 | Tools::logm('tag entry deleted'); |
24696800 | 303 | if ($this->store->cleanUnusedTag($tag_id)) { |
304 | Tools::logm('tag deleted'); | |
305 | } | |
9c743ab9 | 306 | $this->messages->add('s', _('The tag has been successfully deleted')); |
c432fa16 NL |
307 | Tools::redirect(); |
308 | break; | |
eb1af592 NL |
309 | default: |
310 | break; | |
311 | } | |
312 | } | |
313 | ||
314 | function displayView($view, $id = 0) | |
315 | { | |
316 | $tpl_vars = array(); | |
317 | ||
318 | switch ($view) | |
319 | { | |
eb1af592 | 320 | case 'config': |
11c680f9 NL |
321 | $dev_infos = $this->getPocheVersion('dev'); |
322 | $dev = trim($dev_infos[0]); | |
323 | $check_time_dev = date('d-M-Y H:i', $dev_infos[1]); | |
324 | $prod_infos = $this->getPocheVersion('prod'); | |
325 | $prod = trim($prod_infos[0]); | |
326 | $check_time_prod = date('d-M-Y H:i', $prod_infos[1]); | |
031df528 NL |
327 | $compare_dev = version_compare(POCHE, $dev); |
328 | $compare_prod = version_compare(POCHE, $prod); | |
3602405e NL |
329 | $themes = $this->tpl->getInstalledThemes(); |
330 | $languages = $this->language->getInstalledLanguages(); | |
72c20a52 | 331 | $token = $this->user->getConfigValue('token'); |
1810c13b | 332 | $http_auth = (isset($_SERVER['PHP_AUTH_USER']) || isset($_SERVER['REMOTE_USER'])) ? true : false; |
4d99bae8 | 333 | $only_user = ($this->store->listUsers() > 1) ? false : true; |
32520785 | 334 | $tpl_vars = array( |
00dbaf90 | 335 | 'themes' => $themes, |
5011388f | 336 | 'languages' => $languages, |
32520785 NL |
337 | 'dev' => $dev, |
338 | 'prod' => $prod, | |
11c680f9 NL |
339 | 'check_time_dev' => $check_time_dev, |
340 | 'check_time_prod' => $check_time_prod, | |
32520785 NL |
341 | 'compare_dev' => $compare_dev, |
342 | 'compare_prod' => $compare_prod, | |
72c20a52 NL |
343 | 'token' => $token, |
344 | 'user_id' => $this->user->getId(), | |
df6afaf0 | 345 | 'http_auth' => $http_auth, |
4d99bae8 | 346 | 'only_user' => $only_user |
32520785 | 347 | ); |
eb1af592 NL |
348 | Tools::logm('config view'); |
349 | break; | |
6cab59c3 NL |
350 | case 'edit-tags': |
351 | # tags | |
b89d5a2b NL |
352 | $entry = $this->store->retrieveOneById($id, $this->user->getId()); |
353 | if (!$entry) { | |
354 | $this->messages->add('e', _('Article not found!')); | |
355 | Tools::logm('error : article not found'); | |
356 | Tools::redirect(); | |
357 | } | |
6cab59c3 NL |
358 | $tags = $this->store->retrieveTagsByEntry($id); |
359 | $tpl_vars = array( | |
c432fa16 | 360 | 'entry_id' => $id, |
6cab59c3 | 361 | 'tags' => $tags, |
032e0ca1 | 362 | 'entry' => $entry, |
4886ed6d NL |
363 | ); |
364 | break; | |
2e2ebe5e | 365 | case 'tags': |
f778e472 | 366 | $token = $this->user->getConfigValue('token'); |
fb26cc93 MR |
367 | //if term is set - search tags for this term |
368 | $term = Tools::checkVar('term'); | |
369 | $tags = $this->store->retrieveAllTags($this->user->getId(), $term); | |
370 | if (Tools::isAjaxRequest()) { | |
371 | $result = array(); | |
372 | foreach ($tags as $tag) { | |
373 | $result[] = $tag['value']; | |
374 | } | |
375 | echo json_encode($result); | |
376 | exit; | |
377 | } | |
2e2ebe5e | 378 | $tpl_vars = array( |
f778e472 NL |
379 | 'token' => $token, |
380 | 'user_id' => $this->user->getId(), | |
2e2ebe5e NL |
381 | 'tags' => $tags, |
382 | ); | |
383 | break; | |
a4585f7e MR |
384 | case 'search': |
385 | if (isset($_GET['search'])) { | |
386 | $search = filter_var($_GET['search'], FILTER_SANITIZE_STRING); | |
387 | $tpl_vars['entries'] = $this->store->search($search, $this->user->getId()); | |
388 | $count = count($tpl_vars['entries']); | |
389 | $this->pagination->set_total($count); | |
390 | $page_links = str_replace(array('previous', 'next'), array(_('previous'), _('next')), | |
391 | $this->pagination->page_links('?view=' . $view . '?search=' . $search . '&sort=' . $_SESSION['sort'] . '&' )); | |
392 | $tpl_vars['page_links'] = $page_links; | |
393 | $tpl_vars['nb_results'] = $count; | |
394 | $tpl_vars['search_term'] = $search; | |
395 | } | |
396 | break; | |
eb1af592 | 397 | case 'view': |
8d3275be | 398 | $entry = $this->store->retrieveOneById($id, $this->user->getId()); |
eb1af592 NL |
399 | if ($entry != NULL) { |
400 | Tools::logm('view link #' . $id); | |
401 | $content = $entry['content']; | |
402 | if (function_exists('tidy_parse_string')) { | |
403 | $tidy = tidy_parse_string($content, array('indent'=>true, 'show-body-only' => true), 'UTF8'); | |
404 | $tidy->cleanRepair(); | |
405 | $content = $tidy->value; | |
3408ed48 | 406 | } |
a3223127 | 407 | |
3408ed48 NL |
408 | # flattr checking |
409 | $flattr = new FlattrItem(); | |
7b171c73 NL |
410 | $flattr->checkItem($entry['url'], $entry['id']); |
411 | ||
412 | # tags | |
413 | $tags = $this->store->retrieveTagsByEntry($entry['id']); | |
a3223127 | 414 | |
3408ed48 | 415 | $tpl_vars = array( |
7b171c73 NL |
416 | 'entry' => $entry, |
417 | 'content' => $content, | |
418 | 'flattr' => $flattr, | |
419 | 'tags' => $tags | |
3408ed48 | 420 | ); |
eb1af592 NL |
421 | } |
422 | else { | |
d8d1542e | 423 | Tools::logm('error in view call : entry is null'); |
eb1af592 NL |
424 | } |
425 | break; | |
032e0ca1 | 426 | default: # home, favorites, archive and tag views |
eb1af592 | 427 | $tpl_vars = array( |
3eb04903 N |
428 | 'entries' => '', |
429 | 'page_links' => '', | |
7f9f5281 | 430 | 'nb_results' => '', |
6065553c | 431 | 'listmode' => (isset($_COOKIE['listmode']) ? true : false), |
eb1af592 | 432 | ); |
182faf26 | 433 | |
3602405e | 434 | //if id is given - we retrieve entries by tag: id is tag id |
032e0ca1 MR |
435 | if ($id) { |
436 | $tpl_vars['tag'] = $this->store->retrieveTag($id, $this->user->getId()); | |
437 | $tpl_vars['id'] = intval($id); | |
438 | } | |
439 | ||
440 | $count = $this->store->getEntriesByViewCount($view, $this->user->getId(), $id); | |
441 | ||
442 | if ($count > 0) { | |
443 | $this->pagination->set_total($count); | |
c515ffec | 444 | $page_links = str_replace(array('previous', 'next'), array(_('previous'), _('next')), |
032e0ca1 MR |
445 | $this->pagination->page_links('?view=' . $view . '&sort=' . $_SESSION['sort'] . (($id)?'&id='.$id:'') . '&' )); |
446 | $tpl_vars['entries'] = $this->store->getEntriesByView($view, $this->user->getId(), $this->pagination->get_limit(), $id); | |
3eb04903 | 447 | $tpl_vars['page_links'] = $page_links; |
032e0ca1 | 448 | $tpl_vars['nb_results'] = $count; |
3eb04903 | 449 | } |
6a361945 | 450 | Tools::logm('display ' . $view . ' view'); |
eb1af592 NL |
451 | break; |
452 | } | |
453 | ||
454 | return $tpl_vars; | |
455 | } | |
c765c367 | 456 | |
07ee09f4 | 457 | /** |
182faf26 MR |
458 | * update the password of the current user. |
459 | * if MODE_DEMO is TRUE, the password can't be updated. | |
07ee09f4 NL |
460 | * @todo add the return value |
461 | * @todo set the new password in function header like this updatePassword($newPassword) | |
462 | * @return boolean | |
463 | */ | |
c765c367 NL |
464 | public function updatePassword() |
465 | { | |
55821e04 | 466 | if (MODE_DEMO) { |
8d3275be | 467 | $this->messages->add('i', _('in demo mode, you can\'t update your password')); |
55821e04 | 468 | Tools::logm('in demo mode, you can\'t do this'); |
6a361945 | 469 | Tools::redirect('?view=config'); |
55821e04 NL |
470 | } |
471 | else { | |
472 | if (isset($_POST['password']) && isset($_POST['password_repeat'])) { | |
473 | if ($_POST['password'] == $_POST['password_repeat'] && $_POST['password'] != "") { | |
8d3275be NL |
474 | $this->messages->add('s', _('your password has been updated')); |
475 | $this->store->updatePassword($this->user->getId(), Tools::encodeString($_POST['password'] . $this->user->getUsername())); | |
c765c367 | 476 | Session::logout(); |
8d3275be | 477 | Tools::logm('password updated'); |
c765c367 NL |
478 | Tools::redirect(); |
479 | } | |
480 | else { | |
8d3275be | 481 | $this->messages->add('e', _('the two fields have to be filled & the password must be the same in the two fields')); |
6a361945 | 482 | Tools::redirect('?view=config'); |
c765c367 NL |
483 | } |
484 | } | |
485 | } | |
486 | } | |
182faf26 | 487 | |
df6afaf0 DS |
488 | /** |
489 | * get credentials from differents sources | |
490 | * it redirects the user to the $referer link | |
491 | * @return array | |
492 | */ | |
1810c13b NL |
493 | private function credentials() { |
494 | if(isset($_SERVER['PHP_AUTH_USER'])) { | |
6af66b11 | 495 | return array($_SERVER['PHP_AUTH_USER'],'php_auth',true); |
1810c13b NL |
496 | } |
497 | if(!empty($_POST['login']) && !empty($_POST['password'])) { | |
6af66b11 | 498 | return array($_POST['login'],$_POST['password'],false); |
1810c13b NL |
499 | } |
500 | if(isset($_SERVER['REMOTE_USER'])) { | |
6af66b11 | 501 | return array($_SERVER['REMOTE_USER'],'http_auth',true); |
1810c13b | 502 | } |
5cfafc61 | 503 | |
6af66b11 MR |
504 | return array(false,false,false); |
505 | } | |
df6afaf0 | 506 | |
07ee09f4 NL |
507 | /** |
508 | * checks if login & password are correct and save the user in session. | |
509 | * it redirects the user to the $referer link | |
510 | * @param string $referer the url to redirect after login | |
511 | * @todo add the return value | |
512 | * @return boolean | |
513 | */ | |
c765c367 NL |
514 | public function login($referer) |
515 | { | |
6af66b11 | 516 | list($login,$password,$isauthenticated)=$this->credentials(); |
df6afaf0 DS |
517 | if($login === false || $password === false) { |
518 | $this->messages->add('e', _('login failed: you have to fill all fields')); | |
519 | Tools::logm('login failed'); | |
520 | Tools::redirect(); | |
521 | } | |
522 | if (!empty($login) && !empty($password)) { | |
6af66b11 | 523 | $user = $this->store->login($login, Tools::encodeString($password . $login), $isauthenticated); |
7ce7ec4c NL |
524 | if ($user != array()) { |
525 | # Save login into Session | |
6af66b11 MR |
526 | $longlastingsession = isset($_POST['longlastingsession']); |
527 | $passwordTest = ($isauthenticated) ? $user['password'] : Tools::encodeString($password . $login); | |
528 | Session::login($user['username'], $user['password'], $login, $passwordTest, $longlastingsession, array('poche_user' => new User($user))); | |
26929c08 | 529 | $this->messages->add('s', _('welcome to your wallabag')); |
8d3275be | 530 | Tools::logm('login successful'); |
c765c367 NL |
531 | Tools::redirect($referer); |
532 | } | |
8d3275be | 533 | $this->messages->add('e', _('login failed: bad login or password')); |
c765c367 NL |
534 | Tools::logm('login failed'); |
535 | Tools::redirect(); | |
c765c367 NL |
536 | } |
537 | } | |
538 | ||
07ee09f4 NL |
539 | /** |
540 | * log out the poche user. It cleans the session. | |
541 | * @todo add the return value | |
182faf26 | 542 | * @return boolean |
07ee09f4 | 543 | */ |
c765c367 NL |
544 | public function logout() |
545 | { | |
7ce7ec4c | 546 | $this->user = array(); |
c765c367 | 547 | Session::logout(); |
b916bcfc | 548 | Tools::logm('logout'); |
c765c367 NL |
549 | Tools::redirect(); |
550 | } | |
551 | ||
07ee09f4 NL |
552 | /** |
553 | * import datas into your poche | |
182faf26 | 554 | * @return boolean |
07ee09f4 | 555 | */ |
182faf26 MR |
556 | public function import() { |
557 | ||
558 | if ( isset($_FILES['file']) ) { | |
5ce39784 MR |
559 | Tools::logm('Import stated: parsing file'); |
560 | ||
182faf26 MR |
561 | // assume, that file is in json format |
562 | $str_data = file_get_contents($_FILES['file']['tmp_name']); | |
563 | $data = json_decode($str_data, true); | |
564 | ||
565 | if ( $data === null ) { | |
566 | //not json - assume html | |
567 | $html = new simple_html_dom(); | |
568 | $html->load_file($_FILES['file']['tmp_name']); | |
569 | $data = array(); | |
570 | $read = 0; | |
571 | foreach (array('ol','ul') as $list) { | |
572 | foreach ($html->find($list) as $ul) { | |
86da3988 MR |
573 | foreach ($ul->find('li') as $li) { |
574 | $tmpEntry = array(); | |
a8ef1f3f MR |
575 | $a = $li->find('a'); |
576 | $tmpEntry['url'] = $a[0]->href; | |
577 | $tmpEntry['tags'] = $a[0]->tags; | |
578 | $tmpEntry['is_read'] = $read; | |
579 | if ($tmpEntry['url']) { | |
580 | $data[] = $tmpEntry; | |
581 | } | |
86da3988 MR |
582 | } |
583 | # the second <ol/ul> is for read links | |
584 | $read = ((sizeof($data) && $read)?0:1); | |
182faf26 MR |
585 | } |
586 | } | |
63c35580 | 587 | } |
182faf26 | 588 | |
a297fb1e MR |
589 | //for readability structure |
590 | foreach ($data as $record) { | |
591 | if (is_array($record)) { | |
592 | $data[] = $record; | |
593 | foreach ($record as $record2) { | |
594 | if (is_array($record2)) { | |
86da3988 | 595 | $data[] = $record2; |
a297fb1e MR |
596 | } |
597 | } | |
598 | } | |
599 | } | |
600 | ||
86da3988 | 601 | $urlsInserted = array(); //urls of articles inserted |
182faf26 | 602 | foreach ($data as $record) { |
a297fb1e | 603 | $url = trim( isset($record['article__url']) ? $record['article__url'] : (isset($record['url']) ? $record['url'] : '') ); |
86da3988 | 604 | if ( $url and !in_array($url, $urlsInserted) ) { |
182faf26 MR |
605 | $title = (isset($record['title']) ? $record['title'] : _('Untitled - Import - ').'</a> <a href="./?import">'._('click to finish import').'</a><a>'); |
606 | $body = (isset($record['content']) ? $record['content'] : ''); | |
a297fb1e MR |
607 | $isRead = (isset($record['is_read']) ? intval($record['is_read']) : (isset($record['archive'])?intval($record['archive']):0)); |
608 | $isFavorite = (isset($record['is_fav']) ? intval($record['is_fav']) : (isset($record['favorite'])?intval($record['favorite']):0) ); | |
182faf26 MR |
609 | //insert new record |
610 | $id = $this->store->add($url, $title, $body, $this->user->getId(), $isFavorite, $isRead); | |
611 | if ( $id ) { | |
86da3988 MR |
612 | $urlsInserted[] = $url; //add |
613 | ||
182faf26 | 614 | if ( isset($record['tags']) && trim($record['tags']) ) { |
86da3988 | 615 | //@TODO: set tags |
182faf26 MR |
616 | |
617 | } | |
618 | } | |
619 | } | |
620 | } | |
621 | ||
86da3988 | 622 | $i = sizeof($urlsInserted); |
182faf26 MR |
623 | if ( $i > 0 ) { |
624 | $this->messages->add('s', _('Articles inserted: ').$i._('. Please note, that some may be marked as "read".')); | |
625 | } | |
5ce39784 | 626 | Tools::logm('Import of articles finished: '.$i.' articles added (w/o content if not provided).'); |
182faf26 MR |
627 | } |
628 | //file parsing finished here | |
629 | ||
630 | //now download article contents if any | |
631 | ||
632 | //check if we need to download any content | |
633 | $recordsDownloadRequired = $this->store->retrieveUnfetchedEntriesCount($this->user->getId()); | |
634 | if ( $recordsDownloadRequired == 0 ) { | |
635 | //nothing to download | |
636 | $this->messages->add('s', _('Import finished.')); | |
5ce39784 | 637 | Tools::logm('Import finished completely'); |
182faf26 MR |
638 | Tools::redirect(); |
639 | } | |
640 | else { | |
641 | //if just inserted - don't download anything, download will start in next reload | |
642 | if ( !isset($_FILES['file']) ) { | |
643 | //download next batch | |
5ce39784 | 644 | Tools::logm('Fetching next batch of articles...'); |
182faf26 MR |
645 | $items = $this->store->retrieveUnfetchedEntries($this->user->getId(), IMPORT_LIMIT); |
646 | ||
0f859c6f | 647 | $purifier = $this->getPurifier(); |
182faf26 MR |
648 | |
649 | foreach ($items as $item) { | |
86da3988 | 650 | $url = new Url(base64_encode($item['url'])); |
5ce39784 | 651 | Tools::logm('Fetching article '.$item['id']); |
86da3988 | 652 | $content = Tools::getPageContent($url); |
182faf26 | 653 | |
86da3988 MR |
654 | $title = (($content['rss']['channel']['item']['title'] != '') ? $content['rss']['channel']['item']['title'] : _('Untitled')); |
655 | $body = (($content['rss']['channel']['item']['description'] != '') ? $content['rss']['channel']['item']['description'] : _('Undefined')); | |
182faf26 | 656 | |
86da3988 MR |
657 | //clean content to prevent xss attack |
658 | $title = $purifier->purify($title); | |
659 | $body = $purifier->purify($body); | |
182faf26 | 660 | |
86da3988 | 661 | $this->store->updateContentAndTitle($item['id'], $title, $body, $this->user->getId()); |
5ce39784 | 662 | Tools::logm('Article '.$item['id'].' updated.'); |
182faf26 MR |
663 | } |
664 | ||
63c35580 | 665 | } |
182faf26 MR |
666 | } |
667 | ||
668 | return array('includeImport'=>true, 'import'=>array('recordsDownloadRequired'=>$recordsDownloadRequired, 'recordsUnderDownload'=> IMPORT_LIMIT, 'delay'=> IMPORT_DELAY * 1000) ); | |
63c35580 | 669 | } |
c765c367 | 670 | |
07ee09f4 NL |
671 | /** |
672 | * export poche entries in json | |
673 | * @return json all poche entries | |
674 | */ | |
a8ef1f3f MR |
675 | public function export() { |
676 | $filename = "wallabag-export-".$this->user->getId()."-".date("Y-m-d").".json"; | |
677 | header('Content-Disposition: attachment; filename='.$filename); | |
678 | ||
679 | $entries = $this->store->retrieveAll($this->user->getId()); | |
680 | echo $this->tpl->render('export.twig', array( | |
681 | 'export' => Tools::renderJson($entries), | |
682 | )); | |
683 | Tools::logm('export view'); | |
c765c367 | 684 | } |
32520785 | 685 | |
07ee09f4 | 686 | /** |
a3436d4c | 687 | * Checks online the latest version of poche and cache it |
07ee09f4 NL |
688 | * @param string $which 'prod' or 'dev' |
689 | * @return string latest $which version | |
690 | */ | |
a8ef1f3f MR |
691 | private function getPocheVersion($which = 'prod') { |
692 | $cache_file = CACHE . '/' . $which; | |
693 | $check_time = time(); | |
694 | ||
695 | # checks if the cached version file exists | |
696 | if (file_exists($cache_file) && (filemtime($cache_file) > (time() - 86400 ))) { | |
697 | $version = file_get_contents($cache_file); | |
698 | $check_time = filemtime($cache_file); | |
699 | } else { | |
700 | $version = file_get_contents('http://static.wallabag.org/versions/' . $which); | |
701 | file_put_contents($cache_file, $version, LOCK_EX); | |
702 | } | |
703 | return array($version, $check_time); | |
32520785 | 704 | } |
72c20a52 NL |
705 | |
706 | public function generateToken() | |
707 | { | |
a8ef1f3f MR |
708 | if (ini_get('open_basedir') === '') { |
709 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { | |
710 | echo 'This is a server using Windows!'; | |
711 | // alternative to /dev/urandom for Windows | |
712 | $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20); | |
713 | } else { | |
714 | $token = substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15); | |
72c20a52 | 715 | } |
a8ef1f3f MR |
716 | } |
717 | else { | |
718 | $token = substr(base64_encode(uniqid(mt_rand(), true)), 0, 20); | |
719 | } | |
72c20a52 | 720 | |
a8ef1f3f MR |
721 | $token = str_replace('+', '', $token); |
722 | $this->store->updateUserConfig($this->user->getId(), 'token', $token); | |
723 | $currentConfig = $_SESSION['poche_user']->config; | |
724 | $currentConfig['token'] = $token; | |
725 | $_SESSION['poche_user']->setConfig($currentConfig); | |
726 | Tools::redirect(); | |
72c20a52 NL |
727 | } |
728 | ||
f778e472 | 729 | public function generateFeeds($token, $user_id, $tag_id, $type = 'home') |
72c20a52 | 730 | { |
f778e472 | 731 | $allowed_types = array('home', 'fav', 'archive', 'tag'); |
72c20a52 NL |
732 | $config = $this->store->getConfigUser($user_id); |
733 | ||
17b2afef | 734 | if ($config == null) { |
30bd2735 | 735 | die(sprintf(_('User with this id (%d) does not exist.'), $user_id)); |
17b2afef NL |
736 | } |
737 | ||
cbc75bef | 738 | if (!in_array($type, $allowed_types) || $token != $config['token']) { |
72c20a52 NL |
739 | die(_('Uh, there is a problem while generating feeds.')); |
740 | } | |
741 | // Check the token | |
742 | ||
9e7c840b | 743 | $feed = new FeedWriter(RSS2); |
2e4440c3 | 744 | $feed->setTitle('wallabag — ' . $type . ' feed'); |
72c20a52 | 745 | $feed->setLink(Tools::getPocheUrl()); |
223268c2 NL |
746 | $feed->setChannelElement('pubDate', date(DATE_RSS , time())); |
747 | $feed->setChannelElement('generator', 'wallabag'); | |
748 | $feed->setDescription('wallabag ' . $type . ' elements'); | |
72c20a52 | 749 | |
f778e472 | 750 | if ($type == 'tag') { |
b89d5a2b | 751 | $entries = $this->store->retrieveEntriesByTag($tag_id, $user_id); |
f778e472 NL |
752 | } |
753 | else { | |
754 | $entries = $this->store->getEntriesByView($type, $user_id); | |
755 | } | |
756 | ||
72c20a52 NL |
757 | if (count($entries) > 0) { |
758 | foreach ($entries as $entry) { | |
759 | $newItem = $feed->createNewItem(); | |
0b57c682 | 760 | $newItem->setTitle($entry['title']); |
f86784c2 | 761 | $newItem->setSource(Tools::getPocheUrl() . '?view=view&id=' . $entry['id']); |
ed02e38e | 762 | $newItem->setLink($entry['url']); |
72c20a52 NL |
763 | $newItem->setDate(time()); |
764 | $newItem->setDescription($entry['content']); | |
765 | $feed->addItem($newItem); | |
766 | } | |
767 | } | |
768 | ||
769 | $feed->genarateFeed(); | |
770 | exit; | |
771 | } | |
6285e57c NL |
772 | |
773 | public function emptyCache() { | |
774 | $files = new RecursiveIteratorIterator( | |
775 | new RecursiveDirectoryIterator(CACHE, RecursiveDirectoryIterator::SKIP_DOTS), | |
776 | RecursiveIteratorIterator::CHILD_FIRST | |
777 | ); | |
778 | ||
779 | foreach ($files as $fileinfo) { | |
780 | $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink'); | |
781 | $todo($fileinfo->getRealPath()); | |
782 | } | |
783 | ||
784 | Tools::logm('empty cache'); | |
785 | $this->messages->add('s', _('Cache deleted.')); | |
786 | Tools::redirect(); | |
787 | } | |
0f859c6f MR |
788 | |
789 | /** | |
790 | * return new purifier object with actual config | |
791 | */ | |
792 | protected function getPurifier() { | |
ec15d0a7 | 793 | $config = HTMLPurifier_Config::createDefault(); |
794 | $config->set('Cache.SerializerPath', CACHE); | |
795 | $config->set('HTML.SafeIframe', true); | |
35d4e275 | 796 | |
0b9bb8cb MR |
797 | //allow YouTube, Vimeo and dailymotion videos |
798 | $config->set('URI.SafeIframeRegexp', '%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/|www\.dailymotion\.com/embed/video/)%'); | |
ec15d0a7 | 799 | |
0f859c6f MR |
800 | return new HTMLPurifier($config); |
801 | } | |
cbc75bef | 802 | |
87090d8a | 803 | /** |
804 | * handle epub | |
805 | */ | |
806 | public function createEpub() { | |
cbc75bef | 807 | |
7ec445b0 | 808 | switch ($_GET['method']) { |
809 | case 'id': | |
87090d8a | 810 | $entryID = filter_var($_GET['id'],FILTER_SANITIZE_NUMBER_INT); |
811 | $entry = $this->store->retrieveOneById($entryID, $this->user->getId()); | |
812 | $entries = array($entry); | |
f2b6b4e2 | 813 | $bookTitle = $entry['title']; |
f3f0b113 | 814 | $bookFileName = substr($bookTitle, 0, 200); |
7ec445b0 | 815 | break; |
816 | case 'all': | |
817 | $entries = $this->store->retrieveAll($this->user->getId()); | |
f3f0b113 | 818 | $bookTitle = sprintf(_('All my articles on '), date(_('d.m.y'))); #translatable because each country has it's own date format system |
819 | $bookFileName = _('Allarticles') . date(_('dmY')); | |
7ec445b0 | 820 | break; |
821 | case 'tag': | |
822 | $tag = filter_var($_GET['tag'],FILTER_SANITIZE_STRING); | |
823 | $tags_id = $this->store->retrieveAllTags($this->user->getId(),$tag); | |
824 | $tag_id = $tags_id[0]["id"]; // we take the first result, which is supposed to match perfectly. There must be a workaround. | |
825 | $entries = $this->store->retrieveEntriesByTag($tag_id,$this->user->getId()); | |
f3f0b113 | 826 | $bookTitle = sprintf(_('Articles tagged %s'),$tag); |
827 | $bookFileName = substr(sprintf(_('Tag %s'),$tag), 0, 200); | |
7ec445b0 | 828 | break; |
829 | case 'category': | |
830 | $category = filter_var($_GET['category'],FILTER_SANITIZE_STRING); | |
831 | $entries = $this->store->getEntriesByView($category,$this->user->getId()); | |
f3f0b113 | 832 | $bookTitle = sprintf(_('All articles in category %s'), $category); |
833 | $bookFileName = substr(sprintf(_('Category %s'),$category), 0, 200); | |
7ec445b0 | 834 | break; |
835 | case 'search': | |
836 | $search = filter_var($_GET['search'],FILTER_SANITIZE_STRING); | |
837 | $entries = $this->store->search($search,$this->user->getId()); | |
f3f0b113 | 838 | $bookTitle = sprintf(_('All articles for search %s'), $search); |
839 | $bookFileName = substr(sprintf(_('Search %s'), $search), 0, 200); | |
7ec445b0 | 840 | break; |
841 | case 'default': | |
842 | die(_('Uh, there is a problem while generating epub.')); | |
cbc75bef | 843 | |
87090d8a | 844 | } |
7ec445b0 | 845 | |
87090d8a | 846 | $content_start = |
f2b6b4e2 | 847 | "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" |
848 | . "<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n" | |
849 | . "<head>" | |
850 | . "<meta http-equiv=\"Default-Style\" content=\"text/html; charset=utf-8\" />\n" | |
851 | . "<title>wallabag articles book</title>\n" | |
87090d8a | 852 | . "</head>\n" |
853 | . "<body>\n"; | |
854 | ||
855 | $bookEnd = "</body>\n</html>\n"; | |
cbc75bef | 856 | |
7ec445b0 | 857 | $log = new Logger("wallabag", TRUE); |
87090d8a | 858 | $fileDir = CACHE; |
87090d8a | 859 | |
ec15d0a7 | 860 | $book = new EPub(EPub::BOOK_VERSION_EPUB3, DEBUG_POCHE); |
87090d8a | 861 | $log->logLine("new EPub()"); |
862 | $log->logLine("EPub class version: " . EPub::VERSION); | |
863 | $log->logLine("EPub Req. Zip version: " . EPub::REQ_ZIP_VERSION); | |
864 | $log->logLine("Zip version: " . Zip::VERSION); | |
865 | $log->logLine("getCurrentServerURL: " . $book->getCurrentServerURL()); | |
866 | $log->logLine("getCurrentPageURL..: " . $book->getCurrentPageURL()); | |
cbc75bef | 867 | |
34acb02c | 868 | $book->setTitle(_('wallabag\'s articles')); |
87090d8a | 869 | $book->setIdentifier("http://$_SERVER[HTTP_HOST]", EPub::IDENTIFIER_URI); // Could also be the ISBN number, prefered for published books, or a UUID. |
870 | //$book->setLanguage("en"); // Not needed, but included for the example, Language is mandatory, but EPub defaults to "en". Use RFC3066 Language codes, such as "en", "da", "fr" etc. | |
34acb02c | 871 | $book->setDescription(_("Some articles saved on my wallabag")); |
87090d8a | 872 | $book->setAuthor("wallabag","wallabag"); |
873 | $book->setPublisher("wallabag","wallabag"); // I hope this is a non existant address :) | |
874 | $book->setDate(time()); // Strictly not needed as the book date defaults to time(). | |
875 | //$book->setRights("Copyright and licence information specific for the book."); // As this is generated, this _could_ contain the name or licence information of the user who purchased the book, if needed. If this is used that way, the identifier must also be made unique for the book. | |
876 | $book->setSourceURL("http://$_SERVER[HTTP_HOST]"); | |
cbc75bef | 877 | |
87090d8a | 878 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "PHP"); |
4877836b | 879 | $book->addDublinCoreMetadata(DublinCore::CONTRIBUTOR, "wallabag"); |
cbc75bef | 880 | |
87090d8a | 881 | $cssData = "body {\n margin-left: .5em;\n margin-right: .5em;\n text-align: justify;\n}\n\np {\n font-family: serif;\n font-size: 10pt;\n text-align: justify;\n text-indent: 1em;\n margin-top: 0px;\n margin-bottom: 1ex;\n}\n\nh1, h2 {\n font-family: sans-serif;\n font-style: italic;\n text-align: center;\n background-color: #6b879c;\n color: white;\n width: 100%;\n}\n\nh1 {\n margin-bottom: 2px;\n}\n\nh2 {\n margin-top: -2px;\n margin-bottom: 2px;\n}\n"; |
cbc75bef | 882 | |
4877836b | 883 | $log->logLine("Add Cover"); |
cbc75bef | 884 | |
f2b6b4e2 | 885 | $fullTitle = "<h1> " . $bookTitle . "</h1>\n"; |
cbc75bef | 886 | |
f2b6b4e2 | 887 | $book->setCoverImage("Cover.png", file_get_contents("themes/baggy/img/apple-touch-icon-152.png"), "image/png", $fullTitle); |
cbc75bef | 888 | |
e212e6b1 | 889 | $cover = $content_start . '<div style="text-align:center;"><p>' . _('Produced by wallabag with PHPePub') . '</p><p>'. _('Please open <a href="https://github.com/wallabag/wallabag/issues" >an issue</a> if you have trouble with the display of this E-Book on your device.') . '</p></div>' . $bookEnd; |
cbc75bef | 890 | |
f2b6b4e2 | 891 | //$book->addChapter("Table of Contents", "TOC.xhtml", NULL, false, EPub::EXTERNAL_REF_IGNORE); |
892 | $book->addChapter("Notices", "Cover2.html", $cover); | |
cbc75bef | 893 | |
f2b6b4e2 | 894 | $book->buildTOC(); |
cbc75bef | 895 | |
e212e6b1 | 896 | foreach ($entries as $entry) { //set tags as subjects |
87090d8a | 897 | $tags = $this->store->retrieveTagsByEntry($entry['id']); |
898 | foreach ($tags as $tag) { | |
f2b6b4e2 | 899 | $book->setSubject($tag['value']); |
87090d8a | 900 | } |
cbc75bef | 901 | |
87090d8a | 902 | $log->logLine("Set up parameters"); |
cbc75bef | 903 | |
87090d8a | 904 | $chapter = $content_start . $entry['content'] . $bookEnd; |
7ec445b0 | 905 | $book->addChapter($entry['title'], htmlspecialchars($entry['title']) . ".html", $chapter, true, EPub::EXTERNAL_REF_ADD); |
4877836b | 906 | $log->logLine("Added chapter " . $entry['title']); |
f2b6b4e2 | 907 | } |
87090d8a | 908 | |
cbc75bef | 909 | if (DEBUG_POCHE) { |
e212e6b1 | 910 | $epuplog = $book->getLog(); |
911 | $book->addChapter("Log", "Log.html", $content_start . $log->getLog() . "\n</pre>" . $bookEnd); // log generation | |
87090d8a | 912 | } |
913 | $book->finalize(); | |
f3f0b113 | 914 | $zipData = $book->sendBook($bookFileName); |
87090d8a | 915 | } |
df6afaf0 | 916 | } |