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