96f9f456aad39c0323e1bbe7fecd4666fb5d1062
[moodle.git] / mod / book / locallib.php
1 <?php
2 // This file is part of Book module for Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Book module local lib functions
19  *
20  * @package    mod_book
21  * @copyright  2010-2011 Petr Skoda {@link http://skodak.org}
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 defined('MOODLE_INTERNAL') || die;
27 require_once(dirname(__FILE__).'/lib.php');
28 require_once($CFG->libdir.'/filelib.php');
30 /**
31  * The following defines are used to define how the chapters and subchapters of a book should be displayed in that table of contents.
32  * BOOK_NUM_NONE        No special styling will applied and the editor will be able to do what ever thay want in the title
33  * BOOK_NUM_NUMBERS     Chapters and subchapters are numbered (1, 1.1, 1.2, 2, ...)
34  * BOOK_NUM_BULLETS     Subchapters are indented and displayed with bullets
35  * BOOK_NUM_INDENTED    Subchapters are indented
36  */
37 define('BOOK_NUM_NONE',     '0');
38 define('BOOK_NUM_NUMBERS',  '1');
39 define('BOOK_NUM_BULLETS',  '2');
40 define('BOOK_NUM_INDENTED', '3');
42 /**
43  * Preload book chapters and fix toc structure if necessary.
44  *
45  * Returns array of chapters with standard 'pagenum', 'id, pagenum, subchapter, title, hidden'
46  * and extra 'parent, number, subchapters, prev, next'.
47  * Please note the content/text of chapters is not included.
48  *
49  * @param  stdClass $book
50  * @return array of id=>chapter
51  */
52 function book_preload_chapters($book) {
53     global $DB;
54     $chapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum', 'id, pagenum, subchapter, title, hidden');
55     if (!$chapters) {
56         return array();
57     }
59     $prev = null;
60     $prevsub = null;
62     $first = true;
63     $hidesub = true;
64     $parent = null;
65     $pagenum = 0; // chapter sort
66     $i = 0;       // main chapter num
67     $j = 0;       // subchapter num
68     foreach ($chapters as $id => $ch) {
69         $oldch = clone($ch);
70         $pagenum++;
71         $ch->pagenum = $pagenum;
72         if ($first) {
73             // book can not start with a subchapter
74             $ch->subchapter = 0;
75             $first = false;
76         }
77         if (!$ch->subchapter) {
78             $ch->prev = $prev;
79             $ch->next = null;
80             if ($prev) {
81                 $chapters[$prev]->next = $ch->id;
82             }
83             if ($ch->hidden) {
84                 if ($book->numbering == BOOK_NUM_NUMBERS) {
85                     $ch->number = 'x';
86                 } else {
87                     $ch->number = null;
88                 }
89             } else {
90                 $i++;
91                 $ch->number = $i;
92             }
93             $j = 0;
94             $prevsub = null;
95             $hidesub = $ch->hidden;
96             $parent = $ch->id;
97             $ch->parent = null;
98             $ch->subchapters = array();
99         } else {
100             $ch->prev = $prevsub;
101             $ch->next = null;
102             if ($prevsub) {
103                 $chapters[$prevsub]->next = $ch->id;
104             }
105             $ch->parent = $parent;
106             $ch->subchapters = null;
107             $chapters[$parent]->subchapters[$ch->id] = $ch->id;
108             if ($hidesub) {
109                 // all subchapters in hidden chapter must be hidden too
110                 $ch->hidden = 1;
111             }
112             if ($ch->hidden) {
113                 if ($book->numbering == BOOK_NUM_NUMBERS) {
114                     $ch->number = 'x';
115                 } else {
116                     $ch->number = null;
117                 }
118             } else {
119                 $j++;
120                 $ch->number = $j;
121             }
122         }
123         if ($oldch->subchapter != $ch->subchapter or $oldch->pagenum != $ch->pagenum or $oldch->hidden != $ch->hidden) {
124             // update only if something changed
125             $DB->update_record('book_chapters', $ch);
126         }
127         $chapters[$id] = $ch;
128     }
130     return $chapters;
133 /**
134  * Returns the title for a given chapter
135  *
136  * @param int $chid
137  * @param array $chapters
138  * @param stdClass $book
139  * @param context_module $context
140  * @return string
141  */
142 function book_get_chapter_title($chid, $chapters, $book, $context) {
143     $ch = $chapters[$chid];
144     $title = trim(format_string($ch->title, true, array('context'=>$context)));
145     $numbers = array();
146     if ($book->numbering == BOOK_NUM_NUMBERS) {
147         if ($ch->parent and $chapters[$ch->parent]->number) {
148             $numbers[] = $chapters[$ch->parent]->number;
149         }
150         if ($ch->number) {
151             $numbers[] = $ch->number;
152         }
153     }
155     if ($numbers) {
156         $title = implode('.', $numbers).' '.$title;
157     }
159     return $title;
162 /**
163  * General logging to table
164  * @param string $str1
165  * @param string $str2
166  * @param int $level
167  * @return void
168  */
169 function book_log($str1, $str2, $level = 0) {
170     switch ($level) {
171         case 1:
172             echo '<tr><td><span class="dimmed_text">'.$str1.'</span></td><td><span class="dimmed_text">'.$str2.'</span></td></tr>';
173             break;
174         case 2:
175             echo '<tr><td><span style="color: rgb(255, 0, 0);">'.$str1.'</span></td><td><span style="color: rgb(255, 0, 0);">'.$str2.'</span></td></tr>';
176             break;
177         default:
178             echo '<tr><td>'.$str1.'</class></td><td>'.$str2.'</td></tr>';
179             break;
180     }
183 /**
184  * Add the book TOC sticky block to the 1st region available
185  *
186  * @param array $chapters
187  * @param stdClass $chapter
188  * @param stdClass $book
189  * @param stdClass $cm
190  * @param bool $edit
191  */
192 function book_add_fake_block($chapters, $chapter, $book, $cm, $edit) {
193     global $OUTPUT, $PAGE;
195     $toc = book_get_toc($chapters, $chapter, $book, $cm, $edit, 0);
197     if ($edit) {
198         $toc .= '<div class="book_faq">';
199         $toc .=  $OUTPUT->help_icon('faq', 'mod_book', get_string('faq', 'mod_book'));
200         $toc .=  '</div>';
201     }
203     $bc = new block_contents();
204     $bc->title = get_string('toc', 'mod_book');
205     $bc->attributes['class'] = 'block';
206     $bc->content = $toc;
208     $regions = $PAGE->blocks->get_regions();
209     $firstregion = reset($regions);
210     $PAGE->blocks->add_fake_block($bc, $firstregion);
213 /**
214  * Generate toc structure
215  *
216  * @param array $chapters
217  * @param stdClass $chapter
218  * @param stdClass $book
219  * @param stdClass $cm
220  * @param bool $edit
221  * @return string
222  */
223 function book_get_toc($chapters, $chapter, $book, $cm, $edit) {
224     global $USER, $OUTPUT;
226     $toc = '';  // Representation of toc (HTML)
227     $nch = 0;   // Chapter number
228     $ns = 0;    // Subchapter number
229     $first = 1;
231     $context = context_module::instance($cm->id);
233     switch ($book->numbering) {
234         case BOOK_NUM_NONE:
235             $toc .= '<div class="book_toc_none">';
236             break;
237         case BOOK_NUM_NUMBERS:
238             $toc .= '<div class="book_toc_numbered">';
239             break;
240         case BOOK_NUM_BULLETS:
241             $toc .= '<div class="book_toc_bullets">';
242             break;
243         case BOOK_NUM_INDENTED:
244             $toc .= '<div class="book_toc_indented">';
245             break;
246     }
248     if ($edit) { // Teacher's TOC
249         $toc .= '<ul>';
250         $i = 0;
251         foreach ($chapters as $ch) {
252             $i++;
253             $title = trim(format_string($ch->title, true, array('context'=>$context)));
254             if (!$ch->subchapter) {
255                 $toc .= ($first) ? '<li>' : '</ul></li><li>';
256                 if (!$ch->hidden) {
257                     $nch++;
258                     $ns = 0;
259                     if ($book->numbering == BOOK_NUM_NUMBERS) {
260                         $title = "$nch $title";
261                     }
262                 } else {
263                     if ($book->numbering == BOOK_NUM_NUMBERS) {
264                         $title = "x $title";
265                     }
266                     $title = '<span class="dimmed_text">'.$title.'</span>';
267                 }
268             } else {
269                 $toc .= ($first) ? '<li><ul><li>' : '<li>';
270                 if (!$ch->hidden) {
271                     $ns++;
272                     if ($book->numbering == BOOK_NUM_NUMBERS) {
273                         $title = "$nch.$ns $title";
274                     }
275                 } else {
276                     if ($book->numbering == BOOK_NUM_NUMBERS) {
277                         $title = "x.x $title";
278                     }
279                     $title = '<span class="dimmed_text">'.$title.'</span>';
280                 }
281             }
283             if ($ch->id == $chapter->id) {
284                 $toc .= '<strong>'.$title.'</strong>';
285             } else {
286                 $toc .= '<a title="'.s($title).'" href="view.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'">'.$title.'</a>';
287             }
288             $toc .=  '&nbsp;&nbsp;';
289             if ($i != 1) {
290                 $toc .=  ' <a title="'.get_string('up').'" href="move.php?id='.$cm->id.'&amp;chapterid='.$ch->id.
291                         '&amp;up=1&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/up').'" class="iconsmall" alt="'.get_string('up').'" /></a>';
292             }
293             if ($i != count($chapters)) {
294                 $toc .=  ' <a title="'.get_string('down').'" href="move.php?id='.$cm->id.'&amp;chapterid='.$ch->id.
295                         '&amp;up=0&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/down').'" class="iconsmall" alt="'.get_string('down').'" /></a>';
296             }
297             $toc .=  ' <a title="'.get_string('edit').'" href="edit.php?cmid='.$cm->id.'&amp;id='.$ch->id.'"><img src="'.
298                     $OUTPUT->pix_url('t/edit').'" class="iconsmall" alt="'.get_string('edit').'" /></a>';
299             $toc .=  ' <a title="'.get_string('delete').'" href="delete.php?id='.$cm->id.'&amp;chapterid='.$ch->id.
300                     '&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/delete').'" class="iconsmall" alt="'.get_string('delete').'" /></a>';
301             if ($ch->hidden) {
302                 $toc .= ' <a title="'.get_string('show').'" href="show.php?id='.$cm->id.'&amp;chapterid='.$ch->id.
303                         '&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/show').'" class="iconsmall" alt="'.get_string('show').'" /></a>';
304             } else {
305                 $toc .= ' <a title="'.get_string('hide').'" href="show.php?id='.$cm->id.'&amp;chapterid='.$ch->id.
306                         '&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/hide').'" class="iconsmall" alt="'.get_string('hide').'" /></a>';
307             }
308             $toc .= ' <a title="'.get_string('addafter', 'mod_book').'" href="edit.php?cmid='.$cm->id.
309                     '&amp;pagenum='.$ch->pagenum.'&amp;subchapter='.$ch->subchapter.'"><img src="'.
310                     $OUTPUT->pix_url('add', 'mod_book').'" class="iconsmall" alt="'.get_string('addafter', 'mod_book').'" /></a>';
312             $toc .= (!$ch->subchapter) ? '<ul>' : '</li>';
313             $first = 0;
314         }
315         $toc .= '</ul></li></ul>';
316     } else { // Normal students view
317         $toc .= '<ul>';
318         foreach ($chapters as $ch) {
319             $title = trim(format_string($ch->title, true, array('context'=>$context)));
320             if (!$ch->hidden) {
321                 if (!$ch->subchapter) {
322                     $nch++;
323                     $ns = 0;
324                     $toc .= ($first) ? '<li>' : '</ul></li><li>';
325                     if ($book->numbering == BOOK_NUM_NUMBERS) {
326                           $title = "$nch $title";
327                     }
328                 } else {
329                     $ns++;
330                     $toc .= ($first) ? '<li><ul><li>' : '<li>';
331                     if ($book->numbering == BOOK_NUM_NUMBERS) {
332                           $title = "$nch.$ns $title";
333                     }
334                 }
335                 if ($ch->id == $chapter->id) {
336                     $toc .= '<strong>'.$title.'</strong>';
337                 } else {
338                     $toc .= '<a title="'.s($title).'" href="view.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'">'.$title.'</a>';
339                 }
340                 $toc .= (!$ch->subchapter) ? '<ul>' : '</li>';
341                 $first = 0;
342             }
343         }
344         $toc .= '</ul></li></ul>';
345     }
347     $toc .= '</div>';
349     $toc = str_replace('<ul></ul>', '', $toc); // Cleanup of invalid structures.
351     return $toc;
355 /**
356  * File browsing support class
357  *
358  * @copyright  2010-2011 Petr Skoda {@link http://skodak.org}
359  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
360  */
361 class book_file_info extends file_info {
362     /** @var stdClass Course object */
363     protected $course;
364     /** @var stdClass Course module object */
365     protected $cm;
366     /** @var array Available file areas */
367     protected $areas;
368     /** @var string File area to browse */
369     protected $filearea;
371     /**
372      * Constructor
373      *
374      * @param file_browser $browser file_browser instance
375      * @param stdClass $course course object
376      * @param stdClass $cm course module object
377      * @param stdClass $context module context
378      * @param array $areas available file areas
379      * @param string $filearea file area to browse
380      */
381     public function __construct($browser, $course, $cm, $context, $areas, $filearea) {
382         parent::__construct($browser, $context);
383         $this->course   = $course;
384         $this->cm       = $cm;
385         $this->areas    = $areas;
386         $this->filearea = $filearea;
387     }
389     /**
390      * Returns list of standard virtual file/directory identification.
391      * The difference from stored_file parameters is that null values
392      * are allowed in all fields
393      * @return array with keys contextid, filearea, itemid, filepath and filename
394      */
395     public function get_params() {
396         return array('contextid'=>$this->context->id,
397                      'component'=>'mod_book',
398                      'filearea' =>$this->filearea,
399                      'itemid'   =>null,
400                      'filepath' =>null,
401                      'filename' =>null);
402     }
404     /**
405      * Returns localised visible name.
406      * @return string
407      */
408     public function get_visible_name() {
409         return $this->areas[$this->filearea];
410     }
412     /**
413      * Can I add new files or directories?
414      * @return bool
415      */
416     public function is_writable() {
417         return false;
418     }
420     /**
421      * Is directory?
422      * @return bool
423      */
424     public function is_directory() {
425         return true;
426     }
428     /**
429      * Returns list of children.
430      * @return array of file_info instances
431      */
432     public function get_children() {
433         global $DB;
435         $children = array();
436         $chapters = $DB->get_records('book_chapters', array('bookid'=>$this->cm->instance), 'pagenum', 'id, pagenum');
437         foreach ($chapters as $itemid => $unused) {
438             if ($child = $this->browser->get_file_info($this->context, 'mod_book', $this->filearea, $itemid)) {
439                 $children[] = $child;
440             }
441         }
442         return $children;
443     }
445     /**
446      * Returns parent file_info instance
447      * @return file_info or null for root
448      */
449     public function get_parent() {
450         return $this->browser->get_file_info($this->context);
451     }