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