PageRenderTime 66ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/application/models/chapter.php

https://github.com/fcibecchini/FoOlSlide
PHP | 1309 lines | 802 code | 167 blank | 340 comment | 127 complexity | 9a727515cb3280e11137f9aa447de406 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, GPL-2.0
  1. <?php
  2. if (!defined('BASEPATH'))
  3. exit('No direct script access allowed');
  4. class Chapter extends DataMapper
  5. {
  6. var $has_one = array('comic', 'team', 'joint');
  7. var $has_many = array('page');
  8. var $validation = array(
  9. 'comic_id' => array(
  10. 'rules' => array('is_int', 'required', 'max_length' => 256),
  11. 'label' => 'Comic ID',
  12. 'type' => 'hidden'
  13. ),
  14. 'name' => array(
  15. 'rules' => array('max_length' => 256),
  16. 'label' => 'Name',
  17. 'type' => 'input',
  18. ),
  19. 'team_id' => array(
  20. 'rules' => array('is_int', 'max_length' => 256),
  21. 'label' => 'Team ID'
  22. ),
  23. 'joint_id' => array(
  24. 'rules' => array('is_int', 'max_length' => 256),
  25. 'label' => 'Joint ID'
  26. ),
  27. 'stub' => array(
  28. 'rules' => array('stub', 'required', 'max_length' => 256),
  29. 'label' => 'Stub'
  30. ),
  31. 'volume' => array(
  32. 'rules' => array('is_int'),
  33. 'label' => 'Volume number',
  34. 'type' => 'input'
  35. ),
  36. 'chapter' => array(
  37. 'rules' => array('is_int', 'required'),
  38. 'label' => 'Chapter number',
  39. 'type' => 'input',
  40. 'placeholder' => 'required'
  41. ),
  42. 'subchapter' => array(
  43. 'rules' => array('is_int'),
  44. 'label' => 'Subchapter number',
  45. 'type' => 'input'
  46. ),
  47. 'language' => array(
  48. 'rules' => array('required'),
  49. 'label' => 'Language',
  50. 'type' => 'language'
  51. ),
  52. 'uniqid' => array(
  53. 'rules' => array('required', 'max_length' => 256),
  54. 'label' => 'Uniqid'
  55. ),
  56. 'hidden' => array(
  57. 'rules' => array('is_int'),
  58. 'label' => 'Hidden',
  59. 'type' => 'checkbox',
  60. 'values' => array('0' => 'Visible', '1' => 'Hidden')
  61. ),
  62. 'description' => array(
  63. 'rules' => array(),
  64. 'label' => 'Description'
  65. ),
  66. 'thumbnail' => array(
  67. 'rules' => array('max_length' => 512),
  68. 'label' => 'Thumbnail'
  69. ),
  70. 'pagesnum' => array(
  71. 'rules' => array(),
  72. 'label' => 'Number of pages'
  73. ),
  74. 'size' => array(
  75. 'rules' => array(),
  76. 'label' => 'Total size of pages',
  77. ),
  78. 'dirsize' => array(
  79. 'rules' => array(),
  80. 'label' => 'Directory size'
  81. ),
  82. 'lastseen' => array(
  83. 'rules' => array(),
  84. 'label' => 'Lastseen'
  85. ),
  86. 'creator' => array(
  87. 'rules' => array('required'),
  88. 'label' => 'Creator'
  89. ),
  90. 'editor' => array(
  91. 'rules' => array('required'),
  92. 'label' => 'Editor'
  93. )
  94. );
  95. function __construct($id = NULL)
  96. {
  97. // Set the translations
  98. $this->help_lang();
  99. parent::__construct(NULL);
  100. // We've overwrote some functions, and we need to use the get() from THIS model
  101. if (!empty($id) && is_numeric($id))
  102. {
  103. $this->where('id', $id)->get();
  104. }
  105. }
  106. function post_model_init($from_cache = FALSE)
  107. {
  108. }
  109. /**
  110. * This function sets the translations for the validation values.
  111. *
  112. * @author Woxxy
  113. * @return void
  114. */
  115. function help_lang()
  116. {
  117. $this->validation['name']['label'] = _('Name');
  118. $this->validation['name']['help'] = _('Insert the title of the chapter, if available.');
  119. $this->validation['chapter']['label'] = _('Chapter Number');
  120. $this->validation['chapter']['help'] = _('Insert the chapter number.');
  121. $this->validation['subchapter']['label'] = _('Subchapter Number');
  122. $this->validation['subchapter']['help'] = _('Insert a subchapter number to identify extra chapters. Zero for main chapter. Start counting subchapters from 1, not from 5.');
  123. $this->validation['volume']['label'] = _('Volume');
  124. $this->validation['volume']['help'] = _('Insert the volume number.');
  125. $this->validation['language']['label'] = _('Language');
  126. $this->validation['language']['help'] = _('Select the language of the chapter.');
  127. $this->validation['description']['label'] = _('Description');
  128. $this->validation['description']['help'] = _('Insert a description.');
  129. $this->validation['hidden']['label'] = _('Visibility');
  130. $this->validation['hidden']['help'] = _('Hide the chapter from public view.');
  131. $this->validation['hidden']['text'] = _('Hidden');
  132. }
  133. /**
  134. * This function can determine if it's a team member accessing to protected
  135. * chapter functions.
  136. *
  137. * @author Woxxy
  138. * @return DataMapper Returns true if the chapter is of the team of the user;
  139. */
  140. public function is_team()
  141. {
  142. if (!$this->teams)
  143. {
  144. $team = new Team();
  145. $this->teams = $team->get_teams($this->team_id, $this->joint_id);
  146. }
  147. }
  148. /**
  149. * Overwrite of the get() function to add filters to the search.
  150. * Refer to DataMapper ORM for get() function details.
  151. *
  152. * @author Woxxy
  153. * @param integer|NULL $limit Limit the number of results.
  154. * @param integer|NULL $offset Offset the results when limiting.
  155. * @return DataMapper Returns self for method chaining.
  156. */
  157. public function get($limit = NULL, $offset = NULL)
  158. {
  159. // Get the CodeIgniter instance, since it isn't set in this file.
  160. $CI = & get_instance();
  161. // Check if the user is allowed to see protected chapters.
  162. if (!$CI->tank_auth->is_allowed())
  163. $this->where('hidden', 0);
  164. $result = parent::get($limit, $offset);
  165. foreach ($this->all as $key => $item)
  166. {
  167. if (!$item->get_comic())
  168. {
  169. unset($this->all[$key]);
  170. }
  171. }
  172. return $result;
  173. }
  174. /**
  175. * Overwrite of the get_iterated() function to add filters to the search.
  176. * Refer to DataMapper ORM for get_iterated() function details.
  177. *
  178. * @author Woxxy
  179. * @param integer|NULL $limit Limit the number of results.
  180. * @param integer|NULL $offset Offset the results when limiting.
  181. * @return DataMapper Returns self for method chaining.
  182. */
  183. public function get_iterated($limit = NULL, $offset = NULL)
  184. {
  185. // Get the CodeIgniter instance, since it isn't set in this file.
  186. $CI = & get_instance();
  187. // Check if the user is allowed to see protected chapters.
  188. if (!$CI->tank_auth->is_allowed())
  189. $this->where('hidden', 0);
  190. /**
  191. * @todo figure out why those variables don't get unset... it would be
  192. * way better to use the iterated in almost all cases in FoOlSlide
  193. */
  194. return parent::get_iterated($limit, $offset);
  195. }
  196. /**
  197. * Comodity get() function that fetches extra data for the chapter selected.
  198. * It doesn't get the pages. For pages, see: $this->get_pages()
  199. *
  200. * @author Woxxy
  201. * @param integer|NULL $limit Limit the number of results.
  202. * @param integer|NULL $offset Offset the results when limiting.
  203. * @return DataMapper Returns self for method chaining.
  204. */
  205. public function get_bulk($limit = NULL, $offset = NULL)
  206. {
  207. // Call the get()
  208. $result = $this->get($limit, $offset);
  209. // Return instantly on false.
  210. if (!$result)
  211. return $result;
  212. // For each item we fetched, add the data, beside the pages
  213. foreach ($this->all as $item)
  214. {
  215. $item->comic = new Comic($this->comic_id);
  216. $teams = new Team();
  217. $item->teams = $teams->get_teams($item->team_id, $item->joint_id);
  218. }
  219. return $result;
  220. }
  221. /**
  222. * Sets the $this->comic variable if it hasn't been done before
  223. *
  224. * @author Woxxy
  225. * @return boolean True on success, false on failure.
  226. */
  227. public function get_comic()
  228. {
  229. if (isset($this->comic))
  230. return TRUE;
  231. $this->comic = new Comic($this->comic_id);
  232. if ($this->comic->result_count() == 0)
  233. {
  234. unset($this->comic);
  235. return FALSE;
  236. }
  237. if (isset($this->all))
  238. foreach ($this->all as $key => $item)
  239. {
  240. if (!isset($item->comic))
  241. {
  242. $item->comic = new Comic($item->comic_id);
  243. if ($item->comic->result_count() != 1)
  244. {
  245. unset($this->all[$key]->comic);
  246. return FALSE;
  247. }
  248. }
  249. }
  250. // All good, return true.
  251. return TRUE;
  252. }
  253. /**
  254. * Sets the $this->teams variable if it hasn't been done before
  255. *
  256. * @author Woxxy
  257. * @return boolean True on success, false on failure.
  258. */
  259. public function get_teams()
  260. {
  261. if (isset($this->teams))
  262. return true;
  263. $teams = new Team();
  264. $this->teams = $teams->get_teams($this->team_id, $this->joint_id);
  265. foreach ($this->all as $item)
  266. {
  267. if (isset($item->teams))
  268. continue;
  269. $teams = new Team();
  270. $item->teams = $teams->get_teams($item->team_id, $item->joint_id);
  271. }
  272. // All good, return true.
  273. return true;
  274. }
  275. /**
  276. * Function to create a new entry for a chapter from scratch. It creates
  277. * both a directory and a database entry, and removes them if something
  278. * goes wrong.
  279. *
  280. * @author Woxxy
  281. * @param array $data with the minimal values, or the function will return
  282. * false and do nothing.
  283. * @return boolean true on success, false on failure.
  284. */
  285. public function add($data)
  286. {
  287. // Let's make so subchapters aren't empty, so it's at least 0 for all
  288. // the addition of the chapter.
  289. if (!isset($data["subchapter"]) || !is_natural($data["subchapter"]))
  290. $data["subchapter"] = 0;
  291. // Create a stub that is humanly readable, for the worst cases.
  292. $this->to_stub = $data['chapter'] . "_" . $data['subchapter'] . "_" . $data['name'];
  293. // uniqid prevents us from having sad moments due to identical directory names
  294. $this->uniqid = uniqid();
  295. // Stub function converts the $this->to_stub if available, and processes it.
  296. $this->stub = $this->stub();
  297. // Check if comic_id is set and confirm there's a corresponding serie
  298. // If not, make an error message and stop adding the chapter
  299. $comic = new Comic($data['comic_id']);
  300. if ($comic->result_count() == 0)
  301. {
  302. set_notice('error', _('The series you were adding the chapter to doesn\'t exist.'));
  303. log_message('error', 'add: comic_id does not exist in comic database');
  304. return false;
  305. }
  306. // The series exists? Awesome, set it as soon as possible.
  307. $this->comic_id = $data['comic_id'];
  308. // Create the directory. The GUI error messages are inside the function.
  309. if (!$this->add_chapter_dir())
  310. {
  311. log_message('error', 'add: failed creating dir');
  312. return false;
  313. }
  314. // Hoping we got enough $data, let's throw it to the database function.
  315. // In case it fails, it will remove the directory.
  316. if (!$this->update_chapter_db($data))
  317. {
  318. $this->remove_chapter_dir();
  319. log_message('error', 'add: failed adding to database');
  320. return false;
  321. }
  322. // Oh, since we already have the series, let's put it into its variable.
  323. // This is very comfy for redirection!
  324. $this->comic = $comic;
  325. // All good? Return true!
  326. return true;
  327. }
  328. /**
  329. * Removes chapter from database, all its pages, and its directory.
  330. * There's no going back from this!
  331. *
  332. * @author Woxxy
  333. * @return object the comic the chapter derives from.
  334. */
  335. public function remove()
  336. {
  337. // Get series and check if existant. We don't want to have empty stub on this!
  338. $comic = new Comic($this->comic_id);
  339. if ($this->result_count() == 0)
  340. {
  341. set_notice('error', _('You\'re trying to delete something that doesn\'t even have a related series\'?'));
  342. log_message('error', 'remove_chapter: failed to find requested id');
  343. return false;
  344. }
  345. // Remove all the chapter files. GUI errors inside the function.
  346. if (!$this->remove_chapter_dir())
  347. {
  348. log_message('error', 'remove_chapter: failed to delete dir');
  349. return false;
  350. }
  351. // Remove the chapter from DB, and all its pages too.
  352. if (!$this->remove_chapter_db())
  353. {
  354. log_message('error', 'remove_chapter: failed to delete database entry');
  355. return false;
  356. }
  357. // Return the $comic for redirects.
  358. return $comic;
  359. }
  360. /**
  361. * Handles both creating of new chapters in the database and editing old ones.
  362. * It determines if it should update or not by checking if $this->id has
  363. * been set. It can get the values from both the $data array and direct
  364. * variable assignation. Be aware that array > variables. The latter ones
  365. * will be overwritten. Particularly, the variables that the user isn't
  366. * allowed to set personally are unset and reset with the automated values.
  367. * It's quite safe to throw stuff at it.
  368. *
  369. * @author Woxxy
  370. * @param array $data contains the minimal data
  371. * @return object the series the chapter derives from.
  372. */
  373. public function update_chapter_db($data = array())
  374. {
  375. // Check if we're updating or creating a new chapter by looking at $data["id"].
  376. // False is returned if the chapter ID was not found.
  377. if (isset($data["id"]) && $data['id'] != "")
  378. {
  379. $this->where("id", $data["id"])->get();
  380. if ($this->result_count() == 0)
  381. {
  382. set_notice('error', _('The chapter you tried to edit doesn\'t exist.'));
  383. log_message('error', 'update_chapter_db: failed to find requested id');
  384. return false;
  385. }
  386. // Save the stub in case it gets changed (different chapter number/name etc.)
  387. // Stub is always automatized.
  388. $old_stub = $this->stub;
  389. }
  390. else
  391. { // if we're here, it means that we're creating a new chapter
  392. // Set the creator name if it's a new chapter.
  393. if (!isset($this->comic_id))
  394. {
  395. set_notice('error', 'You didn\'t select a series to refer to.');
  396. log_message('error', 'update_chapter_db: comic_id was not set');
  397. return false;
  398. }
  399. // Check that the related series is defined, and exists.
  400. $comic = new Comic($this->comic_id);
  401. if ($comic->result_count() == 0)
  402. {
  403. set_notice('error', _('The series you were referring to doesn\'t exist.'));
  404. log_message('error', 'update_chapter_db: comic_id does not exist in comic database');
  405. return false;
  406. }
  407. // Set the creator. This happens only on new chapter creation.
  408. $this->creator = $this->logged_id();
  409. }
  410. // Always set the editor
  411. $this->editor = $this->logged_id();
  412. // Unset the sensible variables.
  413. // Not even admins should touch these, for database stability.
  414. unset($data["creator"]);
  415. unset($data["editor"]);
  416. unset($data["uniqid"]);
  417. unset($data["stub"]);
  418. unset($data["team_id"]);
  419. unset($data["joint_id"]);
  420. // Loop over the array and assign values to the variables.
  421. foreach ($data as $key => $value)
  422. {
  423. $this->$key = $value;
  424. }
  425. // Double check that we have all the necessary automated variables
  426. if (!isset($this->uniqid))
  427. $this->uniqid = uniqid();
  428. if (!isset($this->stub))
  429. $this->stub = $this->stub();
  430. // This is necessary to make the checkbox work.
  431. if (!isset($data['hidden']) || $data['hidden'] != 1)
  432. $this->hidden = 0;
  433. // Prepare a new stub.
  434. $this->stub = $this->chapter . '_' . $this->subchapter . '_' . $this->name;
  435. // stub() is also able to restub the $this->stub. Already stubbed values won't change.
  436. $this->stub = $this->stub();
  437. // If the new stub is different from the old one (if the chapter was
  438. // already existing), rename the folder.
  439. if (isset($old_stub) && $old_stub != $this->stub)
  440. {
  441. $this->get_comic();
  442. $dir_old = "content/comics/" . $this->comic->directory() . "/" . $old_stub . "_" . $this->uniqid;
  443. $dir_new = "content/comics/" . $this->comic->directory() . "/" . $this->stub . "_" . $this->uniqid;
  444. rename($dir_old, $dir_new);
  445. }
  446. // $data['team'] must be an array of team NAMES
  447. if (isset($data['team']))
  448. {
  449. // Remove the empty values in the array of team names.
  450. // It happens that the POST contains extra empty values.
  451. if (is_array($data['team']))
  452. {
  453. foreach ($data['team'] as $key => $value)
  454. {
  455. if ($value == "")
  456. {
  457. unset($data['team'][$key]);
  458. }
  459. }
  460. sort($data["team"]);
  461. }
  462. // In case there's more than a team name in array, get the joint_id.
  463. // The joint model is able to create new joints on the fly, do not worry.
  464. // Worry rather that the team names must exist.
  465. if (count($data['team']) > 1)
  466. {
  467. // Set team_id to 0 since it's a joint.
  468. $this->team_id = 0;
  469. $joint = new Joint();
  470. // If the search returns false, something went wrong.
  471. // GUI errors are inside the function.
  472. if (!$this->joint_id = $joint->add_joint_via_name($data['team']))
  473. {
  474. log_message('error', 'update_chapter_db: error with joint_id');
  475. return false;
  476. }
  477. }
  478. // In case there's only one team in the array, find the team.
  479. // return false in case one of the names doesn't exist.
  480. else if (count($data['team']) == 1)
  481. {
  482. // Set joint_id to 0 since it's a single team
  483. $this->joint_id = 0;
  484. $team = new Team();
  485. $team->where("name", $data['team'][0])->get();
  486. if ($team->result_count() == 0)
  487. {
  488. set_notice('error', _('The team you were referring this chapter to doesn\'t exist.'));
  489. log_message('error', 'update_chapter_db: team_id does not exist in team database');
  490. return false;
  491. }
  492. $this->team_id = $team->id;
  493. }
  494. else
  495. {
  496. set_notice('error', _('You must select at least one team for this chapter'));
  497. log_message('error', 'update_chapter_db: team_id not defined');
  498. return false;
  499. }
  500. }
  501. else if (!isset($this->team))
  502. { // If we're here it means that this is a new chapter with no teams assigned.
  503. // The system doesn't allow chapters without related teams. It must be at
  504. // least "anonymous" or a default anonymous team.
  505. set_notice('error', _('You haven\'t selected any team in relation to this chapter.'));
  506. log_message('error', 'update_chapter_db: team_id does not defined');
  507. return false;
  508. }
  509. // Save with validation. Push false if fail, true if good.
  510. $success = $this->save();
  511. if (!$success)
  512. {
  513. if (!$this->valid)
  514. {
  515. log_message('error', $this->error->string);
  516. set_notice('error', _('Check that you have inputted all the required fields.'));
  517. log_message('error', 'update_chapter_db: failed validation');
  518. }
  519. else
  520. {
  521. set_notice('error', _('Failed to save to database for unknown reasons.'));
  522. log_message('error', 'update_chapter_db: failed to save');
  523. }
  524. return false;
  525. }
  526. else
  527. {
  528. // Here we go!
  529. return true;
  530. }
  531. }
  532. /**
  533. * Removes the chapter from the database, but before it removes all the
  534. * related pages from the database (not the files).
  535. *
  536. * @author Woxxy
  537. * @return boolean true if success, false if failure.
  538. */
  539. public function remove_chapter_db()
  540. {
  541. // get all the pages of this chapter. Use iterated because they could be many.
  542. $pages = new Page();
  543. $pages->where('chapter_id', $this->id)->get_iterated();
  544. // remove them with the page model function
  545. foreach ($pages as $page)
  546. {
  547. $page->remove_page_db();
  548. }
  549. // And now, remove the chapter itself. There should be little chance for this to fail.
  550. $success = $this->delete();
  551. if (!$success)
  552. {
  553. set_notice('error', _('Failed to remove the chapter from the database for unknown reasons.'));
  554. log_message('error', 'remove_chapter_db: id found but entry not removed');
  555. return false;
  556. }
  557. // It's gone.
  558. return true;
  559. }
  560. /**
  561. * Creates the necessary empty folder for a chapter
  562. *
  563. * @author Woxxy
  564. * @return boolean true if success, false if failure.
  565. */
  566. public function add_chapter_dir()
  567. {
  568. // Get the series if we didn't yet.
  569. if (!$this->get_comic())
  570. {
  571. set_notice('error', _('No series related to this chapter.'));
  572. log_message('error', 'add_chapter_dir: comic did not exist');
  573. return false;
  574. }
  575. // Create the directory and return false on failure. It's most likely file permissions anyway.
  576. $dir = "content/comics/" . $this->comic->directory() . "/" . $this->directory();
  577. if (!mkdir($dir))
  578. {
  579. set_notice('error', _('Failed to create the chapter directory. Please, check file permissions.'));
  580. log_message('error', 'add_chapter_dir: folder could not be created');
  581. return false;
  582. }
  583. return true;
  584. }
  585. /**
  586. * Removes the chapter folder with all the data that was inside of it.
  587. * This means pages and props too.
  588. *
  589. * @author Woxxy
  590. * @return boolean true if success, false if failure.
  591. */
  592. public function remove_chapter_dir()
  593. {
  594. // Get the series if we didn't yet.
  595. if (!$this->get_comic())
  596. {
  597. set_notice('error', _('No series related to this chapter.'));
  598. log_message('error', 'remove_chapter_dir: comic did not exist');
  599. return false;
  600. }
  601. // Create the direcotry name
  602. $dir = "content/comics/" . $this->comic->directory() . "/" . $this->directory() . "/";
  603. // Delete all files inside of it
  604. if (!delete_files($dir, TRUE))
  605. {
  606. set_notice('error', _('Failed to remove the files inside the chapter directory. Please, check file permissions.'));
  607. log_message('error', 'remove_chapter_dir: files inside folder could not be removed');
  608. return false;
  609. }
  610. else
  611. {
  612. // On success of emptying, remove the chapter directory itself.
  613. if (!rmdir($dir))
  614. {
  615. set_notice('error', _('Failed to remove the chapter directory. Please, check file permissions.'));
  616. log_message('error', 'remove_chapter_dir: folder could not be removed');
  617. return false;
  618. }
  619. }
  620. return true;
  621. }
  622. /**
  623. * Comfy function to just empty the chapter off pages.
  624. *
  625. * @author Woxxy
  626. * @return boolean true, doesn't have error check.
  627. */
  628. public function remove_all_pages()
  629. {
  630. $page = new Page();
  631. // Lets get the pages in iterated because there could be many
  632. $page->where('chapter_id', $this->id)->get_iterated();
  633. // Loop and remove each. The page model will take care of database and directories.
  634. $return = true;
  635. foreach ($page as $key => $item)
  636. {
  637. if (!$item->remove_page())
  638. {
  639. // Set false to say there was a failure, but don't stop removal.
  640. $return = false;
  641. log_message('error', 'remove_all_pages: page could not be removed');
  642. }
  643. }
  644. // Even if false is returned all removable pages will be removed.
  645. return $return;
  646. }
  647. function check($repair = FALSE)
  648. {
  649. // make sure we got the comic
  650. if ($this->get_comic() === FALSE)
  651. {
  652. $errors[] = 'chapter_comic_entry_not_found';
  653. set_notice('warning', _('Found a chapter entry without a comic entry, Chapter ID: ' . $this->id));
  654. log_message('debug', 'check: chapter entry without comic entry');
  655. if ($repair)
  656. {
  657. $this->remove_chapter_db();
  658. }
  659. return FALSE;
  660. }
  661. $errors = array();
  662. // check if the directory exists at all
  663. $path = 'content/comics/' . $this->comic->directory() . '/' . $this->directory() . '/';
  664. if (!is_dir($path))
  665. {
  666. $errors[] = 'chapter_directory_not_found';
  667. set_notice('warning', _('No directory found for:') . ' ' . $this->comic->name . ' > ' . $this->title());
  668. log_message('debug', 'check: chapter directory missing at ' . $path);
  669. // the folder doesn't exist, so get rid of the entry from database
  670. if ($repair)
  671. {
  672. $this->remove_chapter_db();
  673. }
  674. // there's no recovery from this, return the error codes
  675. return $errors;
  676. }
  677. // check if there are extraneous files in the folder
  678. $files = get_dir_file_info($path);
  679. foreach ($files as $key => $file)
  680. {
  681. // check that the file is writable
  682. if (!is_writable($file['relative_path']))
  683. {
  684. // non writable files are horrendous, send a notice and stop the machines
  685. $errors[] = 'chapter_non_writable_file';
  686. set_notice('warning', _('Found non writable files in the comics folder. Check your files permissions.'));
  687. log_message('debug', 'check: non writable file: ' . $file['relative_path']);
  688. return $errors;
  689. }
  690. // get the extension
  691. $ext = strtolower(substr($file['name'], -4));
  692. if (in_array($ext, array('.zip')))
  693. {
  694. // maybe it's just the zip created by the archive system
  695. $archives = new Archive();
  696. $archives->where('comic_id', $this->comic_id)->where('chapter_id', $this->id)->where('volume_id', 0)->get();
  697. if ($archives->result_count())
  698. {
  699. foreach ($archives as $archive)
  700. {
  701. // we actually have an archive, but is it the same file?
  702. if ($file['name'] == $archive->filename)
  703. {
  704. // same file, unset to confirm
  705. unset($files[$key]);
  706. continue;
  707. }
  708. }
  709. }
  710. }
  711. if (in_array($ext, array('.png', '.jpg', 'jpeg', '.gif')))
  712. {
  713. $page = new Page();
  714. $page->where('chapter_id', $this->id)->where('filename', $file['name'])->get();
  715. if ($page->result_count() == 1)
  716. {
  717. // it's a simple page, unset to confirm
  718. unset($files[$key]);
  719. continue;
  720. }
  721. }
  722. }
  723. // now we have an array with files that don't belong here
  724. foreach ($files as $file)
  725. {
  726. $errors[] = 'chapter_unidentified_file';
  727. set_notice('warning', _('Unidentified file found in:') . ' ' . $this->comic->name . ' > ' . $this->title() . ': ' . $file['name']);
  728. log_message('debug', 'check: unidentified file ' . $file['relative_path'] . $file['name']);
  729. // repairing this means getting rid of extraneous files
  730. if ($repair)
  731. {
  732. // it's possible the file is not removeable
  733. if (is_writable($file['relative_path'] . $file['name']))
  734. {
  735. // the files SHOULD be writable, we checked it earlier
  736. if (is_dir($file['relative_path'] . $file['name']))
  737. {
  738. delete_files($file['relative_path'] . $file['name']);
  739. rmdir($file['relative_path'] . $file['name']);
  740. }
  741. else
  742. {
  743. unlink($file['relative_path'] . $file['name']);
  744. }
  745. }
  746. }
  747. }
  748. // everything's been checked. The errors are in the set_notice system
  749. return $errors;
  750. }
  751. function get_dirsize()
  752. {
  753. $this->get_comic();
  754. $filearray = get_dir_file_info("content/comics/" . $this->comic->directory() . "/" . $this->directory() . "/", FALSE);
  755. $size = 0;
  756. foreach ($filearray as $file)
  757. {
  758. $size += $file["size"];
  759. }
  760. return $size;
  761. }
  762. /**
  763. * Returns directory name without slashes
  764. *
  765. * @author Woxxy
  766. * @return string Directory name.
  767. */
  768. public function directory()
  769. {
  770. return $this->stub . '_' . $this->uniqid;
  771. }
  772. /**
  773. * Returns all the pages in a complete array, useful for displaying or sending
  774. * to a json function.
  775. *
  776. * @author Woxxy
  777. * @return array all pages with their data
  778. */
  779. public function get_pages()
  780. {
  781. // if we already used the function, no need to recalc it
  782. if (isset($this->pages))
  783. return $this->pages;
  784. // Check that the comic is loaded, else load it.
  785. $this->get_comic();
  786. // Get the pages in filename order. Without order_by it would get them by ID which doesn't really work nicely.
  787. $pages = new Page();
  788. $pages->where('chapter_id', $this->id)->order_by('filename')->get();
  789. // Create the array with all page details for simple return.
  790. $return = array();
  791. foreach ($pages->all as $key => $item)
  792. {
  793. $return[$key] = $item->to_array();
  794. // Let's add to it the object itelf? Uncomment next line to do so.
  795. // $return[$key]['object'] = $item;
  796. // The URLs need to be completed. This function will also trigger the load balancing if enabled.
  797. $return[$key]['url'] = balance_url() . "content/comics/" . $this->comic->directory() . "/" . $this->directory() . "/" . $item->filename;
  798. $return[$key]['thumb_url'] = balance_url() . "content/comics/" . $this->comic->directory() . "/" . $this->directory() . "/" . $item->thumbnail . $item->filename;
  799. }
  800. // Put the pages in a comfy variable.
  801. $this->pages = $return;
  802. return $return;
  803. }
  804. /**
  805. * Returns the date of release of the chapter WITHOUT hours, minutes and seconds
  806. *
  807. * @author Woxxy
  808. * @return string date d/m/y
  809. */
  810. public function date()
  811. {
  812. return relative_date(strtotime($this->created));
  813. }
  814. /**
  815. * Returns a ready to use html <a> link that points to the reader
  816. *
  817. * @param $text string
  818. * @author Woxxy
  819. * @return string <a> to reader
  820. */
  821. public function url($text = NULL)
  822. {
  823. return '<a href="' . $this->href() . '" title="' . $this->title() . '">' . ((is_null($text)) ? $this->title() : $text) . '</a>';
  824. }
  825. /**
  826. * Returns a ready to use html <a> link that points to the download
  827. *
  828. * @param $text string
  829. * @author Woxxy
  830. * @return string <a> to reader
  831. */
  832. public function download_url($text = NULL, $class = "")
  833. {
  834. if (get_setting('fs_dl_enabled'))
  835. return '<div class="icon_wrapper ' . $class . '"><a href="' . $this->download_href() . '"><img class="icon off" src="' . glyphish(50) . '" /><img class="icon on" src="' . glyphish(50, TRUE) . '" /></a></div>';
  836. }
  837. public function download_volume_url($text = NULL, $class = "")
  838. {
  839. if (get_setting('fs_dl_enabled') && get_setting('fs_dl_volume_enabled'))
  840. return '<div class="icon_wrapper ' . $class . '"><a href="' . $this->download_volume_href() . '"><img class="icon off" src="' . glyphish(50) . '" /><img class="icon on" src="' . glyphish(50, TRUE) . '" /></a></div>';
  841. }
  842. /**
  843. * Returns a nicely built title for a chapter
  844. *
  845. * @author Woxxy
  846. * @return string the formatted title for the chapter, with chapter and subchapter
  847. */
  848. public function title($volume = true)
  849. {
  850. $echo = "";
  851. if ($this->volume > 0 && $volume === true)
  852. {
  853. $echo .= _('Vol.') . $this->volume . ' ';
  854. }
  855. if ($this->chapter > 0) // if chapter == 0 it means this is a one-shot
  856. {
  857. if ($this->customchaptertitle()) // support for custom chapter titles
  858. {
  859. $echo .= $this->customchaptertitle();
  860. }
  861. else
  862. {
  863. $echo .= _('Chapter') . ' ' . $this->chapter;
  864. }
  865. }
  866. if ($this->subchapter && $this->chapter > 0) // if it's a one-shot, we still use subchapter for sorting, but don't display it
  867. $echo .= '.' . $this->subchapter;
  868. if ($this->name != "")
  869. {
  870. // if it's a one-shot with title, hide the : because there's no numbering
  871. if ($this->chapter > 0)
  872. $echo .= ': ';
  873. $echo .= $this->name;
  874. }
  875. else
  876. {
  877. if ($this->chapter == 0)
  878. {
  879. // one-shots without a title gets the comic title
  880. $this->get_comic(); // play safe and check if we have it
  881. $echo .= $this->comic->name;
  882. }
  883. }
  884. return $echo;
  885. }
  886. public function customchaptertitle()
  887. {
  888. $this->get_comic();
  889. $echo = "";
  890. // Generate Ordinal Numbers Suffix (English)
  891. $ordinal = 'th';
  892. if (!in_array(($this->chapter % 100), array(11, 12, 13)))
  893. {
  894. switch ($this->chapter % 10)
  895. {
  896. case 1:
  897. $ordinal = 'st';
  898. break;
  899. case 2:
  900. $ordinal = 'nd';
  901. break;
  902. case 3:
  903. $ordinal = 'rd';
  904. break;
  905. }
  906. }
  907. $echo = str_replace(
  908. array('{num}', '{ord}'), array($this->chapter, $ordinal), $this->comic->customchapter
  909. );
  910. return $echo;
  911. }
  912. /**
  913. * Returns a string with the teams that worked on this chapter and the relative URLs
  914. *
  915. * @author Woxxy
  916. * @return string <a> to teams
  917. */
  918. public function team_url()
  919. {
  920. $this->get_teams();
  921. $echo = "";
  922. foreach ($this->teams as $key => $team)
  923. {
  924. if ($key > 0)
  925. $echo .= " | ";
  926. $echo .= '<a href="' . site_url('team/' . $team->stub) . '" title="' . $team->name . '" >' . $team->name . '</a>';
  927. }
  928. return $echo;
  929. }
  930. /**
  931. * Returns the href to the chapter editing
  932. *
  933. * @author Woxxy
  934. * @return string href to chapter editing
  935. */
  936. public function edit_href()
  937. {
  938. $CI = & get_instance();
  939. if (!$CI->tank_auth->is_team_leader_array($this->teams))
  940. return "";
  941. $this->get_comic();
  942. return site_url('/admin/series/series/' . $this->comic->stub . '/' . $this->id);
  943. }
  944. /**
  945. * Returns the url to the chapter editing
  946. *
  947. * @author Woxxy
  948. * @return string <a> to chapter editing
  949. */
  950. public function edit_url()
  951. {
  952. $CI = & get_instance();
  953. if (!$CI->tank_auth->is_team_leader_array($this->teams))
  954. return "";
  955. return '<a href="' . $this->edit_href() . '" title="' . _('Edit') . ' ' . $this->title() . '">' . _('Edit') . '</a>';
  956. }
  957. /**
  958. * Returns the href to the reader. This will create the shortest possible URL.
  959. *
  960. * @author Woxxy
  961. * @returns string href to reader.
  962. */
  963. public function href()
  964. {
  965. return site_url('read/' . $this->unique_href());
  966. }
  967. /**
  968. * Returns the download href. This will create the shortest possible URL.
  969. *
  970. * @author Woxxy
  971. * @returns string href to reader.
  972. */
  973. public function download_href()
  974. {
  975. return site_url('download/' . $this->unique_href());
  976. }
  977. public function download_volume_href()
  978. {
  979. return site_url('download/' . $this->unique_volume_href());
  980. }
  981. public function unique_href()
  982. {
  983. // If we already used this function, no need to recalc it.
  984. if (isset($this->unique_href))
  985. return $this->unique_href;
  986. // We need the comic
  987. $this->get_comic();
  988. // Identify the chapter through data, not ID. This allows us to find out if there are multiple similar chapters.
  989. $chapter = new Chapter();
  990. $chapter->where('comic_id', $this->comic->id)->where('volume', $this->volume)->where('chapter', $this->chapter)->where('language', $this->language)->where('subchapter', $this->subchapter)->get();
  991. // This part of the URL won't change for sure.
  992. $url = $this->comic->stub . '/' . $this->language . '/' . $this->volume . '/' . $this->chapter . '/';
  993. // Find out if there are multiple versions of the chapter, it means there are multiple groups with the same chapter.
  994. // Let's set the whole URL with subchapters, teams and maybe joint too in it.
  995. if ($chapter->result_count() > 1)
  996. {
  997. $url .= $this->subchapter . '/';
  998. if ($this->team_id != 0)
  999. {
  1000. $team = new Team($this->team_id);
  1001. $url .= $team->stub . '/';
  1002. }
  1003. else if ($this->joint_id != 0)
  1004. $url .= '0/' . $this->joint_id . '/';
  1005. // save the value in a variable for reuse.
  1006. $this->unique_href = $url;
  1007. }
  1008. else
  1009. { // There's only one chapter like this.
  1010. // If possiblee can make it even shorter without subchapter!
  1011. if ($this->subchapter != 0)
  1012. {
  1013. $url .= $this->subchapter . '/';
  1014. }
  1015. // Save the value in a variable for reuse.
  1016. $this->unique_href = $url;
  1017. }
  1018. return $this->unique_href;
  1019. }
  1020. public function unique_volume_href()
  1021. {
  1022. if (isset($this->unique_volume_href))
  1023. return $this->unique_volume_href;
  1024. // We need the comic
  1025. $this->get_comic();
  1026. // Identify the chapter through data, not ID. This allows us to find out if there are multiple similar chapters.
  1027. $chapter = new Chapter();
  1028. $chapter->where('comic_id', $this->comic->id)->where('volume', $this->volume)->where('chapter', $this->chapter)->where('language', $this->language)->where('subchapter', $this->subchapter)->get();
  1029. // This part of the URL won't change for sure.
  1030. $this->unique_volume_href = $this->comic->stub . '/' . $this->language . '/' . $this->volume . '/';
  1031. return $this->unique_volume_href;
  1032. }
  1033. /**
  1034. * Returns the URL of the reader for the next chapter
  1035. *
  1036. * @author Woxxy
  1037. * @return string the href to the next chapter
  1038. */
  1039. public function next()
  1040. {
  1041. // If we've already used this function, it's ready for use, no need to calc it again
  1042. if (isset($this->next))
  1043. return $this->next;
  1044. // Needs the comic
  1045. $this->get_comic();
  1046. $chapter = new Chapter();
  1047. // Check if there are subchapters for this chapter.
  1048. $chapter->where('comic_id', $this->comic->id)->where('volume', $this->volume)->where('chapter', $this->chapter)->where('language', $this->language)->having('subchapter >', $this->subchapter)->order_by('subchapter', 'asc')->limit(1)->get();
  1049. if ($chapter->result_count() == 0)
  1050. {
  1051. // There aren't subchapters for this chapter. Then let's look for the next chapter
  1052. $chapter = new Chapter();
  1053. $chapter->where('comic_id', $this->comic->id)->where('volume', $this->volume)->having('chapter > ', $this->chapter)->where('language', $this->language)->order_by('chapter', 'asc')->order_by('subchapter', 'asc')->limit(1)->get();
  1054. if ($chapter->result_count() == 0)
  1055. {
  1056. // Check if there's a chapter in the next volume.
  1057. // This works even if chapter goes vol2 33 -> vol3 34 or vol2 33 -> vol3 1
  1058. $chapter = new Chapter();
  1059. $chapter->where('comic_id', $this->comic->id)->having('volume > ', $this->volume)->where('language', $this->language)->order_by('volume', 'asc')->order_by('chapter', 'asc')->order_by('subchapter', 'asc')->limit(1)->get();
  1060. if ($chapter->result_count() == 0)
  1061. {
  1062. // There's no next chapter. Redirect to the comic page.
  1063. return site_url('read/' . $this->comic->stub);
  1064. }
  1065. }
  1066. }
  1067. // We do have a chapter or more. Get them.
  1068. $chaptere = new Chapter();
  1069. $chaptere->where('comic_id', $this->comic->id)->where('volume', $chapter->volume)->where('chapter', $chapter->chapter)->where('language', $this->language)->where('subchapter', $chapter->subchapter)->get();
  1070. $done = false;
  1071. // Do we have more than a next chapter? Make so it has the same teams on it.
  1072. if ($chaptere->result_count() > 1)
  1073. {
  1074. foreach ($chaptere->all as $chap)
  1075. {
  1076. if ($chap->team_id == $this->team_id && $chap->joint_id == $this->joint_id)
  1077. {
  1078. $chapter = $chap;
  1079. $done = true;
  1080. break;
  1081. }
  1082. }
  1083. // What if the teams changed, and the old teams stopped working on it? Get a different team.
  1084. // There must be multiple teams on the next chapter for this to happen. Rare but happens.
  1085. if (!$done)
  1086. {
  1087. /**
  1088. * @todo This is a pretty random way to select the next chapter version, needs refinement.
  1089. */
  1090. $chapter = $chaptere->all['0'];
  1091. }
  1092. }
  1093. // There's only one chapter, simply use it.
  1094. else
  1095. {
  1096. $chapter = $chaptere;
  1097. }
  1098. // This is a heavy function. Let's play it smart and cache the value.
  1099. // Send to the href function that returns a nice URL.
  1100. $this->next = $chapter->href();
  1101. // finally, return the URL.
  1102. return $this->next;
  1103. }
  1104. /**
  1105. * Returns the URL for the next page in the same chapter. It's used for
  1106. * page-change in systems that don't support JavaScript.
  1107. *
  1108. * @author Woxxy
  1109. * @todo this function has a quite rough method to work, though it saves
  1110. * lots of calc power. Maybe it can be written more elegantly?
  1111. * @return string with href to next page
  1112. */
  1113. public function next_page($page, $max = 0)
  1114. {
  1115. if ($max != 0 && $max > $page)
  1116. return $this->next();
  1117. $url = current_url();
  1118. // If the page hasn't been set yet, just add to the URL.
  1119. if (!$post = strpos($url, '/page'))
  1120. {
  1121. return current_url() . 'page/' . ($page + 1);
  1122. }
  1123. // Just remove everything after the page segment and readd it with proper number.
  1124. return substr(current_url(), 0, $post) . '/page/' . ($page + 1);
  1125. }
  1126. /**
  1127. * Overwrites the original DataMapper to_array() to add some elements
  1128. *
  1129. * @param array $fields
  1130. * @return array
  1131. */
  1132. public function to_array($fields = '')
  1133. {
  1134. $result = parent::to_array($fields = '');
  1135. $result["href"] = $this->href();
  1136. $result["title"] = $this->title();
  1137. $result["download_href"] = $this->download_href();
  1138. return $result;
  1139. }
  1140. }
  1141. /* End of file chapter.php */
  1142. /* Location: ./application/models/chapter.php */