Merge branch 'wip-files23-fixes' of git://github.com/marinaglancy/moodle
authorDan Poltawski <dan@moodle.com>
Tue, 22 May 2012 06:32:35 +0000 (14:32 +0800)
committerDan Poltawski <dan@moodle.com>
Tue, 22 May 2012 06:32:35 +0000 (14:32 +0800)
110 files changed:
admin/repository.php
admin/repositoryinstance.php
admin/settings/development.php
admin/settings/plugins.php
admin/timezone.php
backup/util/helper/convert_helper.class.php
course/dndupload.js
course/dnduploadlib.php
course/format/renderer.php
course/format/topics/format.js
course/format/topics/renderer.php
course/format/weeks/format.js
course/yui/coursebase/coursebase.js
course/yui/dragdrop/dragdrop.js
course/yui/toolboxes/toolboxes.js
enrol/mnet/enrol.php
lang/en/admin.php
lang/en/moodle.php
lib/filelib.php
lib/filestorage/stored_file.php
lib/form/filemanager.js
lib/medialib.php
lib/navigationlib.php
lib/pluginlib.php
lib/tests/medialib_test.php
lib/weblib.php
mod/assign/gradingbatchoperationsform.php
mod/assign/gradingoptionsform.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/locallib.php
mod/assign/module.js
mod/assign/quickgradingform.php [new file with mode: 0644]
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/styles.css
mod/book/README.md [new file with mode: 0644]
mod/book/backup/moodle1/lib.php [new file with mode: 0755]
mod/book/backup/moodle2/backup_book_activity_task.class.php [new file with mode: 0644]
mod/book/backup/moodle2/backup_book_settingslib.php [new file with mode: 0644]
mod/book/backup/moodle2/backup_book_stepslib.php [new file with mode: 0644]
mod/book/backup/moodle2/restore_book_activity_task.class.php [new file with mode: 0644]
mod/book/backup/moodle2/restore_book_stepslib.php [new file with mode: 0644]
mod/book/db/access.php [new file with mode: 0644]
mod/book/db/install.xml [new file with mode: 0644]
mod/book/db/log.php [new file with mode: 0644]
mod/book/db/subplugins.php [new file with mode: 0644]
mod/book/db/upgrade.php [new file with mode: 0644]
mod/book/delete.php [new file with mode: 0644]
mod/book/edit.php [new file with mode: 0644]
mod/book/edit_form.php [new file with mode: 0644]
mod/book/index.php [new file with mode: 0644]
mod/book/lang/en/book.php [new file with mode: 0644]
mod/book/lib.php [new file with mode: 0644]
mod/book/locallib.php [new file with mode: 0644]
mod/book/mod_form.php [new file with mode: 0644]
mod/book/move.php [new file with mode: 0644]
mod/book/pix/add.png [new file with mode: 0644]
mod/book/pix/chapter.png [new file with mode: 0644]
mod/book/pix/icon.png [new file with mode: 0644]
mod/book/pix/nav_exit.png [new file with mode: 0644]
mod/book/pix/nav_next.png [new file with mode: 0644]
mod/book/pix/nav_prev.png [new file with mode: 0644]
mod/book/pix/nav_prev_dis.png [new file with mode: 0644]
mod/book/pix/nav_sep.png [new file with mode: 0644]
mod/book/settings.php [new file with mode: 0644]
mod/book/show.php [new file with mode: 0644]
mod/book/styles.css [new file with mode: 0644]
mod/book/tool/exportimscp/db/access.php [new file with mode: 0644]
mod/book/tool/exportimscp/db/log.php [new file with mode: 0644]
mod/book/tool/exportimscp/imscp.css [new file with mode: 0644]
mod/book/tool/exportimscp/index.php [new file with mode: 0644]
mod/book/tool/exportimscp/lang/en/booktool_exportimscp.php [new file with mode: 0644]
mod/book/tool/exportimscp/lib.php [new file with mode: 0644]
mod/book/tool/exportimscp/locallib.php [new file with mode: 0644]
mod/book/tool/exportimscp/pix/generate.png [new file with mode: 0644]
mod/book/tool/exportimscp/version.php [new file with mode: 0644]
mod/book/tool/importhtml/db/access.php [new file with mode: 0644]
mod/book/tool/importhtml/import_form.php [new file with mode: 0644]
mod/book/tool/importhtml/index.php [new file with mode: 0644]
mod/book/tool/importhtml/lang/en/booktool_importhtml.php [new file with mode: 0644]
mod/book/tool/importhtml/lib.php [new file with mode: 0644]
mod/book/tool/importhtml/locallib.php [new file with mode: 0644]
mod/book/tool/importhtml/version.php [new file with mode: 0644]
mod/book/tool/print/db/access.php [new file with mode: 0644]
mod/book/tool/print/db/log.php [new file with mode: 0644]
mod/book/tool/print/index.php [new file with mode: 0644]
mod/book/tool/print/lang/en/booktool_print.php [new file with mode: 0644]
mod/book/tool/print/lib.php [new file with mode: 0644]
mod/book/tool/print/locallib.php [new file with mode: 0644]
mod/book/tool/print/pix/book.png [new file with mode: 0644]
mod/book/tool/print/pix/chapter.png [new file with mode: 0644]
mod/book/tool/print/print.css [new file with mode: 0644]
mod/book/tool/print/version.php [new file with mode: 0644]
mod/book/version.php [new file with mode: 0644]
mod/book/view.php [new file with mode: 0644]
mod/folder/edit_form.php
mod/forum/lib.php
repository/filepicker.js
repository/repository_ajax.php
repository/url/db/install.php [new file with mode: 0644]
repository/wikimedia/db/install.php [new file with mode: 0644]
repository/youtube/db/install.php [new file with mode: 0644]
theme/afterburner/style/afterburner_styles.css
theme/base/style/core.css
theme/base/style/course.css
theme/formal_white/config.php
theme/mymobile/layout/embedded.php
user/filesedit.php
user/filesedit_form.php

index a3af2dd..b3494ca 100644 (file)
@@ -21,7 +21,7 @@ require_once($CFG->libdir . '/adminlib.php');
 $repository       = optional_param('repos', '', PARAM_ALPHANUMEXT);
 $action           = optional_param('action', '', PARAM_ACTION);
 $sure             = optional_param('sure', '', PARAM_ALPHA);
-$downloadcontents = optional_param('downloadcontents', '', PARAM_ALPHA);
+$downloadcontents = optional_param('downloadcontents', false, PARAM_BOOL);
 
 $display = true; // fall through to normal display
 
@@ -205,12 +205,6 @@ if (($action == 'edit') || ($action == 'new')) {
             print_error('confirmsesskeybad', '', $baseurl);
         }
 
-        if (!empty($downloadcontents) and $downloadcontents == 'yes') {
-            $downloadcontents = true;
-        } else {
-            $downloadcontents = false;
-        }
-
         if ($repositorytype->delete($downloadcontents)) {
             redirect($baseurl);
         } else {
@@ -237,7 +231,7 @@ if (($action == 'edit') || ($action == 'new')) {
             'action' =>'delete',
             'repos'=> $repository,
             'sure' => 'yes',
-            'downloadcontents' => 'yes',
+            'downloadcontents' => 1,
         ));
 
         $output .= $OUTPUT->single_button($removeurl, get_string('continueuninstall', 'repository'));
index f2b6e7f..fe75146 100644 (file)
@@ -27,7 +27,7 @@ $hide    = optional_param('hide', 0, PARAM_INT);
 $delete  = optional_param('delete', 0, PARAM_INT);
 $sure    = optional_param('sure', '', PARAM_ALPHA);
 $type    = optional_param('type', '', PARAM_PLUGIN);
-$downloadcontents = optional_param('downloadcontents', '', PARAM_ALPHA);
+$downloadcontents = optional_param('downloadcontents', false, PARAM_BOOL);
 
 $context = context_system::instance();
 
@@ -123,11 +123,6 @@ if (!empty($edit) || !empty($new)) {
             throw new repository_exception('readonlyinstance', 'repository');
      }
     if ($sure) {
-        if (!empty($downloadcontents) and $downloadcontents == 'yes') {
-            $downloadcontents = true;
-        } else {
-            $downloadcontents = false;
-        }
         if ($instance->delete($downloadcontents)) {
             $deletedstr = get_string('instancedeleted', 'repository');
             redirect($parenturl, $deletedstr, 3);
@@ -145,7 +140,7 @@ if (!empty($edit) || !empty($new)) {
         'sure' => 'yes',
     ));
     $continueanddownloadurl = new moodle_url($continueurl, array(
-        'downloadcontents' => 'yes'
+        'downloadcontents' => 1
     ));
     $message = get_string('confirmdelete', 'repository', $instance->name);
     echo html_writer::tag('p', $message);
index 9d7b5a0..e55e1f2 100644 (file)
@@ -13,6 +13,8 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configcheckbox('enablesafebrowserintegration', new lang_string('enablesafebrowserintegration', 'admin'), new lang_string('configenablesafebrowserintegration', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('enablegroupmembersonly', new lang_string('enablegroupmembersonly', 'admin'), new lang_string('configenablegroupmembersonly', 'admin'), 0));
 
+    $temp->add(new admin_setting_configcheckbox('dndallowtextandlinks', new lang_string('dndallowtextandlinks', 'admin'), new lang_string('configdndallowtextandlinks', 'admin'), 0));
+
     $ADMIN->add('experimental', $temp);
 
     // "debugging" settingpage
index 6d192b6..96d0455 100644 (file)
@@ -330,12 +330,12 @@ if ($hassiteconfig) {
       $typeoptionnames = repository::static_function($repositorytype->get_typename(), 'get_type_option_names');
       $instanceoptionnames = repository::static_function($repositorytype->get_typename(), 'get_instance_option_names');
       if (!empty($typeoptionnames) || !empty($instanceoptionnames)) {
-            $ADMIN->add('repositorysettings',
-                new admin_externalpage('repositorysettings'.$repositorytype->get_typename(),
-                        $repositorytype->get_readablename(),
-                        $url . '?action=edit&repos=' . $repositorytype->get_typename()),
-                        'moodle/site:config');
-        }
+
+          $params = array('action'=>'edit', 'sesskey'=>sesskey(), 'repos'=>$repositorytype->get_typename());
+          $settingsurl = new moodle_url("/$CFG->admin/repository.php", $params);
+          $repositoryexternalpage = new admin_externalpage('repositorysettings'.$repositorytype->get_typename(), $repositorytype->get_readablename(), $settingsurl);
+          $ADMIN->add('repositorysettings', $repositoryexternalpage);
+      }
     }
 }
 
index 34e1fa4..e031be4 100644 (file)
@@ -9,6 +9,9 @@
          $zone = clean_param($zone, PARAM_PATH);
     }
 
+    $PAGE->set_url('/admin/timezone.php');
+    $PAGE->set_context(context_system::instance());
+
     require_login();
 
     require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM));
@@ -18,7 +21,6 @@
     $strusers = get_string("users");
     $strall = get_string("all");
 
-    $PAGE->set_url('/admin/timezone.php');
     $PAGE->set_title($strtimezone);
     $PAGE->set_heading($strtimezone);
     $PAGE->navbar->add($strtimezone);
