lesson MDL-19812 Updated print_header and build_navigation to OUTPUT and PAGE equivalents
[moodle.git] / mod / lesson / importppt.php
1 <?php // $Id$
2 /**
3  * This is a very rough importer for powerpoint slides
4  * Export a powerpoint presentation with powerpoint as html pages
5  * Do it with office 2002 (I think?) and no special settings
6  * Then zip the directory with all of the html pages 
7  * and the zip file is what you want to upload
8  * 
9  * The script supports book and lesson.
10  *
11  * @version $Id$
12  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
13  * @package lesson
14  **/
16     require_once("../../config.php");
17     require_once("locallib.php");
19     $id     = required_param('id', PARAM_INT);         // Course Module ID
20     $pageid = optional_param('pageid', '', PARAM_INT); // Page ID
21     global $matches;
22     
23     if (! $cm = get_coursemodule_from_id('lesson', $id)) {
24         print_error('invalidcoursemodule');
25     }
27     if (! $course = $DB->get_record("course", array("id" => $cm->course))) {
28         print_error('coursemisconf');
29     }
30     
31     // allows for adaption for multiple modules
32     if(! $modname = $DB->get_field('modules', 'name', array('id' => $cm->module))) {
33         print_error('invalidmoduleid', '', '', $cm->module);
34     }
36     if (! $mod = $DB->get_record($modname, array("id" => $cm->instance))) {
37         print_error('invalidcoursemodule');
38     }
40     require_login($course->id, false, $cm);
41     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
42     require_capability('mod/lesson:edit', $context);
44     $strimportppt = get_string("importppt", "lesson");
45     $strlessons = get_string("modulenameplural", "lesson");
47     $PAGE->navbar->add($strimportppt);
48     $PAGE->set_title($strimportppt);
49     $PAGE->set_heading($strimportppt);
50     echo $OUTPUT->header();
52     if ($form = data_submitted()) {   /// Filename
54         if (empty($_FILES['newfile'])) {      // file was just uploaded
55             echo $OUTPUT->notification(get_string("uploadproblem") );
56         }
58         if ((!is_uploaded_file($_FILES['newfile']['tmp_name']) or $_FILES['newfile']['size'] == 0)) {
59             echo $OUTPUT->notification(get_string("uploadnofilefound") );
61         } else {  // Valid file is found
62             
63             if ($rawpages = readdata($_FILES, $course->id, $modname)) {  // first try to reall all of the data in
64                 $pageobjects = extract_data($rawpages, $course->id, $mod->name, $modname); // parse all the html files into objects
65                 clean_temp(); // all done with files so dump em
66                                 
67                 $mod_create_objects = $modname.'_create_objects';  
68                 $mod_save_objects = $modname.'_save_objects'; 
69                 
70                 $objects = $mod_create_objects($pageobjects, $mod->id);  // function to preps the data to be sent to DB
71                 
72                 if(! $mod_save_objects($objects, $mod->id, $pageid)) {  // sends it to DB
73                     print_error('cannotsavedata');
74                 }
75             } else {
76                 print_error('cannotgetdata');
77             }
79             echo "<hr>";
80             echo $OUTPUT->continue_button("$CFG->wwwroot/mod/$modname/view.php?id=$cm->id");
81             echo $OUTPUT->footer();
82             exit;
83         }
84     }
86     /// Print upload form
88     print_heading_with_help($strimportppt, "importppt", "lesson");
90     echo $OUTPUT->box_start('generalbox boxaligncenter');
91     echo "<form id=\"theform\" enctype=\"multipart/form-data\" method=\"post\">";
92     echo "<input type=\"hidden\" name=\"id\" value=\"$cm->id\" />\n";
93     echo "<input type=\"hidden\" name=\"pageid\" value=\"$pageid\" />\n";
94     echo "<table cellpadding=\"5\">";
96     echo "<tr><td align=\"right\">";
97     print_string("upload");
98     echo ":</td><td>";
99     echo "<input name=\"newfile\" type=\"file\" size=\"50\" />";
100     echo "</td></tr><tr><td>&nbsp;</td><td>";
101     echo "<input type=\"submit\" name=\"save\" value=\"".get_string("uploadthisfile")."\" />";
102     echo "</td></tr>";
104     echo "</table>";
105     echo "</form>";
106     echo $OUTPUT->box_end();
108     echo $OUTPUT->footer();
109     
110 // START OF FUNCTIONS
112 function readdata($file, $courseid, $modname) {
113 // this function expects a zip file to be uploaded.  Then it parses
114 // outline.htm to determine the slide path.  Then parses each
115 // slide to get data for the content
117     global $CFG;
119     // create an upload directory in temp
120     make_upload_directory('temp/'.$modname);   
122     $base = $CFG->dataroot."/temp/$modname/";
124     $zipfile = $_FILES["newfile"]["name"];
125     $tempzipfile = $_FILES["newfile"]["tmp_name"];
126     
127     // create our directory
128     $path_parts = pathinfo($zipfile);
129     $dirname = substr($zipfile, 0, strpos($zipfile, '.'.$path_parts['extension'])); // take off the extension
130     if (!file_exists($base.$dirname)) {
131         mkdir($base.$dirname, $CFG->directorypermissions);
132     }
134     // move our uploaded file to temp/lesson
135     move_uploaded_file($tempzipfile, $base.$zipfile);
137     // unzip it!
138     unzip_file($base.$zipfile, $base, false);
139     
140     $base = $base.$dirname;  // update the base
141     
142     // this is the file where we get the names of the files for the slides (in the correct order too)
143     $outline = $base.'/outline.htm';
144     
145     $pages = array();
146     
147     if (file_exists($outline) and is_readable($outline)) {
148         $outlinecontents = file_get_contents($outline);
149         $filenames = array();
150         preg_match_all("/javascript:GoToSld\('(.*)'\)/", $outlinecontents, $filenames);  // this gets all of our files names
152         // file $pages with the contents of all of the slides
153         foreach ($filenames[1] as $file) {
154             $path = $base.'/'.$file;
155             if (is_readable($path)) {
156                 $pages[$path] = file_get_contents($path);
157             } else {
158                 return false;
159             }
160         }        
161     } else {
162         // cannot find the outline, so grab all files that start with slide        
163         $dh  = opendir($base);
164         while (false !== ($file = readdir($dh))) {  // read throug the directory
165            if ('slide' == substr($file, 0, 5)) {  // check for name (may want to check extension later)
166                 $path = $base.'/'.$file;
167                 if (is_readable($path)) {
168                     $pages[$path] = file_get_contents($path);
169                 } else {
170                     return false;
171                 }
172             }
173         }
175         ksort($pages);  // order them by file name
176     }
177     
178     if (empty($pages)) {
179         return false;
180     }
181     
182     return $pages;
185 function extract_data($pages, $courseid, $lessonname, $modname) {
186     // this function attempts to extract the content out of the slides
187     // the slides are ugly broken xml.  and the xml is broken... yeah...
188     
189     global $CFG;
190     global $matches;
192     $extratedpages = array();
193     
194     // directory for images
195     make_upload_directory($courseid.'/moddata/'.$modname, false);  // we store our images in a subfolder in here 
196     
197     $imagedir = $CFG->dataroot.'/'.$courseid.'/moddata/'.$modname;
198     
199     require_once($CFG->libdir .'/filelib.php');
200     $imagelink = get_file_url($courseid.'/moddata/'.$modname);
201     
202     // try to make a unique subfolder to store the images
203     $lessonname = str_replace(' ', '_', $lessonname); // get rid of spaces
204     $i = 0;
205     while(true) {
206         if (!file_exists($imagedir.'/'.$lessonname.$i)) {
207             // ok doesnt exist so make the directory and update our paths
208             mkdir($imagedir.'/'.$lessonname.$i, $CFG->directorypermissions);
209             $imagedir = $imagedir.'/'.$lessonname.$i;
210             $imagelink = $imagelink.'/'.$lessonname.$i;
211             break;
212         }
213         $i++;
214     }
215     
216     foreach ($pages as $file => $content) {
217         // to make life easier on our preg_match_alls, we strip out all tags except
218         // for div and img (where our content is).  We want div because sometimes we
219         // can identify the content in the div based on the div's class
220         
221         $tags = '<div><img>'; // should also allow <b><i>
222         $string = strip_tags($content,$tags);
223         //echo s($string);
225         $matches = array();
226         // this will look for a non nested tag that is closed
227         // want to allow <b><i>(maybe more) tags but when we do that
228         // the preg_match messes up.
229         preg_match_all("/(<([\w]+)[^>]*>)([^<\\2>]*)(<\/\\2>)/", $string, $matches);
230         //(<([\w]+)[^>]*>)([^<\\2>]*)(<\/\\2>)  original pattern
231         //(<(div+)[^>]*>)[^(<div*)](<\/div>) work in progress
233         $path_parts = pathinfo($file);      
234         $file = substr($path_parts['basename'], 0, strpos($path_parts['basename'], '.')); // get rid of the extension
236         $imgs = array();
237         // this preg matches all images
238         preg_match_all("/<img[^>]*(src\=\"(".$file."\_image[^>^\"]*)\"[^>]*)>/i", $string, $imgs);
240         // start building our page
241         $page = new stdClass;
242         $page->title = '';
243         $page->contents = array();
244         $page->images = array();
245         $page->source = $path_parts['basename']; // need for book only
247         // this foreach keeps the style intact.  Found it doesn't help much.  But if you want back uncomment
248         // this foreach and uncomment the line with the comment imgstyle in it.  Also need to comment out
249         // the $page->images[]... line in the next foreach
250         /*foreach ($imgs[1] as $img) { 
251             $page->images[] = '<img '.str_replace('src="', "src=\"$imagelink/", $img).' />';
252         }*/
253         foreach ($imgs[2] as $img) {
254             copy($path_parts['dirname'].'/'.$img, $imagedir.'/'.$img);
255             $page->images[] = "<img src=\"$imagelink/$img\" title=\"$img\" />";  // comment out this line if you are using the above foreach loop
256         }
257         for($i = 0; $i < count($matches[1]); $i++) { // go through all of our div matches
258     
259             $class = isolate_class($matches[1][$i]); // first step in isolating the class      
260         
261             // check for any static classes
262             switch ($class) {
263                 case 'T':  // class T is used for Titles
264                     $page->title = $matches[3][$i];
265                     break;
266                 case 'B':  // I would guess that all bullet lists would start with B then go to B1, B2, etc
267                 case 'B1': // B1-B4 are just insurance, should just hit B and all be taken care of
268                 case 'B2':
269                 case 'B3':
270                 case 'B4':
271                     $page->contents[] = build_list('<ul>', $i, 0);  // this is a recursive function that will grab all the bullets and rebuild the list in html
272                     break;
273                 default:
274                     if ($matches[3][$i] != '&#13;') {  // odd crap generated... sigh
275                         if (substr($matches[3][$i], 0, 1) == ':') {  // check for leading :    ... hate MS ...
276                             $page->contents[] = substr($matches[3][$i], 1);  // get rid of :
277                         } else {
278                             $page->contents[] = $matches[3][$i];
279                         }
280                     }
281                     break;
282             }
283         }
284         /*if (count($page->contents) == 0) {  // didnt find anything, grab everything
285                                             // potential to pull in a lot of crap
286             for($i = 0; $i < count($matches[1]); $i++) {        
287                 //if($class = isolate_class($matches[1][$i])) { 
288                     //if ($class == 'O') {
289                         if ($matches[3][$i] != '&#13;') {  // odd crap generated... sigh
290                             if (substr($matches[3][$i], 0, 1) == ':') {  // check for leading :    ... hate MS ...
291                                 $page->contents[] = substr($matches[3][$i], 1);  // get rid of :
292                             } else {
293                                 $page->contents[] = $matches[3][$i];
294                             }
295                         }
296                     //}
297                 //}
298             }
299         }*/
300         // add the page to the array;
301         $extratedpages[] = $page;
302         
303     } // end $pages foreach loop
304     
305     return $extratedpages;
308 /**
309 A recursive function to build a html list
310 */
311 function build_list($list, &$i, $depth) {
312     global $matches; // not sure why I global this...
313     
314     while($i < count($matches[1])) {
315     
316         $class = isolate_class($matches[1][$i]);
318         if (strstr($class, 'B')) {  // make sure we are still working with bullet classes
319             if ($class == 'B') {
320                 $this_depth = 0;  // calling class B depth 0
321             } else {
322                 // set the depth number.  So B1 is depth 1 and B2 is depth 2 and so on
323                 $this_depth = substr($class, 1);
324                 if (!is_numeric($this_depth)) {
325                     print_error('invalidnum');
326                 }
327             }
328             if ($this_depth < $depth) {
329                 // we are moving back a level in the nesting
330                 break;
331             }
332             if ($this_depth > $depth) {
333                 // we are moving in a lvl in nesting
334                 $list .= '<ul>';
335                 $list = build_list($list, $i, $this_depth);
336                 // once we return back, should go to the start of the while
337                 continue;
338             }
339             // no depth changes, so add the match to our list
340             if ($cleanstring = ppt_clean_text($matches[3][$i])) {
341                 $list .= '<li>'.ppt_clean_text($matches[3][$i]).'</li>';
342             }
343             $i++;
344         } else {
345             // not a B class, so get out of here...
346             break;
347         }
348     }
349     // end the list and return it
350     $list .= '</ul>';
351     return $list;
352     
355 /**
356 Given an html tag, this function will 
357 */
358 function isolate_class($string) {
359     if($class = strstr($string, 'class=')) { // first step in isolating the class
360         $class = substr($class, strpos($class, '=')+1);  // this gets rid of <div blawblaw class=  there are no "" or '' around the class name   ...sigh...
361         if (strstr($class, ' ')) {
362             // spaces found, so cut off everything off after the first space
363             return substr($class, 0, strpos($class, ' '));
364         } else {
365             // no spaces so nothing else in the div tag, cut off the >
366             return substr($class, 0, strpos($class, '>'));
367         }
368     } else {
369         // no class defined in the tag
370         return '';
371     }
374 /**
375 This function strips off the random chars that ppt puts infront of bullet lists
376 */
377 function ppt_clean_text($string) {
378     $chop = 1; // default: just a single char infront of the content
379     
380     // look for any other crazy things that may be infront of the content
381     if (strstr($string, '&lt;') and strpos($string, '&lt;') == 0) {  // look for the &lt; in the sting and make sure it is in the front
382         $chop = 4;  // increase the $chop
383     }
384     // may need to add more later....
385     
386     $string = substr($string, $chop);
387     
388     if ($string != '&#13;') {
389         return $string;
390     } else {
391         return false;
392     }
395 /**
396     Clean up the temp directory
397 */
398 function clean_temp() {
399     global $CFG;
400     // this function is broken, use it to clean up later
401     // should only clean up what we made as well because someone else could be importing ppt as well
402     //delDirContents($CFG->dataroot.'/temp/lesson');    
405 /**
406     Creates objects an object with the page and answers that are to be inserted into the database
407 */
408 function lesson_create_objects($pageobjects, $lessonid) {
410     $branchtables = array();
411     $branchtable = new stdClass;
412     
413     // all pages have this info
414     $page->lessonid = $lessonid;
415     $page->prevpageid = 0;
416     $page->nextpageid = 0;
417     $page->qtype = LESSON_BRANCHTABLE;
418     $page->qoption = 0;
419     $page->layout = 1;
420     $page->display = 1;
421     $page->timecreated = time();
422     $page->timemodified = 0;
423     
424     // all answers are the same
425     $answer->lessonid = $lessonid;
426     $answer->jumpto = LESSON_NEXTPAGE;
427     $answer->grade = 0;
428     $answer->score = 0;
429     $answer->flags = 0;
430     $answer->timecreated = time();
431     $answer->timemodified = 0;
432     $answer->answer = "Next";
433     $answer->response = "";
435     $answers[] = clone($answer);
437     $answer->jumpto = LESSON_PREVIOUSPAGE;
438     $answer->answer = "Previous";
439     
440     $answers[] = clone($answer);
441     
442     $branchtable->answers = $answers;
443     
444     $i = 1;
445     
446     foreach ($pageobjects as $pageobject) {     
447         $temp = prep_page($pageobject, $i);  // makes our title and contents
448         $page->title = $temp->title;
449         $page->contents = $temp->contents;
450         $branchtable->page = clone($page);  // add the page
451         $branchtables[] = clone($branchtable);  // add it all to our array
452         $i++;
453     }
455     return $branchtables;
458 /**
459     Creates objects an chapter object that is to be inserted into the database
460 */
461 function book_create_objects($pageobjects, $bookid) {
462     global $DB;
464     $chapters = array();
465     $chapter = new stdClass;
466     
467     // same for all chapters
468     $chapter->bookid = $bookid;
469     $chapter->pagenum = $DB->count_records('book_chapters', array('bookid'=>$bookid))+1;
470     $chapter->timecreated = time();
471     $chapter->timemodified = time();
472     $chapter->subchapter = 0;
474     $i = 1; 
475     foreach ($pageobjects as $pageobject) {
476         $page = prep_page($pageobject, $i);  // get title and contents
477         $chapter->importsrc = $pageobject->source; // add the source
478         $chapter->title = $page->title;
479         $chapter->content = $page->contents;
480         $chapters[] = $chapter; 
481         
482         // increment our page number and our counter
483         $chapter->pagenum = $chapter->pagenum + 1;
484         $i++;
485     }
487     return $chapters;
490 /**
491     Builds the title and content strings from an object
492 */
493 function prep_page($pageobject, $count) {
494     if ($pageobject->title == '') {
495         $page->title = "Page $count";  // no title set so make a generic one
496     } else {
497         $page->title = $pageobject->title;      
498     }
499     
500     $page->contents = '';
501     
502     // nab all the images first
503     foreach ($pageobject->images as $image) {
504         $image = str_replace("\n", '', $image);
505         $image = str_replace("\r", '', $image);
506         $image = str_replace("'", '"', $image);  // imgstyle
507                     
508         $page->contents .= $image;
509     }
510     // go through the contents array and put <p> tags around each element and strip out \n which I have found to be uneccessary
511     foreach ($pageobject->contents as $content) {
512         $content = str_replace("\n", '', $content);
513         $content = str_replace("\r", '', $content);
514         $content = str_replace('&#13;', '', $content);  // puts in returns?
515         $content = '<p>'.$content.'</p>';
516         $page->contents .= $content;
517     }
518     return $page;
521 /**
522     Saves the branchtable objects to the DB
523 */
524 function lesson_save_objects($branchtables, $lessonid, $after) {
525     global $DB;
526     
527     // first set up the prevpageid and nextpageid
528     if ($after == 0) { // adding it to the top of the lesson
529         $prevpageid = 0;
530         // get the id of the first page.  If not found, then no pages in the lesson
531         if (!$nextpageid = $DB->get_field('lesson_pages', 'id', array('prevpageid' => 0, 'lessonid' => $lessonid))) {
532             $nextpageid = 0;
533         }
534     } else {
535         // going after an actual page
536         $prevpageid = $after;
537         $nextpageid = $DB->get_field('lesson_pages', 'nextpageid', array('id' => $after));
538     }
539     
540     foreach ($branchtables as $branchtable) {
541         
542         // set the doubly linked list
543         $branchtable->page->nextpageid = $nextpageid;
544         $branchtable->page->prevpageid = $prevpageid;
545         
546         // insert the page
547         $id = $DB->insert_record('lesson_pages', $branchtable->page);
548     
549         // update the link of the page previous to the one we just updated
550         if ($prevpageid != 0) {  // if not the first page
551             $DB->set_field("lesson_pages", "nextpageid", $id, array("id" => $prevpageid));
552         }
554         // insert the answers
555         foreach ($branchtable->answers as $answer) {
556             $answer->pageid = $id;
557             $DB->insert_record('lesson_answers', $answer);
558         }
559         
560         $prevpageid = $id;
561     }
562     
563     // all done with inserts.  Now check to update our last page (this is when we import between two lesson pages)
564     if ($nextpageid != 0) {  // if the next page is not the end of lesson
565         $DB->set_field("lesson_pages", "prevpageid", $id, array("id" => $nextpageid));
566     }
567     
568     return true;
571 /**
572     Save the chapter objects to the database
573 */
574 function book_save_objects($chapters, $bookid, $pageid='0') {
575     global $DB;
576     
577     // nothing fancy, just save them all in order
578     foreach ($chapters as $chapter) {
579         $chapter->id = $DB->insert_record('book_chapters', $chapter);
580     }
581     return true;
584 ?>