Merge branch 'MDL-33821-master-1' of git://git.luns.net.uk/moodle
[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     $bc = new block_contents();
198     $bc->title = get_string('toc', 'mod_book');
199     $bc->attributes['class'] = 'block';
200     $bc->content = $toc;
202     $regions = $PAGE->blocks->get_regions();
203     $firstregion = reset($regions);
204     $PAGE->blocks->add_fake_block($bc, $firstregion);
207 /**
208  * Generate toc structure
209  *
210  * @param array $chapters
211  * @param stdClass $chapter
212  * @param stdClass $book
213  * @param stdClass $cm
214  * @param bool $edit
215  * @return string
216  */
217 function book_get_toc($chapters, $chapter, $book, $cm, $edit) {
218     global $USER, $OUTPUT;
220     $toc = '';  // Representation of toc (HTML)
221     $nch = 0;   // Chapter number
222     $ns = 0;    // Subchapter number
223     $first = 1;
225     $context = context_module::instance($cm->id);
227     switch ($book->numbering) {
228         case BOOK_NUM_NONE:
229             $toc .= '<div class="book_toc_none">';
230             break;
231         case BOOK_NUM_NUMBERS:
232             $toc .= '<div class="book_toc_numbered">';
233             break;
234         case BOOK_NUM_BULLETS:
235             $toc .= '<div class="book_toc_bullets">';
236             break;
237         case BOOK_NUM_INDENTED:
238             $toc .= '<div class="book_toc_indented">';
239             break;
240     }
242     if ($edit) { // Teacher's TOC
243         $toc .= '<ul>';
244         $i = 0;
245         foreach ($chapters as $ch) {
246             $i++;
247             $title = trim(format_string($ch->title, true, array('context'=>$context)));
248             if (!$ch->subchapter) {
249                 $toc .= ($first) ? '<li>' : '</ul></li><li>';
250                 if (!$ch->hidden) {
251                     $nch++;
252                     $ns = 0;
253                     if ($book->numbering == BOOK_NUM_NUMBERS) {
254                         $title = "$nch $title";
255                     }
256                 } else {
257                     if ($book->numbering == BOOK_NUM_NUMBERS) {
258                         $title = "x $title";
259                     }
260                     $title = '<span class="dimmed_text">'.$title.'</span>';
261                 }
262             } else {
263                 $toc .= ($first) ? '<li><ul><li>' : '<li>';
264                 if (!$ch->hidden) {
265                     $ns++;
266                     if ($book->numbering == BOOK_NUM_NUMBERS) {
267                         $title = "$nch.$ns $title";
268                     }
269                 } else {
270                     if ($book->numbering == BOOK_NUM_NUMBERS) {
271                         if (empty($chapters[$ch->parent]->hidden)) {
272                             $title = "$nch.x $title";
273                         } else {
274                             $title = "x.x $title";
275                         }
276                     }
277                     $title = '<span class="dimmed_text">'.$title.'</span>';
278                 }
279             }
281             if ($ch->id == $chapter->id) {
282                 $toc .= '<strong>'.$title.'</strong>';
283             } else {
284                 $toc .= '<a title="'.s($title).'" href="view.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'">'.$title.'</a>';
285             }
286             $toc .=  '&nbsp;&nbsp;';
287             if ($i != 1) {
288                 $toc .=  ' <a title="'.get_string('up').'" href="move.php?id='.$cm->id.'&amp;chapterid='.$ch->id.
289                         '&amp;up=1&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/up').'" class="iconsmall" alt="'.get_string('up').'" /></a>';
290             }
291             if ($i != count($chapters)) {
292                 $toc .=  ' <a title="'.get_string('down').'" href="move.php?id='.$cm->id.'&amp;chapterid='.$ch->id.
293                         '&amp;up=0&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/down').'" class="iconsmall" alt="'.get_string('down').'" /></a>';
294             }
295             $toc .=  ' <a title="'.get_string('edit').'" href="edit.php?cmid='.$cm->id.'&amp;id='.$ch->id.'"><img src="'.
296                     $OUTPUT->pix_url('t/edit').'" class="iconsmall" alt="'.get_string('edit').'" /></a>';
297             $toc .=  ' <a title="'.get_string('delete').'" href="delete.php?id='.$cm->id.'&amp;chapterid='.$ch->id.
298                     '&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/delete').'" class="iconsmall" alt="'.get_string('delete').'" /></a>';
299             if ($ch->hidden) {
300                 $toc .= ' <a title="'.get_string('show').'" href="show.php?id='.$cm->id.'&amp;chapterid='.$ch->id.
301                         '&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/show').'" class="iconsmall" alt="'.get_string('show').'" /></a>';
302             } else {
303                 $toc .= ' <a title="'.get_string('hide').'" href="show.php?id='.$cm->id.'&amp;chapterid='.$ch->id.
304                         '&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/hide').'" class="iconsmall" alt="'.get_string('hide').'" /></a>';
305             }
306             $toc .= ' <a title="'.get_string('addafter', 'mod_book').'" href="edit.php?cmid='.$cm->id.
307                     '&amp;pagenum='.$ch->pagenum.'&amp;subchapter='.$ch->subchapter.'"><img src="'.
308                     $OUTPUT->pix_url('add', 'mod_book').'" class="iconsmall" alt="'.get_string('addafter', 'mod_book').'" /></a>';
310             $toc .= (!$ch->subchapter) ? '<ul>' : '</li>';
311             $first = 0;
312         }
313         $toc .= '</ul></li></ul>';
314     } else { // Normal students view
315         $toc .= '<ul>';
316         foreach ($chapters as $ch) {
317             $title = trim(format_string($ch->title, true, array('context'=>$context)));
318             if (!$ch->hidden) {
319                 if (!$ch->subchapter) {
320                     $nch++;
321                     $ns = 0;
322                     $toc .= ($first) ? '<li>' : '</ul></li><li>';
323                     if ($book->numbering == BOOK_NUM_NUMBERS) {
324                           $title = "$nch $title";
325                     }
326                 } else {
327                     $ns++;
328                     $toc .= ($first) ? '<li><ul><li>' : '<li>';
329                     if ($book->numbering == BOOK_NUM_NUMBERS) {
330                           $title = "$nch.$ns $title";
331                     }
332                 }
333                 if ($ch->id == $chapter->id) {
334                     $toc .= '<strong>'.$title.'</strong>';
335                 } else {
336                     $toc .= '<a title="'.s($title).'" href="view.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'">'.$title.'</a>';
337                 }
338                 $toc .= (!$ch->subchapter) ? '<ul>' : '</li>';
339                 $first = 0;
340             }
341         }
342         $toc .= '</ul></li></ul>';
343     }
345     $toc .= '</div>';
347     $toc = str_replace('<ul></ul>', '', $toc); // Cleanup of invalid structures.
349     return $toc;
353 /**
354  * File browsing support class
355  *
356  * @copyright  2010-2011 Petr Skoda {@link http://skodak.org}
357  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
358  */
359 class book_file_info extends file_info {
360     /** @var stdClass Course object */
361     protected $course;
362     /** @var stdClass Course module object */
363     protected $cm;
364     /** @var array Available file areas */
365     protected $areas;
366     /** @var string File area to browse */
367     protected $filearea;
369     /**
370      * Constructor
371      *
372      * @param file_browser $browser file_browser instance
373      * @param stdClass $course course object
374      * @param stdClass $cm course module object
375      * @param stdClass $context module context
376      * @param array $areas available file areas
377      * @param string $filearea file area to browse
378      */
379     public function __construct($browser, $course, $cm, $context, $areas, $filearea) {
380         parent::__construct($browser, $context);
381         $this->course   = $course;
382         $this->cm       = $cm;
383         $this->areas    = $areas;
384         $this->filearea = $filearea;
385     }
387     /**
388      * Returns list of standard virtual file/directory identification.
389      * The difference from stored_file parameters is that null values
390      * are allowed in all fields
391      * @return array with keys contextid, filearea, itemid, filepath and filename
392      */
393     public function get_params() {
394         return array('contextid'=>$this->context->id,
395                      'component'=>'mod_book',
396                      'filearea' =>$this->filearea,
397                      'itemid'   =>null,
398                      'filepath' =>null,
399                      'filename' =>null);
400     }
402     /**
403      * Returns localised visible name.
404      * @return string
405      */
406     public function get_visible_name() {
407         return $this->areas[$this->filearea];
408     }
410     /**
411      * Can I add new files or directories?
412      * @return bool
413      */
414     public function is_writable() {
415         return false;
416     }
418     /**
419      * Is directory?
420      * @return bool
421      */
422     public function is_directory() {
423         return true;
424     }
426     /**
427      * Returns list of children.
428      * @return array of file_info instances
429      */
430     public function get_children() {
431         global $DB;
433         $children = array();
434         $chapters = $DB->get_records('book_chapters', array('bookid'=>$this->cm->instance), 'pagenum', 'id, pagenum');
435         foreach ($chapters as $itemid => $unused) {
436             if ($child = $this->browser->get_file_info($this->context, 'mod_book', $this->filearea, $itemid)) {
437                 $children[] = $child;
438             }
439         }
440         return $children;
441     }
443     /**
444      * Returns parent file_info instance
445      * @return file_info or null for root
446      */
447     public function get_parent() {
448         return $this->browser->get_file_info($this->context);
449     }