add numbering default setting
[moodle.git] / locallib.php
CommitLineData
e355240d
PS
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/>.
16
17/**
18 * Book module local lib functions
19 *
20 * @package mod
21 * @subpackage book
ad1c11bd 22 * @copyright 2010-2011 Petr Skoda {@link http://skodak.org}
e355240d
PS
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die;
27
f6ca19ae
PS
28require_once($CFG->dirroot.'/mod/book/lib.php');
29require_once($CFG->libdir.'/filelib.php');
30
2c1e98e6 31/**
ad1c11bd
PS
32 * Preload book chapters and fix toc structure if necessary.
33 *
34 * Returns array of chapters with standard 'pagenum', 'id, pagenum, subchapter, title, hidden'
35 * and extra 'parent, number, subchapters, prev, next'.
36 * Please note the content/text of chapters is not included.
37 *
38 * @param $book
39 * @param $cm
40 * @return array of id=>chapter
2c1e98e6 41 */
ad1c11bd 42function book_preload_chapters($bookid) {
2c1e98e6 43 global $DB;
ad1c11bd
PS
44 $chapters = $DB->get_records('book_chapters', array('bookid'=>$bookid), 'pagenum', 'id, pagenum, subchapter, title, hidden');
45 if (!$chapters) {
46 return array();
47 }
48
49 $prev = null;
50 $prevsub = null;
2c1e98e6 51
ad1c11bd
PS
52 $first = true;
53 $hidesub = true;
54 $parent = null;
55 $pagenum = 0; // chapter sort
56 $i = 0; // main chapter num
57 $j = 0; // subchapter num
58 foreach($chapters as $id=>$ch) {
59 $oldch = clone($ch);
60 $pagenum++;
61 $ch->pagenum = $pagenum;
62 if ($first) {
63 // book can not start with a subchapter
64 $ch->subchapter = 0;
65 $first = false;
66 }
67 if (!$ch->subchapter) {
68 $ch->prev = $prev;
69 $ch->next = null;
70 if ($prev) {
71 $chapters[$prev]->next = $ch->id;
72 }
73 if ($ch->hidden) {
74 $ch->number = null;
75 } else {
76 $i++;
77 $ch->number = $i;
78 }
79 $j = 0;
80 $prevsub = null;
81 $hidesub = $ch->hidden;
82 $parent = $ch->id;
83 $ch->parent = null;
84 $ch->subchpaters = array();
85 } else {
86 $ch->prev = $prevsub;
87 $ch->next = null;
88 if ($prevsub) {
89 $chapters[$prevsub]->next = $ch->id;
e355240d 90 }
ad1c11bd
PS
91 $ch->parent = $parent;
92 $ch->subchpaters = null;
93 $chapters[$parent]->subchapters[$ch->id] = $ch->id;
94 if ($hidesub) {
95 // all subchapters in hidden chapter must be hidden too
bc953600 96 $ch->hidden = 1;
e355240d 97 }
ad1c11bd
PS
98 if ($ch->hidden) {
99 $ch->number = null;
100 } else {
101 $j++;
102 $ch->number = $j;
23b02c4f 103 }
e355240d 104 }
ad1c11bd
PS
105 if ($oldch->subchapter != $ch->subchapter or $oldch->pagenum != $ch->pagenum or $oldch->hidden != $ch->hidden) {
106 // update only if something changed
107 $DB->update_record('book_chapters', $ch);
108 }
109 $chapters[$id] = $ch;
e355240d 110 }
ad1c11bd
PS
111
112 return $chapters;
e355240d
PS
113}
114
f6ca19ae
PS
115/**
116 * General logging to table
117 * @param string $str1
118 * @param string $str2
119 * @param int $level
120 * @return void
121 */
e355240d
PS
122function book_log($str1, $str2, $level = 0) {
123 switch ($level) {
124 case 1:
125 echo '<tr><td><span class="dimmed_text">'.$str1.'</span></td><td><span class="dimmed_text">'.$str2.'</span></td></tr>';
126 break;
127 case 2:
128 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>';
129 break;
130 default:
131 echo '<tr><td>'.$str1.'</class></td><td>'.$str2.'</td></tr>';
132 break;
133 }
134}
135
ad1c11bd
PS
136function book_add_fake_block($chapters, $chapter, $book, $cm, $edit) {
137 global $OUTPUT, $PAGE;
138
139 list($toc, $currtitle, $currsubtitle, $titles) = book_get_toc($chapters, $chapter, $book, $cm, $edit, 0);
140
141 if ($edit) {
142 $toc .= '<div class="faq">';
143 $toc .= $OUTPUT->help_icon('faq', 'mod_book', get_string('faq', 'mod_book'));
144 $toc .= '</div>';
145 }
146
147 $bc = new block_contents();
148 $bc->title = get_string('toc', 'mod_book');
149 $bc->attributes['class'] = 'block';
150 $bc->content = $toc;
151
152 $regions = $PAGE->blocks->get_regions();
153 $firstregion = reset($regions);
154 $PAGE->blocks->add_fake_block($bc, $firstregion);
155}
156
f6ca19ae
PS
157/**
158 * Generate toc structure and titles
159 *
160 * @param array $chapters
161 * @param stdClass $chapter
162 * @param stdClass $book
163 * @param stdClass $cm
164 * @param bool $edit
165 * @param bool $print
166 * @return array
167 */
168function book_get_toc($chapters, $chapter, $book, $cm, $edit, $print) {
169 global $USER, $OUTPUT;
170
171 $currtitle = ''; //active chapter title (plain text)
172 $currsubtitle = ''; //active subchapter if any
173 $prevtitle = '&nbsp;';
174 $toc = ''; //representation of toc (HTML)
175
176 $nch = 0; //chapter number
177 $ns = 0; //subchapter number
178 $first = 1;
179 $titles = array();
180
dc888a31
PS
181 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
182
f6ca19ae
PS
183 switch ($book->numbering) {
184 case BOOK_NUM_NONE:
185 $toc .= '<div class="book_toc_none">';
186 break;
187 case BOOK_NUM_NUMBERS:
188 $toc .= '<div class="book_toc_numbered">';
189 break;
190 case BOOK_NUM_BULLETS:
191 $toc .= '<div class="book_toc_bullets">';
192 break;
193 case BOOK_NUM_INDENTED:
194 $toc .= '<div class="book_toc_indented">';
195 break;
196 }
197
198
199 if ($print) { ///TOC for printing
200 $toc .= '<a name="toc"></a>';
201 if ($book->customtitles) {
202 $toc .= '<h1>'.get_string('toc', 'book').'</h1>';
203 } else {
204 $toc .= '<p class="book_chapter_title">'.get_string('toc', 'book').'</p>';
205 }
206 $toc .= '<ul>';
207 foreach($chapters as $ch) {
dc888a31 208 $title = trim(format_string($ch->title, true, array('context'=>$context)));
f6ca19ae
PS
209 if (!$ch->hidden) {
210 if (!$ch->subchapter) {
211 $nch++;
212 $ns = 0;
213 $toc .= ($first) ? '<li>' : '</ul></li><li>';
214 if ($book->numbering == BOOK_NUM_NUMBERS) {
215 $title = "$nch $title";
216 }
217 } else {
218 $ns++;
219 $toc .= ($first) ? '<li><ul><li>' : '<li>';
220 if ($book->numbering == BOOK_NUM_NUMBERS) {
221 $title = "$nch.$ns $title";
222 }
223 }
224 $titles[$ch->id] = $title;
225 $toc .= '<a title="'.s($title).'" href="#ch'.$ch->id.'">'.$title.'</a>';
226 $toc .= (!$ch->subchapter) ? '<ul>' : '</li>';
227 $first = 0;
228 }
229 }
230 $toc .= '</ul></li></ul>';
231 } else if ($edit) { ///teacher's TOC
232 $toc .= '<ul>';
233 $i = 0;
234 foreach($chapters as $ch) {
235 $i++;
dc888a31 236 $title = trim(format_string($ch->title, true, array('context'=>$context)));
f6ca19ae
PS
237 if (!$ch->subchapter) {
238 $toc .= ($first) ? '<li>' : '</ul></li><li>';
239 if (!$ch->hidden) {
240 $nch++;
241 $ns = 0;
242 if ($book->numbering == BOOK_NUM_NUMBERS) {
243 $title = "$nch $title";
244 }
245 } else {
246 if ($book->numbering == BOOK_NUM_NUMBERS) {
247 $title = "x $title";
248 }
249 $title = '<span class="dimmed_text">'.$title.'</span>';
250 }
251 $prevtitle = $title;
252 } else {
253 $toc .= ($first) ? '<li><ul><li>' : '<li>';
254 if (!$ch->hidden) {
255 $ns++;
256 if ($book->numbering == BOOK_NUM_NUMBERS) {
257 $title = "$nch.$ns $title";
258 }
259 } else {
260 if ($book->numbering == BOOK_NUM_NUMBERS) {
261 $title = "x.x $title";
262 }
263 $title = '<span class="dimmed_text">'.$title.'</span>';
264 }
265 }
266
267 if ($ch->id == $chapter->id) {
268 $toc .= '<strong>'.$title.'</strong>';
269 if ($ch->subchapter) {
270 $currtitle = $prevtitle;
271 $currsubtitle = $title;
272 } else {
273 $currtitle = $title;
274 $currsubtitle = '&nbsp;';
275 }
276 } else {
277 $toc .= '<a title="'.s($title).'" href="view.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'">'.$title.'</a>';
278 }
279 $toc .= '&nbsp;&nbsp;';
280 if ($i != 1) {
281 $toc .= ' <a title="'.get_string('up').'" href="move.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'&amp;up=1&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/up').'" class="iconsmall" alt="'.get_string('up').'" /></a>';
282 }
283 if ($i != count($chapters)) {
284 $toc .= ' <a title="'.get_string('down').'" href="move.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'&amp;up=0&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/down').'" class="iconsmall" alt="'.get_string('down').'" /></a>';
285 }
286 $toc .= ' <a title="'.get_string('edit').'" href="edit.php?cmid='.$cm->id.'&amp;id='.$ch->id.'"><img src="'.$OUTPUT->pix_url('t/edit').'" class="iconsmall" alt="'.get_string('edit').'" /></a>';
287 $toc .= ' <a title="'.get_string('delete').'" href="delete.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/delete').'" class="iconsmall" alt="'.get_string('delete').'" /></a>';
288 if ($ch->hidden) {
289 $toc .= ' <a title="'.get_string('show').'" href="show.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/show').'" class="iconsmall" alt="'.get_string('show').'" /></a>';
290 } else {
291 $toc .= ' <a title="'.get_string('hide').'" href="show.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'&amp;sesskey='.$USER->sesskey.'"><img src="'.$OUTPUT->pix_url('t/hide').'" class="iconsmall" alt="'.get_string('hide').'" /></a>';
292 }
293 $toc .= ' <a title="'.get_string('addafter', 'book').'" href="edit.php?cmid='.$cm->id.'&amp;pagenum='.$ch->pagenum.'&amp;subchapter='.$ch->subchapter.'"><img src="'.$OUTPUT->pix_url('add', 'mod_book').'" class="iconsmall" alt="'.get_string('addafter', 'book').'" /></a>';
294
295 $toc .= (!$ch->subchapter) ? '<ul>' : '</li>';
296 $first = 0;
297 }
298 $toc .= '</ul></li></ul>';
299 } else { //normal students view
300 $toc .= '<ul>';
301 foreach($chapters as $ch) {
dc888a31 302 $title = trim(format_string($ch->title, true, array('context'=>$context)));
f6ca19ae
PS
303 if (!$ch->hidden) {
304 if (!$ch->subchapter) {
305 $nch++;
306 $ns = 0;
307 $toc .= ($first) ? '<li>' : '</ul></li><li>';
308 if ($book->numbering == BOOK_NUM_NUMBERS) {
309 $title = "$nch $title";
310 }
311 $prevtitle = $title;
312 } else {
313 $ns++;
314 $toc .= ($first) ? '<li><ul><li>' : '<li>';
315 if ($book->numbering == BOOK_NUM_NUMBERS) {
316 $title = "$nch.$ns $title";
317 }
318 }
319 if ($ch->id == $chapter->id) {
320 $toc .= '<strong>'.$title.'</strong>';
321 if ($ch->subchapter) {
322 $currtitle = $prevtitle;
323 $currsubtitle = $title;
324 } else {
325 $currtitle = $title;
326 $currsubtitle = '&nbsp;';
327 }
328 } else {
329 $toc .= '<a title="'.s($title).'" href="view.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'">'.$title.'</a>';
330 }
331 $toc .= (!$ch->subchapter) ? '<ul>' : '</li>';
332 $first = 0;
333 }
334 }
335 $toc .= '</ul></li></ul>';
336 }
337
338 $toc .= '</div>';
339
340 $toc = str_replace('<ul></ul>', '', $toc); //cleanup of invalid structures
341
342 return array($toc, $currtitle, $currsubtitle, $titles);
343}
344
345
e355240d 346//=================================================
f6ca19ae 347// import functions - not converted yet!
e355240d
PS
348//=================================================
349
350/// normalize relative links (= remove ..)
351function book_prepare_link($ref) {
352 if ($ref == '') {
353 return '';
354 }
355 $ref = str_replace('\\','/',$ref); //anti MS hack
356 $cnt = substr_count($ref, '..');
357 for($i=0; $i<$cnt; $i++) {
358 $ref = ereg_replace('[^/]+/\.\./', '', $ref);
359 }
360 //still any '..' left?? == error! error!
361 if (substr_count($ref, '..') > 0) {
362 return '';
363 }
364 if (ereg('[\|\`]', $ref)) { // check for other bad characters
365 return '';
366 }
367 return $ref;
368}
369
370/// read chapter content from file
371function book_read_chapter($base, $ref) {
372 $file = $base.'/'.$ref;
373 if (filesize($file) <= 0 or !is_readable($file)) {
374 book_log($ref, get_string('error'), 2);
375 return;
376 }
377 //first read data
378 $handle = fopen($file, "rb");
379 $contents = fread($handle, filesize($file));
380 fclose($handle);
381 //extract title
382 $chapter = new object();
383 if (preg_match('/<title>([^<]+)<\/title>/i', $contents, $matches)) {
384 $chapter->title = $matches[1];
385 } else {
386 $chapter->title = $ref;
387 }
388 //extract page body
389 if (preg_match('/<body[^>]*>(.+)<\/body>/is', $contents, $matches)) {
390 $chapter->content = $matches[1];
391 } else {
392 book_log($ref, get_string('error'), 2);
393 return;
394 }
395 book_log($ref, get_string('ok'));
396 $chapter->importsrc = $ref;
397 //extract page head
398 if (preg_match('/<head[^>]*>(.+)<\/head>/is', $contents, $matches)) {
399 $head = $matches[1];
400 if (preg_match('/charset=([^"]+)/is', $head, $matches)) {
401 $enc = $matches[1];
402 $textlib = textlib_get_instance();
32230603
PS
403 $chapter->content = $textlib->convert($chapter->content, $enc, 'utf-8');
404 $chapter->title = $textlib->convert($chapter->title, $enc, 'utf-8');
e355240d
PS
405 }
406 if (preg_match_all('/<link[^>]+rel="stylesheet"[^>]*>/i', $head, $matches)) { //dlnsk extract links to css
407 for($i=0; $i<count($matches[0]); $i++){
408 $chapter->content = $matches[0][$i]."\n".$chapter->content;
409 }
410 }
411 }
412 return $chapter;
413}
414
415///relink images and relative links
416function book_relink($id, $bookid, $courseid) {
2c1e98e6
PS
417 global $CFG, $DB;
418
e355240d
PS
419 if ($CFG->slasharguments) {
420 $coursebase = $CFG->wwwroot.'/file.php/'.$courseid;
421 } else {
422 $coursebase = $CFG->wwwroot.'/file.php?file=/'.$courseid;
423 }
2c1e98e6 424 $chapters = $DB->get_records('book_chapters', array('bookid'=>$bookid), 'pagenum', 'id, pagenum, title, content, importsrc');
e355240d
PS
425 $originals = array();
426 foreach($chapters as $ch) {
427 $originals[$ch->importsrc] = $ch;
428 }
429 foreach($chapters as $ch) {
430 $rel = substr($ch->importsrc, 0, strrpos($ch->importsrc, '/')+1);
431 $base = $coursebase.strtr(urlencode($rel), array("%2F" => "/")); //for better internationalization (dlnsk)
432 $modified = false;
433 //image relinking
434 if ($ch->importsrc && preg_match_all('/(<img[^>]+src=")([^"]+)("[^>]*>)/i', $ch->content, $images)) {
435 for($i = 0; $i<count($images[0]); $i++) {
436 if (!preg_match('/[a-z]+:/i', $images[2][$i])) { // not absolute link
437 $link = book_prepare_link($base.$images[2][$i]);
438 if ($link == '') {
439 continue;
440 }
441 $origtag = $images[0][$i];
442 $newtag = $images[1][$i].$link.$images[3][$i];
443 $ch->content = str_replace($origtag, $newtag, $ch->content);
444 $modified = true;
445 book_log($ch->title, $images[2][$i].' --> '.$link);
446 }
447 }
448 }
449 //css relinking (dlnsk)
450 if ($ch->importsrc && preg_match_all('/(<link[^>]+href=")([^"]+)("[^>]*>)/i', $ch->content, $csslinks)) {
451 for($i = 0; $i<count($csslinks[0]); $i++) {
452 if (!preg_match('/[a-z]+:/i', $csslinks[2][$i])) { // not absolute link
453 $link = book_prepare_link($base.$csslinks[2][$i]);
454 if ($link == '') {
455 continue;
456 }
457 $origtag = $csslinks[0][$i];
458 $newtag = $csslinks[1][$i].$link.$csslinks[3][$i];
459 $ch->content = str_replace($origtag, $newtag, $ch->content);
460 $modified = true;
461 book_log($ch->title, $csslinks[2][$i].' --> '.$link);
462 }
463 }
464 }
465 //general embed relinking - flash and others??
466 if ($ch->importsrc && preg_match_all('/(<embed[^>]+src=")([^"]+)("[^>]*>)/i', $ch->content, $embeds)) {
467 for($i = 0; $i<count($embeds[0]); $i++) {
468 if (!preg_match('/[a-z]+:/i', $embeds[2][$i])) { // not absolute link
469 $link = book_prepare_link($base.$embeds[2][$i]);
470 if ($link == '') {
471 continue;
472 }
473 $origtag = $embeds[0][$i];
474 $newtag = $embeds[1][$i].$link.$embeds[3][$i];
475 $ch->content = str_replace($origtag, $newtag, $ch->content);
476 $modified = true;
477 book_log($ch->title, $embeds[2][$i].' --> '.$link);
478 }
479 }
480 }
481 //flash in IE <param name=movie value="something" - I do hate IE!
482 if ($ch->importsrc && preg_match_all('/<param[^>]+name\s*=\s*"?movie"?[^>]*>/i', $ch->content, $params)) {
483 for($i = 0; $i<count($params[0]); $i++) {
484 if (preg_match('/(value=\s*")([^"]+)(")/i', $params[0][$i], $values)) {
485 if (!preg_match('/[a-z]+:/i', $values[2])) { // not absolute link
486 $link = book_prepare_link($base.$values[2]);
487 if ($link == '') {
488 continue;
489 }
490 $newvalue = $values[1].$link.$values[3];
491 $newparam = str_replace($values[0], $newvalue, $params[0][$i]);
492 $ch->content = str_replace($params[0][$i], $newparam, $ch->content);
493 $modified = true;
494 book_log($ch->title, $values[2].' --> '.$link);
495 }
496 }
497 }
498 }
499 //java applet - add code bases if not present!!!!
500 if ($ch->importsrc && preg_match_all('/<applet[^>]*>/i', $ch->content, $applets)) {
501 for($i = 0; $i<count($applets[0]); $i++) {
502 if (!stripos($applets[0][$i], 'codebase')) {
503 $newapplet = str_ireplace('<applet', '<applet codebase="."', $applets[0][$i]);
504 $ch->content = str_replace($applets[0][$i], $newapplet, $ch->content);
505 $modified = true;
506 }
507 }
508 }
509 //relink java applet code bases
510 if ($ch->importsrc && preg_match_all('/(<applet[^>]+codebase=")([^"]+)("[^>]*>)/i', $ch->content, $codebases)) {
511 for($i = 0; $i<count($codebases[0]); $i++) {
512 if (!preg_match('/[a-z]+:/i', $codebases[2][$i])) { // not absolute link
513 $link = book_prepare_link($base.$codebases[2][$i]);
514 if ($link == '') {
515 continue;
516 }
517 $origtag = $codebases[0][$i];
518 $newtag = $codebases[1][$i].$link.$codebases[3][$i];
519 $ch->content = str_replace($origtag, $newtag, $ch->content);
520 $modified = true;
521 book_log($ch->title, $codebases[2][$i].' --> '.$link);
522 }
523 }
524 }
525 //relative link conversion
526 if ($ch->importsrc && preg_match_all('/(<a\s[^>]*href=")([^"^#]*)(#[^"]*)?("[^>]*>)/i', $ch->content, $links)) {
527 for($i = 0; $i<count($links[0]); $i++) {
528 if ($links[2][$i] != '' //check for inner anchor links
529 && !preg_match('/[a-z]+:/i', $links[2][$i])) { //not absolute link
530 $origtag = $links[0][$i];
531 $target = book_prepare_link($rel.$links[2][$i]); //target chapter
532 if ($target != '' && array_key_exists($target, $originals)) {
533 $o = $originals[$target];
534 $newtag = $links[1][$i].$CFG->wwwroot.'/mod/book/view.php?id='.$id.'&chapterid='.$o->id.$links[3][$i].$links[4][$i];
535 $newtag = preg_replace('/target=[^\s>]/i','', $newtag);
536 $ch->content = str_replace($origtag, $newtag, $ch->content);
537 $modified = true;
538 book_log($ch->title, $links[2][$i].$links[3][$i].' --> '.$CFG->wwwroot.'/mod/book/view.php?id='.$id.'&chapterid='.$o->id.$links[3][$i]);
539 } else if ($target!='' && (!preg_match('/\.html$|\.htm$/i', $links[2][$i]))) { // other relative non html links converted to download links
540 $target = book_prepare_link($base.$links[2][$i]);
541 $origtag = $links[0][$i];
542 $newtag = $links[1][$i].$target.$links[4][$i];
543 $ch->content = str_replace($origtag, $newtag, $ch->content);
544 $modified = true;
545 book_log($ch->title, $links[2][$i].' --> '.$target);
546 }
547 }
548 }
549 }
550 if ($modified) {
2c1e98e6 551 $DB->update_record('book_chapters', $ch);
e355240d
PS
552 }
553 }
554}