index 8f481de..e5d21b8 100644 (file)
@@ -55,12 +55,6 @@ abstract class convert_helper {
 
         $converters = array();
 
-        // Only apply for backup converters if the (experimental) setting enables it.
-        // This will be out once we get proper support of backup converters. MDL-29956
-        if (!$restore && empty($CFG->enablebackupconverters)) {
-            return $converters;
-        }
-
         $plugins    = get_list_of_plugins('backup/converter');
         foreach ($plugins as $name) {
             $filename = $restore ? 'lib.php' : 'backuplib.php';
index b0d35a3..9b7c4e4 100644 (file)
@@ -92,14 +92,12 @@ M.course_dndupload = {
             this.init_events(el);
         }, this);
 
-        var div = this.add_status_div();
-        div.setContent(M.util.get_string('dndworking', 'moodle'));
+        this.add_status_div();
     },
 
     /**
      * Add a div element to tell the user that drag and drop upload
      * is available (or to explain why it is not available)
-     * @return the DOM element to add messages to
      */
     add_status_div: function() {
         var div = document.createElement('div');
@@ -108,7 +106,34 @@ M.course_dndupload = {
         if (coursecontents) {
             coursecontents.insertBefore(div, coursecontents.firstChild);
         }
-        return this.Y.one(div);
+        div = this.Y.one(div);
+
+        var handlefile = (this.handlers.filehandlers.length > 0);
+        var handletext = false;
+        var handlelink = false;
+        var i;
+        for (i=0; i<this.handlers.types.length; i++) {
+            switch (this.handlers.types[i].identifier) {
+            case 'text':
+            case 'text/html':
+                handletext = true;
+                break;
+            case 'url':
+                handlelink = true;
+                break;
+            }
+        }
+        $msgident = 'dndworking';
+        if (handlefile) {
+            $msgident += 'file';
+        }
+        if (handletext) {
+            $msgident += 'text';
+        }
+        if (handlelink) {
+            $msgident += 'link';
+        }
+        div.setContent(M.util.get_string($msgident, 'moodle'));
     },
 
     /**
@@ -176,14 +201,7 @@ M.course_dndupload = {
      */
     types_includes: function(e, type) {
         var i;
-        if (e._event.dataTransfer === null) {
-            // TODO MDL-33054: If we get here then something has gone wrong.
-            return false;
-        }
         var types = e._event.dataTransfer.types;
-        if (types == null) {
-            return false;
-        }
         for (i=0; i<types.length; i++) {
             if (types[i] == type) {
                 return true;
@@ -204,19 +222,33 @@ M.course_dndupload = {
      *           }
      */
     drag_type: function(e) {
+        // Check there is some data attached.
+        if (e._event.dataTransfer === null) {
+            return false;
+        }
+        if (e._event.dataTransfer.types === null) {
+            return false;
+        }
+        if (e._event.dataTransfer.types.length == 0) {
+            return false;
+        }
+
+        // Check for files first.
         if (this.types_includes(e, 'Files')) {
-            if (this.handlers.filehandlers.length == 0) {
-                return false; // No available file handlers - ignore this drag.
+            if (e.type != 'drop' || e._event.dataTransfer.files.length != 0) {
+                if (this.handlers.filehandlers.length == 0) {
+                    return false; // No available file handlers - ignore this drag.
+                }
+                return {
+                    realtype: 'Files',
+                    addmessage: M.util.get_string('addfilehere', 'moodle'),
+                    namemessage: null, // Should not be asked for anyway
+                    type: 'Files'
+                };
             }
-            return {
-                realtype: 'Files',
-                addmessage: M.util.get_string('addfilehere', 'moodle'),
-                namemessage: null, // Should not be asked for anyway
-                type: 'Files'
-            };
         }
 
-        // Check each of the registered types
+        // Check each of the registered types.
         var types = this.handlers.types;
         for (var i=0; i<types.length; i++) {
             // Check each of the different identifiers for this type
@@ -401,6 +433,7 @@ M.course_dndupload = {
         resel.div.appendChild(resel.a);
 
         resel.icon.src = M.util.image_url('i/ajaxloader');
+        resel.icon.className = 'activityicon';
         resel.a.appendChild(resel.icon);
 
         resel.a.appendChild(document.createTextNode(' '));
@@ -672,6 +705,11 @@ M.course_dndupload = {
                             if (result.onclick) {
                                 resel.a.onclick = result.onclick;
                             }
+                            if (self.Y.UA.gecko > 0) {
+                                // Fix a Firefox bug which makes sites with a '~' in their wwwroot
+                                // log the user out when clicking on the link (before refreshing the page).
+                                resel.div.innerHTML = unescape(resel.div.innerHTML);
+                            }
                             self.add_editing(result.elementid);
                         } else {
                             // Error - remove the dummy element
@@ -813,6 +851,7 @@ M.course_dndupload = {
      * @param contents the actual data that was dropped
      * @param section the DOM element representing the selected course section
      * @param sectionnumber the number of the selected course section
+     * @param module the module chosen to handle this upload
      */
     upload_item: function(name, type, contents, section, sectionnumber, module) {
 
@@ -846,6 +885,11 @@ M.course_dndupload = {
                             if (result.onclick) {
                                 resel.a.onclick = result.onclick;
                             }
+                            if (self.Y.UA.gecko > 0) {
+                                // Fix a Firefox bug which makes sites with a '~' in their wwwroot
+                                // log the user out when clicking on the link (before refreshing the page).
+                                resel.div.innerHTML = unescape(resel.div.innerHTML);
+                            }
                             self.add_editing(result.elementid, sectionnumber);
                         } else {
                             // Error - remove the dummy element
index 7c7756c..01eb3c1 100644 (file)
@@ -52,7 +52,13 @@ function dndupload_add_to_course($course, $modnames) {
         'fullpath' => new moodle_url('/course/dndupload.js'),
         'strings' => array(
             array('addfilehere', 'moodle'),
-            array('dndworking', 'moodle'),
+            array('dndworkingfiletextlink', 'moodle'),
+            array('dndworkingfilelink', 'moodle'),
+            array('dndworkingfiletext', 'moodle'),
+            array('dndworkingfile', 'moodle'),
+            array('dndworkingtextlink', 'moodle'),
+            array('dndworkingtext', 'moodle'),
+            array('dndworkinglink', 'moodle'),
             array('filetoolarge', 'moodle'),
             array('actionchoice', 'moodle'),
             array('servererror', 'moodle'),
@@ -103,7 +109,7 @@ class dndupload_handler {
         // Add some default types to handle.
         // Note: 'Files' type is hard-coded into the Javascript as this needs to be ...
         // ... treated a little differently.
-        $this->add_type('url', array('url', 'text/uri-list'), get_string('addlinkhere', 'moodle'),
+        $this->add_type('url', array('url', 'text/uri-list', 'text/x-moz-url'), get_string('addlinkhere', 'moodle'),
                         get_string('nameforlink', 'moodle'), 10);
         $this->add_type('text/html', array('text/html'), get_string('addpagehere', 'moodle'),
                         get_string('nameforpage', 'moodle'), 20);
@@ -298,17 +304,21 @@ class dndupload_handler {
      * @return object Data to pass on to Javascript code
      */
     public function get_js_data() {
+        global $CFG;
+
         $ret = new stdClass;
 
         // Sort the types by priority.
         uasort($this->types, array($this, 'type_compare'));
 
         $ret->types = array();
-        foreach ($this->types as $type) {
-            if (empty($type->handlers)) {
-                continue; // Skip any types without registered handlers.
+        if (!empty($CFG->dndallowtextandlinks)) {
+            foreach ($this->types as $type) {
+                if (empty($type->handlers)) {
+                    continue; // Skip any types without registered handlers.
+                }
+                $ret->types[] = $type;
             }
-            $ret->types[] = $type;
         }
 
         $ret->filehandlers = $this->filehandlers;
@@ -430,6 +440,10 @@ class dndupload_ajax_processor {
             if ($content != null) {
                 throw new moodle_exception('fileuploadwithcontent', 'moodle');
             }
+        } else {
+            if (empty($content)) {
+                throw new moodle_exception('dnduploadwithoutcontent', 'moodle');
+            }
         }
 
         require_sesskey();
index 45977d4..d969e8b 100644 (file)
@@ -261,10 +261,15 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
      * @return string HTML to output.
      */
     protected function section_summary($section, $course) {
+        // If section is hidden then display grey section link
+        $classattr = 'section-summary clearfix';
+        If (!$section->visible) {
+            $classattr .= ' dimmed_text';
+        }
 
         $o = '';
         $o.= html_writer::start_tag('li', array('id' => 'section-'.$section->section,
-            'class' => 'section-summary clearfix'));
+            'class' => $classattr));
 
         $title = get_section_name($course, $section);
         $o.= html_writer::start_tag('a', array('href' => course_get_url($course, $section->section)));
@@ -351,9 +356,13 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $back = $sectionno - 1;
         while ($back > 0 and empty($links['previous'])) {
             if ($canviewhidden || $sections[$back]->visible) {
+                $params = array();
+                if (!$sections[$back]->visible) {
+                    $params = array('class' => 'dimmed_text');
+                }
                 $previouslink = html_writer::tag('span', $this->output->larrow(), array('class' => 'larrow'));
                 $previouslink .= get_section_name($course, $sections[$back]);
-                $links['previous'] = html_writer::link(course_get_url($course, $back), $previouslink);
+                $links['previous'] = html_writer::link(course_get_url($course, $back), $previouslink, $params);
             }
             $back--;
         }
@@ -361,9 +370,13 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $forward = $sectionno + 1;
         while ($forward <= $course->numsections and empty($links['next'])) {
             if ($canviewhidden || $sections[$forward]->visible) {
+                $params = array();
+                if (!$sections[$forward]->visible) {
+                    $params = array('class' => 'dimmed_text');
+                }
                 $nextlink = get_section_name($course, $sections[$forward]);
                 $nextlink .= html_writer::tag('span', $this->output->rarrow(), array('class' => 'rarrow'));
-                $links['next'] = html_writer::link(course_get_url($course, $forward), $nextlink);
+                $links['next'] = html_writer::link(course_get_url($course, $forward), $nextlink, $params);
             }
             $forward++;
         }
@@ -444,7 +457,6 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
                 echo $this->start_section_list();
                 echo $this->section_hidden($displaysection);
                 echo $this->end_section_list();
-                echo $sectionnavlinks;
             }
             // Can't view this section.
             return;
@@ -463,20 +475,24 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             echo $this->end_section_list();
         }
 
+        // Start single-section div
+        echo html_writer::start_tag('div', array('class' => 'single-section'));
+
         // Title with section navigation links.
         $sectionnavlinks = $this->get_nav_links($course, $sections, $displaysection);
         $sectiontitle = '';
-        $sectiontitle .= html_writer::start_tag('div', array('class' => 'section-navigation headingblock header'));
+        $sectiontitle .= html_writer::start_tag('div', array('class' => 'section-navigation header headingblock'));
         $sectiontitle .= html_writer::tag('span', $sectionnavlinks['previous'], array('class' => 'mdl-left'));
         $sectiontitle .= html_writer::tag('span', $sectionnavlinks['next'], array('class' => 'mdl-right'));
-        $sectiontitle .= html_writer::tag('div', get_section_name($course, $sections[$displaysection]), array('class' => 'mdl-align'));
+        // Title attributes
+        $titleattr = 'mdl-align title';
+        if (!$sections[$displaysection]->visible) {
+            $titleattr .= ' dimmed_text';
+        }
+        $sectiontitle .= html_writer::tag('div', get_section_name($course, $sections[$displaysection]), array('class' => $titleattr));
         $sectiontitle .= html_writer::end_tag('div');
         echo $sectiontitle;
 
-        // Show completion help icon.
-        $completioninfo = new completion_info($course);
-        echo $completioninfo->display_help_icon();
-
         // Copy activity clipboard..
         echo $this->course_activity_clipboard($course, $displaysection);
 
@@ -486,6 +502,10 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         // The requested section page.
         $thissection = $sections[$displaysection];
         echo $this->section_header($thissection, $course, true);
+        // Show completion help icon.
+        $completioninfo = new completion_info($course);
+        echo $completioninfo->display_help_icon();
+
         print_section($course, $thissection, $mods, $modnamesused, true);
         if ($PAGE->user_is_editing()) {
             print_section_add_menus($course, $displaysection, $modnames);
@@ -502,6 +522,9 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $sectionbottomnav .= html_writer::tag('div', $courselink, array('class' => 'mdl-align'));
         $sectionbottomnav .= html_writer::end_tag('div');
         echo $sectionbottomnav;
+
+        // close single-section div.
+        echo html_writer::end_tag('div');
     }
 
     /**
@@ -552,6 +575,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
                 // a section_info object - we will need at least the uservisible
                 // field in it.
                 $thissection->uservisible = true;
+                $thissection->availableinfo = null;
+                $thissection->showavailability = 0;
             }
             // Show the section if the user is permitted to access it, OR if it's not available
             // but showavailability is turned on
index 85aec6d..218492d 100644 (file)
@@ -5,13 +5,24 @@ M.course = M.course || {};
 M.course.format = M.course.format || {};
 
 /**
- * Get section list for this format
+ * Get sections config for this format
  *
- * @param {YUI} Y YUI3 instance
- * @return {string} section list selector
+ * The section structure is:
+ * <ul class="topics">
+ *  <li class="section">...</li>
+ *  <li class="section">...</li>
+ *   ...
+ * </ul>
+ *
+ * @return {object} section list configuration
  */
-M.course.format.get_section_selector = function(Y) {
-    return 'li.section';
+M.course.format.get_config = function() {
+    return {
+        container_node : 'ul',
+        container_class : 'topics',
+        section_node : 'li',
+        section_class : 'section'
+    };
 }
 
 /**
index 7050488..5ef126f 100644 (file)
@@ -102,23 +102,4 @@ class format_topics_renderer extends format_section_renderer_base {
 
         return array_merge($controls, parent::section_edit_controls($course, $section, $onsectionpage));
     }
-
-    /**
-     * Generate the content to displayed on the left part of a section
-     *
-     * before course modules are included
-     * @param stdClass $section The course_section entry from DB
-     * @param stdClass $course The course entry from DB
-     * @param bool $onsectionpage true if being printed on a section page
-     * @return string HTML to output.
-     */
-    protected function section_left_content($section, $course, $onsectionpage) {
-        $o = parent::section_left_content($section, $course, $onsectionpage);
-
-        if ($section->section > 0) {
-            $o.= $section->section;
-        }
-
-        return $o;
-    }
 }
index 78c2063..28aed42 100644 (file)
@@ -5,13 +5,24 @@ M.course = M.course || {};
 M.course.format = M.course.format || {};
 
 /**
- * Get section list for this format
+ * Get sections config for this format
  *
- * @param {YUI} Y YUI3 instance
- * @return {string} section list selector
+ * The section structure is:
+ * <ul class="weeks">
+ *  <li class="section">...</li>
+ *  <li class="section">...</li>
+ *   ...
+ * </ul>
+ *
+ * @return {object} section list configuration
  */
-M.course.format.get_section_selector = function(Y) {
-    return 'li.section';
+M.course.format.get_config = function() {
+    return {
+        container_node : 'ul',
+        container_class : 'weeks',
+        section_node : 'li',
+        section_class : 'section'
+    };
 }
 
 /**
index 713ad70..3dbbf0b 100644 (file)
@@ -52,6 +52,156 @@ YUI.add('moodle-course-coursebase', function(Y) {
     // Ensure that M.course exists and that coursebase is initialised correctly
     M.course = M.course || {};
     M.course.coursebase = M.course.coursebase || new COURSEBASE();
+
+    // Abstract functions that needs to be defined per format (course/format/somename/format.js)
+    M.course.format = M.course.format || {}
+
+   /**
+    * Swap section (should be defined in format.js if requred)
+    *
+    * @param {YUI} Y YUI3 instance
+    * @param {string} node1 node to swap to
+    * @param {string} node2 node to swap with
+    * @return {NodeList} section list
+    */
+    M.course.format.swap_sections = M.course.format.swap_sections || function(Y, node1, node2) {
+        return null;
+    }
+
+
+   /**
+    * Get sections config for this format, for examples see function definition
+    * in the formats.
+    *
+    * @return {object} section list configuration
+    */
+    M.course.format.get_config = M.course.format.get_config || function() {
+        return {
+            container_node : null, // compulsory
+            container_class : null, // compulsory
+            section_wrapper_node : null, // optional
+            section_wrapper_class : null, // optional
+            section_node : null,  // compulsory
+            section_class : null  // compulsory
+        }
+    }
+
+   /**
+    * Get section list for this format (usually items inside container_node.container_class selector)
+    *
+    * @param {YUI} Y YUI3 instance
+    * @return {string} section selector
+    */
+    M.course.format.get_section_selector = M.course.format.get_section_selector || function(Y) {
+        var config = M.course.format.get_config();
+        if (config.section_node && config.section_class) {
+            return config.section_node + '.' + config.section_class;
+        }
+        console.log('section_node and section_class are not defined in M.course.format.get_config');
+        return null;
+    }
+
+   /**
+    * Get section wraper for this format (only used in case when each
+    * container_node.container_class node is wrapped in some other element).
+    *
+    * @param {YUI} Y YUI3 instance
+    * @return {string} section wrapper selector or M.course.format.get_section_selector
+    * if section_wrapper_node and section_wrapper_class are not defined in the format config.
+    */
+    M.course.format.get_section_wrapper = M.course.format.get_section_wrapper || function(Y) {
+        var config = M.course.format.get_config();
+        if (config.section_wrapper_node && config.section_wrapper_class) {
+            return config.section_wrapper_node + '.' + config.section_wrapper_class;
+        }
+        return M.course.format.get_section_selector(Y);
+    }
+
+   /**
+    * Get the tag of container node
+    *
+    * @return {string} tag of container node.
+    */
+    M.course.format.get_containernode = M.course.format.get_containernode || function() {
+        var config = M.course.format.get_config();
+        if (config.container_node) {
+            return config.container_node;
+        } else {
+            console.log('container_node is not defined in M.course.format.get_config');
+        }
+    }
+
+   /**
+    * Get the class of container node
+    *
+    * @return {string} class of the container node.
+    */
+    M.course.format.get_containerclass = M.course.format.get_containerclass || function() {
+        var config = M.course.format.get_config();
+        if (config.container_class) {
+            return config.container_class;
+        } else {
+            console.log('container_class is not defined in M.course.format.get_config');
+        }
+    }
+
+   /**
+    * Get the tag of draggable node (section wrapper if exists, otherwise section)
+    *
+    * @return {string} tag of the draggable node.
+    */
+    M.course.format.get_sectionwrappernode = M.course.format.get_sectionwrappernode || function() {
+        var config = M.course.format.get_config();
+        if (config.section_wrapper_node) {
+            return config.section_wrapper_node;
+        } else {
+            return config.section_node;
+        }
+    }
+
+   /**
+    * Get the class of draggable node (section wrapper if exists, otherwise section)
+    *
+    * @return {string} class of the draggable node.
+    */
+    M.course.format.get_sectionwrapperclass = M.course.format.get_sectionwrapperclass || function() {
+        var config = M.course.format.get_config();
+        if (config.section_wrapper_class) {
+            return config.section_wrapper_class;
+        } else {
+            return config.section_class;
+        }
+    }
+
+   /**
+    * Get the tag of section node
+    *
+    * @return {string} tag of section node.
+    */
+    M.course.format.get_sectionnode = M.course.format.get_sectionnode || function() {
+        var config = M.course.format.get_config();
+        if (config.section_node) {
+            return config.section_node;
+        } else {
+            console.log('section_node is not defined in M.course.format.get_config');
+        }
+    }
+
+   /**
+    * Get the class of section node
+    *
+    * @return {string} class of the section node.
+    */
+    M.course.format.get_sectionclass = M.course.format.get_sectionclass || function() {
+        var config = M.course.format.get_config();
+        if (config.section_class) {
+            return config.section_class;
+        } else {
+            console.log('section_class is not defined in M.course.format.get_config');
+        }
+
+    }
+
 },
 '@VERSION@', {
     requires : ['base', 'node']
index c3ed979..012355e 100644 (file)
@@ -17,9 +17,7 @@ YUI.add('moodle-course-dragdrop', function(Y) {
         SECTION : 'section',
         SECTIONADDMENUS : 'section_add_menus',
         SECTIONHANDLE : 'section-handle',
-        SUMMARY : 'summary',
-        TOPICS : 'topics',
-        WEEKDATES: 'weekdates'
+        SUMMARY : 'summary'
     };
 
     var DRAGSECTION = function() {
@@ -31,16 +29,17 @@ YUI.add('moodle-course-dragdrop', function(Y) {
         initializer : function(params) {
             // Set group for parent class
             this.groups = ['section'];
-            this.samenodeclass = CSS.SECTION;
-            this.parentnodeclass = CSS.TOPICS;
+            this.samenodeclass = M.course.format.get_sectionwrapperclass();
+            this.parentnodeclass = M.course.format.get_containerclass();
 
             // Check if we are in single section mode
             if (Y.Node.one('.'+CSS.JUMPMENU)) {
                 return false;
             }
             // Initialise sections dragging
-            if (M.course.format && M.course.format.get_section_selector && typeof(M.course.format.get_section_selector) == 'function') {
-                this.sectionlistselector = '.'+CSS.COURSECONTENT+' '+M.course.format.get_section_selector(Y);
+            this.sectionlistselector = M.course.format.get_section_wrapper(Y);
+            if (this.sectionlistselector) {
+                this.sectionlistselector = '.'+CSS.COURSECONTENT+' '+this.sectionlistselector;
                 this.setup_for_section(this.sectionlistselector);
             }
         },
@@ -109,14 +108,14 @@ YUI.add('moodle-course-dragdrop', function(Y) {
             // Get our drag object
             var drag = e.target;
             // Creat a dummy structure of the outer elemnents for clean styles application
-            var ul = Y.Node.create('<ul></ul>');
-            ul.addClass(CSS.TOPICS);
-            var li = Y.Node.create('<li></li>');
-            li.addClass(CSS.SECTION);
-            li.setStyle('margin', 0);
-            li.setContent(drag.get('node').get('innerHTML'));
-            ul.appendChild(li);
-            drag.get('dragNode').setContent(ul);
+            var containernode = Y.Node.create('<'+M.course.format.get_containernode()+'></'+M.course.format.get_containernode()+'>');
+            containernode.addClass(M.course.format.get_containerclass());
+            var sectionnode = Y.Node.create('<'+ M.course.format.get_sectionwrappernode()+'></'+ M.course.format.get_sectionwrappernode()+'>');
+            sectionnode.addClass( M.course.format.get_sectionwrapperclass());
+            sectionnode.setStyle('margin', 0);
+            sectionnode.setContent(drag.get('node').get('innerHTML'));
+            containernode.appendChild(sectionnode);
+            drag.get('dragNode').setContent(containernode);
             drag.get('dragNode').addClass(CSS.COURSECONTENT);
         },
 
@@ -186,10 +185,8 @@ YUI.add('moodle-course-dragdrop', function(Y) {
                                     var sectionid = sectionlist.item(i-1).get('id');
                                     sectionlist.item(i-1).set('id', sectionlist.item(i).get('id'));
                                     sectionlist.item(i).set('id', sectionid);
-                                    // See what format needs to be swapped
-                                    if (M.course.format && M.course.format.swap_sections && typeof(M.course.format.swap_sections) == 'function') {
-                                        M.course.format.swap_sections(Y, i-1, i);
-                                    }
+                                    // See what format needs to swap
+                                    M.course.format.swap_sections(Y, i-1, i);
                                     // Update flag
                                     swapped = true;
                                 }
@@ -232,8 +229,9 @@ YUI.add('moodle-course-dragdrop', function(Y) {
             this.parentnodeclass = CSS.SECTION;
 
             // Go through all sections
-            if (M.course.format && M.course.format.get_section_selector && typeof(M.course.format.get_section_selector) == 'function') {
-                var sectionlistselector = '.'+CSS.COURSECONTENT+' '+M.course.format.get_section_selector(Y);
+            var sectionlistselector = M.course.format.get_section_selector(Y);
+            if (sectionlistselector) {
+                sectionlistselector = '.'+CSS.COURSECONTENT+' '+sectionlistselector;
                 this.setup_for_section(sectionlistselector);
                 M.course.coursebase.register_module(this);
                 M.course.dragres = this;
@@ -263,7 +261,7 @@ YUI.add('moodle-course-dragdrop', function(Y) {
                     padding: '20 0 20 0'
                 });
                 // Go through each li element and make them draggable
-                this.setup_for_resource('li#'+sectionnode.get('id')+' li.'+CSS.ACTIVITY);
+                this.setup_for_resource('#'+sectionnode.get('id')+' li.'+CSS.ACTIVITY);
             }, this);
         },
         /**
@@ -317,8 +315,6 @@ YUI.add('moodle-course-dragdrop', function(Y) {
             var dragnode = drag.get('node');
             var dropnode = e.drop.get('node');
 
-            var sectionselector = M.course.format.get_section_selector(Y);
-
             // Add spinner if it not there
             var spinner = M.util.add_spinner(Y, dragnode.one(CSS.COMMANDSPAN));
 
@@ -336,7 +332,7 @@ YUI.add('moodle-course-dragdrop', function(Y) {
             params['class'] = 'resource';
             params.field = 'move';
             params.id = Number(this.get_resource_id(dragnode));
-            params.sectionId = this.get_section_id(dropnode.ancestor(sectionselector));
+            params.sectionId = this.get_section_id(dropnode.ancestor(M.course.format.get_section_wrapper(Y), true));
 
             if (dragnode.next()) {
                 params.beforeId = Number(this.get_resource_id(dragnode.next()));
index 6dfcce0..024785e 100644 (file)
@@ -28,7 +28,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
         MOVELEFTCLASS : 'editing_moveleft',
         MOVERIGHT : 'a.editing_moveright',
         PAGECONTENT : 'div#page-content',
-        RIGHTDIV : 'div.right',
+        RIGHTSIDE : '.right',
         SECTIONHIDDENCLASS : 'hidden',
         SECTIONIDPREFIX : 'section-',
         SECTIONLI : 'li.section',
@@ -384,7 +384,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             e.preventDefault();
 
             // Return early if the current section is hidden
-            var section = e.target.ancestor(CSS.SECTIONLI);
+            var section = e.target.ancestor(M.course.format.get_section_selector(Y));
             if (section && section.hasClass(CSS.SECTIONHIDDENCLASS)) {
                 return;
             }
@@ -649,17 +649,17 @@ YUI.add('moodle-course-toolboxes', function(Y) {
         },
         _setup_for_section : function(toolboxtarget) {
             // Section Highlighting
-            this.replace_button(toolboxtarget, CSS.RIGHTDIV + ' ' + CSS.HIGHLIGHT, this.toggle_highlight);
+            this.replace_button(toolboxtarget, CSS.RIGHTSIDE + ' ' + CSS.HIGHLIGHT, this.toggle_highlight);
 
             // Section Visibility
-            this.replace_button(toolboxtarget, CSS.RIGHTDIV + ' ' + CSS.SHOWHIDE, this.toggle_hide_section);
+            this.replace_button(toolboxtarget, CSS.RIGHTSIDE + ' ' + CSS.SHOWHIDE, this.toggle_hide_section);
         },
         toggle_hide_section : function(e) {
             // Prevent the default button action
             e.preventDefault();
 
             // Get the section we're working on
-            var section = e.target.ancestor(CSS.SECTIONLI);
+            var section = e.target.ancestor(M.course.format.get_section_selector(Y));
             var button = e.target.ancestor('a', true);
             var hideicon = button.one('img');
 
@@ -691,7 +691,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             var data = {
                 'class' : 'section',
                 'field' : 'visible',
-                'id'    : this.get_section_id(section),
+                'id'    : this.get_section_id(section.ancestor(M.course.format.get_section_wrapper(Y), true)),
                 'value' : value
             };
 
@@ -725,7 +725,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             e.preventDefault();
 
             // Get the section we're working on
-            var section = e.target.ancestor(CSS.SECTIONLI);
+            var section = e.target.ancestor(M.course.format.get_section_selector(Y));
             var button = e.target.ancestor('a', true);
             var buttonicon = button.one('img');
 
@@ -736,22 +736,22 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             // Set the current highlighted item text
             var old_string = M.util.get_string('markthistopic', 'moodle');
             Y.one(CSS.PAGECONTENT)
-                .all(CSS.SECTIONLI + '.current ' + CSS.HIGHLIGHT)
+                .all(M.course.format.get_section_selector(Y) + '.current ' + CSS.HIGHLIGHT)
                 .set('title', old_string);
             Y.one(CSS.PAGECONTENT)
-                .all(CSS.SECTIONLI + '.current ' + CSS.HIGHLIGHT + ' img')
+                .all(M.course.format.get_section_selector(Y) + '.current ' + CSS.HIGHLIGHT + ' img')
                 .set('title', old_string)
                 .set('alt', old_string)
                 .set('src', M.util.image_url('i/marker'));
 
             // Remove the highlighting from all sections
-            var allsections = Y.one(CSS.PAGECONTENT).all(CSS.SECTIONLI)
+            var allsections = Y.one(CSS.PAGECONTENT).all(M.course.format.get_section_selector(Y))
                 .removeClass('current');
 
             // Then add it if required to the selected section
             if (!togglestatus) {
                 section.addClass('current');
-                value = this.get_section_id(section);
+                value = this.get_section_id(section.ancestor(M.course.format.get_section_wrapper(Y), true));
                 var new_string = M.util.get_string('markedthistopic', 'moodle');
                 button
                     .set('title', new_string);
index 7a0a0af..c09cd7e 100644 (file)
@@ -145,6 +145,7 @@ class enrol_mnet_mnetservice_enrol {
             // users {@link http://tracker.moodle.org/browse/MDL-21327}
             $user = mnet_strip_user((object)$userdata, mnet_fields_to_import($client));
             $user->mnethostid = $client->id;
+            $user->auth = 'mnet';
             try {
                 $user->id = $DB->insert_record('user', $user);
             } catch (Exception $e) {
index 5cee004..2ac7b1e 100644 (file)
@@ -183,6 +183,7 @@ $string['configdenyemailaddresses'] = 'To deny email addresses from particular d
 $string['configenabledevicedetection'] = 'Enables detection of mobiles, smartphones, tablets or default devices (desktop PCs, laptops, etc) for the application of themes and other features.';
 $string['configdisableuserimages'] = 'Disable the ability for users to change user profile images.';
 $string['configdisplayloginfailures'] = 'This will display information to selected users about previous failed logins.';
+$string['configdndallowtextandlinks'] = 'Enable or disable the dragging and dropping of text and links onto a course page, alongside the dragging and dropping of files. Note that the dragging of text into Firefox or between different browsers is unreliable and may result in no data being uploaded, or corrupted text being uploaded.';
 $string['configdocroot'] = 'Defines the path to the Moodle Docs. You can change this if you wish to have your own custom online documentation. However, if you do that make sure that the paths in your documentation follow the same format as http://docs.moodle.org.';
 $string['configdoctonewwindow'] = 'If you enable this, then links to Moodle Docs will be shown in a new window.';
 $string['configeditordictionary'] = 'This value will be used if aspell doesn\'t have dictionary for users own language.';
@@ -431,6 +432,7 @@ $string['devicetype'] = 'Device type';
 $string['disableuserimages'] = 'Disable user profile images';
 $string['displayerrorswarning'] = 'Enabling the PHP setting <em>display_errors</em> is not recommended on production sites because some error messages may reveal sensitive information about your server.';
 $string['displayloginfailures'] = 'Display login failures to';
+$string['dndallowtextandlinks'] = 'Drag and drop upload of text/links';
 $string['docroot'] = 'Moodle Docs document root';
 $string['doctonewwindow'] = 'Open in new window';
 $string['download'] = 'Download';
index fece43f..073d3eb 100644 (file)
@@ -467,7 +467,14 @@ $string['dndenabled'] = 'Drag and drop available';
 $string['dndenabled_help'] = 'You can drag one or more files from your desktop and drop them onto the box below to upload them.<br />Note: this may not work with other web browsers';
 $string['dndenabled_insentence'] = 'drag and drop available';
 $string['dndenabled_inbox'] = 'drag and drop files here to upload them';
-$string['dndworking'] = 'Drag and drop files, text or links onto course sections to upload them';
+$string['dnduploadwithoutcontent'] = 'This upload does not have any content';
+$string['dndworkingfiletextlink'] = 'Drag and drop files, text or links onto course sections to upload them';
+$string['dndworkingfilelink'] = 'Drag and drop files or links onto course sections to upload them';
+$string['dndworkingfiletext'] = 'Drag and drop files or text onto course sections to upload them';
+$string['dndworkingfile'] = 'Drag and drop files onto course sections to upload them';
+$string['dndworkingtextlink'] = 'Drag and drop text or links onto course sections to upload them';
+$string['dndworkingtext'] = 'Drag and drop text onto course sections to upload it';
+$string['dndworkinglink'] = 'Drag and drop links onto course sections to upload them';
 $string['documentation'] = 'Moodle documentation';
 $string['down'] = 'Down';
 $string['download'] = 'Download';
index 9963485..dbc1133 100644 (file)
@@ -1591,7 +1591,7 @@ function mimeinfo_from_type($element, $mimetype) {
     $mimeinfo = & get_mimetypes_array();
 
     if (!array_key_exists($mimetype, $cached)) {
-        $cached[$mimetype] = null;    
+        $cached[$mimetype] = null;
         foreach($mimeinfo as $filetype => $values) {
             if ($values['type'] == $mimetype) {
                 if ($cached[$mimetype] === null) {
@@ -4214,7 +4214,6 @@ class cache_file {
      * @return string cached file path
      */
     public static function create_from_url($ref, $url, $options = array()) {
-        global $CFG;
         $instance = self::get_instance($options);
         $cachedfilepath = $instance->generate_filepath($ref);
         $fp = fopen($cachedfilepath, 'w');
@@ -4234,7 +4233,6 @@ class cache_file {
      * @return string cached file path
      */
     public static function create_from_string($ref, $string, $options = array()) {
-        global $CFG;
         $instance = self::get_instance($options);
         $cachedfilepath = $instance->generate_filepath($ref);
         $fp = fopen($cachedfilepath, 'w');
index 6423d6b..fb7a783 100644 (file)
@@ -25,8 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-require_once("$CFG->dirroot/repository/lib.php");
-
 /**
  * Class representing local files stored in a sha1 file pool.
  *
@@ -63,6 +61,7 @@ class stored_file {
         $this->filedir     = $filedir; // keep secret, do not expose!
 
         if (!empty($file_record->repositoryid)) {
+            require_once("$CFG->dirroot/repository/lib.php");
             $this->repository = repository::get_repository_by_id($file_record->repositoryid, SYSCONTEXTID);
             if ($this->repository->supported_returntypes() & FILE_REFERENCE != FILE_REFERENCE) {
                 // Repository cannot do file reference.
index 53a07c3..881d181 100644 (file)
@@ -193,11 +193,17 @@ M.form_filemanager.init = function(Y, options) {
             }
         },
         check_buttons: function() {
-            if (this.filecount>0) {this.filemanager.removeClass('fm-nofiles');}
-            else {this.filemanager.addClass('fm-nofiles');}
-            if (this.filecount >= this.maxfiles && this.maxfiles!=-1)
-                {this.filemanager.addClass('fm-maxfiles');}
-            else {this.filemanager.removeClass('fm-maxfiles');}
+            if (this.filecount>0) {
+                this.filemanager.removeClass('fm-nofiles');
+            } else {
+                this.filemanager.addClass('fm-nofiles');
+            }
+            if (this.filecount >= this.maxfiles && this.maxfiles!=-1) {
+                this.filemanager.addClass('fm-maxfiles');
+            }
+            else {
+                this.filemanager.removeClass('fm-maxfiles');
+            }
         },
         refresh: function(filepath) {
             var scope = this;
@@ -372,9 +378,19 @@ M.form_filemanager.init = function(Y, options) {
                 for(var i = 0; i < p.length; i++) {
                     var el = this.pathnode.cloneNode(true);
                     this.pathbar.appendChild(el);
-                    if (i == 0) {el.addClass('first');}
-                    if (i == p.length-1) {el.addClass('last');}
-                    if (i%2) {el.addClass('even');} else {el.addClass('odd');}
+
+                    if (i == 0) {
+                        el.addClass('first');
+                    }
+                    if (i == p.length-1) {
+                        el.addClass('last');
+                    }
+
+                    if (i%2) {
+                        el.addClass('even');
+                    } else {
+                        el.addClass('odd');
+                    }
                     el.one('.fp-path-folder-name').setContent(p[i].name).
                         on('click', function(e, path) {
                             e.preventDefault();
@@ -420,8 +436,8 @@ M.form_filemanager.init = function(Y, options) {
                         scope.currentpath = node.path?node.path:'/';
                     }
                     node.highlight(false);
-                    node.origlist = obj.list?obj.list:null;
-                    node.origpath = obj.path?obj.path:null;
+                    node.origlist = obj.list ? obj.list : null;
+                    node.origpath = obj.path ? obj.path : null;
                     node.children = [];
                     for(k in list) {
                         if (list[k].type == 'folder' && retrieved_children[list[k].filepath]) {
@@ -546,12 +562,16 @@ M.form_filemanager.init = function(Y, options) {
                     }
                 };
             }
-            if (!this.lazyloading) {this.lazyloading={};}
+            if (!this.lazyloading) {
+                this.lazyloading={};
+            }
             this.filemanager.one('.fp-content').fp_display_filelist(options, list, this.lazyloading);
             this.content_scrolled();
         },
         populate_licenses_select: function(node) {
-            if (!node) {return;}
+            if (!node) {
+                return;
+            }
             node.setContent('');
             var licenses = this.options.licenses;
             for (var i in licenses) {
@@ -674,7 +694,7 @@ M.form_filemanager.init = function(Y, options) {
                     buttons      : {}
                 });
                 this.confirm_dlg.plug(Y.Plugin.Drag,{handles:['#'+node.get('id')+' .yui3-widget-hd']});
-                var handleConfirm = function(ev) {
+                var handle_confirm = function(ev) {
                     var dlgopt = this.confirm_dlg.dlgopt;
                     ev.preventDefault();
                     this.confirm_dlg.hide();
@@ -686,12 +706,12 @@ M.form_filemanager.init = function(Y, options) {
                         }
                     }
                 }
-                var handleCancel = function(ev) {
+                var handle_cancel = function(ev) {
                     ev.preventDefault();
                     this.confirm_dlg.hide();
                 }
-                node.one('.fp-dlg-butconfirm').on('click', handleConfirm, this);
-                node.one('.fp-dlg-butcancel').on('click', handleCancel, this);
+                node.one('.fp-dlg-butconfirm').on('click', handle_confirm, this);
+                node.one('.fp-dlg-butcancel').on('click', handle_cancel, this);
             }
             this.confirm_dlg.dlgopt = dialog_options;
             this.confirm_dlg_node.one('.fp-dlg-text').setContent(dialog_options.message);
@@ -823,18 +843,25 @@ M.form_filemanager.init = function(Y, options) {
             }, this);
         },
         get_parent_folder_name: function(node) {
-            if (node.type != 'folder' || node.filepath.length < node.fullname.length+1) { return node.filepath; }
+            if (node.type != 'folder' || node.filepath.length < node.fullname.length+1) {
+                return node.filepath;
+            }
             var basedir = node.filepath.substr(0, node.filepath.length - node.fullname.length - 1);
             var lastdir = node.filepath.substr(node.filepath.length - node.fullname.length - 2);
-            if (lastdir == '/' + node.fullname + '/') { return basedir; }
+            if (lastdir == '/' + node.fullname + '/') {
+                return basedir;
+            }
             return node.filepath;
         },
         select_file: function(node) {
             var selectnode = this.selectnode;
             selectnode.removeClass('loading').removeClass('fp-folder').
                 removeClass('fp-file').removeClass('fp-zip').removeClass('fp-cansetmain');
-            if (node.type == 'folder' || node.type == 'zip') {selectnode.addClass('fp-'+node.type);}
-            else {selectnode.addClass('fp-file');}
+            if (node.type == 'folder' || node.type == 'zip') {
+                selectnode.addClass('fp-'+node.type);
+            } else {
+                selectnode.addClass('fp-file');
+            }
             if (this.enablemainfile && (node.sortorder != 1) && node.type == 'file') {
                 selectnode.addClass('fp-cansetmain');
             }
@@ -846,7 +873,9 @@ M.form_filemanager.init = function(Y, options) {
             selectnode.all('.fp-license select option[value='+node.license+']').set('selected', true);
             selectnode.all('.fp-path select option[selected]').set('selected', false);
             selectnode.all('.fp-path select option').each(function(el){
-                if (el.get('value') == foldername) {el.set('selected', true);}
+                if (el.get('value') == foldername) {
+                    el.set('selected', true);
+                }
             });
             selectnode.all('.fp-author input, .fp-license select').set('disabled',(node.type == 'folder')?'disabled':'');
             // display static information about a file (when known)
index 88775c6..57a9e90 100644 (file)
@@ -172,13 +172,20 @@ abstract class core_media {
      * @return string Filename only (not escaped)
      */
     public static function get_filename(moodle_url $url) {
-        $path = $url->get_path();
+        global $CFG;
+
+        // Use the 'file' parameter if provided (for links created when
+        // slasharguments was off). If not present, just use URL path.
+        $path = $url->get_param('file');
+        if (!$path) {
+            $path = $url->get_path();
+        }
+
         // Remove everything before last / if present. Does not use textlib as / is UTF8-safe.
         $slash = strrpos($path, '/');
         if ($slash !== false) {
             $path = substr($path, $slash + 1);
         }
-
         return $path;
     }
 
index 7e8c704..059e3a4 100644 (file)
@@ -1722,7 +1722,7 @@ class global_navigation extends navigation_node {
                       FROM {course_categories} cc
                       JOIN {context} ctx ON cc.id = ctx.instanceid";
         $sqlwhere = "WHERE ctx.contextlevel = ".CONTEXT_COURSECAT;
-        $sqlorder = "ORDER BY depth ASC, sortorder ASC, id ASC";
+        $sqlorder = "ORDER BY cc.depth ASC, cc.sortorder ASC, cc.id ASC";
         $params = array();
 
         $categoriestoload = array();
@@ -2324,7 +2324,7 @@ class global_navigation extends navigation_node {
 
         $context = get_context_instance(CONTEXT_USER, $USER->id);
         if ($iscurrentuser && has_capability('moodle/user:manageownfiles', $context)) {
-            $url = new moodle_url('/user/files.php');
+            $url = new moodle_url('/user/filesedit.php');
             $usernode->add(get_string('myfiles'), $url, self::TYPE_SETTING);
         }
 
@@ -2931,7 +2931,7 @@ class global_navigation_for_ajax extends global_navigation {
                   JOIN {context} ctx ON cc.id = ctx.instanceid
                  WHERE ctx.contextlevel = ".CONTEXT_COURSECAT." AND
                        (cc.id = :categoryid1 OR cc.parent = :categoryid2)
-              ORDER BY depth ASC, sortorder ASC, id ASC";
+              ORDER BY cc.depth ASC, cc.sortorder ASC, cc.id ASC";
         $params = array('categoryid1' => $categoryid, 'categoryid2' => $categoryid);
         $categories = $DB->get_recordset_sql($sql, $params, 0, $limit);
         $subcategories = array();
index 8a19144..0dc33d0 100644 (file)
@@ -380,6 +380,10 @@ class plugin_manager {
                 'social_activities', 'tag_flickr', 'tag_youtube', 'tags'
             ),
 
+            'booktool' => array(
+                'exportimscp', 'importhtml', 'print'
+            ),
+
             'coursereport' => array(
                 //deprecated!
             ),
@@ -441,7 +445,7 @@ class plugin_manager {
             ),
 
             'mod' => array(
-                'assign', 'assignment', 'chat', 'choice', 'data', 'feedback', 'folder',
+                'assign', 'assignment', 'book', 'chat', 'choice', 'data', 'feedback', 'folder',
                 'forum', 'glossary', 'imscp', 'label', 'lesson', 'lti', 'page',
                 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'
             ),
index a4181d4..dd12e90 100644 (file)
@@ -42,7 +42,7 @@ class medialib_testcase extends advanced_testcase {
         global $CFG;
         parent::setUp();
 
-        // Reset CFG.
+        // Reset $CFG and $SERVER.
         $this->resetAfterTest(true);
 
         // Consistent initial setup: all players disabled.
@@ -81,18 +81,6 @@ class medialib_testcase extends advanced_testcase {
         $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 6.1; rv:8.0) Gecko/20100101 Firefox/8.0';
     }
 
-    /**
-     * Post-test cleanup. Replaces old $CFG.
-     */
-    public function tearDown() {
-        // Replace original user agent.
-        if (isset($this->realserver)) {
-            $_SERVER = $this->realserver;
-        }
-
-        parent::tearDown();
-    }
-
     /**
      * Test for the core_media_player is_enabled.
      */
@@ -108,6 +96,32 @@ class medialib_testcase extends advanced_testcase {
         $this->assertTrue($test->is_enabled());
     }
 
+    /**
+     * Test for core_media::get_filename.
+     */
+    public function test_get_filename() {
+        $this->assertEquals('frog.mp4', core_media::get_filename(new moodle_url(
+                '/pluginfile.php/312/mod_page/content/7/frog.mp4')));
+        // This should work even though slasharguments is true, because we want
+        // it to support 'legacy' links if somebody toggles the option later.
+        $this->assertEquals('frog.mp4', core_media::get_filename(new moodle_url(
+                '/pluginfile.php?file=/312/mod_page/content/7/frog.mp4')));
+    }
+
+    /**
+     * Test for core_media::get_extension.
+     */
+    public function test_get_extension() {
+        $this->assertEquals('mp4', core_media::get_extension(new moodle_url(
+                '/pluginfile.php/312/mod_page/content/7/frog.mp4')));
+        $this->assertEquals('', core_media::get_extension(new moodle_url(
+                '/pluginfile.php/312/mod_page/content/7/frog')));
+        $this->assertEquals('mp4', core_media::get_extension(new moodle_url(
+                '/pluginfile.php?file=/312/mod_page/content/7/frog.mp4')));
+        $this->assertEquals('', core_media::get_extension(new moodle_url(
+                '/pluginfile.php?file=/312/mod_page/content/7/frog')));
+    }
+
     /**
      * Test for the core_media_player list_supported_urls.
      */
@@ -344,6 +358,26 @@ class medialib_testcase extends advanced_testcase {
         $this->assertTrue(self::str_contains($t, '</audio>'));
     }
 
+    /**
+     * Same as test_embed_url MP3 test, but for slash arguments.
+     */
+    public function test_slash_arguments() {
+        global $CFG, $PAGE;
+
+        // Again we do not turn slasharguments actually on, because it has to
+        // work regardless of the setting of that variable in case of handling
+        // links created using previous setting.
+
+        // Enable MP3 and get renderer.
+        $CFG->core_media_enable_mp3 = true;
+        $renderer = new core_media_renderer_test($PAGE, '');
+
+        // Format: mp3.
+        $url = new moodle_url('http://example.org/pluginfile.php?file=x/y/z/test.mp3');
+        $t = $renderer->embed_url($url);
+        $this->assertTrue(self::str_contains($t, 'core_media_mp3_'));
+    }
+
     /**
      * Test for core_media_renderer embed_url.
      * Checks the EMBED_OR_BLANK option.
index 7d99ced..f68f3d5 100644 (file)
@@ -763,7 +763,9 @@ class moodle_url {
      * By default the path includes slash-arguments (for example,
      * '/myfile.php/extra/arguments') so it is what you would expect from a
      * URL path. If you don't want this behaviour, you can opt to exclude the
-     * slash arguments.
+     * slash arguments. (Be careful: if the $CFG variable slasharguments is
+     * disabled, these URLs will have a different format and you may need to
+     * look at the 'file' parameter too.)
      *
      * @param bool $includeslashargument If true, includes slash arguments
      * @return string Path of URL
@@ -771,6 +773,20 @@ class moodle_url {
     public function get_path($includeslashargument = true) {
         return $this->path . ($includeslashargument ? $this->slashargument : '');
     }
+
+    /**
+     * Returns a given parameter value from the URL.
+     *
+     * @param string $name Name of parameter
+     * @return string Value of parameter or null if not set
+     */
+    public function get_param($name) {
+        if (array_key_exists($name, $this->params)) {
+            return $this->params[$name];
+        } else {
+            return null;
+        }
+    }
 }
 
 /**
index 82d08b2..626a7cc 100644 (file)
@@ -53,7 +53,7 @@ class mod_assign_grading_batch_operations_form extends moodleform {
         if ($instance['submissiondrafts']) {
             $options['reverttodraft'] = get_string('reverttodraft', 'assign');
         }
-        $mform->addElement('select', 'operation', get_string('batchoperationsdescription', 'assign'), $options, array('class'=>'operation'));
+        $mform->addElement('select', 'operation', get_string('batchoperationsdescription', 'assign'), $options, array('class'=>'operation ignoredirty'));
         $mform->addHelpButton('operation', 'batchoperationsdescription', 'assign');
         $mform->addElement('hidden', 'action', 'batchgradingoperation');
         $mform->addElement('hidden', 'id', $instance['cm']);
index 5d38834..d9abe0f 100644 (file)
@@ -48,10 +48,16 @@ class mod_assign_grading_options_form extends moodleform {
         $mform->addElement('header', 'general', get_string('gradingoptions', 'assign'));
         // visible elements
         $options = array(-1=>'All',10=>'10', 20=>'20', 50=>'50', 100=>'100');
-        $autosubmit = array('onchange'=>'form.submit();');
-        $mform->addElement('select', 'perpage', get_string('assignmentsperpage', 'assign'), $options, $autosubmit);
+        $mform->addElement('select', 'perpage', get_string('assignmentsperpage', 'assign'), $options, array('class'=>'ignoredirty'));
         $options = array(''=>get_string('filternone', 'assign'), ASSIGN_FILTER_SUBMITTED=>get_string('filtersubmitted', 'assign'), ASSIGN_FILTER_REQUIRE_GRADING=>get_string('filterrequiregrading', 'assign'));
-        $mform->addElement('select', 'filter', get_string('filter', 'assign'), $options, $autosubmit);
+        $mform->addElement('select', 'filter', get_string('filter', 'assign'), $options, array('class'=>'ignoredirty'));
+
+        // quickgrading
+        if ($instance['showquickgrading']) {
+            $mform->addElement('checkbox', 'quickgrading', get_string('quickgrading', 'assign'), '', array('class'=>'ignoredirty'));
+            $mform->addHelpButton('quickgrading', 'quickgrading', 'assign');
+            $mform->setDefault('quickgrading', $instance['quickgrading']);
+        }
 
         // hidden params
         $mform->addElement('hidden', 'contextid', $instance['contextid']);
index 3ab2e07..7d2bddc 100644 (file)
@@ -48,6 +48,8 @@ class assign_grading_table extends table_sql implements renderable {
     private $gradinginfo = null;
     /** @var int $tablemaxrows */
     private $tablemaxrows = 10000;
+    /** @var boolean $quickgrading */
+    private $quickgrading = false;
 
     /**
      * overridden constructor keeps a reference to the assignment class that is displaying this table
@@ -56,12 +58,14 @@ class assign_grading_table extends table_sql implements renderable {
      * @param int $perpage how many per page
      * @param string $filter The current filter
      * @param int $rowoffset For showing a subsequent page of results
+     * @param bool $quickgrading Is this table wrapped in a quickgrading form?
      */
-    function __construct(assign $assignment, $perpage, $filter, $rowoffset=0) {
+    function __construct(assign $assignment, $perpage, $filter, $rowoffset, $quickgrading) {
         global $CFG, $PAGE, $DB;
         parent::__construct('mod_assign_grading');
         $this->assignment = $assignment;
         $this->perpage = $perpage;
+        $this->quickgrading = $quickgrading;
         $this->output = $PAGE->get_renderer('mod_assign');
 
         $this->define_baseurl(new moodle_url($CFG->wwwroot . '/mod/assign/view.php', array('action'=>'grading', 'id'=>$assignment->get_course_module()->id)));
@@ -115,7 +119,7 @@ class assign_grading_table extends table_sql implements renderable {
 
         // Select
         $columns[] = 'select';
-        $headers[] = get_string('select') . '<div class="selectall"><input type="checkbox" name="selectall" title="' . get_string('selectall') . '"/></div>';
+        $headers[] = get_string('select') . '<div class="selectall"><input type="checkbox" class="ignoredirty" name="selectall" title="' . get_string('selectall') . '"/></div>';
 
         // Edit links
         if (!$this->is_downloading()) {
@@ -224,14 +228,16 @@ class assign_grading_table extends table_sql implements renderable {
      * Display a grade with scales etc.
      *
      * @param string $grade
+     * @param boolean $editable
+     * @param int $userid The user id of the user this grade belongs to
+     * @param int $modified Timestamp showing when the grade was last modified
      * @return string The formatted grade
      */
-    function display_grade($grade) {
+    function display_grade($grade, $editable, $userid, $modified) {
         if ($this->is_downloading()) {
             return $grade;
         }
-        $o = $this->assignment->display_grade($grade);
-
+        $o = $this->assignment->display_grade($grade, $editable, $userid, $modified);
         return $o;
     }
 
@@ -247,7 +253,20 @@ class assign_grading_table extends table_sql implements renderable {
             $options = make_grades_menu(-$outcome->scaleid);
 
             $options[0] = get_string('nooutcome', 'grades');
-            $outcomes .= $this->output->container($outcome->name . ': ' . $options[$outcome->grades[$row->userid]->grade], 'outcome');
+            if ($this->quickgrading&& !($outcome->grades[$row->userid]->locked)) {
+                $select = '<select name="outcome_' . $index . '_' . $row->userid . '" class="quickgrade">';
+                foreach ($options as $optionindex => $optionvalue) {
+                    $selected = '';
+                    if ($outcome->grades[$row->userid]->grade == $optionindex) {
+                        $selected = 'selected="selected"';
+                    }
+                    $select .= '<option value="' . $optionindex . '"' . $selected . '>' . $optionvalue . '</option>';
+                }
+                $select .= '</select>';
+                $outcomes .= $this->output->container($outcome->name . ': ' . $select, 'outcome');
+            } else {
+                $outcomes .= $this->output->container($outcome->name . ': ' . $options[$outcome->grades[$row->userid]->grade], 'outcome');
+            }
         }
 
         return $outcomes;
@@ -284,7 +303,7 @@ class assign_grading_table extends table_sql implements renderable {
      * @return string
      */
     function col_select(stdClass $row) {
-        return '<input type="checkbox" name="selectedusers" value="' . $row->userid . '"/>';
+        return '<input type="checkbox" name="selectedusers" value="' . $row->userid . '" class="ignoredirty"/>';
     }
 
     /**
@@ -322,13 +341,7 @@ class assign_grading_table extends table_sql implements renderable {
             $separator = $this->output->spacer(array(), true);
         }
 
-
-        if ($row->grade) {
-            $grade = $this->display_grade($row->grade);
-        } else {
-            $grade = '-';
-        }
-
+        $grade = $this->display_grade($row->grade, $this->quickgrading, $row->userid, $row->timemarked);
 
         //return $grade . $separator . $link;
         return $link . $separator . $grade;
@@ -345,7 +358,7 @@ class assign_grading_table extends table_sql implements renderable {
 
         $grade = $this->get_gradebook_data_for_user($row->userid);
         if ($grade) {
-            $o = $this->display_grade($grade->grade);
+            $o = $this->display_grade($grade->grade, false, $row->userid, $row->timemarked);
         }
 
         return $o;
index b936ece..ace9fab 100644 (file)
@@ -101,6 +101,8 @@ for <i>\'{$a->assignment}\'  at {$a->timeupdated}</i><br /><br />
 It is <a href="{$a->url}">available on the web site</a>.';
 $string['enabled'] = 'Enabled';
 $string['errornosubmissions'] = 'There are no submissions to download';
+$string['errorquickgradingvsadvancedgrading'] = 'The grades were not saved because this assignment is currently using advanced grading';
+$string['errorrecordmodified'] = 'The grades were not saved because someone has modified one or more records more recently than when you loaded the page.';
 $string['feedbackcomments'] = 'Feedback comments';
 $string['feedback'] = 'Feedback';
 $string['feedbackplugins'] = 'Feedback plugins';
@@ -173,11 +175,16 @@ $string['preventlatesubmissions_help'] = 'If enabled, students will not be able
 $string['preventsubmissions'] = 'Prevent the user from making any more submissions to this assignment.';
 $string['preventsubmissionsshort'] = 'Prevent submission changes';
 $string['previous'] = 'Previous';
+$string['quickgrading'] = 'Quick grading';
+$string['quickgradingresult'] = 'Quick grading';
+$string['quickgradingchangessaved'] = 'The grade changes were saved';
+$string['quickgrading_help'] = 'Quick grading allows you to assign grades (and outcomes) directly in the submissions table. Quick grading is not compatible with advanced grading and is not recommended when there are multiple markers.';
 $string['reverttodraftforstudent'] = 'Revert submission to draft for student: (id={$a->id}, fullname={$a->fullname}).';
 $string['reverttodraft'] = 'Revert the submission to draft status.';
 $string['reverttodraftshort'] = 'Revert the submission to draft';
 $string['reviewed'] = 'Reviewed';
 $string['savechanges'] = 'Save changes';
+$string['saveallchanges'] = 'Save all changes';
 $string['savenext'] = 'Save and show next';
 $string['sendnotifications'] = 'Send notifications to graders';
 $string['sendnotifications_help'] = 'If enabled, graders (usually teachers) receive a message whenever a student submits an assignment, early, on time and late. Message methods are configurable.';
index 123c9d4..0e55f45 100644 (file)
@@ -348,6 +348,9 @@ class assign {
                 //cancel button
                 $action = 'grading';
             }
+        }else if ($action == 'quickgrade') {
+            $message = $this->process_save_quick_grades();
+            $action = 'quickgradingresult';
         }else if ($action == 'saveoptions') {
             $this->process_save_grading_options();
             $action = 'grading';
@@ -360,6 +363,9 @@ class assign {
         if ($action == 'previousgrade') {
             $mform = null;
             $o .= $this->view_single_grade_page($mform, -1);
+        } else if ($action == 'quickgradingresult') {
+            $mform = null;
+            $o .= $this->view_quickgrading_result($message);
         } else if ($action == 'nextgrade') {
             $mform = null;
             $o .= $this->view_single_grade_page($mform, 1);
@@ -882,23 +888,31 @@ class assign {
      * Return a grade in user-friendly form, whether it's a scale or not
      *
      * @param mixed $grade int|null
+     * @param boolean $editing Are we allowing changes to this grade?
+     * @param int $userid The user id the grade belongs to
+     * @param int $modified Timestamp from when the grade was last modified
      * @return string User-friendly representation of grade
      */
-    public function display_grade($grade) {
+    public function display_grade($grade, $editing, $userid=0, $modified=0) {
         global $DB;
 
         static $scalegrades = array();
 
-
-
-        if ($this->get_instance()->grade >= 0) {    // Normal number
-            if ($grade == -1 || $grade === null) {
+        if ($this->get_instance()->grade >= 0) {
+            // Normal number
+            if ($editing) {
+                $o = '<input type="text" name="quickgrade_' . $userid . '" value="' . $grade . '" size="6" maxlength="10" class="quickgrade"/>';
+                $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade,2);
+                $o .= '<input type="hidden" name="grademodified_' . $userid . '" value="' . $modified . '"/>';
+                return $o;
+            } else if ($grade == -1 || $grade === null) {
                 return '-';
             } else {
                 return format_float(($grade),2) .'&nbsp;/&nbsp;'. format_float($this->get_instance()->grade,2);
             }
 
-        } else {                                // Scale
+        } else {
+            // Scale
             if (empty($this->cache['scale'])) {
                 if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) {
                     $this->cache['scale'] = make_menu_from_list($scale->scale);
@@ -906,11 +920,26 @@ class assign {
                     return '-';
                 }
             }
-            $scaleid = (int)$grade;
-            if (isset($this->cache['scale'][$scaleid])) {
-                return $this->cache['scale'][$scaleid];
+            if ($editing) {
+                $o = '<select name="quickgrade_' . $userid . '" class="quickgrade">';
+                $o .= '<option value="-1">' . get_string('nograde') . '</option>';
+                foreach ($this->cache['scale'] as $optionid => $option) {
+                    $selected = '';
+                    if ($grade == $optionid) {
+                        $selected = 'selected="selected"';
+                    }
+                    $o .= '<option value="' . $optionid . '" ' . $selected . '>' . $option . '</option>';
+                }
+                $o .= '</select>';
+                $o .= '<input type="hidden" name="grademodified_' . $userid . '" value="' . $modified . '"/>';
+                return $o;
+            } else {
+                $scaleid = (int)$grade;
+                if (isset($this->cache['scale'][$scaleid])) {
+                    return $this->cache['scale'][$scaleid];
+                }
+                return '-';
             }
-            return '-';
         }
     }
 
@@ -961,7 +990,7 @@ class assign {
      */
     private function get_grading_userid_list(){
         $filter = get_user_preferences('assign_filter', '');
-        $table = new assign_grading_table($this, 0, $filter);
+        $table = new assign_grading_table($this, 0, $filter, 0, false);
 
         $useridlist = $table->get_column_data('userid');
 
@@ -987,7 +1016,7 @@ class assign {
         }
 
         $filter = get_user_preferences('assign_filter', '');
-        $table = new assign_grading_table($this, 0, $filter);
+        $table = new assign_grading_table($this, 0, $filter, 0, false);
 
         $userid = $table->get_cell_data($num, 'userid', $last);
 
@@ -1203,11 +1232,28 @@ class assign {
         return $result;
     }
 
+    /**
+     * Display a grading error
+     *
+     * @param string $message - The description of the result
+     * @return string
+     */
+    private function view_quickgrading_result($message) {
+        $o = '';
+        $o .= $this->output->render(new assign_header($this->get_instance(),
+                                                      $this->get_context(),
+                                                      $this->show_intro(),
+                                                      $this->get_course_module()->id,
+                                                      get_string('quickgradingresult', 'assign')));
+        $o .= $this->output->render(new assign_quickgrading_result($message, $this->get_course_module()->id));
+        $o .= $this->view_footer();
+        return $o;
+    }
 
     /**
      * Display the page footer
      *
-     * @return None
+     * @return string
      */
     private function view_footer() {
         return $this->output->render_footer();
@@ -1523,6 +1569,7 @@ class assign {
         // Include grading options form
         require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
         require_once($CFG->dirroot . '/mod/assign/gradingactionsform.php');
+        require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
         require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
         $o = '';
 
@@ -1548,13 +1595,25 @@ class assign {
                                                                   'post', '',
                                                                   array('class'=>'gradingactionsform'));
 
+        $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
+
         $perpage = get_user_preferences('assign_perpage', 10);
         $filter = get_user_preferences('assign_filter', '');
+        $controller = $gradingmanager->get_active_controller();
+        $showquickgrading = empty($controller);
+        if (optional_param('action', '', PARAM_ALPHA) == 'saveoptions') {
+            $quickgrading = optional_param('quickgrading', false, PARAM_BOOL);
+            set_user_preference('assign_quickgrading', $quickgrading);
+        }
+        $quickgrading = get_user_preferences('assign_quickgrading', false);
+
         // print options  for changing the filter and changing the number of results per page
         $gradingoptionsform = new mod_assign_grading_options_form(null,
                                                                   array('cm'=>$this->get_course_module()->id,
                                                                         'contextid'=>$this->context->id,
-                                                                        'userid'=>$USER->id),
+                                                                        'userid'=>$USER->id,
+                                                                        'showquickgrading'=>$showquickgrading,
+                                                                        'quickgrading'=>$quickgrading),
                                                                   'post', '',
                                                                   array('class'=>'gradingoptionsform'));
 
@@ -1577,10 +1636,18 @@ class assign {
         }
 
         $o .= $this->output->render(new assign_form('gradingactionsform', $gradingactionsform));
-        $o .= $this->output->render(new assign_form('gradingoptionsform', $gradingoptionsform));
+        $o .= $this->output->render(new assign_form('gradingoptionsform', $gradingoptionsform, 'M.mod_assign.init_grading_options'));
 
         // load and print the table of submissions
-        $o .= $this->output->render(new assign_grading_table($this, $perpage, $filter));
+        if ($showquickgrading && $quickgrading) {
+            $table = $this->output->render(new assign_grading_table($this, $perpage, $filter, 0, true));
+            $quickgradingform = new mod_assign_quick_grading_form(null,
+                                                                  array('cm'=>$this->get_course_module()->id,
+                                                                        'gradingtable'=>$table));
+            $o .= $this->output->render(new assign_form('quickgradingform', $quickgradingform));
+        } else {
+            $o .= $this->output->render(new assign_grading_table($this, $perpage, $filter, 0, false));
+        }
 
         $currentgroup = groups_get_activity_group($this->get_course_module(), true);
         $users = array_keys($this->list_participants($currentgroup, true));
@@ -1658,7 +1725,7 @@ class assign {
                                                       $this->get_course_module()->id,
                                                       get_string('editsubmission', 'assign')));
 
-        $o .= $this->output->notification('This assignment is no longer accepting submissions');
+        $o .= $this->output->notification(get_string('submissionsclosed', 'assign'));
 
         $o .= $this->view_footer();
 
@@ -1682,9 +1749,7 @@ class assign {
         require_capability('mod/assign:submit', $this->context);
 
         if (!$this->submissions_open()) {
-            $subclosed  = '';
-            $subclosed .= $this->view_student_error_message();
-            return $subclosed;
+            return $this->view_student_error_message();
         }
         $o .= $this->output->render(new assign_header($this->get_instance(),
                                                       $this->get_context(),
@@ -1875,7 +1940,7 @@ class assign {
                                                                  $gradebookgrade->str_long_grade,
                                                                  has_capability('mod/assign:grade', $this->get_context()));
                 } else {
-                    $gradefordisplay = $this->display_grade($gradebookgrade->grade);
+                    $gradefordisplay = $this->display_grade($gradebookgrade->grade, false);
                 }
 
                 $gradeddate = $gradebookgrade->dategraded;
@@ -2256,6 +2321,119 @@ class assign {
         }
     }
 
+    /**
+     * save quick grades
+     *
+     * @global moodle_database $DB
+     * @return string The result of the save operation
+     */
+    private function process_save_quick_grades() {
+        global $USER, $DB, $CFG;
+
+        // Need grade permission
+        require_capability('mod/assign:grade', $this->context);
+
+        // make sure advanced grading is disabled
+        $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
+        $controller = $gradingmanager->get_active_controller();
+        if (!empty($controller)) {
+            return get_string('errorquickgradingvsadvancedgrading', 'assign');
+        }
+
+        $users = array();
+        // first check all the last modified values
+        // Using POST is really unfortunate. A better solution needs to be found here. As we are looking for grades students we could
+        // gets a list of possible users and look for values based upon that.
+        foreach ($_POST as $key => $value) {
+            if (preg_match('#^grademodified_(\d+)$#', $key, $matches)) {
+                // gather the userid, updated grade and last modified value
+                $record = new stdClass();
+                $record->userid = (int)$matches[1];
+                $record->grade = required_param('quickgrade_' . $record->userid, PARAM_INT);
+                $record->lastmodified = clean_param($value, PARAM_INT);
+                $record->gradinginfo = grade_get_grades($this->get_course()->id, 'mod', 'assign', $this->get_instance()->id, array($record->userid));
+                $users[$record->userid] = $record;
+            }
+        }
+        if (empty($users)) {
+            // Quick check to see whether we have any users to update and we don't
+            return get_string('quickgradingchangessaved', 'assign'); // Technical lie
+        }
+
+        list($userids, $params) = $DB->get_in_or_equal(array_keys($users), SQL_PARAMS_NAMED);
+        $params['assignment'] = $this->get_instance()->id;
+        // check them all for currency
+        $sql = 'SELECT u.id as userid, g.grade as grade, g.timemodified as lastmodified
+                  FROM {user} u
+             LEFT JOIN {assign_grades} g ON u.id = g.userid AND g.assignment = :assignment
+                 WHERE u.id ' . $userids;
+        $currentgrades = $DB->get_recordset_sql($sql, $params);
+
+        $modifiedusers = array();
+        foreach ($currentgrades as $current) {
+            $modified = $users[(int)$current->userid];
+
+            // check to see if the outcomes were modified
+            if ($CFG->enableoutcomes) {
+                foreach ($modified->gradinginfo->outcomes as $outcomeid => $outcome) {
+                    $oldoutcome = $outcome->grades[$modified->userid]->grade;
+                    $newoutcome = optional_param('outcome_' . $outcomeid . '_' . $modified->userid, -1, PARAM_INT);
+                    if ($oldoutcome != $newoutcome) {
+                        // can't check modified time for outcomes because it is not reported
+                        $modifiedusers[$modified->userid] = $modified;
+                        continue;
+                    }
+                }
+            }
+
+
+            if (($current->grade < 0 || $current->grade === NULL) &&
+                ($modified->grade < 0 || $modified->grade === NULL)) {
+                // different ways to indicate no grade
+                continue;
+            }
+            if ($current->grade != $modified->grade) {
+                // grade changed
+                if ((int)$current->lastmodified > (int)$modified->lastmodified) {
+                    // error - record has been modified since viewing the page
+                    return get_string('errorrecordmodified', 'assign');
+                } else {
+                    $modifiedusers[$modified->userid] = $modified;
+                }
+            }
+
+        }
+        $currentgrades->close();
+
+        // ok - ready to process the updates
+        foreach ($modifiedusers as $userid => $modified) {
+            $grade = $this->get_user_grade($userid, true);
+            $grade->grade= grade_floatval($modified->grade);
+            $grade->grader= $USER->id;
+
+            $this->update_grade($grade);
+
+            // save outcomes
+            if ($CFG->enableoutcomes) {
+                $data = array();
+                foreach ($modified->gradinginfo->outcomes as $outcomeid => $outcome) {
+                    $oldoutcome = $outcome->grades[$modified->userid]->grade;
+                    $newoutcome = optional_param('outcome_' . $outcomeid . '_' . $modified->userid, -1, PARAM_INT);
+                    if ($oldoutcome != $newoutcome) {
+                        $data[$outcomeid] = $newoutcome;
+                    }
+                }
+                if (count($data) > 0) {
+                    grade_update_outcomes('mod/assign', $this->course->id, 'mod', 'assign', $this->get_instance()->id, $userid, $data);
+                }
+            }
+
+            $this->add_to_log('grade submission', $this->format_grade_for_log($grade));
+        }
+
+        return get_string('quickgradingchangessaved', 'assign');
+    }
+
     /**
      * save grading options
      *
@@ -2270,10 +2448,7 @@ class assign {
         // Need submit permission to submit an assignment
         require_capability('mod/assign:grade', $this->context);
 
-
-
-        $mform = new mod_assign_grading_options_form(null, array('cm'=>$this->get_course_module()->id, 'contextid'=>$this->context->id, 'userid'=>$USER->id));
-
+        $mform = new mod_assign_grading_options_form(null, array('cm'=>$this->get_course_module()->id, 'contextid'=>$this->context->id, 'userid'=>$USER->id, 'showquickgrading'=>false));
         if ($formdata = $mform->get_data()) {
             set_user_preference('assign_perpage', $formdata->perpage);
             set_user_preference('assign_filter', $formdata->filter);
@@ -2295,7 +2470,7 @@ class assign {
 
         $info = get_string('gradestudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user)));
         if ($grade->grade != '') {
-            $info .= get_string('grade') . ': ' . $this->display_grade($grade->grade) . '. ';
+            $info .= get_string('grade') . ': ' . $this->display_grade($grade->grade, false) . '. ';
         } else {
             $info .= get_string('nograde', 'assign');
         }
index 386a117..d361b32 100644 (file)
@@ -39,23 +39,25 @@ M.mod_assign.init_grading_table = function(Y) {
         });
 
         var selectall = Y.one('th.c0 input');
-        selectall.on('change', function(e) {
-            if (e.currentTarget.get('checked')) {
-                checkboxes = Y.all('td.c0 input');
-                checkboxes.each(function(node) {
-                    rowelement = node.get('parentNode').get('parentNode');
-                    node.set('checked', true);
-                    rowelement.setAttribute('class', 'selectedrow');
-                });
-            } else {
-                checkboxes = Y.all('td.c0 input');
-                checkboxes.each(function(node) {
-                    rowelement = node.get('parentNode').get('parentNode');
-                    node.set('checked', false);
-                    rowelement.setAttribute('class', 'unselectedrow');
-                });
-            }
-        });
+        if (selectall) {
+            selectall.on('change', function(e) {
+                if (e.currentTarget.get('checked')) {
+                    checkboxes = Y.all('td.c0 input');
+                    checkboxes.each(function(node) {
+                        rowelement = node.get('parentNode').get('parentNode');
+                        node.set('checked', true);
+                        rowelement.setAttribute('class', 'selectedrow');
+                    });
+                } else {
+                    checkboxes = Y.all('td.c0 input');
+                    checkboxes.each(function(node) {
+                        rowelement = node.get('parentNode').get('parentNode');
+                        node.set('checked', false);
+                        rowelement.setAttribute('class', 'unselectedrow');
+                    });
+                }
+            });
+        }
 
         var batchform = Y.one('form.gradingbatchoperationsform');
         batchform.on('submit', function(e) {
@@ -99,8 +101,70 @@ M.mod_assign.init_grading_table = function(Y) {
 
 
         });
+        var quickgrade = Y.all('.gradingtable .quickgrade');
+        quickgrade.each(function(quick) {
+            quick.on('change', function(e) {
+                this.get('parentNode').addClass('quickgrademodified');
+            });
+        });
+    });
+};
+
+M.mod_assign.check_dirty_quickgrading_form = function(e) {
+    if (!M.core_formchangechecker.get_form_dirty_state()) {
+        // the form is not dirty, so don't display any message
+        return;
+    }
+
+    // This is the error message that we'll show to browsers which support it
+    var warningmessage = 'There are unsaved quickgrading changes. Do you really wanto to leave this page?';
+
+    // Most browsers are happy with the returnValue being set on the event
+    // But some browsers do not consistently pass the event
+    if (e) {
+        e.returnValue = warningmessage;
+    }
+
+    // But some require it to be returned instead
+    return warningmessage;
+}
+M.mod_assign.init_grading_options = function(Y) {
+    Y.use('node', function(Y) {
+
+        var paginationelement = Y.one('#id_perpage');
+        paginationelement.on('change', function(e) {
+            Y.one('form.gradingoptionsform').submit();
+        });
+        var filterelement = Y.one('#id_filter');
+        filterelement.on('change', function(e) {
+            Y.one('form.gradingoptionsform').submit();
+        });
+        var quickgradingelement = Y.one('#id_quickgrading');
+        quickgradingelement.on('change', function(e) {
+            Y.one('form.gradingoptionsform').submit();
+        });
 
     });
 
 
 };
+// override the default dirty form behaviour to ignore any input with the class "ignoredirty"
+M.mod_assign.set_form_changed = M.core_formchangechecker.set_form_changed;
+M.core_formchangechecker.set_form_changed = function(e) {
+    var target = e.currentTarget;
+    if (!target.hasClass('ignoredirty')) {
+        M.mod_assign.set_form_changed(e);
+    }
+}
+
+M.mod_assign.get_form_dirty_state = M.core_formchangechecker.get_form_dirty_state;
+M.core_formchangechecker.get_form_dirty_state = function() {
+    var state = M.core_formchangechecker.stateinformation;
+    if (state.focused_element) {
+        if (state.focused_element.element.hasClass('ignoredirty')) {
+            state.focused_element.initial_value = state.focused_element.element.get('value')
+        }
+    }
+    return M.mod_assign.get_form_dirty_state();
+}
+
diff --git a/mod/assign/quickgradingform.php b/mod/assign/quickgradingform.php
new file mode 100644 (file)
index 0000000..0598b57
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the forms to create and edit an instance of this module
+ *
+ * @package   mod_assign
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+
+/** Include formslib.php */
+require_once ($CFG->libdir.'/formslib.php');
+/** Include locallib.php */
+require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+/**
+ * Assignment quick grading form
+ *
+ * @package   mod_assign
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_assign_quick_grading_form extends moodleform {
+    /**
+     * Define this form - called from the parent constructor
+     */
+    function definition() {
+        $mform = $this->_form;
+        $instance = $this->_customdata;
+
+        // visible elements
+        $mform->addElement('html', $instance['gradingtable']);
+
+        // hidden params
+        $mform->addElement('hidden', 'id', $instance['cm']);
+        $mform->setType('id', PARAM_INT);
+        $mform->addElement('hidden', 'action', 'quickgrade');
+        $mform->setType('action', PARAM_ALPHA);
+
+        // buttons
+        $mform->addElement('header', 'general', get_string('quickgrading', 'assign'));
+        $mform->addElement('submit', 'savequickgrades', get_string('saveallchanges', 'assign'));
+    }
+}
+
index 6ce054c..fa7a9fe 100644 (file)
@@ -48,6 +48,29 @@ class assign_submit_for_grading_page implements renderable {
 
 }
 
+/**
+ * Implements a renderable grading error notification
+ * @package   mod_assign
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assign_quickgrading_result implements renderable {
+    /** @var string $message is the message to display to the user */
+    var $message = '';
+    /** @var int $coursemoduleid */
+    var $coursemoduleid = 0;
+
+    /**
+     * Constructor
+     * @param string $message This is the message to display
+     */
+    public function __construct($message, $coursemoduleid) {
+        $this->message = $message;
+        $this->coursemoduleid = $coursemoduleid;
+    }
+
+}
+
 /**
  * Implements a renderable grading options form
  * @package   mod_assign
@@ -59,19 +82,24 @@ class assign_form implements renderable {
     var $form = null;
     /** @var string $classname is the name of the class to assign to the container */
     var $classname = '';
+    /** @var string $jsinitfunction is an optional js function to add to the page requires */
+    var $jsinitfunction = '';
 
     /**
      * Constructor
-     * @param string $classname
-     * @param moodleform $form
+     * @param string $classname This is the class name for the container div
+     * @param moodleform $form This is the moodleform
+     * @param string $jsinitfunction This is an optional js function to add to the page requires
      */
-    public function __construct($classname, moodleform $form) {
+    public function __construct($classname, moodleform $form, $jsinitfunction = '') {
         $this->classname = $classname;
         $this->form = $form;
+        $this->jsinitfunction = $jsinitfunction;
     }
 
 }
 
+
 /**
  * Implements a renderable user summary
  * @package   mod_assign
index 598969e..9f57157 100644 (file)
@@ -86,6 +86,21 @@ class mod_assign_renderer extends plugin_renderer_base {
         $table->data[] = $row;
     }
 
+    /**
+     * Render a grading error notification
+     * @param assign_quickgrading_result $result The result to render
+     * @return string
+     */
+    public function render_assign_quickgrading_result(assign_quickgrading_result $result) {
+        $url = new moodle_url('/mod/assign/view.php', array('id' => $result->coursemoduleid, 'action'=>'grading'));
+
+        $o = '';
+        $o .= $this->output->heading(get_string('quickgradingresult', 'assign'), 4);
+        $o .= $this->output->notification($result->message);
+        $o .= $this->output->continue_button($url);
+        return $o;
+    }
+
     /**
      * Render the generic form
      * @param assign_form $form The form to render
@@ -93,6 +108,9 @@ class mod_assign_renderer extends plugin_renderer_base {
      */
     public function render_assign_form(assign_form $form) {
         $o = '';
+        if ($form->jsinitfunction) {
+            $this->page->requires->js_init_call($form->jsinitfunction, array());
+        }
         $o .= $this->output->box_start('boxaligncenter ' . $form->classname);
         $o .= $this->moodleform($form->form);
         $o .= $this->output->box_end();
index bfccef5..11b8aa6 100644 (file)
@@ -125,3 +125,6 @@ div.earlysubmission {
     border: 0px;
 }
 
+#page-mod-assign-view div.gradingtable tr .quickgrademodified {
+    background-color: #FFCC99;
+}
\ No newline at end of file
diff --git a/mod/book/README.md b/mod/book/README.md
new file mode 100644 (file)
index 0000000..c4161c1
--- /dev/null
@@ -0,0 +1,51 @@
+Book module for Moodle (http://moodle.org/) - Copyright (C) 2004-2011  Petr Skoda (http://skodak.org/)
+
+The Book module makes it easy to create multi-page resources with a book-like format. This module can be used to build complete book-like websites inside of your Moodle course.
+This module was developed for Technical University of Liberec (Czech Republic). Many ideas and code were taken from other Moodle modules and Moodle itself
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details: http://www.gnu.org/copyleft/gpl.html
+
+
+Created by:
+
+* Petr Skoda (skodak) - most of the coding & design
+* Mojmir Volf, Eloy Lafuente, Antonio Vicent and others
+
+
+
+Project page:
+
+* https://github.com/skodak/moodle-mod_book
+* http://moodle.org/plugins/view.php?plugin=mod_book
+
+
+Installation:
+
+* http://docs.moodle.org/20/en/Installing_contributed_modules_or_plugins
+
+
+Issue tracker:
+
+* https://github.com/skodak/moodle-mod_book/issues?milestone=&labels=
+
+
+Intentionally omitted features:
+
+* more chapter levels - it would encourage teachers to write too much complex and long books, better use standard standalone HTML editor and import it as Resource. DocBook format is another suitable solution.
+* TOC hiding in normal view - instead use printer friendly view
+* PDF export - there is no elegant way AFAIK to convert HTML to PDF, use virtual PDF printer or better use DocBook format for authoring
+* detailed student tracking (postponed till officially supported)
+* export as zipped set of HTML pages - instead use browser command Save page as... in print view
+
+
+Future:
+
+* No more development planned
diff --git a/mod/book/backup/moodle1/lib.php b/mod/book/backup/moodle1/lib.php
new file mode 100755 (executable)
index 0000000..5a58526
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Provides support for the conversion of moodle1 backup to the moodle2 format
+ *
+ * @package    mod_book
+ * @copyright  2011 T├Ánis Tartes <t6nis20@gmail.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Book conversion handler
+ */
+class moodle1_mod_book_handler extends moodle1_mod_handler {
+
+    /** @var moodle1_file_manager */
+    protected $fileman = null;
+
+    /** @var int cmid */
+    protected $moduleid = null;
+
+    /**
+     * Declare the paths in moodle.xml we are able to convert
+     *
+     * The method returns list of {@link convert_path} instances. For each path returned,
+     * at least one of on_xxx_start(), process_xxx() and on_xxx_end() methods must be
+     * defined. The method process_xxx() is not executed if the associated path element is
+     * empty (i.e. it contains none elements or sub-paths only).
+     *
+     * Note that the path /MOODLE_BACKUP/COURSE/MODULES/MOD/BOOK does not
+     * actually exist in the file. The last element with the module name was
+     * appended by the moodle1_converter class.
+     *
+     * @return array of {@link convert_path} instances
+     */
+    public function get_paths() {
+        return array(
+            new convert_path('book', '/MOODLE_BACKUP/COURSE/MODULES/MOD/BOOK',
+                    array(
+                        'renamefields' => array(
+                            'summary' => 'intro',
+                        ),
+                        'newfields' => array(
+                            'introformat' => FORMAT_MOODLE,
+                        ),
+                        'dropfields' => array(
+                            'disableprinting'
+                        ),
+                    )
+                ),
+            new convert_path('book_chapters', '/MOODLE_BACKUP/COURSE/MODULES/MOD/BOOK/CHAPTERS/CHAPTER',
+                    array(
+                        'newfields' => array(
+                            'contentformat' => FORMAT_HTML,
+                        ),
+                    )
+                ),
+        );
+    }
+
+    /**
+     * This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/BOOK
+     * data available
+     * @param array $data
+     */
+    public function process_book($data) {
+        global $CFG;
+
+        // get the course module id and context id
+        $instanceid     = $data['id'];
+        $cminfo         = $this->get_cminfo($instanceid);
+        $this->moduleid = $cminfo['id'];
+        $contextid      = $this->converter->get_contextid(CONTEXT_MODULE, $this->moduleid);
+
+        // replay the upgrade step 2009042006
+        if ($CFG->texteditors !== 'textarea') {
+            $data['intro']       = text_to_html($data['intro'], false, false, true);
+            $data['introformat'] = FORMAT_HTML;
+        }
+
+        // get a fresh new file manager for this instance
+        $this->fileman = $this->converter->get_file_manager($contextid, 'mod_book');
+
+        // convert course files embedded into the intro
+        $this->fileman->filearea = 'intro';
+        $this->fileman->itemid   = 0;
+        $data['intro'] = moodle1_converter::migrate_referenced_files($data['intro'], $this->fileman);
+
+        // start writing book.xml
+        $this->open_xml_writer("activities/book_{$this->moduleid}/book.xml");
+        $this->xmlwriter->begin_tag('activity', array('id' => $instanceid, 'moduleid' => $this->moduleid,
+            'modulename' => 'book', 'contextid' => $contextid));
+        $this->xmlwriter->begin_tag('book', array('id' => $instanceid));
+
+        foreach ($data as $field => $value) {
+            if ($field <> 'id') {
+                $this->xmlwriter->full_tag($field, $value);
+            }
+        }
+    }
+
+    /**
+     * This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/BOOK/CHAPTERS/CHAPTER
+     * data available
+     * @param array $data
+     */
+    public function process_book_chapters($data) {
+        $this->write_xml('chapter', $data, array('/chapter/id'));
+
+        // convert chapter files
+        $this->fileman->filearea = 'chapter';
+        $this->fileman->itemid   = $data['id'];
+        $data['content'] = moodle1_converter::migrate_referenced_files($data['content'], $this->fileman);
+    }
+
+    /**
+     * This is executed when the parser reaches the <OPTIONS> opening element
+     */
+    public function on_book_chapters_start() {
+        $this->xmlwriter->begin_tag('chapters');
+    }
+
+    /**
+     * This is executed when the parser reaches the closing </OPTIONS> element
+     */
+    public function on_book_chapters_end() {
+        $this->xmlwriter->end_tag('chapters');
+    }
+
+    /**
+     * This is executed when we reach the closing </MOD> tag of our 'book' path
+     */
+    public function on_book_end() {
+        // finalize book.xml
+        $this->xmlwriter->end_tag('book');
+        $this->xmlwriter->end_tag('activity');
+        $this->close_xml_writer();
+
+        // write inforef.xml
+        $this->open_xml_writer("activities/book_{$this->moduleid}/inforef.xml");
+        $this->xmlwriter->begin_tag('inforef');
+        $this->xmlwriter->begin_tag('fileref');
+        foreach ($this->fileman->get_fileids() as $fileid) {
+            $this->write_xml('file', array('id' => $fileid));
+        }
+        $this->xmlwriter->end_tag('fileref');
+        $this->xmlwriter->end_tag('inforef');
+        $this->close_xml_writer();
+    }
+}
diff --git a/mod/book/backup/moodle2/backup_book_activity_task.class.php b/mod/book/backup/moodle2/backup_book_activity_task.class.php
new file mode 100644 (file)
index 0000000..e6b2b82
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Description of book backup task
+ *
+ * @package    mod_book
+ * @copyright  2010-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->dirroot.'/mod/book/backup/moodle2/backup_book_stepslib.php');    // Because it exists (must)
+require_once($CFG->dirroot.'/mod/book/backup/moodle2/backup_book_settingslib.php'); // Because it exists (optional)
+
+class backup_book_activity_task extends backup_activity_task {
+
+    /**
+     * Define (add) particular settings this activity can have
+     *
+     * @return void
+     */
+    protected function define_my_settings() {
+        // No particular settings for this activity
+    }
+
+    /**
+     * Define (add) particular steps this activity can have
+     *
+     * @return void
+     */
+    protected function define_my_steps() {
+        // book only has one structure step
+        $this->add_step(new backup_book_activity_structure_step('book_structure', 'book.xml'));
+    }
+
+    /**
+     * Code the transformations to perform in the activity in
+     * order to get transportable (encoded) links
+     *
+     * @param string $content
+     * @return string encoded content
+     */
+    static public function encode_content_links($content) {
+        global $CFG;
+
+        $base = preg_quote($CFG->wwwroot, "/");
+
+        // Link to the list of books
+        $search  = "/($base\/mod\/book\/index.php\?id=)([0-9]+)/";
+        $content = preg_replace($search, '$@BOOKINDEX*$2@$', $content);
+
+        // Link to book view by moduleid
+        $search  = "/($base\/mod\/book\/view.php\?id=)([0-9]+)(&|&amp;)chapterid=([0-9]+)/";
+        $content = preg_replace($search, '$@BOOKVIEWBYIDCH*$2*$4@$', $content);
+
+        $search  = "/($base\/mod\/book\/view.php\?id=)([0-9]+)/";
+        $content = preg_replace($search, '$@BOOKVIEWBYID*$2@$', $content);
+
+        // Link to book view by bookid
+        $search  = "/($base\/mod\/book\/view.php\?b=)([0-9]+)(&|&amp;)chapterid=([0-9]+)/";
+        $content = preg_replace($search, '$@BOOKVIEWBYBCH*$2*$4@$', $content);
+
+        $search  = "/($base\/mod\/book\/view.php\?b=)([0-9]+)/";
+        $content = preg_replace($search, '$@BOOKVIEWBYB*$2@$', $content);
+
+        return $content;
+    }
+}
diff --git a/mod/book/backup/moodle2/backup_book_settingslib.php b/mod/book/backup/moodle2/backup_book_settingslib.php
new file mode 100644 (file)
index 0000000..f52d803
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Description of book backup settings
+ *
+ * @package    mod_book
+ * @copyright  2010 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+ // This activity has not particular settings but the inherited from the generic
+ // backup_activity_task so here there isn't any class definition, like the ones
+ // existing in /backup/moodle2/backup_settingslib.php (activities section)
diff --git a/mod/book/backup/moodle2/backup_book_stepslib.php b/mod/book/backup/moodle2/backup_book_stepslib.php
new file mode 100644 (file)
index 0000000..27f1c89
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Define all the backup steps that will be used by the backup_book_activity_task
+ *
+ * @package    mod_book
+ * @copyright  2010 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Structure step to backup one book activity
+ */
+class backup_book_activity_structure_step extends backup_activity_structure_step {
+
+    protected function define_structure() {
+
+        // Define each element separated
+        $book     = new backup_nested_element('book', array('id'), array('name', 'intro', 'introformat', 'numbering', 'customtitles', 'timecreated', 'timemodified'));
+        $chapters = new backup_nested_element('chapters');
+        $chapter  = new backup_nested_element('chapter', array('id'), array('pagenum', 'subchapter', 'title', 'content', 'contentformat', 'hidden', 'timemcreated', 'timemodified', 'importsrc',));
+
+        $book->add_child($chapters);
+        $chapters->add_child($chapter);
+
+        // Define sources
+        $book->set_source_table('book', array('id' => backup::VAR_ACTIVITYID));
+        $chapter->set_source_table('book_chapters', array('bookid' => backup::VAR_PARENTID));
+
+        // Define file annotations
+        $book->annotate_files('mod_book', 'intro', null); // This file area hasn't itemid
+        $chapter->annotate_files('mod_book', 'chapter', 'id');
+
+        // Return the root element (book), wrapped into standard activity structure
+        return $this->prepare_activity_structure($book);
+    }
+}
diff --git a/mod/book/backup/moodle2/restore_book_activity_task.class.php b/mod/book/backup/moodle2/restore_book_activity_task.class.php
new file mode 100644 (file)
index 0000000..da1b75f
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Description of book restore task
+ *
+ * @package    mod_book
+ * @copyright  2010 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot . '/mod/book/backup/moodle2/restore_book_stepslib.php'); // Because it exists (must)
+
+class restore_book_activity_task extends restore_activity_task {
+
+    /**
+     * Define (add) particular settings this activity can have
+     *
+     * @return void
+     */
+    protected function define_my_settings() {
+        // No particular settings for this activity
+    }
+
+    /**
+     * Define (add) particular steps this activity can have
+     *
+     * @return void
+     */
+    protected function define_my_steps() {
+        // Choice only has one structure step
+        $this->add_step(new restore_book_activity_structure_step('book_structure', 'book.xml'));
+    }
+
+    /**
+     * Define the contents in the activity that must be
+     * processed by the link decoder
+     *
+     * @return array
+     */
+    static public function define_decode_contents() {
+        $contents = array();
+
+        $contents[] = new restore_decode_content('book', array('intro'), 'book');
+        $contents[] = new restore_decode_content('book_chapters', array('content'), 'book_chapter');
+
+        return $contents;
+    }
+
+    /**
+     * Define the decoding rules for links belonging
+     * to the activity to be executed by the link decoder
+     *
+     * @return array
+     */
+    static public function define_decode_rules() {
+        $rules = array();
+
+        // List of books in course
+        $rules[] = new restore_decode_rule('BOOKINDEX', '/mod/book/index.php?id=$1', 'course');
+
+        // book by cm->id
+        $rules[] = new restore_decode_rule('BOOKVIEWBYID', '/mod/book/view.php?id=$1', 'course_module');
+        $rules[] = new restore_decode_rule('BOOKVIEWBYIDCH', '/mod/book/view.php?id=$1&amp;chapterid=$2', array('course_module', 'book_chapter'));
+
+        // book by book->id
+        $rules[] = new restore_decode_rule('BOOKVIEWBYB', '/mod/book/view.php?b=$1', 'book');
+        $rules[] = new restore_decode_rule('BOOKVIEWBYBCH', '/mod/book/view.php?b=$1&amp;chapterid=$2', array('book', 'book_chapter'));
+
+        return $rules;
+    }
+
+    /**
+     * Define the restore log rules that will be applied
+     * by the {@link restore_logs_processor} when restoring
+     * book logs. It must return one array
+     * of {@link restore_log_rule} objects
+     *
+     * @return array
+     */
+    static public function define_restore_log_rules() {
+        $rules = array();
+
+        $rules[] = new restore_log_rule('book', 'add', 'view.php?id={course_module}', '{book}');
+        $rules[] = new restore_log_rule('book', 'update', 'view.php?id={course_module}&chapterid={book_chapter}', '{book}');
+        $rules[] = new restore_log_rule('book', 'update', 'view.php?id={course_module}', '{book}');
+        $rules[] = new restore_log_rule('book', 'view', 'view.php?id={course_module}&chapterid={book_chapter}', '{book}');
+        $rules[] = new restore_log_rule('book', 'view', 'view.php?id={course_module}', '{book}');
+        $rules[] = new restore_log_rule('book', 'print', 'tool/print/index.php?id={course_module}&chapterid={book_chapter}', '{book}');
+        $rules[] = new restore_log_rule('book', 'print', 'tool/print/index.php?id={course_module}', '{book}');
+        $rules[] = new restore_log_rule('book', 'exportimscp', 'tool/exportimscp/index.php?id={course_module}', '{book}');
+        // To convert old 'generateimscp' log entries
+        $rules[] = new restore_log_rule('book', 'generateimscp', 'tool/generateimscp/index.php?id={course_module}', '{book}',
+                'book', 'exportimscp', 'tool/exportimscp/index.php?id={course_module}', '{book}');
+
+        return $rules;
+    }
+
+    /**
+     * Define the restore log rules that will be applied
+     * by the {@link restore_logs_processor} when restoring
+     * course logs. It must return one array
+     * of {@link restore_log_rule} objects
+     *
+     * Note this rules are applied when restoring course logs
+     * by the restore final task, but are defined here at
+     * activity level. All them are rules not linked to any module instance (cmid = 0)
+     *
+     * @return array
+     */
+    static public function define_restore_log_rules_for_course() {
+        $rules = array();
+
+        $rules[] = new restore_log_rule('book', 'view all', 'index.php?id={course}', null);
+
+        return $rules;
+    }
+}
diff --git a/mod/book/backup/moodle2/restore_book_stepslib.php b/mod/book/backup/moodle2/restore_book_stepslib.php
new file mode 100644 (file)
index 0000000..09ee5a0
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Define all the restore steps that will be used by the restore_book_activity_task
+ *
+ * @package    mod_book
+ * @subpackage book
+ * @copyright  2010 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Structure step to restore one book activity
+ */
+class restore_book_activity_structure_step extends restore_activity_structure_step {
+
+    protected function define_structure() {
+
+        $paths = array();
+
+        $paths[] = new restore_path_element('book', '/activity/book');
+        $paths[] = new restore_path_element('book_chapter', '/activity/book/chapters/chapter');
+
+        // Return the paths wrapped into standard activity structure
+        return $this->prepare_activity_structure($paths);
+    }
+
+    /**
+     * Process book tag information
+     * @param array $data information
+     */
+    protected function process_book($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+        $data->course = $this->get_courseid();
+
+        $newitemid = $DB->insert_record('book', $data);
+        $this->apply_activity_instance($newitemid);
+    }
+
+    /**
+     * Process chapter tag information
+     * @param array $data information
+     */
+    protected function process_book_chapter($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $oldid = $data->id;
+        $data->course = $this->get_courseid();
+
+        $data->bookid = $this->get_new_parentid('book');
+
+        $newitemid = $DB->insert_record('book_chapters', $data);
+        $this->set_mapping('book_chapter', $oldid, $newitemid, true);
+    }
+
+    protected function after_execute() {
+        global $DB;
+
+        // Add book related files
+        $this->add_related_files('mod_book', 'intro', null);
+        $this->add_related_files('mod_book', 'chapter', 'book_chapter');
+    }
+}
diff --git a/mod/book/db/access.php b/mod/book/db/access.php
new file mode 100644 (file)
index 0000000..d831e3b
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book module capability definition
+ *
+ * @package    mod_book
+ * @copyright  2009-2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$capabilities = array(
+
+    'mod/book:addinstance' => array(
+        'riskbitmask' => RISK_XSS,
+
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => array(
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW
+        ),
+        'clonepermissionsfrom' => 'moodle/course:manageactivities'
+    ),
+
+    'mod/book:read' => array(
+        'captype' => 'read',
+        'contextlevel' => CONTEXT_MODULE,
+        'archetypes' => array(
+            'guest' => CAP_ALLOW,
+            'frontpage' => CAP_ALLOW,
+            'student' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW,
+        )
+    ),
+
+    'mod/book:viewhiddenchapters' => array(
+        'captype' => 'read',
+        'contextlevel' => CONTEXT_MODULE,
+        'archetypes' => array(
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW,
+        )
+    ),
+
+    'mod/book:edit' => array(
+        'riskbitmask' => RISK_XSS,
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'archetypes' => array(
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW,
+        )
+    ),
+);
diff --git a/mod/book/db/install.xml b/mod/book/db/install.xml
new file mode 100644 (file)
index 0000000..29626a8
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<XMLDB PATH="mod/book/db" VERSION="20120515" COMMENT="XMLDB file for Moodle mod_book"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
+>
+  <TABLES>
+    <TABLE NAME="book" COMMENT="Defines book" NEXT="book_chapters">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" NEXT="course"/>
+        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="name"/>
+        <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="course" NEXT="intro"/>
+        <FIELD NAME="intro" TYPE="text" NOTNULL="false" SEQUENCE="false" PREVIOUS="name" NEXT="introformat"/>
+        <FIELD NAME="introformat" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="intro" NEXT="numbering"/>
+        <FIELD NAME="numbering" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="introformat" NEXT="customtitles"/>
+        <FIELD NAME="customtitles" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="numbering" NEXT="revision"/>
+        <FIELD NAME="revision" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="customtitles" NEXT="timecreated"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="revision" NEXT="timemodified"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="timecreated"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+    </TABLE>
+    <TABLE NAME="book_chapters" COMMENT="Defines book_chapters" PREVIOUS="book">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" NEXT="bookid"/>
+        <FIELD NAME="bookid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="pagenum"/>
+        <FIELD NAME="pagenum" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="bookid" NEXT="subchapter"/>
+        <FIELD NAME="subchapter" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="pagenum" NEXT="title"/>
+        <FIELD NAME="title" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="subchapter" NEXT="content"/>
+        <FIELD NAME="content" TYPE="text" NOTNULL="true" SEQUENCE="false" PREVIOUS="title" NEXT="contentformat"/>
+        <FIELD NAME="contentformat" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="content" NEXT="hidden"/>
+        <FIELD NAME="hidden" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="contentformat" NEXT="timecreated"/>
+        <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="hidden" NEXT="timemodified"/>
+        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="timecreated" NEXT="importsrc"/>
+        <FIELD NAME="importsrc" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="timemodified"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+    </TABLE>
+  </TABLES>
+</XMLDB>
\ No newline at end of file
diff --git a/mod/book/db/log.php b/mod/book/db/log.php
new file mode 100644 (file)
index 0000000..cec6003
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book module log events definition
+ *
+ * @package    mod_book
+ * @copyright  2010 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$logs = array(
+    array('module'=>'book', 'action'=>'add', 'mtable'=>'book', 'field'=>'name'),
+    array('module'=>'book', 'action'=>'update', 'mtable'=>'book', 'field'=>'name'),
+    array('module'=>'book', 'action'=>'view', 'mtable'=>'book', 'field'=>'name')
+);
diff --git a/mod/book/db/subplugins.php b/mod/book/db/subplugins.php
new file mode 100644 (file)
index 0000000..e3e2e90
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book module subplugin types declaration
+ *
+ * @package    mod_book
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$subplugins = array(
+    'booktool'       => 'mod/book/tool',
+);
diff --git a/mod/book/db/upgrade.php b/mod/book/db/upgrade.php
new file mode 100644 (file)
index 0000000..77a1ba9
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book module upgrade code
+ *
+ * @package    mod_book
+ * @copyright  2009-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Book module upgrade task
+ *
+ * @param int $oldversion the version we are upgrading from
+ * @return bool always true
+ */
+function xmldb_book_upgrade($oldversion) {
+    global $CFG, $DB;
+
+    $dbman = $DB->get_manager();
+
+    // Moodle v2.2.0 release upgrade line
+    // Put any upgrade step following this
+
+    return true;
+}
diff --git a/mod/book/delete.php b/mod/book/delete.php
new file mode 100644 (file)
index 0000000..02203bb
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Delete book chapter
+ *
+ * @package    mod_book
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(dirname(__FILE__).'/../../config.php');
+require_once(dirname(__FILE__).'/locallib.php');
+
+$id        = required_param('id', PARAM_INT);        // Course Module ID
+$chapterid = required_param('chapterid', PARAM_INT); // Chapter ID
+$confirm   = optional_param('confirm', 0, PARAM_BOOL);
+
+$cm = get_coursemodule_from_id('book', $id, 0, false, MUST_EXIST);
+$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+$book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
+
+require_login($course, false, $cm);
+require_sesskey();
+
+$context = context_module::instance($cm->id);
+require_capability('mod/book:edit', $context);
+
+$PAGE->set_url('/mod/book/delete.php', array('id'=>$id, 'chapterid'=>$chapterid));
+
+$chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id), '*', MUST_EXIST);
+
+
+// Header and strings.
+$PAGE->set_title(format_string($book->name));
+$PAGE->add_body_class('mod_book');
+$PAGE->set_heading(format_string($course->fullname));
+
+// Form processing.
+if ($confirm) {  // the operation was confirmed.
+    $fs = get_file_storage();
+    if (!$chapter->subchapter) { // Delete all its sub-chapters if any
+        $chapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum', 'id, subchapter');
+        $found = false;
+        foreach ($chapters as $ch) {
+            if ($ch->id == $chapter->id) {
+                $found = true;
+            } else if ($found and $ch->subchapter) {
+                $fs->delete_area_files($context->id, 'mod_book', 'chapter', $ch->id);
+                $DB->delete_records('book_chapters', array('id'=>$ch->id));
+            } else if ($found) {
+                break;
+            }
+        }
+    }
+    $fs->delete_area_files($context->id, 'mod_book', 'chapter', $chapter->id);
+    $DB->delete_records('book_chapters', array('id'=>$chapter->id));
+
+    add_to_log($course->id, 'course', 'update mod', '../mod/book/view.php?id='.$cm->id, 'book '.$book->id);
+    add_to_log($course->id, 'book', 'update', 'view.php?id='.$cm->id, $book->id, $cm->id);
+
+    book_preload_chapters($book); // Fix structure.
+    $DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
+
+    redirect('view.php?id='.$cm->id);
+}
+
+echo $OUTPUT->header();
+
+// The operation has not been confirmed yet so ask the user to do so.
+if ($chapter->subchapter) {
+    $strconfirm = get_string('confchapterdelete', 'mod_book');
+} else {
+    $strconfirm = get_string('confchapterdeleteall', 'mod_book');
+}
+echo '<br />';
+$continue = new moodle_url('/mod/book/delete.php', array('id'=>$cm->id, 'chapterid'=>$chapter->id, 'confirm'=>1));
+$cancel = new moodle_url('/mod/book/view.php', array('id'=>$cm->id, 'chapterid'=>$chapter->id));
+echo $OUTPUT->confirm("<strong>$chapter->title</strong><p>$strconfirm</p>", $continue, $cancel);
+
+echo $OUTPUT->footer();
\ No newline at end of file
diff --git a/mod/book/edit.php b/mod/book/edit.php
new file mode 100644 (file)
index 0000000..50088ad
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Edit book chapter
+ *
+ * @package    mod_book
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(dirname(__FILE__).'/../../config.php');
+require_once(dirname(__FILE__).'/locallib.php');
+require_once(dirname(__FILE__).'/edit_form.php');
+
+$cmid       = required_param('cmid', PARAM_INT);  // Book Course Module ID
+$chapterid  = optional_param('id', 0, PARAM_INT); // Chapter ID
+$pagenum    = optional_param('pagenum', 0, PARAM_INT);
+$subchapter = optional_param('subchapter', 0, PARAM_BOOL);
+
+$cm = get_coursemodule_from_id('book', $cmid, 0, false, MUST_EXIST);
+$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+$book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
+
+require_login($course, false, $cm);
+
+$context = context_module::instance($cm->id);
+require_capability('mod/book:edit', $context);
+
+$PAGE->set_url('/mod/book/edit.php', array('cmid'=>$cmid, 'id'=>$chapterid, 'pagenum'=>$pagenum, 'subchapter'=>$subchapter));
+$PAGE->set_pagelayout('admin'); // TODO: Something. This is a bloody hack!
+
+if ($chapterid) {
+    $chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id), '*', MUST_EXIST);
+} else {
+    $chapter = new stdClass();
+    $chapter->id         = null;
+    $chapter->subchapter = $subchapter;
+    $chapter->pagenum    = $pagenum + 1;
+}
+$chapter->cmid = $cm->id;
+
+$options = array('noclean'=>true, 'subdirs'=>true, 'maxfiles'=>-1, 'maxbytes'=>0, 'context'=>$context);
+$chapter = file_prepare_standard_editor($chapter, 'content', $options, $context, 'mod_book', 'chapter', $chapter->id);
+
+$mform = new book_chapter_edit_form(null, array('chapter'=>$chapter, 'options'=>$options));
+
+// If data submitted, then process and store.
+if ($mform->is_cancelled()) {
+    if (empty($chapter->id)) {
+        redirect("view.php?id=$cm->id");
+    } else {
+        redirect("view.php?id=$cm->id&chapterid=$chapter->id");
+    }
+
+} else if ($data = $mform->get_data()) {
+
+    if ($data->id) {
+        // store the files
+        $data = file_postupdate_standard_editor($data, 'content', $options, $context, 'mod_book', 'chapter', $data->id);
+        $DB->update_record('book_chapters', $data);
+
+        add_to_log($course->id, 'course', 'update mod', '../mod/book/view.php?id='.$cm->id, 'book '.$book->id);
+        add_to_log($course->id, 'book', 'update', 'view.php?id='.$cm->id.'&chapterid='.$data->id, $book->id, $cm->id);
+
+    } else {
+        // adding new chapter
+        $data->bookid        = $book->id;
+        $data->hidden        = 0;
+        $data->timecreated   = time();
+        $data->timemodified  = time();
+        $data->importsrc     = '';
+        $data->content       = '';          // updated later
+        $data->contentformat = FORMAT_HTML; // updated later
+
+        // make room for new page
+        $sql = "UPDATE {book_chapters}
+                   SET pagenum = pagenum + 1
+                 WHERE bookid = ? AND pagenum >= ?";
+        $DB->execute($sql, array($book->id, $data->pagenum));
+
+        $data->id = $DB->insert_record('book_chapters', $data);
+
+        // store the files
+        $data = file_postupdate_standard_editor($data, 'content', $options, $context, 'mod_book', 'chapter', $data->id);
+        $DB->update_record('book_chapters', $data);
+        $DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
+
+        add_to_log($course->id, 'course', 'update mod', '../mod/book/view.php?id='.$cm->id, 'book '.$book->id);
+        add_to_log($course->id, 'book', 'update', 'view.php?id='.$cm->id.'&chapterid='.$data->id, $book->id, $cm->id);
+    }
+
+    book_preload_chapters($book); // fix structure
+    redirect("view.php?id=$cm->id&chapterid=$data->id");
+}
+
+// Otherwise fill and print the form.
+$PAGE->set_title(format_string($book->name));
+$PAGE->add_body_class('mod_book');
+$PAGE->set_heading(format_string($course->fullname));
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading(get_string('editingchapter', 'mod_book'));
+
+$mform->display();
+
+echo $OUTPUT->footer();
diff --git a/mod/book/edit_form.php b/mod/book/edit_form.php
new file mode 100644 (file)
index 0000000..ca1e54f
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Chapter edit form
+ *
+ * @package    mod_book
+ * @copyright  2004-2010 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->libdir.'/formslib.php');
+
+class book_chapter_edit_form extends moodleform {
+
+    function definition() {
+        global $CFG;
+
+        $chapter = $this->_customdata['chapter'];
+        $options = $this->_customdata['options'];
+
+        $mform = $this->_form;
+
+        $mform->addElement('header', 'general', get_string('edit'));
+
+        $mform->addElement('text', 'title', get_string('chaptertitle', 'mod_book'), array('size'=>'30'));
+        $mform->setType('title', PARAM_RAW);
+        $mform->addRule('title', null, 'required', null, 'client');
+
+        $mform->addElement('advcheckbox', 'subchapter', get_string('subchapter', 'mod_book'));
+
+        $mform->addElement('editor', 'content_editor', get_string('content', 'mod_book'), null, $options);
+        $mform->setType('content_editor', PARAM_RAW);
+        $mform->addRule('content_editor', get_string('required'), 'required', null, 'client');
+
+        $mform->addElement('hidden', 'id');
+        $mform->setType('id', PARAM_INT);
+
+        $mform->addElement('hidden', 'cmid');
+        $mform->setType('cmid', PARAM_INT);
+
+        $mform->addElement('hidden', 'pagenum');
+        $mform->setType('pagenum', PARAM_INT);
+
+        $this->add_action_buttons(true);
+
+        // set the defaults
+        $this->set_data($chapter);
+    }
+}
diff --git a/mod/book/index.php b/mod/book/index.php
new file mode 100644 (file)
index 0000000..02ed62d
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This page lists all the instances of book in a particular course
+ *
+ * @package    mod_book
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(dirname(__FILE__).'/../../config.php');
+require_once(dirname(__FILE__).'/locallib.php');
+
+$id = required_param('id', PARAM_INT); // Course ID.
+
+$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
+
+unset($id);
+
+require_course_login($course, true);
+$PAGE->set_pagelayout('incourse');
+
+// Get all required strings
+$strbooks        = get_string('modulenameplural', 'mod_book');
+$strbook         = get_string('modulename', 'mod_book');
+$strsectionname  = get_string('sectionname', 'format_'.$course->format);
+$strname         = get_string('name');
+$strintro        = get_string('moduleintro');
+$strlastmodified = get_string('lastmodified');
+
+$PAGE->set_url('/mod/book/index.php', array('id' => $course->id));
+$PAGE->set_title($course->shortname.': '.$strbooks);
+$PAGE->set_heading($course->fullname);
+$PAGE->navbar->add($strbooks);
+echo $OUTPUT->header();
+
+add_to_log($course->id, 'book', 'view all', 'index.php?id='.$course->id, '');
+
+// Get all the appropriate data
+if (!$books = get_all_instances_in_course('book', $course)) {
+    notice(get_string('thereareno', 'moodle', $strbooks), "$CFG->wwwroot/course/view.php?id=$course->id");
+    die;
+}
+
+$usesections = course_format_uses_sections($course->format);
+if ($usesections) {
+    $sections = get_all_sections($course->id);
+}
+
+$table = new html_table();
+$table->attributes['class'] = 'generaltable mod_index';
+
+if ($usesections) {
+    $table->head  = array ($strsectionname, $strname, $strintro);
+    $table->align = array ('center', 'left', 'left');
+} else {
+    $table->head  = array ($strlastmodified, $strname, $strintro);
+    $table->align = array ('left', 'left', 'left');
+}
+
+$modinfo = get_fast_modinfo($course);
+$currentsection = '';
+foreach ($books as $book) {
+    $cm = $modinfo->get_cm($book->coursemodule);
+    if ($usesections) {
+        $printsection = '';
+        if ($book->section !== $currentsection) {
+            if ($book->section) {
+                $printsection = get_section_name($course, $sections[$book->section]);
+            }
+            if ($currentsection !== '') {
+                $table->data[] = 'hr';
+            }
+            $currentsection = $book->section;
+        }
+    } else {
+        $printsection = '<span class="smallinfo">'.userdate($book->timemodified)."</span>";
+    }
+
+    $class = $book->visible ? '' : 'class="dimmed"'; // hidden modules are dimmed
+
+    $table->data[] = array (
+        $printsection,
+        "<a $class href=\"view.php?id=$cm->id\">".format_string($book->name)."</a>",
+        format_module_intro('book', $book, $cm->id));
+}
+
+echo html_writer::table($table);
+
+echo $OUTPUT->footer();
diff --git a/mod/book/lang/en/book.php b/mod/book/lang/en/book.php
new file mode 100644 (file)
index 0000000..f737bda
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book module language strings
+ *
+ * @package    mod_book
+ * @copyright  2004-2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$string['modulename'] = 'Book';
+$string['modulename_help'] = 'The book module enables a teacher to create a multi-page resource in a book-like format, with chapters and subchapters. Books can contain media files as well as text and are useful for displaying lengthy passages of information which can be broken down into sections.
+
+A book may be used
+
+* To display reading material for individual modules of study
+* As a staff departmental handbook
+* As a showcase portfolio of student work';
+$string['modulename_link'] = 'mod/book/view';
+$string['modulenameplural'] = 'Books';
+$string['pluginname'] = 'Book';
+$string['pluginadministration'] = 'Book administration';
+
+$string['toc'] = 'Table of contents';
+$string['faq'] = 'Book FAQ';
+$string['faq_help'] = '
+*Why only two levels?*
+
+Two levels are generally enough for all books, three levels would lead to poorly structured documents. Book module is designed for
+creation of short multipage study materials. It is usually better to use PDF format for longer documents. The easiest way to create PDFs are
+virtual printers (see
+<a  href="http://sector7g.wurzel6.de/pdfcreator/index_en.htm"  target="_blank">PDFCreator</a>,
+<a  href="http://fineprint.com/products/pdffactory/index.html"  target="_blank">PDFFactory</a>,
+<a  href="http://www.adobe.com/products/acrobatstd/main.html"  target="_blank">Adobe Acrobat</a>,
+etc.).
+
+*Can students edit books?*
+
+Only teachers can create and edit books. There are no plans to implement student editing for books, but somebody may create something
+similar for students (Portfolio?). The main reason is to keep Book module as simple as possible.
+
+*How do I search the books?*
+
+At present there is only one way, use browser\'s search capability in print page. Global searching is now possible only in Moodle forums.
+It would be nice to have global searching for all resources including books, any volunteers?
+
+*My titles do not fit on one line.*
+
+Either rephrase your titles or ask your site admin to change TOC
+width. It is defined globally for all books in module configuration
+page.';
+
+$string['customtitles'] = 'Custom titles';
+$string['customtitles_help'] = 'Chapter titles are displayed automatically only in TOC.';
+
+$string['chapters'] = 'Chapters';
+$string['editingchapter'] = 'Editing chapter';
+$string['chaptertitle'] = 'Chapter title';
+$string['content'] = 'Content';
+$string['subchapter'] = 'Subchapter';
+
+$string['numbering'] = 'Chapter numbering';
+$string['numbering_help'] = '* None - chapter and subchapter titles are not formatted at all, use if you want to define special numbering styles. For example letters: in chapter title type "A First Chapter", "A.1 Some Subchapter",...
+* Numbers - chapters and subchapters are numbered (1, 1.1, 1.2, 2, ...)
+* Bullets - subchapters are indented and displayed with bullets
+* Indented - subchapters are indented';
+
+$string['numbering0'] = 'None';
+$string['numbering1'] = 'Numbers';
+$string['numbering2'] = 'Bullets';
+$string['numbering3'] = 'Indented';
+$string['numberingoptions'] = 'Available numbering options';
+$string['numberingoptions_help'] = 'Select numbering options that should be available when creating new books.';
+
+$string['chapterscount'] = 'Chapters';
+
+$string['addafter'] = 'Add new chapter';
+$string['confchapterdelete'] = 'Do you really want to delete this chapter?';
+$string['confchapterdeleteall'] = 'Do you really want to delete this chapter and all its subchapters?';
+
+$string['top'] = 'top';
+
+$string['navprev'] = 'Previous';
+$string['navnext'] = 'Next';
+$string['navexit'] = 'Exit book';
+
+$string['book:addinstance'] = 'Add a new book';
+$string['book:read'] = 'Read book';
+$string['book:edit'] = 'Edit book chapters';
+$string['book:viewhiddenchapters'] = 'View hidden book chapters';
+
+$string['errorchapter'] = 'Error reading book chapter.';
+
+$string['page-mod-book-x'] = 'Any book module page';
+
+$string['subplugintype_booktool'] = 'Book tool';
+$string['subplugintype_booktool_plural'] = 'Book tools';
diff --git a/mod/book/lib.php b/mod/book/lib.php
new file mode 100644 (file)
index 0000000..80d2116
--- /dev/null
@@ -0,0 +1,454 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book module core interaction API
+ *
+ * @package    mod_book
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Returns list of available numbering types
+ * @return array
+ */
+function book_get_numbering_types() {
+    global $CFG; // required for the include
+    require_once(dirname(__FILE__).'/locallib.php');
+
+    return array (
+        BOOK_NUM_NONE       => get_string('numbering0', 'mod_book'),
+        BOOK_NUM_NUMBERS    => get_string('numbering1', 'mod_book'),
+        BOOK_NUM_BULLETS    => get_string('numbering2', 'mod_book'),
+        BOOK_NUM_INDENTED   => get_string('numbering3', 'mod_book')
+    );
+}
+
+/**
+ * Returns all other caps used in module
+ * @return array
+ */
+function book_get_extra_capabilities() {
+    // used for group-members-only
+    return array('moodle/site:accessallgroups');
+}
+
+/**
+ * Add book instance.
+ *
+ * @param stdClass $data
+ * @param stdClass $mform
+ * @return int new book instance id
+ */
+function book_add_instance($data, $mform) {
+    global $DB;
+
+    $data->timecreated = time();
+    $data->timemodified = $data->timecreated;
+    if (!isset($data->customtitles)) {
+        $data->customtitles = 0;
+    }
+
+    return $DB->insert_record('book', $data);
+}
+
+/**
+ * Update book instance.
+ *
+ * @param stdClass $data
+ * @param stdClass $mform
+ * @return bool true
+ */
+function book_update_instance($data, $mform) {
+    global $DB;
+
+    $data->timemodified = time();
+    $data->id = $data->instance;
+    if (!isset($data->customtitles)) {
+        $data->customtitles = 0;
+    }
+
+    $DB->update_record('book', $data);
+
+    $book = $DB->get_record('book', array('id'=>$data->id));
+    $DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
+
+    return true;
+}
+
+/**
+ * Delete book instance by activity id
+ *
+ * @param int $id
+ * @return bool success
+ */
+function book_delete_instance($id) {
+    global $DB;
+
+    if (!$book = $DB->get_record('book', array('id'=>$id))) {
+        return false;
+    }
+
+    $DB->delete_records('book_chapters', array('bookid'=>$book->id));
+    $DB->delete_records('book', array('id'=>$book->id));
+
+    return true;
+}
+
+/**
+ * Return use outline
+ *
+ * @param stdClass $course
+ * @param stdClass $user
+ * @param stdClass $mod
+ * @param object $book
+ * @return object|null
+ */
+function book_user_outline($course, $user, $mod, $book) {
+    global $DB;
+
+    if ($logs = $DB->get_records('log', array('userid'=>$user->id, 'module'=>'book',
+                                              'action'=>'view', 'info'=>$book->id), 'time ASC')) {
+
+        $numviews = count($logs);
+        $lastlog = array_pop($logs);
+
+        $result = new stdClass();
+        $result->info = get_string('numviews', '', $numviews);
+        $result->time = $lastlog->time;
+
+        return $result;
+    }
+    return null;
+}
+
+/**
+ * Print a detailed representation of what a  user has done with
+ * a given particular instance of this module, for user activity reports.
+ *
+ * @param stdClass $course
+ * @param stdClass $user
+ * @param stdClass $mod
+ * @param stdClass $book
+ * @return bool
+ */
+function book_user_complete($course, $user, $mod, $book) {
+    return true;
+}
+
+/**
+ * Given a course and a time, this module should find recent activity
+ * that has occurred in book activities and print it out.
+ *
+ * @param stdClass $course
+ * @param bool $viewfullnames
+ * @param int $timestart
+ * @return bool true if there was output, or false is there was none
+ */
+function book_print_recent_activity($course, $viewfullnames, $timestart) {
+    return false;  //  True if anything was printed, otherwise false
+}
+
+/**
+ * No cron in book.
+ *
+ * @return bool
+ */
+function book_cron () {
+    return true;
+}
+
+/**
+ * No grading in book.
+ *
+ * @param int $bookid
+ * @return null
+ */
+function book_grades($bookid) {
+    return null;
+}
+
+/**
+ * This function returns if a scale is being used by one book
+ * it it has support for grading and scales. Commented code should be
+ * modified if necessary. See book, glossary or journal modules
+ * as reference.
+ *
+ * @param int $bookid
+ * @param int $scaleid
+ * @return boolean True if the scale is used by any journal
+ */
+function book_scale_used($bookid, $scaleid) {
+    return false;
+}
+
+/**
+ * Checks if scale is being used by any instance of book
+ *
+ * This is used to find out if scale used anywhere
+ *
+ * @param int $scaleid
+ * @return bool true if the scale is used by any book
+ */
+function book_scale_used_anywhere($scaleid) {
+    return false;
+}
+
+/**
+ * Return read actions.
+ * @return array
+ */
+function book_get_view_actions() {
+    global $CFG; // necessary for includes
+
+    $return = array('view', 'view all');
+
+    $plugins = get_plugin_list('booktool');
+    foreach ($plugins as $plugin => $dir) {
+        if (file_exists("$dir/lib.php")) {
+            require_once("$dir/lib.php");
+        }
+        $function = 'booktool_'.$plugin.'_get_view_actions';
+        if (function_exists($function)) {
+            if ($actions = $function()) {
+                $return = array_merge($return, $actions);
+            }
+        }
+    }
+
+    return $return;
+}
+
+/**
+ * Return write actions.
+ * @return array
+ */
+function book_get_post_actions() {
+    global $CFG; // necessary for includes
+
+    $return = array('update');
+
+    $plugins = get_plugin_list('booktool');
+    foreach ($plugins as $plugin => $dir) {
+        if (file_exists("$dir/lib.php")) {
+            require_once("$dir/lib.php");
+        }
+        $function = 'booktool_'.$plugin.'_get_post_actions';
+        if (function_exists($function)) {
+            if ($actions = $function()) {
+                $return = array_merge($return, $actions);
+            }
+        }
+    }
+
+    return $return;
+}
+
+/**
+ * Supported features
+ *
+ * @param string $feature FEATURE_xx constant for requested feature
+ * @return mixed True if module supports feature, false if not, null if doesn't know
+ */
+function book_supports($feature) {
+    switch($feature) {
+        case FEATURE_MOD_ARCHETYPE:           return MOD_ARCHETYPE_RESOURCE;
+        case FEATURE_GROUPS:                  return false;
+        case FEATURE_GROUPINGS:               return false;
+        case FEATURE_GROUPMEMBERSONLY:        return true;
+        case FEATURE_MOD_INTRO:               return true;
+        case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
+        case FEATURE_GRADE_HAS_GRADE:         return false;
+        case FEATURE_GRADE_OUTCOMES:          return false;
+        case FEATURE_BACKUP_MOODLE2:          return true;
+        case FEATURE_SHOW_DESCRIPTION:        return true;
+
+        default: return null;
+    }
+}
+
+/**
+ * Adds module specific settings to the settings block
+ *
+ * @param settings_navigation $settingsnav The settings navigation object
+ * @param navigation_node $booknode The node to add module settings to
+ * @return void
+ */
+function book_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $booknode) {
+    global $USER, $PAGE, $CFG, $DB, $OUTPUT;
+
+    if ($PAGE->cm->modname !== 'book') {
+        return;
+    }
+
+    $plugins = get_plugin_list('booktool');
+    foreach ($plugins as $plugin => $dir) {
+        if (file_exists("$dir/lib.php")) {
+            require_once("$dir/lib.php");
+        }
+        $function = 'booktool_'.$plugin.'_extend_settings_navigation';
+        if (function_exists($function)) {
+            $function($settingsnav, $booknode);
+        }
+    }
+
+    $params = $PAGE->url->params();
+
+    if (!empty($params['id']) and !empty($params['chapterid']) and has_capability('mod/book:edit', $PAGE->cm->context)) {
+        if (!empty($USER->editing)) {
+            $string = get_string("turneditingoff");
+            $edit = '0';
+        } else {
+            $string = get_string("turneditingon");
+            $edit = '1';
+        }
+        $url = new moodle_url('/mod/book/view.php', array('id'=>$params['id'], 'chapterid'=>$params['chapterid'], 'edit'=>$edit, 'sesskey'=>sesskey()));
+        $booknode->add($string, $url, navigation_node::TYPE_SETTING);
+    }
+}
+
+
+/**
+ * Lists all browsable file areas
+ * @param object $course
+ * @param object $cm
+ * @param object $context
+ * @return array
+ */
+function book_get_file_areas($course, $cm, $context) {
+    $areas = array();
+    $areas['chapter'] = get_string('chapters', 'mod_book');
+    return $areas;
+}
+
+/**
+ * File browsing support for book module chapter area.
+ * @param object $browser
+ * @param object $areas
+ * @param object $course
+ * @param object $cm
+ * @param object $context
+ * @param string $filearea
+ * @param int $itemid
+ * @param string $filepath
+ * @param string $filename
+ * @return object file_info instance or null if not found
+ */
+function book_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
+    global $CFG, $DB;
+
+    // note: 'intro' area is handled in file_browser automatically
+
+    if (!has_capability('mod/book:read', $context)) {
+        return null;
+    }
+
+    if ($filearea !== 'chapter') {
+        return null;
+    }
+
+    require_once(dirname(__FILE__).'/locallib.php');
+
+    if (is_null($itemid)) {
+        return new book_file_info($browser, $course, $cm, $context, $areas, $filearea);
+    }
+
+    $fs = get_file_storage();
+    $filepath = is_null($filepath) ? '/' : $filepath;
+    $filename = is_null($filename) ? '.' : $filename;
+    if (!$storedfile = $fs->get_file($context->id, 'mod_book', $filearea, $itemid, $filepath, $filename)) {
+        return null;
+    }
+
+    // modifications may be tricky - may cause caching problems
+    $canwrite = has_capability('mod/book:edit', $context);
+
+    $chaptername = $DB->get_field('book_chapters', 'title', array('bookid'=>$cm->instance, 'id'=>$itemid));
+    $chaptername = format_string($chaptername, true, array('context'=>$context));
+
+    $urlbase = $CFG->wwwroot.'/pluginfile.php';
+    return new file_info_stored($browser, $context, $storedfile, $urlbase, $chaptername, true, true, $canwrite, false);
+}
+
+/**
+ * Serves the book attachments. Implements needed access control ;-)
+ *
+ * @param stdClass $course course object
+ * @param cm_info $cm course module object
+ * @param context $context context object
+ * @param string $filearea file area
+ * @param array $args extra arguments
+ * @param bool $forcedownload whether or not force download
+ * @param array $options additional options affecting the file serving
+ * @return bool false if file not found, does not return if found - just send the file
+ */
+function book_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
+    global $DB;
+
+    if ($context->contextlevel != CONTEXT_MODULE) {
+        return false;
+    }
+
+    require_course_login($course, true, $cm);
+
+    if ($filearea !== 'chapter') {
+        return false;
+    }
+
+    if (!has_capability('mod/book:read', $context)) {
+        return false;
+    }
+
+    $chid = (int)array_shift($args);
+
+    if (!$book = $DB->get_record('book', array('id'=>$cm->instance))) {
+        return false;
+    }
+
+    if (!$chapter = $DB->get_record('book_chapters', array('id'=>$chid, 'bookid'=>$book->id))) {
+        return false;
+    }
+
+    if ($chapter->hidden and !has_capability('mod/book:viewhiddenchapters', $context)) {
+        return false;
+    }
+
+    $fs = get_file_storage();
+    $relativepath = implode('/', $args);
+    $fullpath = "/$context->id/mod_book/chapter/$chid/$relativepath";
+    if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
+        return false;
+    }
+
+    // finally send the file
+    send_stored_file($file, 360, 0, $forcedownload, $options);
+}
+
+/**
+ * Return a list of page types
+ *
+ * @param string $pagetype current page type
+ * @param stdClass $parentcontext Block's parent context
+ * @param stdClass $currentcontext Current context of block
+ * @return array
+ */
+function book_page_type_list($pagetype, $parentcontext, $currentcontext) {
+    $module_pagetype = array('mod-book-*'=>get_string('page-mod-book-x', 'mod_book'));
+    return $module_pagetype;
+}
diff --git a/mod/book/locallib.php b/mod/book/locallib.php
new file mode 100644 (file)
index 0000000..96f9f45
--- /dev/null
@@ -0,0 +1,452 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book module local lib functions
+ *
+ * @package    mod_book
+ * @copyright  2010-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once(dirname(__FILE__).'/lib.php');
+require_once($CFG->libdir.'/filelib.php');
+
+/**
+ * The following defines are used to define how the chapters and subchapters of a book should be displayed in that table of contents.
+ * BOOK_NUM_NONE        No special styling will applied and the editor will be able to do what ever thay want in the title
+ * BOOK_NUM_NUMBERS     Chapters and subchapters are numbered (1, 1.1, 1.2, 2, ...)
+ * BOOK_NUM_BULLETS     Subchapters are indented and displayed with bullets
+ * BOOK_NUM_INDENTED    Subchapters are indented
+ */
+define('BOOK_NUM_NONE',     '0');
+define('BOOK_NUM_NUMBERS',  '1');
+define('BOOK_NUM_BULLETS',  '2');
+define('BOOK_NUM_INDENTED', '3');
+
+/**
+ * Preload book chapters and fix toc structure if necessary.
+ *
+ * Returns array of chapters with standard 'pagenum', 'id, pagenum, subchapter, title, hidden'
+ * and extra 'parent, number, subchapters, prev, next'.
+ * Please note the content/text of chapters is not included.
+ *
+ * @param  stdClass $book
+ * @return array of id=>chapter
+ */
+function book_preload_chapters($book) {
+    global $DB;
+    $chapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum', 'id, pagenum, subchapter, title, hidden');
+    if (!$chapters) {
+        return array();
+    }
+
+    $prev = null;
+    $prevsub = null;
+
+    $first = true;
+    $hidesub = true;
+    $parent = null;
+    $pagenum = 0; // chapter sort
+    $i = 0;       // main chapter num
+    $j = 0;       // subchapter num
+    foreach ($chapters as $id => $ch) {
+        $oldch = clone($ch);
+        $pagenum++;
+        $ch->pagenum = $pagenum;
+        if ($first) {
+            // book can not start with a subchapter
+            $ch->subchapter = 0;
+            $first = false;
+        }
+        if (!$ch->subchapter) {
+            $ch->prev = $prev;
+            $ch->next = null;
+            if ($prev) {
+                $chapters[$prev]->next = $ch->id;
+            }
+            if ($ch->hidden) {
+                if ($book->numbering == BOOK_NUM_NUMBERS) {
+                    $ch->number = 'x';
+                } else {
+                    $ch->number = null;
+                }
+            } else {
+                $i++;
+                $ch->number = $i;
+            }
+            $j = 0;
+            $prevsub = null;
+            $hidesub = $ch->hidden;
+            $parent = $ch->id;
+            $ch->parent = null;
+            $ch->subchapters = array();
+        } else {
+            $ch->prev = $prevsub;
+            $ch->next = null;
+            if ($prevsub) {
+                $chapters[$prevsub]->next = $ch->id;
+            }
+            $ch->parent = $parent;
+            $ch->subchapters = null;
+            $chapters[$parent]->subchapters[$ch->id] = $ch->id;
+            if ($hidesub) {
+                // all subchapters in hidden chapter must be hidden too
+                $ch->hidden = 1;
+            }
+            if ($ch->hidden) {
+                if ($book->numbering == BOOK_NUM_NUMBERS) {
+                    $ch->number = 'x';
+                } else {
+                    $ch->number = null;
+                }
+            } else {
+                $j++;
+                $ch->number = $j;
+            }
+        }
+        if ($oldch->subchapter != $ch->subchapter or $oldch->pagenum != $ch->pagenum or $oldch->hidden != $ch->hidden) {
+            // update only if something changed
+            $DB->update_record('book_chapters', $ch);
+        }
+        $chapters[$id] = $ch;
+    }
+
+    return $chapters;
+}
+
+/**
+ * Returns the title for a given chapter
+ *
+ * @param int $chid
+ * @param array $chapters
+ * @param stdClass $book
+ * @param context_module $context
+ * @return string
+ */
+function book_get_chapter_title($chid, $chapters, $book, $context) {
+    $ch = $chapters[$chid];
+    $title = trim(format_string($ch->title, true, array('context'=>$context)));
+    $numbers = array();
+    if ($book->numbering == BOOK_NUM_NUMBERS) {
+        if ($ch->parent and $chapters[$ch->parent]->number) {
+            $numbers[] = $chapters[$ch->parent]->number;
+        }
+        if ($ch->number) {
+            $numbers[] = $ch->number;
+        }
+    }
+
+    if ($numbers) {
+        $title = implode('.', $numbers).' '.$title;
+    }
+
+    return $title;
+}
+
+/**
+ * General logging to table
+ * @param string $str1
+ * @param string $str2
+ * @param int $level
+ * @return void
+ */
+function book_log($str1, $str2, $level = 0) {
+    switch ($level) {
+        case 1:
+            echo '<tr><td><span class="dimmed_text">'.$str1.'</span></td><td><span class="dimmed_text">'.$str2.'</span></td></tr>';
+            break;
+        case 2:
+            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>';
+            break;
+        default:
+            echo '<tr><td>'.$str1.'</class></td><td>'.$str2.'</td></tr>';
+            break;
+    }
+}
+
+/**
+ * Add the book TOC sticky block to the 1st region available
+ *
+ * @param array $chapters
+ * @param stdClass $chapter
+ * @param stdClass $book
+ * @param stdClass $cm
+ * @param bool $edit
+ */
+function book_add_fake_block($chapters, $chapter, $book, $cm, $edit) {
+    global $OUTPUT, $PAGE;
+
+    $toc = book_get_toc($chapters, $chapter, $book, $cm, $edit, 0);
+
+    if ($edit) {
+        $toc .= '<div class="book_faq">';
+        $toc .=  $OUTPUT->help_icon('faq', 'mod_book', get_string('faq', 'mod_book'));
+        $toc .=  '</div>';
+    }
+
+    $bc = new block_contents();
+    $bc->title = get_string('toc', 'mod_book');
+    $bc->attributes['class'] = 'block';
+    $bc->content = $toc;
+
+    $regions = $PAGE->blocks->get_regions();
+    $firstregion = reset($regions);
+    $PAGE->blocks->add_fake_block($bc, $firstregion);
+}
+
+/**
+ * Generate toc structure
+ *
+ * @param array $chapters
+ * @param stdClass $chapter
+ * @param stdClass $book
+ * @param stdClass $cm
+ * @param bool $edit
+ * @return string
+ */
+function book_get_toc($chapters, $chapter, $book, $cm, $edit) {
+    global $USER, $OUTPUT;
+
+    $toc = '';  // Representation of toc (HTML)
+    $nch = 0;   // Chapter number
+    $ns = 0;    // Subchapter number
+    $first = 1;
+
+    $context = context_module::instance($cm->id);
+
+    switch ($book->numbering) {
+        case BOOK_NUM_NONE:
+            $toc .= '<div class="book_toc_none">';
+            break;
+        case BOOK_NUM_NUMBERS:
+            $toc .= '<div class="book_toc_numbered">';
+            break;
+        case BOOK_NUM_BULLETS:
+            $toc .= '<div class="book_toc_bullets">';
+            break;
+        case BOOK_NUM_INDENTED:
+            $toc .= '<div class="book_toc_indented">';
+            break;
+    }
+
+    if ($edit) { // Teacher's TOC
+        $toc .= '<ul>';
+        $i = 0;
+        foreach ($chapters as $ch) {
+            $i++;
+            $title = trim(format_string($ch->title, true, array('context'=>$context)));
+            if (!$ch->subchapter) {
+                $toc .= ($first) ? '<li>' : '</ul></li><li>';
+                if (!$ch->hidden) {
+                    $nch++;
+                    $ns = 0;
+                    if ($book->numbering == BOOK_NUM_NUMBERS) {
+                        $title = "$nch $title";
+                    }
+                } else {
+                    if ($book->numbering == BOOK_NUM_NUMBERS) {
+                        $title = "x $title";
+                    }
+                    $title = '<span class="dimmed_text">'.$title.'</span>';
+                }
+            } else {
+                $toc .= ($first) ? '<li><ul><li>' : '<li>';
+                if (!$ch->hidden) {
+                    $ns++;
+                    if ($book->numbering == BOOK_NUM_NUMBERS) {
+                        $title = "$nch.$ns $title";
+                    }
+                } else {
+                    if ($book->numbering == BOOK_NUM_NUMBERS) {
+                        $title = "x.x $title";
+                    }
+                    $title = '<span class="dimmed_text">'.$title.'</span>';
+                }
+            }
+
+            if ($ch->id == $chapter->id) {
+                $toc .= '<strong>'.$title.'</strong>';
+            } else {
+                $toc .= '<a title="'.s($title).'" href="view.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'">'.$title.'</a>';
+            }
+            $toc .=  '&nbsp;&nbsp;';
+            if ($i != 1) {
+                $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>';
+            }
+            if ($i != count($chapters)) {
+                $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>';
+            }
+            $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>';
+            $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>';
+            if ($ch->hidden) {
+                $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>';
+            } else {
+                $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>';
+            }
+            $toc .= ' <a title="'.get_string('addafter', 'mod_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', 'mod_book').'" /></a>';
+
+            $toc .= (!$ch->subchapter) ? '<ul>' : '</li>';
+            $first = 0;
+        }
+        $toc .= '</ul></li></ul>';
+    } else { // Normal students view
+        $toc .= '<ul>';
+        foreach ($chapters as $ch) {
+            $title = trim(format_string($ch->title, true, array('context'=>$context)));
+            if (!$ch->hidden) {
+                if (!$ch->subchapter) {
+                    $nch++;
+                    $ns = 0;
+                    $toc .= ($first) ? '<li>' : '</ul></li><li>';
+                    if ($book->numbering == BOOK_NUM_NUMBERS) {
+                          $title = "$nch $title";
+                    }
+                } else {
+                    $ns++;
+                    $toc .= ($first) ? '<li><ul><li>' : '<li>';
+                    if ($book->numbering == BOOK_NUM_NUMBERS) {
+                          $title = "$nch.$ns $title";
+                    }
+                }
+                if ($ch->id == $chapter->id) {
+                    $toc .= '<strong>'.$title.'</strong>';
+                } else {
+                    $toc .= '<a title="'.s($title).'" href="view.php?id='.$cm->id.'&amp;chapterid='.$ch->id.'">'.$title.'</a>';
+                }
+                $toc .= (!$ch->subchapter) ? '<ul>' : '</li>';
+                $first = 0;
+            }
+        }
+        $toc .= '</ul></li></ul>';
+    }
+
+    $toc .= '</div>';
+
+    $toc = str_replace('<ul></ul>', '', $toc); // Cleanup of invalid structures.
+
+    return $toc;
+}
+
+
+/**
+ * File browsing support class
+ *
+ * @copyright  2010-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class book_file_info extends file_info {
+    /** @var stdClass Course object */
+    protected $course;
+    /** @var stdClass Course module object */
+    protected $cm;
+    /** @var array Available file areas */
+    protected $areas;
+    /** @var string File area to browse */
+    protected $filearea;
+
+    /**
+     * Constructor
+     *
+     * @param file_browser $browser file_browser instance
+     * @param stdClass $course course object
+     * @param stdClass $cm course module object
+     * @param stdClass $context module context
+     * @param array $areas available file areas
+     * @param string $filearea file area to browse
+     */
+    public function __construct($browser, $course, $cm, $context, $areas, $filearea) {
+        parent::__construct($browser, $context);
+        $this->course   = $course;
+        $this->cm       = $cm;
+        $this->areas    = $areas;
+        $this->filearea = $filearea;
+    }
+
+    /**
+     * Returns list of standard virtual file/directory identification.
+     * The difference from stored_file parameters is that null values
+     * are allowed in all fields
+     * @return array with keys contextid, filearea, itemid, filepath and filename
+     */
+    public function get_params() {
+        return array('contextid'=>$this->context->id,
+                     'component'=>'mod_book',
+                     'filearea' =>$this->filearea,
+                     'itemid'   =>null,
+                     'filepath' =>null,
+                     'filename' =>null);
+    }
+
+    /**
+     * Returns localised visible name.
+     * @return string
+     */
+    public function get_visible_name() {
+        return $this->areas[$this->filearea];
+    }
+
+    /**
+     * Can I add new files or directories?
+     * @return bool
+     */
+    public function is_writable() {
+        return false;
+    }
+
+    /**
+     * Is directory?
+     * @return bool
+     */
+    public function is_directory() {
+        return true;
+    }
+
+    /**
+     * Returns list of children.
+     * @return array of file_info instances
+     */
+    public function get_children() {
+        global $DB;
+
+        $children = array();
+        $chapters = $DB->get_records('book_chapters', array('bookid'=>$this->cm->instance), 'pagenum', 'id, pagenum');
+        foreach ($chapters as $itemid => $unused) {
+            if ($child = $this->browser->get_file_info($this->context, 'mod_book', $this->filearea, $itemid)) {
+                $children[] = $child;
+            }
+        }
+        return $children;
+    }
+
+    /**
+     * Returns parent file_info instance
+     * @return file_info or null for root
+     */
+    public function get_parent() {
+        return $this->browser->get_file_info($this->context);
+    }
+}
diff --git a/mod/book/mod_form.php b/mod/book/mod_form.php
new file mode 100644 (file)
index 0000000..d58cce3
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Instance add/edit form
+ *
+ * @package    mod_book
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once(dirname(__FILE__).'/locallib.php');
+require_once($CFG->dirroot.'/course/moodleform_mod.php');
+
+class mod_book_mod_form extends moodleform_mod {
+
+    function definition() {
+        global $CFG;
+
+        $mform = $this->_form;
+
+        $config = get_config('book');
+
+        $mform->addElement('header', 'general', get_string('general', 'form'));
+
+        $mform->addElement('text', 'name', get_string('name'), array('size'=>'64'));
+        if (!empty($CFG->formatstringstriptags)) {
+            $mform->setType('name', PARAM_TEXT);
+        } else {
+            $mform->setType('name', PARAM_CLEANHTML);
+        }
+        $mform->addRule('name', null, 'required', null, 'client');
+        $this->add_intro_editor($config->requiremodintro, get_string('summary'));
+
+        $alloptions = book_get_numbering_types();
+        $allowed = explode(',', $config->numberingoptions);
+        $options = array();
+        foreach ($allowed as $type) {
+            if (isset($alloptions[$type])) {
+                $options[$type] = $alloptions[$type];
+            }
+        }
+        if ($this->current->instance) {
+            if (!isset($options[$this->current->numbering])) {
+                if (isset($alloptions[$this->current->numbering])) {
+                    $options[$this->current->numbering] = $alloptions[$this->current->numbering];
+                }
+            }
+        }
+        $mform->addElement('select', 'numbering', get_string('numbering', 'book'), $options);
+        $mform->addHelpButton('numbering', 'numbering', 'mod_book');
+        $mform->setDefault('numbering', $config->numbering);
+
+        $mform->addElement('checkbox', 'customtitles', get_string('customtitles', 'book'));
+        $mform->addHelpButton('customtitles', 'customtitles', 'mod_book');
+        $mform->setDefault('customtitles', 0);
+
+        $this->standard_coursemodule_elements();
+
+        $this->add_action_buttons();
+    }
+}
diff --git a/mod/book/move.php b/mod/book/move.php
new file mode 100644 (file)
index 0000000..b1fd5e4
--- /dev/null
@@ -0,0 +1,187 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Move book chapter
+ *
+ * @package    mod_book
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(dirname(__FILE__).'/../../config.php');
+require_once(dirname(__FILE__).'/locallib.php');
+
+$id        = required_param('id', PARAM_INT);        // Course Module ID
+$chapterid = required_param('chapterid', PARAM_INT); // Chapter ID
+$up        = optional_param('up', 0, PARAM_BOOL);
+
+$cm = get_coursemodule_from_id('book', $id, 0, false, MUST_EXIST);
+$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+$book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
+
+require_login($course, false, $cm);
+require_sesskey();
+
+$context = context_module::instance($cm->id);
+require_capability('mod/book:edit', $context);
+
+$chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id), '*', MUST_EXIST);
+
+
+$oldchapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum', 'id, pagenum, subchapter');
+
+$nothing = 0;
+
+$chapters = array();
+$chs = 0;
+$che = 0;
+$ts = 0;
+$te = 0;
+// create new ordered array and find chapters to be moved
+$i = 1;
+$found = 0;
+foreach ($oldchapters as $ch) {
+    $chapters[$i] = $ch;
+    if ($chapter->id == $ch->id) {
+        $chs = $i;
+        $che = $chs;
+        if ($ch->subchapter) {
+            $found = 1;// Subchapter moves alone.
+        }
+    } else if ($chs) {
+        if ($found) {
+            // Nothing.
+        } else if ($ch->subchapter) {
+            $che = $i; // Chapter with subchapter(s).
+        } else {
+            $found = 1;
+        }
+    }
+    $i++;
+}
+
+// Find target chapter(s).
+if ($chapters[$chs]->subchapter) { // Moving single subchapter up or down.
+    if ($up) {
+        if ($chs == 1) {
+            $nothing = 1; // Already first.
+        } else {
+            $ts = $chs - 1;
+            $te = $ts;
+        }
+    } else { // Down.
+        if ($che == count($chapters)) {
+            $nothing = 1; // Already last.
+        } else {
+            $ts = $che + 1;
+            $te = $ts;
+        }
+    }
+} else { // Moving chapter and looking for next/previous chapter.
+    if ($up) { // Up.
+        if ($chs == 1) {
+            $nothing = 1; // Already first.
+        } else {
+            $te = $chs - 1;
+            for ($i = $chs-1; $i >= 1; $i--) {
+                if ($chapters[$i]->subchapter) {
+                    $ts = $i;
+                } else {
+                    $ts = $i;
+                    break;
+                }
+            }
+        }
+    } else { // Down.
+        if ($che == count($chapters)) {
+            $nothing = 1; // Already last.
+        } else {
+            $ts = $che + 1;
+            $found = 0;
+            for ($i = $che+1; $i <= count($chapters); $i++) {
+                if ($chapters[$i]->subchapter) {
+                    $te = $i;
+                } else {
+                    if ($found) {
+                        break;
+                    } else {
+                        $te = $i;
+                        $found = 1;
+                    }
+                }
+            }
+        }
+    }
+}
+
+// Recreated newly sorted list of chapters.
+if (!$nothing) {
+    $newchapters = array();
+
+    if ($up) {
+        if ($ts > 1) {
+            for ($i=1; $i<$ts; $i++) {
+                $newchapters[] = $chapters[$i];
+            }
+        }
+        for ($i=$chs; $i<=$che; $i++) {
+            $newchapters[$i] = $chapters[$i];
+        }
+        for ($i=$ts; $i<=$te; $i++) {
+            $newchapters[$i] = $chapters[$i];
+        }
+        if ($che<count($chapters)) {
+            for ($i=$che; $i<=count($chapters); $i++) {
+                $newchapters[$i] = $chapters[$i];
+            }
+        }
+    } else {
+        if ($chs > 1) {
+            for ($i=1; $i<$chs; $i++) {
+                $newchapters[] = $chapters[$i];
+            }
+        }
+        for ($i=$ts; $i<=$te; $i++) {
+            $newchapters[$i] = $chapters[$i];
+        }
+        for ($i=$chs; $i<=$che; $i++) {
+            $newchapters[$i] = $chapters[$i];
+        }
+        if ($te<count($chapters)) {
+            for ($i=$te; $i<=count($chapters); $i++) {
+                $newchapters[$i] = $chapters[$i];
+            }
+        }
+    }
+
+    // Store chapters in the new order.
+    $i = 1;
+    foreach ($newchapters as $ch) {
+        $ch->pagenum = $i;
+        $DB->update_record('book_chapters', $ch);
+        $i++;
+    }
+}
+
+add_to_log($course->id, 'course', 'update mod', '../mod/book/view.php?id='.$cm->id, 'book '.$book->id);
+add_to_log($course->id, 'book', 'update', 'view.php?id='.$cm->id, $book->id, $cm->id);
+
+book_preload_chapters($book); // fix structure
+$DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
+
+redirect('view.php?id='.$cm->id.'&chapterid='.$chapter->id);
+
diff --git a/mod/book/pix/add.png b/mod/book/pix/add.png
new file mode 100644 (file)
index 0000000..bda131f
Binary files /dev/null and b/mod/book/pix/add.png differ
diff --git a/mod/book/pix/chapter.png b/mod/book/pix/chapter.png
new file mode 100644 (file)
index 0000000..d84cac0
Binary files /dev/null and b/mod/book/pix/chapter.png differ
diff --git a/mod/book/pix/icon.png b/mod/book/pix/icon.png
new file mode 100644 (file)
index 0000000..41095ef
Binary files /dev/null and b/mod/book/pix/icon.png differ
diff --git a/mod/book/pix/nav_exit.png b/mod/book/pix/nav_exit.png
new file mode 100644 (file)
index 0000000..4c129f0
Binary files /dev/null and b/mod/book/pix/nav_exit.png differ
diff --git a/mod/book/pix/nav_next.png b/mod/book/pix/nav_next.png
new file mode 100644 (file)
index 0000000..1e11bd9
Binary files /dev/null and b/mod/book/pix/nav_next.png differ
diff --git a/mod/book/pix/nav_prev.png b/mod/book/pix/nav_prev.png
new file mode 100644 (file)
index 0000000..b08bbda
Binary files /dev/null and b/mod/book/pix/nav_prev.png differ
diff --git a/mod/book/pix/nav_prev_dis.png b/mod/book/pix/nav_prev_dis.png
new file mode 100644 (file)
index 0000000..3f61922
Binary files /dev/null and b/mod/book/pix/nav_prev_dis.png differ
diff --git a/mod/book/pix/nav_sep.png b/mod/book/pix/nav_sep.png
new file mode 100644 (file)
index 0000000..35139a0
Binary files /dev/null and b/mod/book/pix/nav_sep.png differ
diff --git a/mod/book/settings.php b/mod/book/settings.php
new file mode 100644 (file)
index 0000000..810ad3c
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book plugin settings
+ *
+ * @package    mod_book
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+if ($ADMIN->fulltree) {
+    require_once(dirname(__FILE__).'/lib.php');
+
+    // General settings
+
+    $settings->add(new admin_setting_configcheckbox('book/requiremodintro',
+        get_string('requiremodintro', 'admin'), get_string('configrequiremodintro', 'admin'), 1));
+
+    $options = book_get_numbering_types();
+
+    $settings->add(new admin_setting_configmultiselect('book/numberingoptions',
+        get_string('numberingoptions', 'mod_book'), get_string('numberingoptions_help', 'mod_book'),
+        array_keys($options), $options));
+
+
+    // Modedit defaults.
+
+    $settings->add(new admin_setting_heading('bookmodeditdefaults', get_string('modeditdefaults', 'admin'), get_string('condifmodeditdefaults', 'admin')));
+
+    $settings->add(new admin_setting_configselect('book/numbering',
+        get_string('numbering', 'mod_book'), '', BOOK_NUM_NUMBERS, $options));
+
+}
\ No newline at end of file
diff --git a/mod/book/show.php b/mod/book/show.php
new file mode 100644 (file)
index 0000000..ca52ac5
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Show/hide book chapter
+ *
+ * @package    mod_book
+ * @copyright  2004-2010 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(dirname(__FILE__).'/../../config.php');
+require_once(dirname(__FILE__).'/locallib.php');
+
+$id        = required_param('id', PARAM_INT);        // Course Module ID
+$chapterid = required_param('chapterid', PARAM_INT); // Chapter ID
+
+$cm = get_coursemodule_from_id('book', $id, 0, false, MUST_EXIST);
+$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+$book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
+
+require_login($course, false, $cm);
+require_sesskey();
+
+$context = context_module::instance($cm->id);
+require_capability('mod/book:edit', $context);
+
+$PAGE->set_url('/mod/book/show.php', array('id'=>$id, 'chapterid'=>$chapterid));
+
+$chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id), '*', MUST_EXIST);
+
+// Switch hidden state.
+$chapter->hidden = $chapter->hidden ? 0 : 1;
+
+// Update record.
+$DB->update_record('book_chapters', $chapter);
+
+// Change visibility of subchapters too.
+if (!$chapter->subchapter) {
+    $chapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum', 'id, subchapter, hidden');
+    $found = 0;
+    foreach ($chapters as $ch) {
+        if ($ch->id == $chapter->id) {
+            $found = 1;
+        } else if ($found and $ch->subchapter) {
+            $ch->hidden = $chapter->hidden;
+            $DB->update_record('book_chapters', $ch);
+        } else if ($found) {
+            break;
+        }
+    }
+}
+
+add_to_log($course->id, 'course', 'update mod', '../mod/book/view.php?id='.$cm->id, 'book '.$book->id);
+add_to_log($course->id, 'book', 'update', 'view.php?id='.$cm->id, $book->id, $cm->id);
+
+book_preload_chapters($book); // fix structure
+$DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
+
+redirect('view.php?id='.$cm->id.'&chapterid='.$chapter->id);
+
diff --git a/mod/book/styles.css b/mod/book/styles.css
new file mode 100644 (file)
index 0000000..d09189d
--- /dev/null
@@ -0,0 +1,121 @@
+
+.mod_book .book_chapter_title {
+    font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif;
+    text-align: left;
+    font-size: large;
+    font-weight: bold;
+
+    margin-left: 0px;
+    margin-bottom: 20px;
+}
+
+.mod_book img.bigicon {
+  vertical-align: middle;
+  margin-right: 4px;
+  margin-left: 4px;
+  width: 24px;
+  height: 24px;
+  border: 0px;
+}
+
+.mod_book .navtop {
+    text-align: right;
+    margin-bottom: 0.5em;
+}
+
+.mod_book .navbottom {
+    text-align: right;
+}
+
+/* == Fake toc block == */
+
+.mod_book .book_faq {
+  font-size: 0.7em;
+}
+
+/* toc style NONE */
+.mod_book .book_toc_none {
+  font-size: 0.8em;
+}
+.mod_book .book_toc_none ul {
+    margin-left: 5px;
+    padding-left: 0px;
+}
+.mod_book .book_toc_none ul ul {
+    margin-left: 0px;
+    padding-left: 0px;
+}
+.mod_book .book_toc_none li {
+    margin-top: 5px;
+    list-style: none;
+}
+.mod_book .book_toc_none li li {
+    margin-top: 0px;
+    list-style: none;
+}
+
+
+/* toc style NUMBERED */
+.mod_book .book_toc_numbered {
+  font-size: 0.8em;
+}
+.mod_book .book_toc_numbered ul {
+    margin-left: 5px;
+    padding-left: 0px;
+}
+.mod_book .book_toc_numbered ul ul {
+    margin-left: 0px;
+    padding-left: 0px;
+}
+.mod_book .book_toc_numbered li {
+    margin-top: 5px;
+    list-style: none;
+}
+.mod_book .book_toc_numbered li li {
+    margin-top: 0px;
+    list-style: none;
+}
+
+
+/*toc style BULLETS */
+.mod_book .book_toc_bullets {
+  font-size: 0.8em;
+}
+.mod_book .book_toc_bullets ul {
+    margin-left: 5px;
+    padding-left: 0px;
+}
+.mod_book .book_toc_bullets ul ul {
+    margin-left: 20px;
+    padding-left: 0px;
+}
+.mod_book .book_toc_bullets li {
+    margin-top: 5px;
+    list-style: none;
+}
+.mod_book .book_toc_bullets li li {
+    margin-top: 0px;
+    list-style: circle;
+}
+
+
+/* toc style INDENTED*/
+.mod_book .book_toc_indented {
+  font-size: 0.8em;
+}
+.mod_book .book_toc_indented ul {
+    margin-left: 5px;
+    padding-left: 0px;
+}
+.mod_book .book_toc_indented ul ul {
+    margin-left: 15px;
+    padding-left: 0px;
+}
+.mod_book .book_toc_indented li {
+    margin-top: 5px;
+    list-style: none;
+}
+.mod_book .book_toc_indented li li {
+    margin-top: 0px;
+    list-style: none;
+}
diff --git a/mod/book/tool/exportimscp/db/access.php b/mod/book/tool/exportimscp/db/access.php
new file mode 100644 (file)
index 0000000..5a5cb24
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book module capability definition
+ *
+ * @package    booktool_exportimscp
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$capabilities = array(
+    'booktool/exportimscp:export' => array(
+        'captype' => 'read',
+        'contextlevel' => CONTEXT_MODULE
+    ),
+);
diff --git a/mod/book/tool/exportimscp/db/log.php b/mod/book/tool/exportimscp/db/log.php
new file mode 100644 (file)
index 0000000..4a130dc
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Export to IMSCP booktool log events definition
+ *
+ * @package    booktool_exportimscp
+ * @copyright  2012 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$logs = array(
+    array('module'=>'book', 'action'=>'exportimscp', 'mtable'=>'book', 'field'=>'name')
+);
diff --git a/mod/book/tool/exportimscp/imscp.css b/mod/book/tool/exportimscp/imscp.css
new file mode 100644 (file)
index 0000000..e4f2add
--- /dev/null
@@ -0,0 +1,35 @@
+body, table, td, th, li, p {
+  font-family: Arial, Verdana, Helvetica, sans-serif;
+  font-size: 1em;
+  line-height: 150%;
+  letter-spacing: 0.02em;
+}
+
+th {
+  font-weight: bold;
+}
+
+a:link,
+a:visited {
+  text-decoration: none;
+}
+
+a:hover {
+  text-decoration: underline;
+}
+
+h1.main,
+h2.main,
+h3.main,
+h4.main,
+h5.main,
+h6.main {
+  font-weight: bold;
+}
+
+h1#header {
+  color: #666666;
+  text-align: right;
+  padding-bottom: 0.2em;
+  border-bottom: solid 1px #666666;
+}
diff --git a/mod/book/tool/exportimscp/index.php b/mod/book/tool/exportimscp/index.php
new file mode 100644 (file)
index 0000000..a9cdd0c
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book IMSCP export plugin
+ *
+ * @package    booktool_exportimscp
+ * @copyright  2001-3001 Antonio Vicent          {@link http://ludens.es}
+ * @copyright  2001-3001 Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @copyright  2011 Petr Skoda                   {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(dirname(__FILE__).'/../../../../config.php');
+require_once(dirname(__FILE__).'/locallib.php');
+require_once($CFG->dirroot.'/mod/book/locallib.php');
+require_once($CFG->dirroot.'/backup/lib.php');
+require_once($CFG->libdir.'/filelib.php');
+
+$id = required_param('id', PARAM_INT);           // Course Module ID
+
+$cm = get_coursemodule_from_id('book', $id, 0, false, MUST_EXIST);
+$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+$book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
+
+$PAGE->set_url('/mod/book/tool/exportimscp/index.php', array('id'=>$id));
+
+require_login($course, false, $cm);
+
+$context = context_module::instance($cm->id);
+require_capability('mod/book:read', $context);
+require_capability('booktool/exportimscp:export', $context);
+
+$strbooks = get_string('modulenameplural', 'book');
+$strbook  = get_string('modulename', 'book');
+$strtop  = get_string('top', 'book');
+
+add_to_log($course->id, 'book', 'exportimscp', 'tool/exportimscp/index.php?id='.$cm->id, $book->id, $cm->id);
+
+$file = booktool_exportimscp_build_package($book, $context);
+
+send_stored_file($file, 10, 0, true, array('filename' => clean_filename($book->name).'.zip'));
diff --git a/mod/book/tool/exportimscp/lang/en/booktool_exportimscp.php b/mod/book/tool/exportimscp/lang/en/booktool_exportimscp.php
new file mode 100644 (file)
index 0000000..2273e55
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * exportimscp booktool language strings
+ *
+ * @package    booktool_exportimscp
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$string['exportimscp:export'] = 'Export book as IMS content package';
+$string['generateimscp'] = 'Generate IMS CP';
+$string['nochapters'] = 'No book chapters found, can not export to IMS CP.';
+$string['pluginname'] = 'Book IMS CP export';
diff --git a/mod/book/tool/exportimscp/lib.php b/mod/book/tool/exportimscp/lib.php
new file mode 100644 (file)
index 0000000..f807c13
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * IMSCP export lib
+ *
+ * @package    booktool_exportimscp
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Adds module specific settings to the settings block
+ *
+ * @param settings_navigation $settings The settings navigation object
+ * @param navigation_node $node The node to add module settings to
+ */
+function booktool_exportimscp_extend_settings_navigation(settings_navigation $settings, navigation_node $node) {
+    global $USER, $PAGE, $CFG, $DB, $OUTPUT;
+
+    if ($PAGE->cm->modname !== 'book') {
+        return;
+    }
+
+    if (has_capability('booktool/exportimscp:export', $PAGE->cm->context)) {
+        $url = new moodle_url('/mod/book/tool/exportimscp/index.php', array('id'=>$PAGE->cm->id));
+        $icon = new pix_icon('generate', '', 'booktool_exportimscp', array('class'=>'icon'));
+        $node->add(get_string('generateimscp', 'booktool_exportimscp'), $url, navigation_node::TYPE_SETTING, null, null, $icon);
+    }
+}
diff --git a/mod/book/tool/exportimscp/locallib.php b/mod/book/tool/exportimscp/locallib.php
new file mode 100644 (file)
index 0000000..788c2f4
--- /dev/null
@@ -0,0 +1,254 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book imscp export lib
+ *
+ * @package    booktool_exportimscp
+ * @copyright  2001-3001 Antonio Vicent          {@link http://ludens.es}
+ * @copyright  2001-3001 Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @copyright  2011 Petr Skoda                   {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once(dirname(__FILE__).'/lib.php');
+require_once($CFG->dirroot.'/mod/book/locallib.php');
+
+/**
+ * Export one book as IMSCP package
+ *
+ * @param stdClass $book book instance
+ * @param context_module $context
+ * @return bool|stored_file
+ */
+function booktool_exportimscp_build_package($book, $context) {
+    global $DB;
+
+    $fs = get_file_storage();
+
+    if ($packagefile = $fs->get_file($context->id, 'booktool_exportimscp', 'package', $book->revision, '/', 'imscp.zip')) {
+        return $packagefile;
+    }
+
+    // fix structure and test if chapters present
+    if (!book_preload_chapters($book)) {
+        print_error('nochapters', 'booktool_exportimscp');
+    }
+
+    // prepare temp area with package contents
+    booktool_exportimscp_prepare_files($book, $context);
+
+    $packer = get_file_packer('application/zip');
+    $areafiles = $fs->get_area_files($context->id, 'booktool_exportimscp', 'temp', $book->revision, "sortorder, itemid, filepath, filename", false);
+    $files = array();
+    foreach ($areafiles as $file) {
+        $path = $file->get_filepath().$file->get_filename();
+        $path = ltrim($path, '/');
+        $files[$path] = $file;
+    }
+    unset($areafiles);
+    $packagefile = $packer->archive_to_storage($files, $context->id, 'booktool_exportimscp', 'package', $book->revision, '/', 'imscp.zip');
+
+    // drop temp area
+    $fs->delete_area_files($context->id, 'booktool_exportimscp', 'temp', $book->revision);
+
+    // delete older versions
+    $sql = "SELECT DISTINCT itemid
+              FROM {files}
+             WHERE contextid = :contextid AND component = 'booktool_exportimscp' AND itemid < :revision";
+    $params = array('contextid'=>$context->id, 'revision'=>$book->revision);
+    $revisions = $DB->get_records_sql($sql, $params);
+    foreach ($revisions as $rev => $unused) {
+        $fs->delete_area_files($context->id, 'booktool_exportimscp', 'temp', $rev);
+        $fs->delete_area_files($context->id, 'booktool_exportimscp', 'package', $rev);
+    }
+
+    return $packagefile;
+}
+
+/**
+ * Prepare temp area with the files used by book html contents
+ *
+ * @param stdClass $book book instance
+ * @param context_module $context
+ */
+function booktool_exportimscp_prepare_files($book, $context) {
+    global $CFG, $DB;
+
+    $fs = get_file_storage();
+
+    $temp_file_record = array('contextid'=>$context->id, 'component'=>'booktool_exportimscp', 'filearea'=>'temp', 'itemid'=>$book->revision);
+    $chapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum');
+    $chapterresources = array();
+    foreach ($chapters as $chapter) {
+        $chapterresources[$chapter->id] = array();
+        $files = $fs->get_area_files($context->id, 'mod_book', 'chapter', $chapter->id, "sortorder, itemid, filepath, filename", false);
+        foreach ($files as $file) {
+            $temp_file_record['filepath'] = '/'.$chapter->pagenum.$file->get_filepath();
+            $fs->create_file_from_storedfile($temp_file_record, $file);
+            $chapterresources[$chapter->id][] = $chapter->pagenum.$file->get_filepath().$file->get_filename();
+        }
+        if ($file = $fs->get_file($context->id, 'booktool_exportimscp', 'temp', $book->revision, "/$chapter->pagenum/", 'index.html')) {
+            // this should not exist
+            $file->delete();
+        }
+        $content = booktool_exportimscp_chapter_content($chapter, $context);
+        $index_file_record = array('contextid'=>$context->id, 'component'=>'booktool_exportimscp', 'filearea'=>'temp',
+                'itemid'=>$book->revision, 'filepath'=>"/$chapter->pagenum/", 'filename'=>'index.html');
+        $fs->create_file_from_string($index_file_record, $content);
+    }
+
+    $css_file_record = array('contextid'=>$context->id, 'component'=>'booktool_exportimscp', 'filearea'=>'temp',
+            'itemid'=>$book->revision, 'filepath'=>"/css/", 'filename'=>'styles.css');
+    $fs->create_file_from_pathname($css_file_record, dirname(__FILE__).'/imscp.css');
+
+    // Init imsmanifest and others
+    $imsmanifest = '';
+    $imsitems = '';
+    $imsresources = '';
+
+    // Moodle and Book version
+    $moodle_release = $CFG->release;
+    $moodle_version = $CFG->version;
+    $book_version   = $DB->get_field('modules', 'version', array('name'=>'book'));
+    $bookname       = format_string($book->name, true, array('context'=>$context));
+
+    // Load manifest header
+        $imsmanifest .= '<?xml version="1.0" encoding="UTF-8"?>
+<!-- This package has been created with Moodle ' . $moodle_release . ' (' . $moodle_version . ') http://moodle.org/, Book module version ' . $book_version . ' - https://github.com/skodak/moodle-mod_book -->
+<!-- One idea and implementation by Eloy Lafuente (stronk7) and Antonio Vicent (C) 2001-3001 -->
+<manifest xmlns="http://www.imsglobal.org/xsd/imscp_v1p1" xmlns:imsmd="http://www.imsglobal.org/xsd/imsmd_v1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" identifier="MANIFEST-' . md5($CFG->wwwroot . '-' . $book->course . '-' . $book->id) . '" xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd http://www.imsglobal.org/xsd/imsmd_v1p2 imsmd_v1p2p2.xsd">
+  <organizations default="MOODLE-' . $book->course . '-' . $book->id . '">
+    <organization identifier="MOODLE-' . $book->course . '-' . $book->id . '" structure="hierarchical">
+      <title>' . htmlspecialchars($bookname) . '</title>';
+
+    // To store the prev level (book only have 0 and 1)
+    $prevlevel = null;
+    $currlevel = 0;
+    foreach ($chapters as $chapter) {
+        // Calculate current level ((book only have 0 and 1)
+        $currlevel = empty($chapter->subchapter) ? 0 : 1;
+        // Based upon prevlevel and current one, decide what to close
+        if ($prevlevel !== null) {
+            // Calculate the number of spaces (for visual xml-text formating)
+            $prevspaces = substr('                ', 0, $currlevel * 2);
+
+            // Same level, simply close the item
+            if ($prevlevel == $currlevel) {
+                $imsitems .= $prevspaces . '        </item>' . "\n";
+            }
+            // Bigger currlevel, nothing to close
+            // Smaller currlevel, close both the current item and the parent one
+            if ($prevlevel > $currlevel) {
+                $imsitems .= '          </item>' . "\n";
+                $imsitems .= '        </item>' . "\n";
+            }
+        }
+        // Update prevlevel
+        $prevlevel = $currlevel;
+
+        // Calculate the number of spaces (for visual xml-text formatting)
+        $currspaces = substr('                ', 0, $currlevel * 2);
+
+        $chaptertitle = format_string($chapter->title, true, array('context'=>$context));
+
+        // Add the imsitems
+        $imsitems .= $currspaces .'        <item identifier="ITEM-' . $book->course . '-' . $book->id . '-' . $chapter->pagenum .'" isvisible="true" identifierref="RES-' .
+                $book->course . '-' . $book->id . '-' . $chapter->pagenum . "\">\n" .
+                $currspaces . '         <title>' . htmlspecialchars($chaptertitle) . '</title>' . "\n";
+
+        // Add the imsresources
+        // First, check if we have localfiles
+        $localfiles = array();
+        foreach ($chapterresources[$chapter->id] as $localfile) {
+            $localfiles[] = "\n" . '      <file href="' . $localfile . '" />';
+        }
+        // Now add the dependency to css
+        $cssdependency = "\n" . '      <dependency identifierref="RES-' . $book->course . '-'  . $book->id . '-css" />';
+        // Now build the resources section
+        $imsresources .= '    <resource identifier="RES-' . $book->course . '-'  . $book->id . '-' . $chapter->pagenum . '" type="webcontent" xml:base="' .
+                $chapter->pagenum . '/" href="index.html">' . "\n" .
+                '      <file href="' . $chapter->pagenum . '/index.html" />' . implode($localfiles) . $cssdependency . "\n".
+                '    </resource>' . "\n";
+    }
+
+    // Close items (the latest chapter)
+    // Level 1, close 1
+    if ($currlevel == 0) {
+        $imsitems .= '        </item>' . "\n";
+    }
+    // Level 2, close 2
+    if ($currlevel == 1) {
+        $imsitems .= '          </item>' . "\n";
+        $imsitems .= '        </item>' . "\n";
+    }
+
+    // Define the css common resource
+    $cssresource = '    <resource identifier="RES-' . $book->course . '-'  . $book->id . '-css" type="webcontent" xml:base="css/" href="styles.css">
+      <file href="css/styles.css" />
+    </resource>' . "\n";
+
+    // Add imsitems to manifest
+    $imsmanifest .= "\n" . $imsitems;
+    // Close the organization
+    $imsmanifest .= "    </organization>
+  </organizations>";
+    // Add resources to manifest
+    $imsmanifest .= "\n  <resources>\n" . $imsresources . $cssresource . "  </resources>";
+    // Close manifest
+    $imsmanifest .= "\n</manifest>\n";
+
+    $manifest_file_record = array('contextid'=>$context->id, 'component'=>'booktool_exportimscp', 'filearea'=>'temp',
+            'itemid'=>$book->revision, 'filepath'=>"/", 'filename'=>'imsmanifest.xml');
+    $fs->create_file_from_string($manifest_file_record, $imsmanifest);
+}
+
+/**
+ * Returns the html contents of one book's chapter to be exported as IMSCP
+ *
+ * @param stdClass $chapter the chapter to be exported
+ * @param context_module $context context the chapter belongs to
+ * @return string the contents of the chapter
+ */
+function booktool_exportimscp_chapter_content($chapter, $context) {
+
+    $options = new stdClass();
+    $options->noclean = true;
+    $options->context = $context;
+
+    $chaptercontent = str_replace('@@PLUGINFILE@@/', '', $chapter->content);
+    $chaptercontent = format_text($chaptercontent, $chapter->contentformat, $options);
+
+    $chaptertitle = format_string($chapter->title, true, array('context'=>$context));
+
+    $content = '';
+    $content .= '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">' . "\n";
+    $content .= '<html>' . "\n";
+    $content .= '<head>' . "\n";
+    $content .= '<meta http-equiv="content-type" content="text/html; charset=utf-8" />' . "\n";
+    $content .= '<link rel="stylesheet" type="text/css" href="../css/styles.css" />' . "\n";
+    $content .= '<title>' . $chaptertitle . '</title>' . "\n";
+    $content .= '</head>' . "\n";
+    $content .= '<body>' . "\n";
+    $content .= '<h1 id="header">' . $chaptertitle . '</h1>' ."\n";
+    $content .= $chaptercontent . "\n";
+    $content .= '</body>' . "\n";
+    $content .= '</html>' . "\n";
+
+    return $content;
+}
diff --git a/mod/book/tool/exportimscp/pix/generate.png b/mod/book/tool/exportimscp/pix/generate.png
new file mode 100644 (file)
index 0000000..de7090a
Binary files /dev/null and b/mod/book/tool/exportimscp/pix/generate.png differ
diff --git a/mod/book/tool/exportimscp/version.php b/mod/book/tool/exportimscp/version.php
new file mode 100644 (file)
index 0000000..3fd6550
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book IMSCP export plugin version info
+ *
+ * @package    booktool_exportimscp
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$plugin->component = 'booktool_exportimscp'; // Full name of the plugin (used for diagnostics)
+$plugin->version   = 2012052100; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2012051900; // Requires this Moodle version
diff --git a/mod/book/tool/importhtml/db/access.php b/mod/book/tool/importhtml/db/access.php
new file mode 100644 (file)
index 0000000..d1fb35d
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book import capability definition
+ *
+ * @package    booktool_importhtml
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$capabilities = array(
+    'booktool/importhtml:import' => array(
+        'riskbitmask' => RISK_XSS,
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'archetypes' => array(
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW,
+        )
+    ),
+);
diff --git a/mod/book/tool/importhtml/import_form.php b/mod/book/tool/importhtml/import_form.php
new file mode 100644 (file)
index 0000000..ddbae84
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book import form
+ *
+ * @package    booktool_importhtml
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->libdir.'/formslib.php');
+
+class booktool_importhtml_form extends moodleform {
+
+    function definition() {
+        $mform = $this->_form;
+        $data  = $this->_customdata;
+
+        $mform->addElement('header', 'general', get_string('import'));
+
+        $options = array(
+                // '0'=>get_string('typeonefile', 'booktool_importhtml'),
+                '1'=>get_string('typezipdirs', 'booktool_importhtml'),
+                '2'=>get_string('typezipfiles', 'booktool_importhtml'),
+        );
+        $mform->addElement('select', 'type', get_string('type', 'booktool_importhtml'), $options);
+        $mform->setDefault('type', 2);
+
+        $mform->addElement('filepicker', 'importfile', get_string('ziparchive', 'booktool_importhtml'));
+        $mform->addHelpButton('importfile', 'ziparchive', 'booktool_importhtml');
+        $mform->addRule('importfile', null, 'required');
+
+        $mform->addElement('hidden', 'id');
+        $mform->setType('id', PARAM_INT);
+
+        $mform->addElement('hidden', 'chapterid');
+        $mform->setType('chapterid', PARAM_INT);
+
+        $this->add_action_buttons(true, get_string('doimport', 'booktool_importhtml'));
+
+        $this->set_data($data);
+    }
+
+    function validation($data, $files) {
+        global $USER;
+
+        if ($errors = parent::validation($data, $files)) {
+            return $errors;
+        }
+
+        $usercontext = context_user::instance($USER->id);
+        $fs = get_file_storage();
+
+        if (!$files = $fs->get_area_files($usercontext->id, 'user', 'draft', $data['importfile'], 'id', false)) {
+            $errors['importfile'] = get_string('required');
+            return $errors;
+        } else {
+            $file = reset($files);
+            if ($file->get_mimetype() != 'application/zip') {
+                $errors['importfile'] = get_string('invalidfiletype', 'error', $file->get_filename());
+                // better delete current file, it is not usable anyway
+                $fs->delete_area_files($usercontext->id, 'user', 'draft', $data['importfile']);
+            } else {
+                if (!$chpterfiles = toolbook_importhtml_get_chapter_files($file, $data['type'])) {
+                    $errors['importfile'] = get_string('errornochapters', 'booktool_importhtml');
+                }
+            }
+        }
+
+        return $errors;
+    }
+}
diff --git a/mod/book/tool/importhtml/index.php b/mod/book/tool/importhtml/index.php
new file mode 100644 (file)
index 0000000..95bdd38
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book import
+ *
+ * @package    booktool_importhtml
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(dirname(__FILE__).'/../../../../config.php');
+require_once(dirname(__FILE__).'/locallib.php');
+require_once(dirname(__FILE__).'/import_form.php');
+
+$id        = required_param('id', PARAM_INT);           // Course Module ID
+$chapterid = optional_param('chapterid', 0, PARAM_INT); // Chapter ID
+
+$cm = get_coursemodule_from_id('book', $id, 0, false, MUST_EXIST);
+$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+$book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
+
+require_login($course, false, $cm);
+
+$context = context_module::instance($cm->id);
+require_capability('booktool/importhtml:import', $context);
+
+$PAGE->set_url('/mod/book/tool/importhtml/index.php', array('id'=>$id, 'chapterid'=>$chapterid));
+
+if ($chapterid) {
+    if (!$chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id))) {
+        $chapterid = 0;
+    }
+} else {
+    $chapter = false;
+}
+
+$PAGE->set_title(format_string($book->name));
+$PAGE->add_body_class('mod_book');
+$PAGE->set_heading(format_string($course->fullname));
+
+// Prepare the page header.
+$strbook = get_string('modulename', 'mod_book');
+$strbooks = get_string('modulenameplural', 'mod_book');
+
+$mform = new booktool_importhtml_form(null, array('id'=>$id, 'chapterid'=>$chapterid));
+
+// If data submitted, then process and store.
+if ($mform->is_cancelled()) {
+    if (empty($chapter->id)) {
+        redirect("/mod/book/view.php?id=$cm->id");
+    } else {
+        redirect("/mod/book/view.php?id=$cm->id&chapterid=$chapter->id");
+    }
+
+} else if ($data = $mform->get_data()) {
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(get_string('importingchapters', 'booktool_importhtml'));
+
+    // this is a bloody hack - children do not try this at home!
+    $fs = get_file_storage();
+    $draftid = file_get_submitted_draft_itemid('importfile');
+    if (!$files = $fs->get_area_files(context_user::instance($USER->id)->id, 'user', 'draft', $draftid, 'id DESC', false)) {
+        redirect($PAGE->url);
+    }
+    $file = reset($files);
+    toolbook_importhtml_import_chapters($file, $data->type, $book, $context);
+
+    echo $OUTPUT->continue_button(new moodle_url('/mod/book/view.php', array('id'=>$id)));
+    echo $OUTPUT->footer();
+    die;
+}
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading(get_string('import', 'booktool_importhtml'));
+
+$mform->display();
+
+echo $OUTPUT->footer();
diff --git a/mod/book/tool/importhtml/lang/en/booktool_importhtml.php b/mod/book/tool/importhtml/lang/en/booktool_importhtml.php
new file mode 100644 (file)
index 0000000..c564009
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book import language strings
+ *
+ * @package    booktool_importhtml
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$string['doimport'] = 'Import';
+$string['errornochapters'] = 'Can not find chapters in selected file';
+$string['import'] = 'Import from HTML';
+$string['importhtml:import'] = 'Import chapters';
+$string['importing'] = 'Importing';
+$string['importingchapters'] = 'Importing chapters into book';
+$string['pluginname'] = 'Book HTML import';
+$string['relinking'] = 'Relinking';
+$string['type'] = 'Type';
+$string['typeonefile'] = 'One HTML file with headings as chapters';
+$string['typezipfiles'] = 'Each HTML file represents one chapter';
+$string['typezipdirs'] = 'Each directory represents one chapter';
+$string['ziparchive'] = 'Zip archive';
+$string['ziparchive_help'] = 'Select a ZIP archive that contains HTML files and other media. File or directory names ending with "_sub" indicate subchapters. You can use copy and paste in text editor for simple HML files without embedded media.';
\ No newline at end of file
diff --git a/mod/book/tool/importhtml/lib.php b/mod/book/tool/importhtml/lib.php
new file mode 100644 (file)
index 0000000..bf9c7d7
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * HTML import lib
+ *
+ * @package    booktool_importhtml
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Adds module specific settings to the settings block
+ *
+ * @param settings_navigation $settings The settings navigation object
+ * @param navigation_node $node The node to add module settings to
+ */
+function booktool_importhtml_extend_settings_navigation(settings_navigation $settings, navigation_node $node) {
+    global $USER, $PAGE, $CFG, $DB, $OUTPUT;
+
+    if ($PAGE->cm->modname !== 'book') {
+        return;
+    }
+
+    if (has_capability('booktool/importhtml:import', $PAGE->cm->context)) {
+        $url = new moodle_url('/mod/book/tool/importhtml/index.php', array('id'=>$PAGE->cm->id));
+        $node->add(get_string('import', 'booktool_importhtml'), $url, navigation_node::TYPE_SETTING, null, null, null);
+    }
+}
diff --git a/mod/book/tool/importhtml/locallib.php b/mod/book/tool/importhtml/locallib.php
new file mode 100644 (file)
index 0000000..7aa04ca
--- /dev/null
@@ -0,0 +1,346 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * HTML import lib
+ *
+ * @package    booktool_importhtml
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once(dirname(__FILE__).'/lib.php');
+require_once($CFG->dirroot.'/mod/book/locallib.php');
+
+/**
+ * Import HTML pages packaged into one zip archive
+ *
+ * @param stored_file $package
+ * @param string $type type of the package ('typezipdirs' or 'typezipfiles')
+ * @param stdClass $book
+ * @param context_module $context
+ * @param bool $verbose
+ */
+function toolbook_importhtml_import_chapters($package, $type, $book, $context, $verbose = true) {
+    global $DB, $OUTPUT;
+
+    $fs = get_file_storage();
+    $chapterfiles = toolbook_importhtml_get_chapter_files($package, $type);
+    $packer = get_file_packer('application/zip');
+    $fs->delete_area_files($context->id, 'mod_book', 'importhtmltemp', 0);
+    $package->extract_to_storage($packer, $context->id, 'mod_book', 'importhtmltemp', 0, '/');
+    // $datafiles = $fs->get_area_files($context->id, 'mod_book', 'importhtmltemp', 0, 'id', false);
+    // echo "<pre>";p(var_export($datafiles, true));
+
+    $chapters = array();
+
+    if ($verbose) {
+        echo $OUTPUT->notification(get_string('importing', 'booktool_importhtml'), 'notifysuccess');
+    }
+    if ($type == 0) {
+        $chapterfile = reset($chapterfiles);
+        if ($file = $fs->get_file_by_hash("$context->id/mod_book/importhtmltemp/0/$chapterfile->pathname")) {
+            $htmlcontent = toolbook_importhtml_fix_encoding($file->get_content());
+            $htmlchapters = toolbook_importhtml_parse_headings(toolbook_importhtml_parse_body($htmlcontent));
+            // TODO: process h1 as main chapter and h2 as subchapters
+        }
+    } else {
+        foreach ($chapterfiles as $chapterfile) {
+            if ($file = $fs->get_file_by_hash(sha1("/$context->id/mod_book/importhtmltemp/0/$chapterfile->pathname"))) {
+                $chapter = new stdClass();
+                $htmlcontent = toolbook_importhtml_fix_encoding($file->get_content());
+
+                $chapter->bookid        = $book->id;
+                $chapter->pagenum       = $DB->get_field_sql('SELECT MAX(pagenum) FROM {book_chapters} WHERE bookid = ?', array($book->id)) + 1;
+                $chapter->importsrc     = '/'.$chapterfile->pathname;
+                $chapter->content       = toolbook_importhtml_parse_styles($htmlcontent);
+                $chapter->content       .= toolbook_importhtml_parse_body($htmlcontent);
+                $chapter->title         = toolbook_importhtml_parse_title($htmlcontent, $chapterfile->pathname);
+                $chapter->contentformat = FORMAT_HTML;
+                $chapter->hidden        = 0;
+                $chapter->timecreated   = time();
+                $chapter->timemodified  = time();
+                if (preg_match('/_sub(\/|\.htm)/i', $chapter->importsrc)) { // If filename or directory ends with *_sub treat as subchapters
+                    $chapter->subchapter = 1;
+                } else {
+                    $chapter->subchapter = 0;
+                }
+
+                $chapter->id = $DB->insert_record('book_chapters', $chapter);
+                $chapters[$chapter->id] = $chapter;
+
+                add_to_log($book->course, 'book', 'update', 'view.php?id='.$context->instanceid.'&chapterid='.$chapter->id, $book->id, $context->instanceid);
+            }
+        }
+    }
+
+    if ($verbose) {
+        echo $OUTPUT->notification(get_string('relinking', 'booktool_importhtml'), 'notifysuccess');
+    }
+    $allchapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum');
+    foreach ($chapters as $chapter) {
+        // find references to all files and copy them + relink them
+        $matches = null;
+        if (preg_match_all('/(src|codebase|name|href)\s*=\s*"([^"]+)"/i', $chapter->content, $matches)) {
+            $file_record = array('contextid'=>$context->id, 'component'=>'mod_book', 'filearea'=>'chapter', 'itemid'=>$chapter->id);
+            foreach ($matches[0] as $i => $match) {
+                $filepath = dirname($chapter->importsrc).'/'.$matches[2][$i];
+                $filepath = toolbook_importhtml_fix_path($filepath);
+
+                if (strtolower($matches[1][$i]) === 'href') {
+                    // skip linked html files, we will try chapter relinking later
+                    foreach ($allchapters as $target) {
+                        if ($target->importsrc === $filepath) {
+                            continue 2;
+                        }
+                    }
+                }
+
+                if ($file = $fs->get_file_by_hash(sha1("/$context->id/mod_book/importhtmltemp/0$filepath"))) {
+                    if (!$oldfile = $fs->get_file_by_hash(sha1("/$context->id/mod_book/chapter/$chapter->id$filepath"))) {
+                        $fs->create_file_from_storedfile($file_record, $file);
+                    }
+                    $chapter->content = str_replace($match, $matches[1][$i].'="@@PLUGINFILE@@'.$filepath.'"', $chapter->content);
+                }
+            }
+            $DB->set_field('book_chapters', 'content', $chapter->content, array('id'=>$chapter->id));
+        }
+    }
+    unset($chapters);
+
+    $allchapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum');
+    foreach ($allchapters as $chapter) {
+        $newcontent = $chapter->content;
+        $matches = null;
+        if (preg_match_all('/(href)\s*=\s*"([^"]+)"/i', $chapter->content, $matches)) {
+            foreach ($matches[0] as $i => $match) {
+                if (strpos($matches[2][$i], ':') !== false or strpos($matches[2][$i], '@') !== false) {
+                    // it is either absolute or pluginfile link
+                    continue;
+                }
+                $chapterpath = dirname($chapter->importsrc).'/'.$matches[2][$i];
+                $chapterpath = toolbook_importhtml_fix_path($chapterpath);
+                foreach ($allchapters as $target) {
+                    if ($target->importsrc === $chapterpath) {
+                        $newcontent = str_replace($match, 'href="'.new moodle_url('/mod/book/view.php',
+                                array('id'=>$context->instanceid, 'chapter'=>$target->id)).'"', $newcontent);
+                    }
+                }
+            }
+        }
+        if ($newcontent !== $chapter->content) {
+            $DB->set_field('book_chapters', 'content', $newcontent, array('id'=>$chapter->id));
+        }
+    }
+
+    add_to_log($book->course, 'course', 'update mod', '../mod/book/view.php?id='.$context->instanceid, 'book '.$book->id);
+    $fs->delete_area_files($context->id, 'mod_book', 'importhtmltemp', 0);
+
+    // update the revision flag - this takes a long time, better to refetch the current value
+    $book = $DB->get_record('book', array('id'=>$book->id));
+    $DB->set_field('book', 'revision', $book->revision+1, array('id'=>$book->id));
+}
+
+/**
+ * Parse the headings of the imported package of type 'typeonefile'
+ * (currently unsupported)
+ *
+ * @param string $html html content to parse
+ * @todo implement this once the type 'typeonefile' is enabled
+ */
+function toolbook_importhtml_parse_headings($html) {
+}
+
+/**
+ * Parse the links to external css sheets of the imported html content
+ *
+ * @param string $html html content to parse
+ * @return string all the links to external css sheets
+ */
+function toolbook_importhtml_parse_styles($html) {
+    $styles = '';
+    if (preg_match('/<head[^>]*>(.+)<\/head>/is', $html, $matches)) {
+        $head = $matches[1];
+        if (preg_match_all('/<link[^>]+rel="stylesheet"[^>]*>/i', $head, $matches)) { // Extract links to css.
+            for ($i=0; $i<count($matches[0]); $i++) {
+                $styles .= $matches[0][$i]."\n";
+            }
+        }
+    }
+    return $styles;
+}
+
+/**
+ * Normalize paths to be absolute
+ *
+ * @param string $path original path with MS/relative separators
+ * @return string the normalized and cleaned absolute path
+ */
+function toolbook_importhtml_fix_path($path) {
+    $path = str_replace('\\', '/', $path); // anti MS hack
+    $path = '/'.ltrim($path, './'); // dirname() produces . for top level files + our paths start with /
+
+    $cnt = substr_count($path, '..');
+    for ($i=0; $i<$cnt; $i++) {
+        $path = preg_replace('|[^/]+/\.\./|', '', $path, 1);
+    }
+
+    $path = clean_param($path, PARAM_PATH);
+    return $path;
+}
+
+/**
+ * Convert some html content to utf8, getting original encoding from html headers
+ *
+ * @param string $html html content to convert
+ * @return string html content converted to utf8
+ */
+function toolbook_importhtml_fix_encoding($html) {
+    if (preg_match('/<head[^>]*>(.+)<\/head>/is', $html, $matches)) {
+        $head = $matches[1];
+        if (preg_match('/charset=([^"]+)/is', $head, $matches)) {
+            $enc = $matches[1];
+            return textlib::convert($html, $enc, 'utf-8');
+        }
+    }
+    return iconv('UTF-8', 'UTF-8//IGNORE', $html);
+}
+
+/**
+ * Extract the body from any html contents
+ *
+ * @param string $html the html to parse
+ * @return string the contents of the body
+ */
+function toolbook_importhtml_parse_body($html) {
+    $matches = null;
+    if (preg_match('/<body[^>]*>(.+)<\/body>/is', $html, $matches)) {
+        return $matches[1];
+    } else {
+        return '';
+    }
+}
+
+/**
+ * Extract the title of any html content, getting it from the title tag
+ *
+ * @param string $html the html to parse
+ * @param string $default default title to apply if no title is found
+ * @return string the resulting title
+ */
+function toolbook_importhtml_parse_title($html, $default) {
+    $matches = null;
+    if (preg_match('/<title>([^<]+)<\/title>/i', $html, $matches)) {
+        return $matches[1];
+    } else {
+        return $default;
+    }
+}
+
+/**
+ * Returns all the html files (chapters) from a file package
+ *
+ * @param stored_file $package file to be processed
+ * @param string $type type of the package ('typezipdirs' or 'typezipfiles')
+ *
+ * @return array the html files found in the package
+ */
+function toolbook_importhtml_get_chapter_files($package, $type) {
+    $packer = get_file_packer('application/zip');
+    $files = $package->list_files($packer);
+    $tophtmlfiles = array();
+    $subhtmlfiles = array();
+    $topdirs = array();
+
+    foreach ($files as $file) {
+        if (empty($file->pathname)) {
+            continue;
+        }
+        if (substr($file->pathname, -1) === '/') {
+            if (substr_count($file->pathname, '/') !== 1) {
+                // skip subdirs
+                continue;
+            }
+            if (!isset($topdirs[$file->pathname])) {
+                $topdirs[$file->pathname] = array();
+            }
+
+        } else {
+            $mime = mimeinfo('icon', $file->pathname);
+            if ($mime !== 'html') {
+                continue;
+            }
+            $level = substr_count($file->pathname, '/');
+            if ($level === 0) {
+                $tophtmlfiles[$file->pathname] = $file;
+            } else if ($level === 1) {
+                $subhtmlfiles[$file->pathname] = $file;
+                $dir = preg_replace('|/.*$|', '', $file->pathname);
+                $topdirs[$dir][$file->pathname] = $file;
+            } else {
+                // lower levels are not interesting
+                continue;
+            }
+        }
+    }
+    // TODO: natural dir sorting would be nice here...
+    textlib::asort($tophtmlfiles);
+    textlib::asort($subhtmlfiles);
+    textlib::asort($topdirs);
+
+    $chapterfiles = array();
+
+    if ($type == 2) {
+        $chapterfiles = $tophtmlfiles;
+
+    } else if ($type == 1) {
+        foreach ($topdirs as $dir => $htmlfiles) {
+            if (empty($htmlfiles)) {
+                continue;
+            }
+            textlib::asort($htmlfiles);
+            if (isset($htmlfiles[$dir.'/index.html'])) {
+                $htmlfile = $htmlfiles[$dir.'/index.html'];
+            } else if (isset($htmlfiles[$dir.'/index.htm'])) {
+                $htmlfile = $htmlfiles[$dir.'/index.htm'];
+            } else if (isset($htmlfiles[$dir.'/Default.htm'])) {
+                $htmlfile = $htmlfiles[$dir.'/Default.htm'];
+            } else {
+                $htmlfile = reset($htmlfiles);
+            }
+            $chapterfiles[$htmlfile->pathname] = $htmlfile;
+        }
+    } else if ($type == 0) {
+        if ($tophtmlfiles) {
+            if (isset($tophtmlfiles['index.html'])) {
+                $htmlfile = $tophtmlfiles['index.html'];
+            } else if (isset($tophtmlfiles['index.htm'])) {
+                $htmlfile = $tophtmlfiles['index.htm'];
+            } else if (isset($tophtmlfiles['Default.htm'])) {
+                $htmlfile = $tophtmlfiles['Default.htm'];
+            } else {
+                $htmlfile = reset($tophtmlfiles);
+            }
+        } else {
+            $htmlfile = reset($subhtmlfiles);
+        }
+        $chapterfiles[$htmlfile->pathname] = $htmlfile;
+    }
+
+    return $chapterfiles;
+}
diff --git a/mod/book/tool/importhtml/version.php b/mod/book/tool/importhtml/version.php
new file mode 100644 (file)
index 0000000..e7f46c6
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book import plugin version info
+ *
+ * @package    booktool_importhtml
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$plugin->component = 'booktool_importhtml'; // Full name of the plugin (used for diagnostics)
+$plugin->version   = 2012052100; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2012051900; // Requires this Moodle version
diff --git a/mod/book/tool/print/db/access.php b/mod/book/tool/print/db/access.php
new file mode 100644 (file)
index 0000000..5e5c2b1
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book module capability definition
+ *
+ * @package    booktool_print
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$capabilities = array(
+    'booktool/print:print' => array(
+        'captype' => 'read',
+        'contextlevel' => CONTEXT_MODULE,
+        'archetypes' => array(
+            'guest' => CAP_ALLOW,
+            'frontpage' => CAP_ALLOW,
+            'student' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW,
+        )
+    ),
+);
diff --git a/mod/book/tool/print/db/log.php b/mod/book/tool/print/db/log.php
new file mode 100644 (file)
index 0000000..efaa684
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Print booktool log events definition
+ *
+ * @package    booktool_print
+ * @copyright  2012 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$logs = array(
+    array('module'=>'book', 'action'=>'print', 'mtable'=>'book', 'field'=>'name')
+);
diff --git a/mod/book/tool/print/index.php b/mod/book/tool/print/index.php
new file mode 100644 (file)
index 0000000..5205435
--- /dev/null
@@ -0,0 +1,175 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book printing
+ *
+ * @package    booktool_print
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(dirname(__FILE__).'/../../../../config.php');
+require_once(dirname(__FILE__).'/locallib.php');
+
+$id        = required_param('id', PARAM_INT);           // Course Module ID
+$chapterid = optional_param('chapterid', 0, PARAM_INT); // Chapter ID
+
+// =========================================================================
+// security checks START - teachers and students view
+// =========================================================================
+
+$cm = get_coursemodule_from_id('book', $id, 0, false, MUST_EXIST);
+$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+$book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
+
+require_course_login($course, true, $cm);
+
+$context = context_module::instance($cm->id);
+require_capability('mod/book:read', $context);
+require_capability('booktool/print:print', $context);
+
+// Check all variables.
+if ($chapterid) {
+    // Single chapter printing - only visible!
+    $chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id), '*', MUST_EXIST);
+} else {
+    // Complete book.
+    $chapter = false;
+}
+
+$PAGE->set_url('/mod/book/print.php', array('id'=>$id, 'chapterid'=>$chapterid));
+
+unset($id);
+unset($chapterid);
+
+// Security checks END.
+
+// read chapters
+$chapters = book_preload_chapters($book);
+
+$strbooks = get_string('modulenameplural', 'mod_book');
+$strbook  = get_string('modulename', 'mod_book');
+$strtop   = get_string('top', 'mod_book');
+
+@header('Cache-Control: private, pre-check=0, post-check=0, max-age=0');
+@header('Pragma: no-cache');
+@header('Expires: ');
+@header('Accept-Ranges: none');
+@header('Content-type: text/html; charset=utf-8');
+
+if ($chapter) {
+
+    if ($chapter->hidden) {
+        require_capability('mod/book:viewhiddenchapters', $context);
+    }
+
+    add_to_log($course->id, 'book', 'print', 'tool/print/index.php?id='.$cm->id.'&chapterid='.$chapter->id, $book->id, $cm->id);
+
+    // page header
+    ?>
+    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+    <html>
+    <head>
+      <title><?php echo format_string($book->name, true, array('context'=>$context)) ?></title>
+      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+      <meta name="description" content="<?php echo s(format_string($book->name, true, array('context'=>$context))) ?>" />
+      <link rel="stylesheet" type="text/css" href="print.css" />
+    </head>
+    <body>
+    <a name="top"></a>
+    <div class="chapter">
+    <?php
+
+
+    if (!$book->customtitles) {
+        if (!$chapter->subchapter) {
+            $currtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
+            echo '<p class="book_chapter_title">'.$currtitle.'</p>';
+        } else {
+            $currtitle = book_get_chapter_title($chapters[$chapter->id]->parent, $chapters, $book, $context);
+            $currsubtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
+            echo '<p class="book_chapter_title">'.$currtitle.'<br />'.$currsubtitle.'</p>';
+        }
+    }
+
+    $chaptertext = file_rewrite_pluginfile_urls($chapter->content, 'pluginfile.php', $context->id, 'mod_book', 'chapter', $chapter->id);
+    echo format_text($chaptertext, $chapter->contentformat, array('noclean'=>true, 'context'=>$context));
+    echo '</div>';
+    echo '</body> </html>';
+
+} else {
+    add_to_log($course->id, 'book', 'print', 'tool/print/index.php?id='.$cm->id, $book->id, $cm->id);
+    $allchapters = $DB->get_records('book_chapters', array('bookid'=>$book->id), 'pagenum');
+
+    // page header
+    ?>
+    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+    <html>
+    <head>
+      <title><?php echo format_string($book->name, true, array('context'=>$context)) ?></title>
+      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+      <meta name="description" content="<?php echo s(format_string($book->name, true, array('noclean'=>true, 'context'=>$context))) ?>" />
+      <link rel="stylesheet" type="text/css" href="print.css" />
+    </head>
+    <body>
+    <a name="top"></a>
+    <p class="book_title"><?php echo format_string($book->name, true, array('context'=>$context)) ?></p>
+    <p class="book_summary"><?php echo format_text($book->intro, $book->introformat, array('noclean'=>true, 'context'=>$context)) ?></p>
+    <div class="book_info"><table>
+    <tr>
+    <td><?php echo get_string('site') ?>:</td>
+    <td><a href="<?php echo $CFG->wwwroot ?>"><?php echo format_string($SITE->fullname, true, array('context'=>$context)) ?></a></td>
+    </tr><tr>
+    <td><?php echo get_string('course') ?>:</td>
+    <td><?php echo format_string($course->fullname, true, array('context'=>$context)) ?></td>
+    </tr><tr>
+    <td><?php echo get_string('modulename', 'mod_book') ?>:</td>
+    <td><?php echo format_string($book->name, true, array('context'=>$context)) ?></td>
+    </tr><tr>
+    <td><?php echo get_string('printedby', 'booktool_print') ?>:</td>
+    <td><?php echo fullname($USER, true) ?></td>
+    </tr><tr>
+    <td><?php echo get_string('printdate', 'booktool_print') ?>:</td>
+    <td><?php echo userdate(time()) ?></td>
+    </tr>
+    </table></div>
+
+    <?php
+    list($toc, $titles) = booktool_print_get_toc($chapters, $book, $cm);
+    echo $toc;
+    // chapters
+    $link1 = $CFG->wwwroot.'/mod/book/view.php?id='.$course->id.'&chapterid=';
+    $link2 = $CFG->wwwroot.'/mod/book/view.php?id='.$course->id;
+    foreach ($chapters as $ch) {
+        $chapter = $allchapters[$ch->id];
+        if ($chapter->hidden) {
+            continue;
+        }
+        echo '<div class="book_chapter"><a name="ch'.$ch->id.'"></a>';
+        if (!$book->customtitles) {
+            echo '<p class="book_chapter_title">'.$titles[$ch->id].'</p>';
+        }
+        $content = str_replace($link1, '#ch', $chapter->content);
+        $content = str_replace($link2, '#top', $content);
+        $content = file_rewrite_pluginfile_urls($content, 'pluginfile.php', $context->id, 'mod_book', 'chapter', $ch->id);
+        echo format_text($content, $chapter->contentformat, array('noclean'=>true, 'context'=>$context));
+        echo '</div>';
+        // echo '<a href="#toc">'.$strtop.'</a>';
+    }
+    echo '</body> </html>';
+}
+
diff --git a/mod/book/tool/print/lang/en/booktool_print.php b/mod/book/tool/print/lang/en/booktool_print.php
new file mode 100644 (file)
index 0000000..e059fe1
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book module language strings
+ *
+ * @package    booktool_print
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$string['pluginname'] = 'Book printing';
+$string['printbook'] = 'Print book';
+$string['printchapter'] = 'Print this chapter';
+$string['printdate'] = 'Date';
+$string['printedby'] = 'Printed by';
+$string['print:print'] = 'Print book';
diff --git a/mod/book/tool/print/lib.php b/mod/book/tool/print/lib.php
new file mode 100644 (file)
index 0000000..8172c07
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Print lib
+ *
+ * @package    booktool_print
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Adds module specific settings to the settings block
+ *
+ * @param settings_navigation $settings The settings navigation object
+ * @param navigation_node $node The node to add module settings to
+ */
+function booktool_print_extend_settings_navigation(settings_navigation $settings, navigation_node $node) {
+    global $USER, $PAGE, $CFG, $DB, $OUTPUT;
+
+    if ($PAGE->cm->modname !== 'book') {
+        return;
+    }
+
+    $params = $PAGE->url->params();
+
+    if (empty($params['id']) or empty($params['chapterid'])) {
+        return;
+    }
+
+    if (has_capability('booktool/print:print', $PAGE->cm->context)) {
+        $url1 = new moodle_url('/mod/book/tool/print/index.php', array('id'=>$params['id']));
+        $url2 = new moodle_url('/mod/book/tool/print/index.php', array('id'=>$params['id'], 'chapterid'=>$params['chapterid']));
+        $action = new action_link($url1, get_string('printbook', 'booktool_print'), new popup_action('click', $url1));
+        $node->add(get_string('printbook', 'booktool_print'), $action, navigation_node::TYPE_SETTING, null, null,
+                new pix_icon('book', '', 'booktool_print', array('class'=>'icon')));
+        $action = new action_link($url2, get_string('printchapter', 'booktool_print'), new popup_action('click', $url2));
+        $node->add(get_string('printchapter', 'booktool_print'), $action, navigation_node::TYPE_SETTING, null, null,
+                new pix_icon('chapter', '', 'booktool_print', array('class'=>'icon')));
+    }
+}
+
+/**
+ * Return read actions.
+ * @return array
+ */
+function booktool_print_get_view_actions() {
+    return array('print');
+}
diff --git a/mod/book/tool/print/locallib.php b/mod/book/tool/print/locallib.php
new file mode 100644 (file)
index 0000000..524ad16
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book print lib
+ *
+ * @package    booktool_print
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once(dirname(__FILE__).'/lib.php');
+require_once($CFG->dirroot.'/mod/book/locallib.php');
+
+/**
+ * Generate toc structure and titles
+ *
+ * @param array $chapters
+ * @param stdClass $book
+ * @param stdClass $cm
+ * @return array
+ */
+function booktool_print_get_toc($chapters, $book, $cm) {
+    $first = true;
+    $titles = array();
+
+    $context = context_module::instance($cm->id);
+
+    $toc = ''; // Representation of toc (HTML).
+
+    switch ($book->numbering) {
+        case BOOK_NUM_NONE:
+            $toc .= '<div class="book_toc_none">';
+            break;
+        case BOOK_NUM_NUMBERS:
+            $toc .= '<div class="book_toc_numbered">';
+            break;
+        case BOOK_NUM_BULLETS:
+            $toc .= '<div class="book_toc_bullets">';
+            break;
+        case BOOK_NUM_INDENTED:
+            $toc .= '<div class="book_toc_indented">';
+            break;
+    }
+
+    $toc .= '<a name="toc"></a>'; // Representation of toc (HTML).
+
+    if ($book->customtitles) {
+        $toc .= '<h1>'.get_string('toc', 'mod_book').'</h1>';
+    } else {
+        $toc .= '<p class="book_chapter_title">'.get_string('toc', 'mod_book').'</p>';
+    }
+    $toc .= '<ul>';
+    foreach ($chapters as $ch) {
+        if (!$ch->hidden) {
+            $title = book_get_chapter_title($ch->id, $chapters, $book, $context);
+            if (!$ch->subchapter) {
+                $toc .= $first ? '<li>' : '</ul></li><li>';
+            } else {
+                $toc .= $first ? '<li><ul><li>' : '<li>';
+            }
+            $titles[$ch->id] = $title;
+            $toc .= '<a title="'.s($title).'" href="#ch'.$ch->id.'">'.$title.'</a>';
+            $toc .= (!$ch->subchapter) ? '<ul>' : '</li>';
+            $first = false;
+        }
+    }
+    $toc .= '</ul></li></ul>';
+    $toc .= '</div>';
+    $toc = str_replace('<ul></ul>', '', $toc); // Cleanup of invalid structures.
+
+    return array($toc, $titles);
+}
diff --git a/mod/book/tool/print/pix/book.png b/mod/book/tool/print/pix/book.png
new file mode 100644 (file)
index 0000000..a5c9080
Binary files /dev/null and b/mod/book/tool/print/pix/book.png differ
diff --git a/mod/book/tool/print/pix/chapter.png b/mod/book/tool/print/pix/chapter.png
new file mode 100644 (file)
index 0000000..99ecd29
Binary files /dev/null and b/mod/book/tool/print/pix/chapter.png differ
diff --git a/mod/book/tool/print/print.css b/mod/book/tool/print/print.css
new file mode 100644 (file)
index 0000000..7b7cced
--- /dev/null
@@ -0,0 +1,157 @@
+
+h1, h2, h3, h4, h5, h6 {
+    margin-left: 0px;
+    font-family: "Times New Roman", Times, serif;
+    page-break-after: avoid;
+    page-break-inside: avoid;
+}
+
+.book_title {
+    margin-left: -40px;
+    font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif;
+    font-size: 3em;
+    font-weight: bold;
+    margin-top: 120px;
+    margin-bottom: 30px;
+    text-align: center;
+}
+
+.book_summary {
+    margin-left: -40px;
+    text-align: center;
+    margin-bottom: 120px;
+}
+
+.book_chapter_title {
+    font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif;
+    text-align: left;
+    font-size: 1.7em;
+    font-weight: bold;
+
+    border-style: solid;
+    border-top-width: 0px;
+    border-right-width: 0px;
+    border-bottom-width: 1px;
+    border-left-width: 0px;
+
+    margin-left: 0px;
+    margin-bottom: 20px;
+}
+
+.book_chapter {
+    page-break-before: always;
+}
+
+body {
+    margin-left: 50px;
+    margin-right: 10px;
+    color: #000000;
+    background-color: #FFFFFF;
+    font-family: "Times New Roman", Times, serif;
+    font-size: 1em;
+    font-weight: normal;
+    text-decoration: none;
+}
+
+/* link rewriting for mozilla - collides with filters :-( */
+/*
+a[href^="http://"]:after, a[href^="ftp://"]:after {
+    content: " ["attr(href)"]";
+}
+*/
+
+/* just some hack - ignore user defined <font> */
+font {
+    color: #000000;
+    background-color: #EEEEEE;
+    font-family: "Times New Roman", Times, serif;
+    font-size: 1em;
+    font-weight: normal;
+    text-decoration: none;
+}
+
+/* ===== TOC numbering styles ===== */
+
+/* numbering == NONE */
+.book_toc_none {
+    page-break-before: always;
+}
+.book_toc_none ul {
+    margin-left: 0px;
+    padding-left: 0px;
+}
+.book_toc_none ul ul {
+    margin-left: 0px;
+    padding-left: 0px;
+}
+.book_toc_none li {
+    margin-top: 10px;
+    list-style: none;
+}
+.book_toc_none li li {
+    margin-top: 0px;
+    list-style: none;
+}
+
+/* numbering == NUMBERED */
+.book_toc_numbered {
+    page-break-before: always;
+}
+.book_toc_numbered ul {
+    margin-left: 0px;
+    padding-left: 0px;
+}
+.book_toc_numbered ul ul {
+    margin-left: 0px;
+    padding-left: 0px;
+}
+.book_toc_numbered li {
+    margin-top: 10px;
+    list-style: none;
+}
+.book_toc_numbered li li {
+    margin-top: 0px;
+    list-style: none;
+}
+
+/* numbering == BULLETS */
+.book_toc_bullets {
+    page-break-before: always;
+}
+.book_toc_bullets ul {
+    margin-left: 0px;
+    padding-left: 0px;
+}
+.book_toc_bullets ul ul {
+    margin-left: 20px;
+    padding-left: 0px;
+}
+.book_toc_bullets li {
+    margin-top: 10px;
+    list-style: none;
+}
+.book_toc_bullets li li {
+    margin-top: 0px;
+    list-style: circle;
+}
+
+/* numbering == INDENTED */
+.book_toc_indented {
+    page-break-before: always;
+}
+.book_toc_indented ul {
+    margin-left: 0px;
+    padding-left: 0px;
+}
+.book_toc_indented ul ul {
+    margin-left: 20px;
+    padding-left: 0px;
+}
+.book_toc_indented li {
+    margin-top: 10px;
+    list-style: none;
+}
+.book_toc_indented li li {
+    margin-top: 0px;
+    list-style: none;
+}
diff --git a/mod/book/tool/print/version.php b/mod/book/tool/print/version.php
new file mode 100644 (file)
index 0000000..0251d1f
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+// This file is part of Book plugin for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book print plugin version info
+ *
+ * @package    booktool_print
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$plugin->component = 'booktool_print'; // Full name of the plugin (used for diagnostics)
+$plugin->version   = 2012052100; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2012051900; // Requires this Moodle version
diff --git a/mod/book/version.php b/mod/book/version.php
new file mode 100644 (file)
index 0000000..fe07130
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book plugin version info
+ *
+ * @package    mod_book
+ * @copyright  2004-2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$module->component = 'mod_book'; // Full name of the plugin (used for diagnostics)
+$module->version   = 2012052100; // The current module version (Date: YYYYMMDDXX)
+$module->requires  = 2012051900; // Requires this Moodle version
+$module->cron      = 0;          // Period for cron to check this module (secs)
diff --git a/mod/book/view.php b/mod/book/view.php
new file mode 100644 (file)
index 0000000..0ec34cf
--- /dev/null
@@ -0,0 +1,199 @@
+<?php
+// This file is part of Book module for Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Book view page
+ *
+ * @package    mod_book
+ * @copyright  2004-2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require(dirname(__FILE__).'/../../config.php');
+require_once(dirname(__FILE__).'/locallib.php');
+require_once($CFG->libdir.'/completionlib.php');
+
+$id        = optional_param('id', 0, PARAM_INT);        // Course Module ID
+$bid       = optional_param('b', 0, PARAM_INT);         // Book id
+$chapterid = optional_param('chapterid', 0, PARAM_INT); // Chapter ID
+$edit      = optional_param('edit', -1, PARAM_BOOL);    // Edit mode
+
+// =========================================================================
+// security checks START - teachers edit; students view
+// =========================================================================
+if ($id) {
+    $cm = get_coursemodule_from_id('book', $id, 0, false, MUST_EXIST);
+    $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+    $book = $DB->get_record('book', array('id'=>$cm->instance), '*', MUST_EXIST);
+} else {
+    $book = $DB->get_record('book', array('id'=>$bid), '*', MUST_EXIST);
+    $cm = get_coursemodule_from_instance('book', $book->id, 0, false, MUST_EXIST);
+    $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+    $id = $cm->id;
+}
+
+require_course_login($course, true, $cm);
+
+$context = context_module::instance($cm->id);
+require_capability('mod/book:read', $context);
+
+$allowedit  = has_capability('mod/book:edit', $context);
+$viewhidden = has_capability('mod/book:viewhiddenchapters', $context);
+
+if ($allowedit) {
+    if ($edit != -1 and confirm_sesskey()) {
+        $USER->editing = $edit;
+    } else {
+        if (isset($USER->editing)) {
+            $edit = $USER->editing;
+        } else {
+            $edit = 0;
+        }
+    }
+} else {
+    $edit = 0;
+}
+
+// read chapters
+$chapters = book_preload_chapters($book);
+
+if ($allowedit and !$chapters) {
+    redirect('edit.php?cmid='.$cm->id); // No chapters - add new one.
+}
+// Check chapterid and read chapter data
+if ($chapterid == '0') { // Go to first chapter if no given.
+    foreach ($chapters as $ch) {
+        if ($edit) {
+            $chapterid = $ch->id;
+            break;
+        }
+        if (!$ch->hidden) {
+            $chapterid = $ch->id;
+            break;
+        }
+    }
+}
+
+if (!$chapterid or !$chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id))) {
+    print_error('errorchapter', 'mod_book', new moodle_url('/course/view.php', array('id'=>$course->id)));
+}
+
+// chapter is hidden for students
+if ($chapter->hidden and !$viewhidden) {
+    print_error('errorchapter', 'mod_book', new moodle_url('/course/view.php', array('id'=>$course->id)));
+}
+
+$PAGE->set_url('/mod/book/view.php', array('id'=>$id, 'chapterid'=>$chapterid));
+
+
+// Unset all page parameters.
+unset($id);
+unset($bid);
+unset($chapterid);
+
+// Security checks END.
+
+add_to_log($course->id, 'book', 'view', 'view.php?id='.$cm->id.'&amp;chapterid='.$chapter->id, $book->id, $cm->id);
+
+// Read standard strings.
+$strbooks = get_string('modulenameplural', 'mod_book');
+$strbook  = get_string('modulename', 'mod_book');
+$strtoc   = get_string('toc', 'mod_book');
+
+// prepare header
+$PAGE->set_title(format_string($book->name));
+$PAGE->add_body_class('mod_book');
+$PAGE->set_heading(format_string($course->fullname));
+
+book_add_fake_block($chapters, $chapter, $book, $cm, $edit);
+
+// prepare chapter navigation icons
+$previd = null;
+$nextid = null;
+$last = null;
+foreach ($chapters as $ch) {
+    if (!$edit and $ch->hidden) {
+        continue;
+    }
+    if ($last == $chapter->id) {
+        $nextid = $ch->id;
+        break;
+    }
+    if ($ch->id != $chapter->id) {
+        $previd = $ch->id;
+    }
+    $last = $ch->id;
+}
+
+$chnavigation = '';
+if ($previd) {
+    $chnavigation .= '<a title="'.get_string('navprev', 'book').'" href="view.php?id='.$cm->id.
+            '&amp;chapterid='.$previd.'"><img src="'.$OUTPUT->pix_url('nav_prev', 'mod_book').'" class="bigicon" alt="'.get_string('navprev', 'book').'"/></a>';
+} else {
+    $chnavigation .= '<img src="'.$OUTPUT->pix_url('nav_prev_dis', 'mod_book').'" class="bigicon" alt="" />';
+}
+if ($nextid) {
+    $chnavigation .= '<a title="'.get_string('navnext', 'book').'" href="view.php?id='.$cm->id.
+            '&amp;chapterid='.$nextid.'"><img src="'.$OUTPUT->pix_url('nav_next', 'mod_book').'" class="bigicon" alt="'.get_string('navnext', 'book').'" /></a>';
+} else {
+    $sec = '';
+    if ($section = $DB->get_record('course_sections', array('id'=>$cm->section))) {
+        $sec = $section->section;
+    }
+    if ($course->id == $SITE->id) {
+        $returnurl = "$CFG->wwwroot/";
+    } else {
+        $returnurl = "$CFG->wwwroot/course/view.php?id=$course->id#section-$sec";
+    }
+    $chnavigation .= '<a title="'.get_string('navexit', 'book').'" href="'.$returnurl.'"><img src="'.$OUTPUT->pix_url('nav_exit', 'mod_book').
+            '" class="bigicon" alt="'.get_string('navexit', 'book').'" /></a>';
+
+    // we are cheating a bit here, viewing the last page means user has viewed the whole book
+    $completion = new completion_info($course);
+    $completion->set_module_viewed($cm);
+}
+
+// =====================================================
+// Book display HTML code
+// =====================================================
+
+echo $OUTPUT->header();
+
+// upper nav
+echo '<div class="navtop">'.$chnavigation.'</div>';
+
+// chapter itself
+echo $OUTPUT->box_start('generalbox book_content');
+if (!$book->customtitles) {
+    $hidden = $chapter->hidden ? 'dimmed_text' : '';
+    if (!$chapter->subchapter) {
+        $currtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
+        echo '<p class="book_chapter_title '.$hidden.'">'.$currtitle.'</p>';
+    } else {
+        $currtitle = book_get_chapter_title($chapters[$chapter->id]->parent, $chapters, $book, $context);
+        $currsubtitle = book_get_chapter_title($chapter->id, $chapters, $book, $context);
+        echo '<p class="book_chapter_title '.$hidden.'">'.$currtitle.'<br />'.$currsubtitle.'</p>';
+    }
+}
+$chaptertext = file_rewrite_pluginfile_urls($chapter->content, 'pluginfile.php', $context->id, 'mod_book', 'chapter', $chapter->id);
+echo format_text($chaptertext, $chapter->contentformat, array('noclean'=>true, 'context'=>$context));
+
+echo $OUTPUT->box_end();
+
+// lower navigation
+echo '<div class="navbottom">'.$chnavigation.'</div>';
+
+echo $OUTPUT->footer();
index 69f79ce..85f9ca5 100644 (file)
@@ -37,7 +37,7 @@ class mod_folder_edit_form extends moodleform {
 
         $mform->addElement('hidden', 'id', $data->id);
         $mform->addElement('filemanager', 'files_filemanager', get_string('files'), null, $options);
-        $submit_string = get_string('submit');
+        $submit_string = get_string('savechanges');
         $this->add_action_buttons(true, $submit_string);
 
         $this->set_data($data);
index 3d6aebd..b2f1410 100644 (file)
@@ -44,6 +44,11 @@ define('FORUM_TRACKING_OFF', 0);
 define('FORUM_TRACKING_OPTIONAL', 1);
 define('FORUM_TRACKING_ON', 2);
 
+if (!defined('FORUM_CRON_USER_CACHE')) {
+    /** Defines how many full user records are cached in forum cron. */
+    define('FORUM_CRON_USER_CACHE', 5000);
+}
+
 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
 
 /**
@@ -378,6 +383,28 @@ function forum_get_email_message_id($postid, $usertoid, $hostname) {
     return '<'.hash('sha256',$postid.'to'.$usertoid.'@'.$hostname).'>';
 }
 
+/**
+ * Removes properties from user record that are not necessary
+ * for sending post notifications.
+ * @param stdClass $user
+ * @return void, $user parameter is modified
+ */
+function forum_cron_minimise_user_record(stdClass $user) {
+
+    // We store large amount of users in one huge array,
+    // make sure we do not store info there we do not actually need
+    // in mail generation code or messaging.
+
+    unset($user->institution);
+    unset($user->department);
+    unset($user->address);
+    unset($user->city);
+    unset($user->url);
+    unset($user->currentlogin);
+    unset($user->description);
+    unset($user->descriptionformat);
+}
+
 /**
  * Function to be run periodically according to the moodle cron
  * Finds all posts that have yet to be mailed out, and mails them
@@ -397,8 +424,11 @@ function forum_cron() {
 
     $site = get_site();
 
-    // all users that are subscribed to any post that needs sending
+    // All users that are subscribed to any post that needs sending,
+    // please increase $CFG->extramemorylimit on large sites that
+    // send notifications to a large number of users.
     $users = array();
+    $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
 
     // status arrays
     $mailcount  = array();
@@ -479,13 +509,23 @@ function forum_cron() {
                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
                     foreach ($subusers as $postuser) {
-                        unset($postuser->description); // not necessary
                         // this user is subscribed to this forum
                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
-                        // this user is a user we have to process later
-                        $users[$postuser->id] = $postuser;
+                        $userscount++;
+                        if ($userscount > FORUM_CRON_USER_CACHE) {
+                            // Store minimal user info.
+                            $minuser = new stdClass();
+                            $minuser->id = $postuser->id;
+                            $users[$postuser->id] = $minuser;
+                        } else {
+                            // Cache full user record.
+                            forum_cron_minimise_user_record($postuser);
+                            $users[$postuser->id] = $postuser;
+                        }
                     }
-                    unset($subusers); // release memory
+                    // Release memory.
+                    unset($subusers);
+                    unset($postuser);
                 }
             }
 
@@ -503,16 +543,23 @@ function forum_cron() {
 
             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
 
-            // set this so that the capabilities are cached, and environment matches receiving user
-            cron_setup_user($userto);
-
             mtrace('Processing user '.$userto->id);
 
-   &nb