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