Merge branch 'MDL-22504_drag_and_drop_upload_integration' of git://github.com/davosmi...
authorDan Poltawski <dan@moodle.com>
Mon, 21 May 2012 09:22:13 +0000 (17:22 +0800)
committerDan Poltawski <dan@moodle.com>
Mon, 21 May 2012 09:22:13 +0000 (17:22 +0800)
130 files changed:
admin/webservice/testclient_forms.php
backup/moodle2/restore_stepslib.php
backup/util/helper/convert_helper.class.php
course/externallib.php
course/format/renderer.php
course/lib.php
course/renderer.php
course/style.css [new file with mode: 0644]
course/view.php
course/yui/modchooser/modchooser.js [new file with mode: 0644]
course/yui/toolboxes/toolboxes.js
index.php
install/lang/ar/admin.php
install/lang/et/admin.php
install/lang/rm_surs/admin.php [new file with mode: 0644]
install/lang/sr_cr/install.php
install/lang/sr_lt/install.php
install/lang/zh_cn/install.php
iplookup/lib.php
iplookup/tests/geoip_test.php [new file with mode: 0644]
iplookup/tests/geoplugin_test.php [new file with mode: 0644]
lang/en/completion.php
lang/en/error.php
lang/en/moodle.php
lang/en/webservice.php
lib/db/services.php
lib/navigationlib.php
lib/pluginlib.php
lib/setuplib.php
lib/yui/chooserdialogue/chooserdialogue.js [new file with mode: 0644]
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/upgradelib.php
mod/assignment/lang/en/assignment.php
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/chat/lang/en/chat.php
mod/choice/lang/en/choice.php
mod/data/lang/en/data.php
mod/data/lib.php
mod/data/locallib.php
mod/data/view.php
mod/feedback/lang/en/feedback.php
mod/folder/lang/en/folder.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/locallib.php
mod/glossary/lang/en/glossary.php
mod/glossary/lib.php
mod/glossary/locallib.php
mod/imscp/lang/en/imscp.php
mod/label/lang/en/label.php
mod/lesson/lang/en/lesson.php
mod/lti/lang/en/lti.php
mod/page/lang/en/page.php
mod/quiz/db/upgrade.php
mod/quiz/lang/en/quiz.php
mod/quiz/lib.php
mod/resource/lang/en/resource.php
mod/scorm/lang/en/scorm.php
mod/survey/lang/en/survey.php
mod/url/lang/en/url.php
mod/wiki/lang/en/wiki.php
mod/workshop/lang/en/workshop.php
phpunit.xml.dist
question/type/calculated/questiontype.php
report/completion/index.php
report/progress/index.php
theme/base/style/core.css
version.php

index c51c74a..04cc360 100644 (file)
@@ -727,3 +727,255 @@ class moodle_group_delete_groupmembers_form extends moodleform {
         return $params;
     }
 }
+
+/**
+ * Form class for create_categories() web service function test.
+ *
+ * @package   core_webservice
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright 2012 Fabio Souto
+ */
+class core_course_create_categories_form extends moodleform {
+    /**
+     * The form definition.
+     */
+    public function definition() {
+        global $CFG;
+
+        $mform = $this->_form;
+
+        $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
+
+        // Note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters.
+        $data = $this->_customdata;
+        if ($data['authmethod'] == 'simple') {
+            $mform->addElement('text', 'wsusername', 'wsusername');
+            $mform->addElement('text', 'wspassword', 'wspassword');
+        } else if ($data['authmethod'] == 'token') {
+            $mform->addElement('text', 'token', 'token');
+        }
+
+        $mform->addElement('hidden', 'authmethod', $data['authmethod']);
+        $mform->setType('authmethod', PARAM_SAFEDIR);
+        $mform->addElement('text', 'name[0]', 'name[0]');
+        $mform->addElement('text', 'parent[0]', 'parent[0]');
+        $mform->addElement('text', 'idnumber[0]', 'idnumber[0]');
+        $mform->addElement('text', 'description[0]', 'description[0]');
+        $mform->addElement('text', 'name[1]', 'name[1]');
+        $mform->addElement('text', 'parent[1]', 'parent[1]');
+        $mform->addElement('text', 'idnumber[1]', 'idnumber[1]');
+        $mform->addElement('text', 'description[1]', 'description[1]');
+
+        $mform->addElement('hidden', 'function');
+        $mform->setType('function', PARAM_SAFEDIR);
+
+        $mform->addElement('hidden', 'protocol');
+        $mform->setType('protocol', PARAM_SAFEDIR);
+
+        $this->add_action_buttons(true, get_string('execute', 'webservice'));
+    }
+
+    /**
+     * Get the parameters that the user submitted using the form.
+     * @return array|null
+     */
+    public function get_params() {
+        if (!$data = $this->get_data()) {
+            return null;
+        }
+        // Remove unused from form data.
+        unset($data->submitbutton);
+        unset($data->protocol);
+        unset($data->function);
+        unset($data->wsusername);
+        unset($data->wspassword);
+        unset($data->token);
+        unset($data->authmethod);
+
+        $params = array();
+        $params['categories'] = array();
+        for ($i=0; $i<10; $i++) {
+            if (empty($data->name[$i]) or empty($data->parent[$i])) {
+                continue;
+            }
+            $params['categories'][] = array('name'=>$data->name[$i], 'parent'=>$data->parent[$i],
+                                            'idnumber'=>$data->idnumber[$i], 'description'=>$data->description[$i]);
+        }
+        return $params;
+    }
+}
+
+/**
+ * Form class for delete_categories() web service function test.
+ *
+ * @package   core_webservice
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright 2012 Fabio Souto
+ */
+class core_course_delete_categories_form extends moodleform {
+    /**
+     * The form definition.
+     */
+    public function definition() {
+        global $CFG;
+
+        $mform = $this->_form;
+
+        $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
+
+        // Note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters.
+        $data = $this->_customdata;
+        if ($data['authmethod'] == 'simple') {
+            $mform->addElement('text', 'wsusername', 'wsusername');
+            $mform->addElement('text', 'wspassword', 'wspassword');
+        } else if ($data['authmethod'] == 'token') {
+            $mform->addElement('text', 'token', 'token');
+        }
+
+        $mform->addElement('hidden', 'authmethod', $data['authmethod']);
+        $mform->setType('authmethod', PARAM_SAFEDIR);
+        $mform->addElement('text', 'id[0]', 'id[0]');
+        $mform->addElement('text', 'newparent[0]', 'newparent[0]');
+        $mform->addElement('text', 'recursive[0]', 'recursive[0]');
+        $mform->addElement('text', 'id[1]', 'id[1]');
+        $mform->addElement('text', 'newparent[1]', 'newparent[1]');
+        $mform->addElement('text', 'recursive[1]', 'recursive[1]');
+
+        $mform->addElement('hidden', 'function');
+        $mform->setType('function', PARAM_SAFEDIR);
+
+        $mform->addElement('hidden', 'protocol');
+        $mform->setType('protocol', PARAM_SAFEDIR);
+
+        $this->add_action_buttons(true, get_string('execute', 'webservice'));
+    }
+
+    /**
+     * Get the parameters that the user submitted using the form.
+     * @return array|null
+     */
+    public function get_params() {
+        if (!$data = $this->get_data()) {
+            return null;
+        }
+        // Remove unused from form data.
+        unset($data->submitbutton);
+        unset($data->protocol);
+        unset($data->function);
+        unset($data->wsusername);
+        unset($data->wspassword);
+        unset($data->token);
+        unset($data->authmethod);
+
+        $params = array();
+        $params['categories'] = array();
+        for ($i=0; $i<10; $i++) {
+            if (empty($data->id[$i])) {
+                continue;
+            }
+            $attrs = array();
+            $attrs['id'] = $data->id[$i];
+            if (!empty($data->newparent[$i])) {
+                $attrs['newparent'] = $data->newparent[$i];
+            }
+            if (!empty($data->recursive[$i])) {
+                $attrs['recursive'] = $data->recursive[$i];
+            }
+            $params['categories'][] = $attrs;
+        }
+        return $params;
+    }
+}
+
+/**
+ * Form class for create_categories() web service function test.
+ *
+ * @package   core_webservice
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright 2012 Fabio Souto
+ */
+class core_course_update_categories_form extends moodleform {
+    /**
+     * The form definition.
+     */
+    public function definition() {
+        global $CFG;
+
+        $mform = $this->_form;
+
+        $mform->addElement('header', 'wstestclienthdr', get_string('testclient', 'webservice'));
+
+        // Note: these values are intentionally PARAM_RAW - we want users to test any rubbish as parameters.
+        $data = $this->_customdata;
+        if ($data['authmethod'] == 'simple') {
+            $mform->addElement('text', 'wsusername', 'wsusername');
+            $mform->addElement('text', 'wspassword', 'wspassword');
+        } else if ($data['authmethod'] == 'token') {
+            $mform->addElement('text', 'token', 'token');
+        }
+
+        $mform->addElement('hidden', 'authmethod', $data['authmethod']);
+        $mform->setType('authmethod', PARAM_SAFEDIR);
+        $mform->addElement('text', 'id[0]', 'id[0]');
+        $mform->addElement('text', 'name[0]', 'name[0]');
+        $mform->addElement('text', 'parent[0]', 'parent[0]');
+        $mform->addElement('text', 'idnumber[0]', 'idnumber[0]');
+        $mform->addElement('text', 'description[0]', 'description[0]');
+        $mform->addElement('text', 'id[1]', 'id[1]');
+        $mform->addElement('text', 'name[1]', 'name[1]');
+        $mform->addElement('text', 'parent[1]', 'parent[1]');
+        $mform->addElement('text', 'idnumber[1]', 'idnumber[1]');
+        $mform->addElement('text', 'description[1]', 'description[1]');
+
+        $mform->addElement('hidden', 'function');
+        $mform->setType('function', PARAM_SAFEDIR);
+
+        $mform->addElement('hidden', 'protocol');
+        $mform->setType('protocol', PARAM_SAFEDIR);
+
+        $this->add_action_buttons(true, get_string('execute', 'webservice'));
+    }
+
+    /**
+     * Get the parameters that the user submitted using the form.
+     * @return array|null
+     */
+    public function get_params() {
+        if (!$data = $this->get_data()) {
+            return null;
+        }
+        // Remove unused from form data.
+        unset($data->submitbutton);
+        unset($data->protocol);
+        unset($data->function);
+        unset($data->wsusername);
+        unset($data->wspassword);
+        unset($data->token);
+        unset($data->authmethod);
+
+        $params = array();
+        $params['categories'] = array();
+        for ($i=0; $i<10; $i++) {
+
+            if (empty($data->id[$i])) {
+                continue;
+            }
+            $attrs = array();
+            $attrs['id'] = $data->id[$i];
+            if (!empty($data->name[$i])) {
+                $attrs['name'] = $data->name[$i];
+            }
+            if (!empty($data->parent[$i])) {
+                $attrs['parent'] = $data->parent[$i];
+            }
+            if (!empty($data->idnumber[$i])) {
+                $attrs['idnumber'] = $data->idnumber[$i];
+            }
+            if (!empty($data->description[$i])) {
+                $attrs['description'] = $data->description[$i];
+            }
+            $params['categories'][] = $attrs;
+        }
+        return $params;
+    }
+}
\ No newline at end of file
index 5fcca63..fda860c 100644 (file)
@@ -719,7 +719,7 @@ class restore_groups_structure_step extends restore_structure_step {
         // Only allow the idnumber to be set if the user has permission and the idnumber is not already in use by
         // another a group in the same course
         $context = context_course::instance($data->courseid);
-        if (has_capability('moodle/course:changeidnumber', $context, $this->task->get_userid())) {
+        if (isset($data->idnumber) and has_capability('moodle/course:changeidnumber', $context, $this->task->get_userid())) {
             if (groups_get_group_by_idnumber($data->courseid, $data->idnumber)) {
                 unset($data->idnumber);
             }
@@ -779,7 +779,7 @@ class restore_groups_structure_step extends restore_structure_step {
         // Only allow the idnumber to be set if the user has permission and the idnumber is not already in use by
         // another a grouping in the same course
         $context = context_course::instance($data->courseid);
-        if (has_capability('moodle/course:changeidnumber', $context, $this->task->get_userid())) {
+        if (isset($data->idnumber) and has_capability('moodle/course:changeidnumber', $context, $this->task->get_userid())) {
             if (groups_get_grouping_by_idnumber($data->courseid, $data->idnumber)) {
                 unset($data->idnumber);
             }
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 66ec3de..98a68d6 100644 (file)
@@ -252,7 +252,7 @@ class core_course_external extends external_api {
      * Returns description of method parameters
      *
      * @return external_function_parameters
-     * @since Moodle 2.2
+     * @since Moodle 2.3
      */
     public static function get_courses_parameters() {
         return new external_function_parameters(
@@ -501,7 +501,6 @@ class core_course_external extends external_api {
         require_once($CFG->dirroot . "/course/lib.php");
         require_once($CFG->libdir . '/completionlib.php');
 
-
         $params = self::validate_parameters(self::create_courses_parameters(),
                         array('courses' => $courses));
 
@@ -595,7 +594,9 @@ class core_course_external extends external_api {
 
     /**
      * Returns description of method parameters
+     *
      * @return external_function_parameters
+     * @since Moodle 2.2
      */
     public static function delete_courses_parameters() {
         return new external_function_parameters(
@@ -607,7 +608,9 @@ class core_course_external extends external_api {
 
     /**
      * Delete courses
+     *
      * @param array $courseids A list of course ids
+     * @since Moodle 2.2
      */
     public static function delete_courses($courseids) {
         global $CFG, $DB;
@@ -627,7 +630,8 @@ class core_course_external extends external_api {
 
             // Check if the current user has enought permissions.
             if (!can_delete_course($courseid)) {
-                throw new moodle_exception('cannotdeletecategorycourse', 'error', '', format_string($course->fullname)." (id: $courseid)");
+                throw new moodle_exception('cannotdeletecategorycourse', 'error',
+                    '', format_string($course->fullname)." (id: $courseid)");
             }
 
             delete_course($course, false);
@@ -640,7 +644,9 @@ class core_course_external extends external_api {
 
     /**
      * Returns description of method result value
+     *
      * @return external_description
+     * @since Moodle 2.2
      */
     public static function delete_courses_returns() {
         return null;
@@ -880,6 +886,603 @@ class core_course_external extends external_api {
         );
     }
 
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.3
+     */
+    public static function get_categories_parameters() {
+        return new external_function_parameters(
+            array(
+                'criteria' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'key' => new external_value(PARAM_ALPHA,
+                                         'The category column to search, expected keys (value format) are:'.
+                                         '"id" (int) the category id,'.
+                                         '"name" (string) the category name,'.
+                                         '"parent" (int) the parent category id,'.
+                                         '"idnumber" (string) category idnumber'.
+                                         ' - user must have \'moodle/category:manage\' to search on idnumber,'.
+                                         '"visible" (int) whether the category is visible or not'.
+                                         ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
+                                         '"theme" (string) category theme'.
+                                         ' - user must have \'moodle/category:manage\' to search on theme'),
+                            'value' => new external_value(PARAM_RAW, 'the value to match')
+                        )
+                    ), VALUE_DEFAULT, array()
+                ),
+                'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
+                                          (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
+            )
+        );
+    }
+
+    /**
+     * Get categories
+     *
+     * @param array $criteria Criteria to match the results
+     * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default)
+     * @return array list of categories
+     * @since Moodle 2.3
+     */
+    public static function get_categories($criteria = array(), $addsubcategories = true) {
+        global $CFG, $DB;
+        require_once($CFG->dirroot . "/course/lib.php");
+
+        // Validate parameters.
+        $params = self::validate_parameters(self::get_categories_parameters(),
+                array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
+
+        // Retrieve the categories.
+        $categories = array();
+        if (!empty($params['criteria'])) {
+
+            $conditions = array();
+            $wheres = array();
+            foreach ($params['criteria'] as $crit) {
+                $key = trim($crit['key']);
+
+                // Trying to avoid duplicate keys.
+                if (!isset($conditions[$key])) {
+
+                    $context = context_system::instance();
+                    $value = null;
+                    switch ($key) {
+                        case 'id':
+                            $value = clean_param($crit['value'], PARAM_INT);
+                            break;
+
+                        case 'idnumber':
+                            if (has_capability('moodle/category:manage', $context)) {
+                                $value = clean_param($crit['value'], PARAM_RAW);
+                            } else {
+                                // We must throw an exception.
+                                // Otherwise the dev client would think no idnumber exists.
+                                throw new moodle_exception('criteriaerror',
+                                        'webservice', '', null,
+                                        'You don\'t have the permissions to search on the "idnumber" field.');
+                            }
+                            break;
+
+                        case 'name':
+                            $value = clean_param($crit['value'], PARAM_TEXT);
+                            break;
+
+                        case 'parent':
+                            $value = clean_param($crit['value'], PARAM_INT);
+                            break;
+
+                        case 'visible':
+                            if (has_capability('moodle/category:manage', $context)
+                                or has_capability('moodle/category:viewhiddencategories',
+                                        context_system::instance())) {
+                                $value = clean_param($crit['value'], PARAM_INT);
+                            } else {
+                                throw new moodle_exception('criteriaerror',
+                                        'webservice', '', null,
+                                        'You don\'t have the permissions to search on the "visible" field.');
+                            }
+                            break;
+
+                        case 'theme':
+                            if (has_capability('moodle/category:manage', $context)) {
+                                $value = clean_param($crit['value'], PARAM_THEME);
+                            } else {
+                                throw new moodle_exception('criteriaerror',
+                                        'webservice', '', null,
+                                        'You don\'t have the permissions to search on the "theme" field.');
+                            }
+                            break;
+
+                        default:
+                            throw new moodle_exception('criteriaerror',
+                                    'webservice', '', null,
+                                    'You can not search on this criteria: ' . $key);
+                    }
+
+                    if (isset($value)) {
+                        $conditions[$key] = $crit['value'];
+                        $wheres[] = $key . " = :" . $key;
+                    }
+                }
+            }
+
+            if (!empty($wheres)) {
+                $wheres = implode(" AND ", $wheres);
+
+                $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
+
+                // Retrieve its sub subcategories (all levels).
+                if ($categories and !empty($params['addsubcategories'])) {
+                    $newcategories = array();
+
+                    foreach ($categories as $category) {
+                        $sqllike = $DB->sql_like('path', ':path');
+                        $sqlparams = array('path' => $category->path.'/%'); // It will NOT include the specified category.
+                        $subcategories = $DB->get_records_select('course_categories', $sqllike, $sqlparams);
+                        $newcategories = $newcategories + $subcategories;   // Both arrays have integer as keys.
+                    }
+                    $categories = $categories + $newcategories;
+                }
+            }
+
+        } else {
+            // Retrieve all categories in the database.
+            $categories = $DB->get_records('course_categories');
+        }
+
+        // The not returned categories. key => category id, value => reason of exclusion.
+        $excludedcats = array();
+
+        // The returned categories.
+        $categoriesinfo = array();
+
+        // We need to sort the categories by path.
+        // The parent cats need to be checked by the algo first.
+        usort($categories, "core_course_external::compare_categories_by_path");
+
+        foreach ($categories as $category) {
+
+            // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return).
+            $parents = explode('/', $category->path);
+            unset($parents[0]); // First key is always empty because path start with / => /1/2/4.
+            foreach ($parents as $parentid) {
+                // Note: when the parent exclusion was due to the context,
+                // the sub category could still be returned.
+                if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') {
+                    $excludedcats[$category->id] = 'parent';
+                }
+            }
+
+            // Check category depth is <= maxdepth (do not check for user who can manage categories).
+            if ((!empty($CFG->maxcategorydepth) && count($parents) > $CFG->maxcategorydepth)
+                    and !has_capability('moodle/category:manage', $context)) {
+                $excludedcats[$category->id] = 'depth';
+            }
+
+            // Check the user can use the category context.
+            $context = context_coursecat::instance($category->id);
+            try {
+                self::validate_context($context);
+            } catch (Exception $e) {
+                $excludedcats[$category->id] = 'context';
+
+                // If it was the requested category then throw an exception.
+                if (isset($params['categoryid']) && $category->id == $params['categoryid']) {
+                    $exceptionparam = new stdClass();
+                    $exceptionparam->message = $e->getMessage();
+                    $exceptionparam->catid = $category->id;
+                    throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
+                }
+            }
+
+            // Return the category information.
+            if (!isset($excludedcats[$category->id])) {
+
+                // Final check to see if the category is visible to the user.
+                if ($category->visible
+                        or has_capability('moodle/category:viewhiddencategories', context_system::instance())
+                        or has_capability('moodle/category:manage', $context)) {
+
+                    $categoryinfo = array();
+                    $categoryinfo['id'] = $category->id;
+                    $categoryinfo['name'] = $category->name;
+                    $categoryinfo['description'] = file_rewrite_pluginfile_urls($category->description,
+                            'webservice/pluginfile.php', $context->id, 'coursecat', 'description', null);
+                    $options = new stdClass;
+                    $options->noclean = true;
+                    $options->para = false;
+                    $categoryinfo['description'] = format_text($categoryinfo['description'],
+                            $category->descriptionformat, $options);
+                    $categoryinfo['parent'] = $category->parent;
+                    $categoryinfo['sortorder'] = $category->sortorder;
+                    $categoryinfo['coursecount'] = $category->coursecount;
+                    $categoryinfo['depth'] = $category->depth;
+                    $categoryinfo['path'] = $category->path;
+
+                    // Some fields only returned for admin.
+                    if (has_capability('moodle/category:manage', $context)) {
+                        $categoryinfo['idnumber'] = $category->idnumber;
+                        $categoryinfo['visible'] = $category->visible;
+                        $categoryinfo['visibleold'] = $category->visibleold;
+                        $categoryinfo['timemodified'] = $category->timemodified;
+                        $categoryinfo['theme'] = $category->theme;
+                    }
+
+                    $categoriesinfo[] = $categoryinfo;
+                } else {
+                    $excludedcats[$category->id] = 'visibility';
+                }
+            }
+        }
+
+        // Sorting the resulting array so it looks a bit better for the client developer.
+        usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder");
+
+        return $categoriesinfo;
+    }
+
+    /**
+     * Sort categories array by path
+     * private function: only used by get_categories
+     *
+     * @param array $category1
+     * @param array $category2
+     * @return int result of strcmp
+     * @since Moodle 2.3
+     */
+    private static function compare_categories_by_path($category1, $category2) {
+        return strcmp($category1->path, $category2->path);
+    }
+
+    /**
+     * Sort categories array by sortorder
+     * private function: only used by get_categories
+     *
+     * @param array $category1
+     * @param array $category2
+     * @return int result of strcmp
+     * @since Moodle 2.3
+     */
+    private static function compare_categories_by_sortorder($category1, $category2) {
+        return strcmp($category1['sortorder'], $category2['sortorder']);
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     * @since Moodle 2.3
+     */
+    public static function get_categories_returns() {
+        return new external_multiple_structure(
+            new external_single_structure(
+                array(
+                    'id' => new external_value(PARAM_INT, 'category id'),
+                    'name' => new external_value(PARAM_TEXT, 'category name'),
+                    'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
+                    'description' => new external_value(PARAM_RAW, 'category description'),
+                    'parent' => new external_value(PARAM_INT, 'parent category id'),
+                    'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
+                    'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
+                    'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
+                    'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
+                    'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL),
+                    'depth' => new external_value(PARAM_INT, 'category depth'),
+                    'path' => new external_value(PARAM_TEXT, 'category path'),
+                    'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL),
+                ), 'List of categories'
+            )
+        );
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.3
+     */
+    public static function create_categories_parameters() {
+        return new external_function_parameters(
+            array(
+                'categories' => new external_multiple_structure(
+                        new external_single_structure(
+                            array(
+                                'name' => new external_value(PARAM_TEXT, 'new category name'),
+                                'parent' => new external_value(PARAM_INT,
+                                        'the parent category id inside which the new category will be created'),
+                                'idnumber' => new external_value(PARAM_RAW,
+                                        'the new category idnumber', VALUE_OPTIONAL),
+                                'description' => new external_value(PARAM_RAW,
+                                        'the new category description', VALUE_OPTIONAL),
+                                'theme' => new external_value(PARAM_THEME,
+                                        'the new category theme. This option must be enabled on moodle',
+                                        VALUE_OPTIONAL),
+                        )
+                    )
+                )
+            )
+        );
+    }
+
+    /**
+     * Create categories
+     *
+     * @param array $categories - see create_categories_parameters() for the array structure
+     * @return array - see create_categories_returns() for the array structure
+     * @since Moodle 2.3
+     */
+    public static function create_categories($categories) {
+        global $CFG, $DB;
+        require_once($CFG->dirroot . "/course/lib.php");
+
+        $params = self::validate_parameters(self::create_categories_parameters(),
+                        array('categories' => $categories));
+
+        $transaction = $DB->start_delegated_transaction();
+
+        $createdcategories = array();
+        foreach ($params['categories'] as $category) {
+            if ($category['parent']) {
+                if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) {
+                    throw new moodle_exception('unknowcategory');
+                }
+                $context = context_coursecat::instance($category['parent']);
+            } else {
+                $context = context_system::instance();
+            }
+            self::validate_context($context);
+            require_capability('moodle/category:manage', $context);
+
+            // Check id number.
+            if (!empty($category['idnumber'])) { // Same as in course/editcategory_form.php .
+                if (textlib::strlen($category['idnumber'])>100) {
+                    throw new moodle_exception('idnumbertoolong');
+                }
+                if ($existing = $DB->get_record('course_categories', array('idnumber' => $category['idnumber']))) {
+                    if ($existing->id) {
+                        throw new moodle_exception('idnumbertaken');
+                    }
+                }
+            }
+            // Check name.
+            if (textlib::strlen($category['name'])>255) {
+                throw new moodle_exception('categorytoolong');
+            }
+
+            $newcategory = new stdClass();
+            $newcategory->name = $category['name'];
+            $newcategory->parent = $category['parent'];
+            $newcategory->idnumber = $category['idnumber'];
+            $newcategory->sortorder = 999; // Same as in the course/editcategory.php .
+            // Format the description.
+            if (!empty($category['description'])) {
+                $newcategory->description = $category['description'];
+            }
+            $newcategory->descriptionformat = FORMAT_HTML;
+            if (isset($category['theme']) and !empty($CFG->allowcategorythemes)) {
+                $newcategory->theme = $category['theme'];
+            }
+
+            $newcategory = create_course_category($newcategory);
+            // Populate special fields.
+            fix_course_sortorder();
+
+            $createdcategories[] = array('id' => $newcategory->id, 'name' => $newcategory->name);
+        }
+
+        $transaction->allow_commit();
+
+        return $createdcategories;
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.3
+     */
+    public static function create_categories_returns() {
+        return new external_multiple_structure(
+            new external_single_structure(
+                array(
+                    'id' => new external_value(PARAM_INT, 'new category id'),
+                    'name' => new external_value(PARAM_TEXT, 'new category name'),
+                )
+            )
+        );
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.3
+     */
+    public static function update_categories_parameters() {
+        return new external_function_parameters(
+            array(
+                'categories' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'id'       => new external_value(PARAM_INT, 'course id'),
+                            'name' => new external_value(PARAM_TEXT, 'category name', VALUE_OPTIONAL),
+                            'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
+                            'parent' => new external_value(PARAM_INT, 'parent category id', VALUE_OPTIONAL),
+                            'description' => new external_value(PARAM_RAW, 'category description', VALUE_OPTIONAL),
+                            'theme' => new external_value(PARAM_THEME,
+                                    'the category theme. This option must be enabled on moodle', VALUE_OPTIONAL),
+                        )
+                    )
+                )
+            )
+        );
+    }
+
+    /**
+     * Update categories
+     *
+     * @param array $categories The list of categories to update
+     * @return null
+     * @since Moodle 2.3
+     */
+    public static function update_categories($categories) {
+        global $CFG, $DB;
+        require_once($CFG->dirroot . "/course/lib.php");
+
+        // Validate parameters.
+        $params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories));
+
+        $transaction = $DB->start_delegated_transaction();
+
+        foreach ($params['categories'] as $cat) {
+            if (!$category = $DB->get_record('course_categories', array('id' => $cat['id']))) {
+                throw new moodle_exception('unknowcategory');
+            }
+
+            $categorycontext = context_coursecat::instance($cat['id']);
+            self::validate_context($categorycontext);
+            require_capability('moodle/category:manage', $categorycontext);
+
+            if (!empty($cat['name'])) {
+                if (textlib::strlen($cat['name'])>255) {
+                     throw new moodle_exception('categorytoolong');
+                }
+                $category->name = $cat['name'];
+            }
+            if (!empty($cat['idnumber'])) {
+                if (textlib::strlen($cat['idnumber'])>100) {
+                    throw new moodle_exception('idnumbertoolong');
+                }
+                $category->idnumber = $cat['idnumber'];
+            }
+            if (!empty($cat['description'])) {
+                $category->description = $cat['description'];
+                $category->descriptionformat = FORMAT_HTML;
+            }
+            if (!empty($cat['theme'])) {
+                $category->theme = $cat['theme'];
+            }
+            if (!empty($cat['parent']) && ($category->parent != $cat['parent'])) {
+                // First check if parent exists.
+                if (!$parent_cat = $DB->get_record('course_categories', array('id' => $cat['parent']))) {
+                    throw new moodle_exception('unknowcategory');
+                }
+                // Then check if we have capability.
+                self::validate_context(get_category_or_system_context((int)$cat['parent']));
+                require_capability('moodle/category:manage', get_category_or_system_context((int)$cat['parent']));
+                // Finally move the category.
+                move_category($category, $parent_cat);
+                $category->parent = $cat['parent'];
+            }
+            $DB->update_record('course_categories', $category);
+        }
+
+        $transaction->allow_commit();
+    }
+
+    /**
+     * Returns description of method result value
+     *
+     * @return external_description
+     * @since Moodle 2.3
+     */
+    public static function update_categories_returns() {
+        return null;
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.3
+     */
+    public static function delete_categories_parameters() {
+        return new external_function_parameters(
+            array(
+                'categories' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'id' => new external_value(PARAM_INT, 'category id to delete'),
+                            'newparent' => new external_value(PARAM_INT,
+                                'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
+                            'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
+                                category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
+                        )
+                    )
+                )
+            )
+        );
+    }
+
+    /**
+     * Delete categories
+     *
+     * @param array $categories A list of category ids
+     * @return array
+     * @since Moodle 2.3
+     */
+    public static function delete_categories($categories) {
+        global $CFG, $DB;
+        require_once($CFG->dirroot . "/course/lib.php");
+
+        // Validate parameters.
+        $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
+
+        $transaction = $DB->start_delegated_transaction();
+
+        foreach ($params['categories'] as $category) {
+            if (!$deletecat = $DB->get_record('course_categories', array('id' => $category['id']))) {
+                throw new moodle_exception('unknowcategory');
+            }
+            $context = context_coursecat::instance($deletecat->id);
+            require_capability('moodle/category:manage', $context);
+            self::validate_context($context);
+            self::validate_context(get_category_or_system_context($deletecat->parent));
+
+            if ($category['recursive']) {
+                // If recursive was specified, then we recursively delete the category's contents.
+                category_delete_full($deletecat, false);
+            } else {
+                // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
+                // If the parent is the root, moving is not supported (because a course must always be inside a category).
+                // We must move to an existing category.
+                if (!empty($category['newparent'])) {
+                    if (!$DB->record_exists('course_categories', array('id' => $category['newparent']))) {
+                        throw new moodle_exception('unknowcategory');
+                    }
+                    $newparent = $category['newparent'];
+                } else {
+                    $newparent = $deletecat->parent;
+                }
+
+                // This operation is not allowed. We must move contents to an existing category.
+                if ($newparent == 0) {
+                    throw new moodle_exception('movecatcontentstoroot');
+                }
+
+                $parentcontext = get_category_or_system_context($newparent);
+                require_capability('moodle/category:manage', $parentcontext);
+                self::validate_context($parentcontext);
+                category_delete_move($deletecat, $newparent, false);
+            }
+        }
+
+        $transaction->allow_commit();
+    }
+
+    /**
+     * Returns description of method parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 2.3
+     */
+    public static function delete_categories_returns() {
+        return null;
+    }
+
 }
 
 /**
index 25e0b35..45977d4 100644 (file)
@@ -291,7 +291,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
      */
     protected function section_availability_message($section) {
         $o = '';
-        if (!$section->uservisible) {
+        if (!$section->uservisible || $section->availableinfo) {
             $o .= html_writer::start_tag('div', array('class' => 'availabilityinfo'));
             if (!empty($section->availableinfo)) {
                 $o .= $section->availableinfo;
@@ -547,6 +547,11 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             } else {
                 // This will create a course section if it doesn't exist..
                 $thissection = get_course_section($section, $course->id);
+
+                // The returned section is only a bare database object rather than
+                // a section_info object - we will need at least the uservisible
+                // field in it.
+                $thissection->uservisible = true;
             }
             // Show the section if the user is permitted to access it, OR if it's not available
             // but showavailability is turned on
index 2c6c0e5..9a64052 100644 (file)
@@ -1827,10 +1827,10 @@ function print_section_add_menus($course, $section, $modnames, $vertical=false,
     $straddactivity = get_string('addactivity');
     $straddresource = get_string('addresource');
 
-    $output  = '<div class="section_add_menus">';
+    $output = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' . $section));
 
     if (!$vertical) {
-        $output .= '<div class="horizontal">';
+        $output .= html_writer::start_tag('div', array('class' => 'horizontal'));
     }
 
     if (!empty($resources)) {
@@ -1846,11 +1846,34 @@ function print_section_add_menus($course, $section, $modnames, $vertical=false,
     }
 
     if (!$vertical) {
-        $output .= '</div>';
+        $output .= html_writer::end_tag('div');
+    }
+
+    $output .= html_writer::end_tag('div');
+
+    if (course_ajax_enabled($course)) {
+        $straddeither = get_string('addresourceoractivity');
+        // The module chooser link
+        $modchooser = html_writer::start_tag('div', array('class' => 'mdl-right'));
+        $modchooser.= html_writer::start_tag('div', array('class' => 'section-modchooser'));
+        $icon = $OUTPUT->pix_icon('t/add', $straddeither);
+        $span = html_writer::tag('span', $straddeither, array('class' => 'section-modchooser-text'));
+        $modchooser.= html_writer::link('#', $icon.$span, array('class' => 'section-modchooser-link'));
+        $modchooser.= html_writer::end_tag('div');
+        $modchooser.= html_writer::end_tag('div');
+
+        // Wrap the normal output in a noscript div
+        $usemodchooser = get_user_preferences('usemodchooser', 1);
+        if ($usemodchooser) {
+            $output = html_writer::tag('div', $output, array('class' => 'hiddenifjs addresourcedropdown'));
+            $modchooser = html_writer::tag('div', $modchooser, array('class' => 'visibleifjs addresourcemodchooser'));
+        } else {
+            $output = html_writer::tag('div', $output, array('class' => 'visibleifjs addresourcedropdown'));
+            $modchooser = html_writer::tag('div', $modchooser, array('class' => 'hiddenifjs addresourcemodchooser'));
+        }
+        $output = $modchooser . $output;
     }
 
-    $output .= '</div>';
-
     if ($return) {
         return $output;
     } else {
@@ -1936,8 +1959,14 @@ function get_module_metadata($course, $modnames) {
             $module->name = $modname;
             $module->link = $urlbase . $modname;
             $module->icon = $OUTPUT->pix_icon('icon', '', $module->name, array('class' => 'icon'));
-            if (get_string_manager()->string_exists('modulename_help', $modname)) {
+            $sm = get_string_manager();
+            if ($sm->string_exists('modulename_help', $modname)) {
                 $module->help = get_string('modulename_help', $modname);
+                if ($sm->string_exists('modulename_link', $modname)) {  // Link to further info in Moodle docs
+                    $link = get_string('modulename_link', $modname);
+                    $linktext = get_string('morehelp');
+                    $module->help .= html_writer::tag('div', $OUTPUT->doc_link($link, $linktext), array('class' => 'helpdoclink'));
+                }
             }
             $module->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
             $modlist[$course->id][$modname] = $module;
@@ -2752,8 +2781,10 @@ function get_course_section($section, $courseid) {
     $cw->summaryformat = FORMAT_HTML;
     $cw->sequence = "";
     $id = $DB->insert_record("course_sections", $cw);
+    rebuild_course_cache($courseid, true);
     return $DB->get_record("course_sections", array("id"=>$id));
 }
+
 /**
  * Given a full mod object with section and course already defined, adds this module to that section.
  *
@@ -3491,10 +3522,14 @@ function category_delete_move($category, $newparentid, $showfeedback=true) {
 
     if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC', 'id')) {
         if (!move_courses(array_keys($courses), $newparentid)) {
-            echo $OUTPUT->notification("Error moving courses");
+            if ($showfeedback) {
+                echo $OUTPUT->notification("Error moving courses");
+            }
             return false;
         }
-        echo $OUTPUT->notification(get_string('coursesmovedout', '', format_string($category->name)), 'notifysuccess');
+        if ($showfeedback) {
+            echo $OUTPUT->notification(get_string('coursesmovedout', '', format_string($category->name)), 'notifysuccess');
+        }
     }
 
     // move or delete cohorts in this context
@@ -3503,7 +3538,9 @@ function category_delete_move($category, $newparentid, $showfeedback=true) {
     // now delete anything that may depend on course category context
     grade_course_category_delete($category->id, $newparentid, $showfeedback);
     if (!question_delete_course_category($category, $newparentcat, $showfeedback)) {
-        echo $OUTPUT->notification(get_string('errordeletingquestionsfromcategory', 'question', $category), 'notifysuccess');
+        if ($showfeedback) {
+            echo $OUTPUT->notification(get_string('errordeletingquestionsfromcategory', 'question', $category), 'notifysuccess');
+        }
         return false;
     }
 
@@ -3513,8 +3550,9 @@ function category_delete_move($category, $newparentid, $showfeedback=true) {
 
     events_trigger('course_category_deleted', $category);
 
-    echo $OUTPUT->notification(get_string('coursecategorydeleted', '', format_string($category->name)), 'notifysuccess');
-
+    if ($showfeedback) {
+        echo $OUTPUT->notification(get_string('coursecategorydeleted', '', format_string($category->name)), 'notifysuccess');
+    }
     return true;
 }
 
@@ -4566,6 +4604,17 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules =
     // Load drag and drop upload AJAX.
     dndupload_add_to_course($course, $enabledmodules);
 
+    // Add the module chooser
+    $PAGE->requires->yui_module('moodle-course-modchooser',
+        'M.course.init_chooser',
+        array(array('courseid' => $course->id))
+    );
+    $PAGE->requires->strings_for_js(array(
+            'addresourceoractivity',
+            'modchooserenable',
+            'modchooserdisable',
+    ), 'moodle');
+
     return true;
 }
 
index 1e1d7ad..2831fed 100644 (file)
@@ -154,4 +154,155 @@ class core_course_renderer extends plugin_renderer_base {
         $content .= html_writer::end_tag('div');
         return $content;
     }
+
+    /**
+     * Build the HTML for the module chooser javascript popup
+     *
+     * @param array $modules A set of modules as returned form @see
+     * get_module_metadata
+     * @param object $course The course that will be displayed
+     * @return string The composed HTML for the module
+     */
+    public function course_modchooser($modules, $course) {
+        global $OUTPUT;
+
+        // Add the header
+        $header = html_writer::tag('div', get_string('addresourceoractivity', 'moodle'),
+                array('id' => 'choosertitle', 'class' => 'hd'));
+
+        $formcontent = html_writer::start_tag('form', array('action' => new moodle_url('/course/jumpto.php'),
+                'id' => 'chooserform', 'method' => 'post'));
+        $formcontent .= html_writer::start_tag('div', array('id' => 'typeformdiv'));
+        $formcontent .= html_writer::tag('input', '', array('type' => 'hidden', 'id' => 'course',
+                'name' => 'course', 'value' => $course->id));
+        $formcontent .= html_writer::tag('input', '',
+                array('type' => 'hidden', 'id' => 'jump', 'name' => 'jump', 'value' => ''));
+        $formcontent .= html_writer::tag('input', '', array('type' => 'hidden', 'name' => 'sesskey',
+                'value' => sesskey()));
+        $formcontent .= html_writer::end_tag('div');
+
+        // Put everything into one tag 'options'
+        $formcontent .= html_writer::start_tag('div', array('class' => 'options'));
+        $formcontent .= html_writer::tag('div', get_string('selectmoduletoviewhelp', 'moodle'),
+                array('class' => 'instruction'));
+        // Put all options into one tag 'alloptions' to allow us to handle scrolling
+        $formcontent .= html_writer::start_tag('div', array('class' => 'alloptions'));
+
+        // First display Resources
+        $resources = array_filter($modules,
+                create_function('$mod', 'return ($mod->archetype === MOD_CLASS_RESOURCE);'));
+        if (count($resources)) {
+            $formcontent .= $this->course_modchooser_title('resources');
+            $formcontent .= $this->course_modchooser_module_types($resources);
+        }
+
+        // Then activities
+        $activities = array_filter($modules,
+                create_function('$mod', 'return ($mod->archetype !== MOD_CLASS_RESOURCE);'));
+        if (count($activities)) {
+            $formcontent .= $this->course_modchooser_title('activities');
+            $formcontent .= $this->course_modchooser_module_types($activities);
+        }
+
+        $formcontent .= html_writer::end_tag('div'); // modoptions
+        $formcontent .= html_writer::end_tag('div'); // types
+
+        $formcontent .= html_writer::start_tag('div', array('class' => 'submitbuttons'));
+        $formcontent .= html_writer::tag('input', '',
+                array('type' => 'submit', 'name' => 'addcancel', 'id' => 'addcancel', 'value' => get_string('cancel')));
+        $formcontent .= html_writer::tag('input', '',
+                array('type' => 'submit', 'name' => 'submitbutton', 'id' => 'submitbutton', 'value' => get_string('add')));
+        $formcontent .= html_writer::end_tag('div');
+        $formcontent .= html_writer::end_tag('form');
+
+        // Wrap the whole form in a div
+        $formcontent = html_writer::tag('div', $formcontent, array('id' => 'chooseform'));
+
+        // Put all of the content together
+        $content = $formcontent;
+
+        $content = html_writer::tag('div', $content, array('id' => 'choosercontainer'));
+        return $header . html_writer::tag('div', $content, array('id' => 'chooserdialogue'));
+    }
+
+    /**
+     * Build the HTML for a specified set of modules
+     *
+     * @param array $modules A set of modules as used by the
+     * course_modchooser_module function
+     * @return string The composed HTML for the module
+     */
+    protected function course_modchooser_module_types($modules) {
+        $return = '';
+        foreach ($modules as $module) {
+            if (!isset($module->types)) {
+                $return .= $this->course_modchooser_module($module);
+            } else {
+                $return .= $this->course_modchooser_module($module, array('nonoption'));
+                foreach ($module->types as $type) {
+                    $return .= $this->course_modchooser_module($type, array('option', 'subtype'));
+                }
+            }
+        }
+        return $return;
+    }
+
+    /**
+     * Return the HTML for the specified module adding any required classes
+     *
+     * @param object $module An object containing the title, and link. An
+     * icon, and help text may optionally be specified. If the module
+     * contains subtypes in the types option, then these will also be
+     * displayed.
+     * @param array $classes Additional classes to add to the encompassing
+     * div element
+     * @return string The composed HTML for the module
+     */
+    protected function course_modchooser_module($module, $classes = array('option')) {
+        $output = '';
+        $output .= html_writer::start_tag('div', array('class' => implode(' ', $classes)));
+        $output .= html_writer::start_tag('label', array('for' => 'module_' . $module->name));
+        if (!isset($module->types)) {
+            $output .= html_writer::tag('input', '', array('type' => 'radio',
+                    'name' => 'jumplink', 'id' => 'module_' . $module->name, 'value' => $module->link));
+        }
+
+        $output .= html_writer::start_tag('span', array('class' => 'modicon'));
+        if (isset($module->icon)) {
+            // Add an icon if we have one
+            $output .= $module->icon;
+        }
+        $output .= html_writer::end_tag('span');
+
+        $output .= html_writer::tag('span', $module->title, array('class' => 'typename'));
+        if (!isset($module->help)) {
+            // Add help if found
+            $module->help = get_string('nohelpforactivityorresource', 'moodle');
+        }
+
+        // Format the help text using markdown with the following options
+        $options = new stdClass();
+        $options->trusted = false;
+        $options->noclean = false;
+        $options->smiley = false;
+        $options->filter = false;
+        $options->para = true;
+        $options->newlines = false;
+        $options->overflowdiv = false;
+        $module->help = format_text($module->help, FORMAT_MARKDOWN, $options);
+        $output .= html_writer::tag('span', $module->help, array('class' => 'typesummary'));
+        $output .= html_writer::end_tag('label');
+        $output .= html_writer::end_tag('div');
+
+        return $output;
+    }
+
+    protected function course_modchooser_title($title, $identifier = null) {
+        $module = new stdClass();
+        $module->name = $title;
+        $module->types = array();
+        $module->title = get_string($title, $identifier);
+        $module->help = '';
+        return $this->course_modchooser_module($module, array('moduletypetitle'));
+    }
 }
diff --git a/course/style.css b/course/style.css
new file mode 100644 (file)
index 0000000..e69de29
index 8bd732d..a7b1784 100644 (file)
@@ -18,6 +18,7 @@
     $move        = optional_param('move', 0, PARAM_INT);
     $marker      = optional_param('marker',-1 , PARAM_INT);
     $switchrole  = optional_param('switchrole',-1, PARAM_INT);
+    $modchooser  = optional_param('modchooser', -1, PARAM_BOOL);
 
     $params = array();
     if (!empty($name)) {
                 redirect($PAGE->url);
             }
         }
+        if (($modchooser == 1) && confirm_sesskey()) {
+            set_user_preference('usemodchooser', $modchooser);
+        } else if (($modchooser == 0) && confirm_sesskey()) {
+            set_user_preference('usemodchooser', $modchooser);
+        }
 
         if (has_capability('moodle/course:update', $context)) {
             if ($hide && confirm_sesskey()) {
 
     echo html_writer::end_tag('div');
 
-    // Include the command toolbox YUI module
-    include_course_ajax($course, $modnamesused, $modnames);
+    // Include course AJAX
+    if (include_course_ajax($course, $modnamesused)) {
+        // Add the module chooser
+        $renderer = $PAGE->get_renderer('core', 'course');
+        echo $renderer->course_modchooser(get_module_metadata($course, $modnames), $course);
+    }
 
     echo $OUTPUT->footer();
diff --git a/course/yui/modchooser/modchooser.js b/course/yui/modchooser/modchooser.js
new file mode 100644 (file)
index 0000000..5d95a02
--- /dev/null
@@ -0,0 +1,185 @@
+YUI.add('moodle-course-modchooser', function(Y) {
+    var CSS = {
+        PAGECONTENT : 'div#page-content',
+        SECTION : 'li.section',
+        SECTIONMODCHOOSER : 'a.section-modchooser-link',
+        SITEMENU : 'div.block_site_main_menu',
+        SITETOPIC : 'div.sitetopic'
+    };
+
+    var MODCHOOSERNAME = 'course-modchooser';
+
+    var MODCHOOSER = function() {
+        MODCHOOSER.superclass.constructor.apply(this, arguments);
+    }
+
+    Y.extend(MODCHOOSER, M.core.chooserdialogue, {
+        // The current section ID
+        sectionid : null,
+
+        // The hidden element holding the jump param
+        jumplink : null,
+
+        initializer : function(config) {
+            var dialogue = Y.one('#chooserdialogue');
+            var header = Y.one('#choosertitle');
+            var params = {
+                width: '540px'
+            };
+            this.setup_chooser_dialogue(dialogue, header, params);
+            this.overlay.get('boundingBox').addClass('modchooser');
+
+            this.jumplink = this.container.one('#jump');
+
+            // Initialize existing sections and register for dynamically created sections
+            this.setup_for_section();
+            M.course.coursebase.register_module(this);
+
+            // Catch the page toggle
+            Y.all('.block_settings #settingsnav .type_course .modchoosertoggle a').on('click', this.toggle_mod_chooser, this);
+
+            // Ensure that help links are opened in an appropriate popup
+            this.container.all('div.helpdoclink a').on('click', function(e) {
+                var anchor = e.target.ancestor('a', true);
+
+                var args = {
+                    'name'          : 'popup',
+                    'url'           : anchor.getAttribute('href'),
+                    'option'        : ''
+                };
+                var options = [
+                    'height=600',
+                    'width=800',
+                    'top=0',
+                    'left=0',
+                    'menubar=0',
+                    'location=0',
+                    'scrollbars',
+                    'resizable',
+                    'toolbar',
+                    'status',
+                    'directories=0',
+                    'fullscreen=0',
+                    'dependent'
+                ]
+                args.options = options.join(',');
+
+                // Note: openpopup is provided by lib/javascript-static.js
+                openpopup(e, args);
+            });
+        },
+        /**
+         * Update any section areas within the scope of the specified
+         * selector with AJAX equivalents
+         *
+         * @param baseselector The selector to limit scope to
+         * @return void
+         */
+        setup_for_section : function(baseselector) {
+            if (!baseselector) {
+                var baseselector = CSS.PAGECONTENT;
+            }
+
+            // Setup for site topics
+            Y.one(baseselector).all(CSS.SITETOPIC).each(function(section) {
+                // The site topic has a sectionid of 1
+                this._setup_for_section(section, 1);
+            }, this);
+
+            // Setup for the site menu
+            Y.one(baseselector).all(CSS.SITEMENU).each(function(section) {
+                // The site menu has a sectionid of 0
+                this._setup_for_section(section, 0);
+            }, this);
+
+            // Setup for standard course topics
+            Y.one(baseselector).all(CSS.SECTION).each(function(section) {
+                // Determine the sectionid for this section
+                var sectionid = section.get('id').replace('section-', '');
+                this._setup_for_section(section, sectionid);
+            }, this);
+        },
+        _setup_for_section : function(section, sectionid) {
+            var chooserlink = section.one(CSS.SECTIONMODCHOOSER);
+            chooserlink.on('click', this.display_mod_chooser, this, sectionid);
+        },
+        /**
+         * Display the module chooser
+         *
+         * @param e Event Triggering Event
+         * @param secitonid integer The ID of the section triggering the dialogue
+         * @return void
+         */
+        display_mod_chooser : function (e, sectionid) {
+            // Set the section for this version of the dialogue
+            this.sectionid = sectionid;
+            this.display_chooser(e);
+        },
+        toggle_mod_chooser : function(e) {
+            // Get the add section link
+            var modchooserlinks = Y.all('div.addresourcemodchooser');
+
+            // Get the dropdowns
+            var dropdowns = Y.all('div.addresourcedropdown');
+
+            if (modchooserlinks.size() == 0) {
+                // Continue with non-js action if there are no modchoosers to add
+                return;
+            }
+
+            // We need to update the text and link
+            var togglelink = Y.one('.block_settings #settingsnav .type_course .modchoosertoggle a');
+
+            // The actual text is in the last child
+            var toggletext = togglelink.get('lastChild');
+
+            var usemodchooser;
+            // Determine whether they're currently hidden
+            if (modchooserlinks.item(0).hasClass('visibleifjs')) {
+                // The modchooser is currently visible, hide it
+                usemodchooser = 0;
+                modchooserlinks
+                    .removeClass('visibleifjs')
+                    .addClass('hiddenifjs');
+                dropdowns
+                    .addClass('visibleifjs')
+                    .removeClass('hiddenifjs');
+                toggletext.set('data', M.util.get_string('modchooserenable', 'moodle'));
+                togglelink.set('href', togglelink.get('href').replace('off', 'on'));
+            } else {
+                // The modchooser is currently not visible, show it
+                usemodchooser = 1;
+                modchooserlinks
+                    .addClass('visibleifjs')
+                    .removeClass('hiddenifjs');
+                dropdowns
+                    .removeClass('visibleifjs')
+                    .addClass('hiddenifjs');
+                toggletext.set('data', M.util.get_string('modchooserdisable', 'moodle'));
+                togglelink.set('href', togglelink.get('href').replace('on', 'off'));
+            }
+
+            M.util.set_user_preference('usemodchooser', usemodchooser);
+
+            // Prevent the page from reloading
+            e.preventDefault();
+        },
+        option_selected : function(thisoption) {
+            // Add the sectionid to the URL
+            this.jumplink.set('value', thisoption.get('value') + '&section=' + this.sectionid);
+        }
+    },
+    {
+        NAME : MODCHOOSERNAME,
+        ATTRS : {
+        }
+    });
+    M.course = M.course || {};
+    M.course.init_chooser = function(config) {
+        return new MODCHOOSER(config);
+    }
+},
+'@VERSION@', {
+    requires:['base', 'overlay', 'moodle-core-chooserdialogue', 'transition']
+}
+);
index 0c21648..6dfcce0 100644 (file)
@@ -3,7 +3,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
     // The CSS selectors we use
     var CSS = {
         ACTIVITYLI : 'li.activity',
-        COMMANDSPAN : 'li.activity span.commands',
+        COMMANDSPAN : 'span.commands',
         SPINNERCOMMANDSPAN : 'span.commands',
         CONTENTAFTERLINK : 'div.contentafterlink',
         DELETE : 'a.editing_delete',
@@ -252,7 +252,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
          */
         setup_for_resource : function(baseselector) {
             if (!baseselector) {
-                var baseselector = CSS.PAGECONTENT;
+                var baseselector = CSS.PAGECONTENT + ' ' + CSS.ACTIVITYLI;;
             }
 
             Y.all(baseselector).each(this._setup_for_resource, this);
@@ -499,10 +499,12 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             var titletext = oldtitle;
             var editbutton = element.one('a.' + CSS.EDITTITLECLASS + ' img');
 
-            // Disable the current href to prevent redirections when editing
+            // Handle events for edit_resource_title
+            var listenevents = [];
+            var thisevent;
+
+            // Grab the anchor so that we can swap it with the edit form
             var anchor = instancename.ancestor('a');
-            anchor.setAttribute('oldhref', anchor.getAttribute('href'));
-            anchor.removeAttribute('href');
 
             var data = {
                 'class'   : 'resource',
@@ -536,35 +538,54 @@ YUI.add('moodle-course-toolboxes', function(Y) {
             // Clear the existing content and put the editor in
             currenttitle.set('data', '');
             editform.appendChild(editor);
-            instancename.appendChild(editform);
+            anchor.replace(editform);
             element.appendChild(editinstructions);
             e.preventDefault();
 
             // Focus and select the editor text
             editor.focus().select();
 
+            // Handle removal of the editor
+            var clear_edittitle = function() {
+                // Detach all listen events to prevent duplicate triggers
+                var thisevent;
+                while (thisevent = listenevents.shift()) {
+                    thisevent.detach();
+                }
+
+                if (editinstructions) {
+                    // Convert back to anchor and remove instructions
+                    editform.replace(anchor);
+                    editinstructions.remove();
+                    editinstructions = null;
+                }
+            }
+
             // Handle cancellation of the editor
-            editor.on('blur', function(e) {
-                // Detach the blur event before removing as some actions trigger multiple blurs in
-                // some browser
-                editor.detach('blur');
-                editform.remove();
-                editinstructions.remove();
+            var cancel_edittitle = function(e) {
+                clear_edittitle();
 
                 // Set the title and anchor back to their previous settings
                 currenttitle.set('data', oldtitle);
-                anchor.setAttribute('href', anchor.getAttribute('oldhref'));
-                anchor.removeAttribute('oldhref');
+            };
+
+            // Cancel the edit if we lose focus or the escape key is pressed
+            thisevent = editor.on('blur', cancel_edittitle);
+            listenevents.push(thisevent);
+            thisevent = Y.one('document').on('keyup', function(e) {
+                if (e.keyCode == 27) {
+                    cancel_edittitle(e);
+                }
             });
+            listenevents.push(thisevent);
 
             // Handle form submission
-            editform.on('submit', function(e) {
+            thisevent = editform.on('submit', function(e) {
                 // We don't actually want to submit anything
                 e.preventDefault();
 
-                // Detach the handlers to prevent multiple submissions
-                editform.detach('submit');
-                editor.detach('blur');
+                // Clear the edit title boxes
+                clear_edittitle();
 
                 // We only accept strings which have valid content
                 var newtitle = Y.Lang.trim(editor.get('value'));
@@ -583,17 +604,8 @@ YUI.add('moodle-course-toolboxes', function(Y) {
                     // Invalid content. Set the title back to it's original contents
                     currenttitle.set('data', oldtitle);
                 }
-
-                editform.remove();
-                editinstructions.remove();
-
-                // We need a timeout here otherwise hitting return to save in some browsers triggers
-                // the anchor
-                setTimeout(function(e) {
-                    anchor.setAttribute('href', anchor.getAttribute('oldhref'));
-                    anchor.removeAttribute('oldhref');
-                }, 500);
             }, this);
+            listenevents.push(thisevent);
         }
     }, {
         NAME : 'course-resource-toolbox',
index a27a4ed..da6545b 100644 (file)
--- a/index.php
+++ b/index.php
             echo $OUTPUT->box_end();
         }
     }
-    include_course_ajax($SITE, $modnamesused, $modnames);
-
+    // Include course AJAX
+    if (include_course_ajax($SITE, $modnamesused)) {
+        // Add the module chooser
+        $renderer = $PAGE->get_renderer('core', 'course');
+        echo $renderer->course_modchooser(get_module_metadata($SITE, $modnames), $SITE);
+    }
 
     if (isloggedin() and !isguestuser() and isset($CFG->frontpageloggedin)) {
         $frontpagelayout = $CFG->frontpageloggedin;
index 8b7cfb9..cd5cf16 100644 (file)
@@ -36,6 +36,9 @@ $string['cliincorrectvalueerror'] = 'خطأ، القيمة "{$a->value}" غير
 $string['cliincorrectvalueretry'] = 'قيمة غير صحيحة، حاول مرة أخرى';
 $string['clitypevalue'] = 'أدخل القيمة';
 $string['clitypevaluedefault'] = 'ادخل القيم أو اضغط انتر (Enter) لإستخدام القيم الأفتراضية  ({$a})';
+$string['cliunknowoption'] = 'خيارات غير معروفة 
+{$a}
+الرجاء استخدام خيار المساعدة';
 $string['cliyesnoprompt'] = 'ادخل (Y) تعني نعم أو (N) تعني لأ';
 $string['environmentrequireinstall'] = 'مطلوب تثبيته/تفيله';
 $string['environmentrequireversion'] = 'مطلوب الأصدار {$a->needed} ، وانت تستخدم الأصدار {$a->current}';
index cae3554..7a183b8 100644 (file)
@@ -36,7 +36,7 @@ $string['cliincorrectvalueerror'] = 'Viga, vigane väärtus "{$a->value}" "{$a->
 $string['cliincorrectvalueretry'] = 'Vale väärtus(value), palun proovige uuesti';
 $string['clitypevalue'] = 'tüübi väärtus';
 $string['clitypevaluedefault'] = 'sisesta väärtus, vajuta Enter kasutamaks vaikeväärtust ({$a})';
-$string['cliunknowoption'] = 'Tundmatud valikud:
+$string['cliunknowoption'] = 'Tundmatud valikud: 
    {$a}
 Palun kasuta --help valikut.';
 $string['cliyesnoprompt'] = 'kirjuta y (tähendab jah) või n (tähendab ei)';
diff --git a/install/lang/rm_surs/admin.php b/install/lang/rm_surs/admin.php
new file mode 100644 (file)
index 0000000..46047dd
--- /dev/null
@@ -0,0 +1,37 @@
+<?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/>.
+
+/**
+ * Automatically generated strings for Moodle 2.3dev installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['clianswerno'] = 'na';
+$string['cliansweryes'] = 'gie';
+$string['cliincorrectvalueretry'] = 'Quei ei buca correct, emprova aunc inagada';
+$string['clitypevalue'] = 'Tipar valur (value)';
+$string['cliyesnoprompt'] = 'Tippar gie ni na';
index 52e3fe2..676ee68 100644 (file)
@@ -39,7 +39,7 @@ $string['clialreadyinstalled'] = 'Датотека config.php већ посто
 $string['cliinstallheader'] = 'Moodle {$a} програм за инсталацију из командне линије';
 $string['databasehost'] = 'Сервер базе података :';
 $string['databasename'] = 'Име базе података';
-$string['databasetypehead'] = 'Изабери драјвер базе података';
+$string['databasetypehead'] = 'Изаберите драјвер базе података';
 $string['dataroot'] = 'Директоријум података';
 $string['datarootpermission'] = 'Овлашћења над директоријумом података';
 $string['dbprefix'] = 'Префикс табеле';
@@ -67,10 +67,10 @@ $string['pathshead'] = 'Потврди путање';
 $string['pathsrodataroot'] = 'У директоријум за податке није могућ упис';
 $string['pathsroparentdataroot'] = 'Није могућ упис у надређени директоријум ({$a->parent}).  Инсталациони програм не може да креира директоријум за податке ({$a->dataroot}).';
 $string['pathssubadmindir'] = 'Врло мали број веб сервера користи /admin као специјални URL за приступ разним подешавањима (контролни панел и сл.). Нажалост, то доводи до конфликта са стандардном локацијом за администраторске странице у Moodleu. Овај проблем можете решити тако што ћете променити име администраторског директоријума у вашој инсталацији, и овде уписати то ново име. На пример <em>moodleadmin</em>. Ово подешавање ће преправити администраторске линкове у Moodle систему.';
-$string['pathssubdataroot'] = 'Ð\9fоÑ\82Ñ\80ебан Ð²Ð°Ð¼ Ñ\98е Ð¿Ñ\80оÑ\81Ñ\82оÑ\80 Ð³Ð´Ðµ Ñ\9bе Moodle Ñ\87Ñ\83ваÑ\82и Ð¿Ð¾Ñ\81Ñ\82авÑ\9aене Ð´Ð°Ñ\82оÑ\82еке. Ð\9eваÑ\98 Ð´Ð¸Ñ\80екÑ\82оÑ\80иÑ\98Ñ\83м Ð±Ð¸ Ñ\82Ñ\80ебало Ð´Ð° Ð±Ñ\83де Ð¿Ð¾Ð´ÐµÑ\88ен Ñ\82ако Ð´Ð° Ñ\81е Ð¼Ð¾Ð¶Ðµ Ñ\87иÑ\82аÑ\82и Ð¸ Ñ\83 Ñ\9aега Ñ\83пиÑ\81иваÑ\82и Ð¾Ð´ Ñ\81Ñ\82Ñ\80ане ÐºÐ¾Ñ\80иÑ\81ника Ð²ÐµÐ± Ñ\81еÑ\80веÑ\80а (обиÑ\87но \'nobody\' Ð¸Ð»Ð¸ \'apache\'), Ð°Ð»Ð¸ Ð¸Ñ\81Ñ\82овÑ\80емено Ð¼Ð¾Ñ\80а Ð±Ð¸Ñ\82и Ð´Ð¾Ñ\81Ñ\82Ñ\83пан Ð´Ð¸Ñ\80екÑ\82но Ð¿Ñ\80еко Ð²ÐµÐ±Ð°. Ð£ÐºÐ¾Ð»Ð¸ÐºÐ¾ Ð¾Ð²Ð°Ñ\98 Ð´Ð¸Ñ\80екÑ\82оÑ\80иÑ\98Ñ\83м Ð½Ðµ Ð¿Ð¾Ñ\81Ñ\82оÑ\98и Moodle Ñ\9bе Ð¿Ð¸ÐºÑ\83Ñ\88аÑ\82и Ð´Ð° Ð³Ð° ÐºÑ\80еиÑ\80а Ñ\82оком Ð¸Ð½Ñ\81Ñ\82алаÑ\86иÑ\98е,';
+$string['pathssubdataroot'] = 'Ð\9fоÑ\82Ñ\80ебан Ð²Ð°Ð¼ Ñ\98е Ð¿Ñ\80оÑ\81Ñ\82оÑ\80 Ð³Ð´Ðµ Ñ\9bе Moodle Ñ\87Ñ\83ваÑ\82и Ð¿Ð¾Ñ\81Ñ\82авÑ\99ене Ð´Ð°Ñ\82оÑ\82еке. Ð\9eваÑ\98 Ð´Ð¸Ñ\80екÑ\82оÑ\80иÑ\98Ñ\83м Ñ\82Ñ\80еба Ð´Ð° Ð±Ñ\83де Ð¿Ð¾Ð´ÐµÑ\88ен Ñ\82ако Ð´Ð° Ñ\81е Ð¼Ð¾Ð¶Ðµ Ñ\87иÑ\82аÑ\82и Ð¸ Ñ\83 Ñ\9aега Ñ\83пиÑ\81иваÑ\82и Ð¾Ð´ Ñ\81Ñ\82Ñ\80ане ÐºÐ¾Ñ\80иÑ\81ника Ð²ÐµÐ± Ñ\81еÑ\80веÑ\80а (обиÑ\87но \'nobody\' Ð¸Ð»Ð¸ \'apache\'), Ð°Ð»Ð¸ Ð¸Ñ\81Ñ\82овÑ\80емено Ð¼Ð¾Ñ\80а Ð±Ð¸Ñ\82и Ð´Ð¾Ñ\81Ñ\82Ñ\83пан Ð´Ð¸Ñ\80екÑ\82но Ð¿Ñ\80еко Ð²ÐµÐ±Ð°. Ð£ÐºÐ¾Ð»Ð¸ÐºÐ¾ Ð¾Ð²Ð°Ñ\98 Ð´Ð¸Ñ\80екÑ\82оÑ\80иÑ\98Ñ\83м Ð½Ðµ Ð¿Ð¾Ñ\81Ñ\82оÑ\98и Moodle Ñ\9bе Ð¿Ð¾ÐºÑ\83Ñ\88аÑ\82и Ð´Ð° Ð³Ð° ÐºÑ\80еиÑ\80а Ñ\82оком Ð¸Ð½Ñ\81Ñ\82алаÑ\86иÑ\98е.';
 $string['pathssubdirroot'] = 'Пуна путања до директотијума за инсталацију Moodlea.';
-$string['pathssubwwwroot'] = 'Пуна веб адреса путем које ће се приступати Moodleу. Није могуће приступати Moodleу користећи више адреса. 
\90ко Ð²Ð°Ñ\88 Ñ\81аÑ\98Ñ\82 Ð¸Ð¼Ð° Ð²Ð¸Ñ\88е Ñ\98авниÑ\85 Ð°Ð´Ñ\80еÑ\81а Ð¾Ð½Ð´Ð° Ð½Ð° Ñ\81вима Ð¼Ð¾Ñ\80аÑ\82е Ð´Ð° Ð¿Ð¾Ð´ÐµÑ\81иÑ\82е Ð¿ÐµÑ\80маненÑ\82не Ñ\80едиÑ\80екÑ\86иÑ\98е Ð¾Ñ\81им Ð·Ð° Ð¾Ð²Ñ\83
+$string['pathssubwwwroot'] = 'Пуна веб адреса путем које ће се приступати Moodleu. Није могуће приступати Moodleu користећи више адреса. 
\90ко Ð²Ð°Ñ\88 Ñ\81аÑ\98Ñ\82 Ð¸Ð¼Ð° Ð²Ð¸Ñ\88е Ñ\98авниÑ\85 Ð°Ð´Ñ\80еÑ\81а Ð¾Ð½Ð´Ð° Ð½Ð° Ñ\81вима Ð¼Ð¾Ñ\80аÑ\82е Ð´Ð° Ð¿Ð¾Ð´ÐµÑ\81иÑ\82е Ð¿ÐµÑ\80маненÑ\82не Ñ\80едиÑ\80екÑ\86иÑ\98е Ð¾Ñ\81им Ð½Ð° Ð¾Ð²Ð¾Ñ\98
 Ако је ваш сајт доступан са интернета али и из интранет окружења овде употребите јавну адресу и подесите DNS тако да и интранет корисници могу да користе јавну адресу.
 Ако је адреса нетачна промените URL у свом веб читачу да бисте поново покренули инсталацију са другачијом вредношћу.';
 $string['pathsunsecuredataroot'] = 'Dataroot локација није безбедна';
index 7a215e3..bdc611b 100644 (file)
@@ -39,7 +39,7 @@ $string['clialreadyinstalled'] = 'Datoteka config.php već postoji. Upotrebite k
 $string['cliinstallheader'] = 'Moodle {$a} program za instalaciju iz komandne linije';
 $string['databasehost'] = 'Server baze podataka :';
 $string['databasename'] = 'Ime baze podataka';
-$string['databasetypehead'] = 'Izaberi drajver baze podataka';
+$string['databasetypehead'] = 'Izaberite drajver baze podataka';
 $string['dataroot'] = 'Direktorijum podataka';
 $string['datarootpermission'] = 'Ovlašćenja nad direktorijumom podataka';
 $string['dbprefix'] = 'Prefiks tabele';
@@ -67,10 +67,10 @@ $string['pathshead'] = 'Potvrdi putanje';
 $string['pathsrodataroot'] = 'U direktorijum za podatke nije moguć upis';
 $string['pathsroparentdataroot'] = 'Nije moguć upis u nadređeni direktorijum ({$a->parent}).  Instalacioni program ne može da kreira direktorijum za podatke ({$a->dataroot}).';
 $string['pathssubadmindir'] = 'Vrlo mali broj veb servera koristi /admin kao specijalni URL za pristup raznim podešavanjima (kontrolni panel i sl.). Nažalost, to dovodi do konflikta sa standardnom lokacijom za administratorske stranice u Moodleu. Ovaj problem možete rešiti tako što ćete promeniti ime administratorskog direktorijuma u vašoj instalaciji, i ovde upisati to novo ime. Na primer <em>moodleadmin</em>. Ovo podešavanje će prepraviti administratorske linkove u Moodle sistemu.';
-$string['pathssubdataroot'] = 'Potreban vam je prostor gde će Moodle čuvati postavnjene datoteke. Ovaj direktorijum bi trebalo da bude podešen tako da se može čitati i u njega upisivati od strane korisnika Web servera (obično \'nobody\' ili \'apache\'), ali istovremeno mora biti dostupan direktno preko Weba. Ukoliko ovaj direktorijum ne postoji Moodle će pikušati da ga kreira tokom instalacije,';
+$string['pathssubdataroot'] = 'Potreban vam je prostor gde će Moodle čuvati postavljene datoteke. Ovaj direktorijum treba da bude podešen tako da se može čitati i u njega upisivati od strane korisnika veb servera (obično \'nobody\' ili \'apache\'), ali istovremeno mora biti dostupan direktno preko veba. Ukoliko ovaj direktorijum ne postoji Moodle će pokušati da ga kreira tokom instalacije.';
 $string['pathssubdirroot'] = 'Puna putanja do direktotijuma za instalaciju Moodlea.';
 $string['pathssubwwwroot'] = 'Puna veb adresa putem koje će se pristupati Moodleu. Nije moguće pristupati Moodleu koristeći više adresa. 
-Ako vaš sajt ima više javnih adresa onda na svima morate da podesite permanentne redirekcije osim za ovu
+Ako vaš sajt ima više javnih adresa onda na svima morate da podesite permanentne redirekcije osim na ovoj
 Ako je vaš sajt dostupan sa interneta ali i iz intranet okruženja ovde upotrebite javnu adresu i podesite DNS tako da i intranet korisnici mogu da koriste javnu adresu.
 Ako je adresa netačna promenite URL u svom veb čitaču da biste ponovo pokrenuli instalaciju sa drugačijom vrednošću.';
 $string['pathsunsecuredataroot'] = 'Dataroot lokacija nije bezbedna';
index 8c7194a..38b59b3 100644 (file)
@@ -34,6 +34,7 @@ $string['admindirname'] = '管理目录';
 $string['availablelangs'] = '可用的语言包';
 $string['chooselanguagehead'] = '选择一种语言';
 $string['chooselanguagesub'] = '请选择在安装过程中使用的语言。这个语言也会成为网站的缺省语言,不过以后可以随时更改。';
+$string['clialreadyconfigured'] = 'config.php 文件已存在。如果您想安装此网站,请使用 admin/cli/install_database.php。';
 $string['clialreadyinstalled'] = '文件config.php已存在。如果您想升级此网站,请使用admin/cli/upgrade.php。';
 $string['cliinstallheader'] = 'Moodle {$a}命令行安装程序';
 $string['databasehost'] = '数据库主机';
index fea3ed2..5ca07d2 100644 (file)
@@ -52,16 +52,21 @@ function iplookup_find_location($ip) {
             $info['title'][] = $info['city'];
         }
 
-        if (!empty($location->country_code)) {
+        if (!empty($location->countryCode)) {
             $countries = get_string_manager()->get_list_of_countries(true);
-            if (isset($countries[$location->country_code])) {
+            if (isset($countries[$location->countryCode])) {
                 // prefer our localized country names
-                $info['country'] = $countries[$location->country_code];
+                $info['country'] = $countries[$location->countryCode];
             } else {
-                $info['country'] = $location->country_name;
+                $info['country'] = $location->countryName;
             }
             $info['title'][] = $info['country'];
+
+        } else if (!empty($location->countryName)) {
+            $info['country'] = $location->countryName;
+            $info['title'][] = $info['country'];
         }
+
         $info['longitude'] = $location->longitude;
         $info['latitude']  = $location->latitude;
         $info['note'] = get_string('iplookupmaxmindnote', 'admin');
@@ -95,8 +100,8 @@ function iplookup_find_location($ip) {
 
         $info['note'] = get_string('iplookupgeoplugin', 'admin');
 
-        $info['title'][] = $info['country'];
         $info['title'][] = $info['city'];
+        $info['title'][] = $info['country'];
 
         return $info;
     }
diff --git a/iplookup/tests/geoip_test.php b/iplookup/tests/geoip_test.php
new file mode 100644 (file)
index 0000000..5a363c7
--- /dev/null
@@ -0,0 +1,86 @@
+<?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/>.
+
+/**
+ * GeoIP tests
+ *
+ * @package    core_iplookup
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * GeoIp data file parsing test.
+ */
+class geoip_testcase extends advanced_testcase {
+
+    public function test_geoip() {
+        global $CFG;
+        require_once("$CFG->libdir/filelib.php");
+        require_once("$CFG->dirroot/iplookup/lib.php");
+
+        if (!PHPUNIT_LONGTEST) {
+            // this may take a long time
+            return;
+        }
+
+        $this->resetAfterTest();
+
+        // let's store the file somewhere
+        $gzfile = "$CFG->dataroot/phpunit/geoip/GeoLiteCity.dat.gz";
+        check_dir_exists(dirname($gzfile));
+        if (file_exists($gzfile) and (filemtime($gzfile) < time() - 60*60*24*30)) {
+            // delete file if older than 1 month
+            unlink($gzfile);
+        }
+
+        if (!file_exists($gzfile)) {
+            download_file_content('http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz', null, null, false, 300, 20, false, $gzfile);
+        }
+
+        $this->assertTrue(file_exists($gzfile));
+
+        $zd = gzopen($gzfile, "r");
+        $contents = gzread($zd, 50000000);
+        gzclose($zd);
+
+        $geoipfile = "$CFG->dataroot/geoip/GeoLiteCity.dat";
+        check_dir_exists(dirname($geoipfile));
+        $fp = fopen($geoipfile, 'w');
+        fwrite($fp, $contents);
+        fclose($fp);
+
+        $this->assertTrue(file_exists($geoipfile));
+
+        $CFG->geoipfile = $geoipfile;
+
+        $result = iplookup_find_location('147.230.16.1');
+
+        $this->assertEquals('array', gettype($result));
+        $this->assertEquals('Liberec', $result['city']);
+        $this->assertEquals(15.0653, $result['longitude'], '', 0.001);
+        $this->assertEquals(50.7639, $result['latitude'], '', 0.001);
+        $this->assertNull($result['error']);
+        $this->assertEquals('array', gettype($result['title']));
+        $this->assertEquals('Liberec', $result['title'][0]);
+        $this->assertEquals('Czech Republic', $result['title'][1]);
+    }
+}
+
diff --git a/iplookup/tests/geoplugin_test.php b/iplookup/tests/geoplugin_test.php
new file mode 100644 (file)
index 0000000..5cf0ecc
--- /dev/null
@@ -0,0 +1,60 @@
+<?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/>.
+
+/**
+ * GeoIP tests
+ *
+ * @package    core_iplookup
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * GeoIp data file parsing test.
+ */
+class geoplugin_testcase extends advanced_testcase {
+
+    public function test_geoip() {
+        global $CFG;
+        require_once("$CFG->libdir/filelib.php");
+        require_once("$CFG->dirroot/iplookup/lib.php");
+
+        if (!PHPUNIT_LONGTEST) {
+            // we do not want to DDOS their server, right?
+            return;
+        }
+
+        $this->resetAfterTest();
+
+        $CFG->geoipfile = '';
+
+        $result = iplookup_find_location('147.230.16.1');
+
+        $this->assertEquals('array', gettype($result));
+        $this->assertEquals('Liberec', $result['city']);
+        $this->assertEquals(15.0653, $result['longitude'], '', 0.001);
+        $this->assertEquals(50.7639, $result['latitude'], '', 0.001);
+        $this->assertNull($result['error']);
+        $this->assertEquals('array', gettype($result['title']));
+        $this->assertEquals('Liberec', $result['title'][0]);
+        $this->assertEquals('Czech Republic', $result['title'][1]);
+    }
+}
+
index e737dbf..f1de5c0 100644 (file)
@@ -34,14 +34,18 @@ $string['completion_help'] = 'If enabled, activity completion is tracked, either
 
 A tick next to the activity name on the course page indicates when the activity is complete.';
 $string['completion_link'] = 'activity/completion';
-$string['completion-alt-auto-enabled'] = 'The system marks this item complete according to conditions';
-$string['completion-alt-auto-fail'] = 'Completed (did not achieve pass grade)';
+$string['completion-alt-auto-enabled'] = 'The system marks this item complete according to conditions: {$a}';
+$string['completion-alt-auto-fail'] = 'Completed: {$a} (did not achieve pass grade)';
 $string['completion-alt-auto-n'] = 'Not completed: {$a}';
-$string['completion-alt-auto-pass'] = 'Completed (achieved pass grade)';
+$string['completion-alt-auto-pass'] = 'Completed: {$a} (achieved pass grade)';
 $string['completion-alt-auto-y'] = 'Completed: {$a}';
-$string['completion-alt-manual-enabled'] = 'Students can manually mark this item complete';
+$string['completion-alt-manual-enabled'] = 'Students can manually mark this item complete: {$a}';
 $string['completion-alt-manual-n'] = 'Not completed: {$a}. Select to mark as complete.';
 $string['completion-alt-manual-y'] = 'Completed: {$a}. Select to mark as not complete.';
+$string['completion-y'] = 'Completed';
+$string['completion-n'] = 'Not completed';
+$string['completion-fail'] = 'Completed (did not achieve pass grade)';
+$string['completion-pass'] = 'Completed (achieved pass grade)';
 $string['completion_automatic'] = 'Show activity as complete when conditions are met';
 $string['completiondisabled'] = 'Disabled, not shown in activity settings';
 $string['completionexpected'] = 'Expect completed on';
@@ -95,6 +99,7 @@ $string['completiononunenrolment']='Completion on unenrolment';
 $string['completionsettingslocked']='Completion settings locked';
 $string['completionstartonenrol']='Completion tracking begins on enrolment';
 $string['completionstartonenrolhelp']='Begin tracking a student\'s progress in course completion after course enrolment';
+$string['completed'] = 'Completed';
 $string['confirmselfcompletion']='Confirm self completion';
 $string['coursealreadycompleted']='You have already completed this course';
 $string['coursecomplete']='Course complete';
@@ -125,6 +130,7 @@ $string['markcomplete']='Mark complete';
 $string['markedcompleteby']='Marked complete by {$a}';
 $string['markingyourselfcomplete']='Marking yourself complete';
 $string['moredetails']='More details';
+$string['notcompleted'] = 'Not completed';
 $string['nocriteriaset']='No completion criteria set for this course';
 $string['notenroled']='You are not enrolled in this course';
 $string['notyetstarted']='Not yet started';
index 96ed4ef..75fb0be 100644 (file)
@@ -158,6 +158,7 @@ $string['cannotviewprofile'] = 'You cannot view the profile of this user';
 $string['cannotviewreport'] = 'You cannot view this report';
 $string['cannotwritefile'] = 'Cannot write to file ({$a})';
 $string['categoryerror'] = 'Category error';
+$string['categorytoolong'] = 'Category name too long';
 $string['commentmisconf'] = 'Comment ID is misconfigured';
 $string['componentisuptodate'] = 'Component is up-to-date';
 $string['confirmsesskeybad'] = 'Sorry, but your session key could not be confirmed to carry out this action.  This security feature prevents against accidental or malicious execution of important functions in your name.  Please make sure you really wanted to execute this function.';
@@ -253,6 +254,7 @@ $string['hackdetected'] = 'Hack attack detected!';
 $string['hashpoolproblem'] = 'Incorrect pool file content {$a}.';
 $string['headersent'] = 'Headers already sent';
 $string['idnumbertaken'] = 'ID number is already used for another course';
+$string['idnumbertoolong'] = 'ID number is too long';
 $string['importformatnotimplement'] = 'Sorry, importing this format is not yet implemented!';
 $string['incorrectext'] = 'File has an incorrect extension';
 $string['installproblem'] = 'It is usually not possible to recover from errors triggered during installation, you may need to create a new database or use a different database prefix if you want to retry the installation.';
@@ -359,6 +361,7 @@ $string['moduledisable'] = 'This module ({$a}) has been disabled for this partic
 $string['moduledoesnotexist'] = 'This module does not exist';
 $string['moduleinstancedoesnotexist'] = 'The instance of this module does not exist';
 $string['modulemissingcode'] = 'Module {$a} is missing the code needed to perform this function';
+$string['movecatcontentstoroot'] = 'Moving the category content to root is not allowed. You must move the contents to an existant category!';
 $string['multiplerecordsfound'] = 'Multiple records found, only one record expected.';
 $string['multiplerestorenotallow'] = 'Multiple restore execution not allowed!';
 $string['mustbeloggedin'] = 'You must be logged in to do this';
index 859e874..64ae940 100644 (file)
@@ -66,6 +66,7 @@ $string['addnewuser'] = 'Add a new user';
 $string['addnousersrecip'] = 'Add users who haven\'t accessed this {$a} to recipient list';
 $string['addpagehere'] = 'Add page here';
 $string['addresource'] = 'Add a resource...';
+$string['addresourceoractivity'] = 'Add an activity or resource';
 $string['address'] = 'Address';
 $string['addstudent'] = 'Add student';
 $string['addsubcategory'] = 'Add a sub-category';
@@ -1060,6 +1061,8 @@ $string['missingurl'] = 'Missing URL';
 $string['missingusername'] = 'Missing username';
 $string['moddoesnotsupporttype'] = 'Module {$a->modname} does not support uploads of type {$a->type}';
 $string['modified'] = 'Modified';
+$string['modchooserenable'] = 'Activity chooser on';
+$string['modchooserdisable'] = 'Activity chooser off';
 $string['moduledeleteconfirm'] = 'You are about to completely delete the module \'{$a}\'.  This will completely delete everything in the database associated with this activity module.  Are you SURE you want to continue?';
 $string['moduledeletefiles'] = 'All data associated with the module \'{$a->module}\' has been deleted from the database.  To complete the deletion (and prevent the module re-installing itself), you should now delete this directory from your server: {$a->directory}';
 $string['moduleintro'] = 'Description';
@@ -1174,6 +1177,7 @@ $string['nodstpresets'] = 'The administrator has not enabled Daylight Savings Ti
 $string['nofilesselected'] = 'No files have been selected to restore';
 $string['nofilesyet'] = 'No files have been uploaded to your course yet';
 $string['nograde'] = 'No grade';
+$string['nohelpforactivityorresource'] = 'There is currently no help associated with this resource or activity';
 $string['nochange'] = 'No change';
 $string['noimagesyet'] = 'No images have been uploaded to your course yet';
 $string['nologsfound'] = 'No logs have been found';
@@ -1496,6 +1500,7 @@ $string['selectdefault'] = 'Select default';
 $string['selectedfile'] = 'Selected file';
 $string['selectednowmove'] = '{$a} files selected for moving. Now go into the destination folder and press \'Move files to here\'';
 $string['selectfiles'] = 'Select files';
+$string['selectmoduletoviewhelp'] = 'Select a module to view its help.';
 $string['selectnos'] = 'Select all \'no\'';
 $string['selectperiod'] = 'Select period';
 $string['senddetails'] = 'Send my details via email';
index 7eb7ce4..2b8aefb 100644 (file)
@@ -54,6 +54,7 @@ $string['createtokenforuserauto'] = 'Create a token for a user automatically';
 $string['createtokenforuserdescription'] = 'Create a token for the web services user.';
 $string['createuser'] = 'Create a specific user';
 $string['createuserdescription'] = 'A web services user is required to represent the system controlling Moodle.';
+$string['criteriaerror'] = 'Missing permissions to search on a criteria.';
 $string['default'] = 'Default to "{$a}"';
 $string['deleteaservice'] = 'Delete service';
 $string['deleteservice'] = 'Delete the service: {$a->name} (id: {$a->id})';
index fe54546..4df607a 100644 (file)
@@ -433,6 +433,15 @@ $functions = array(
 
     // === course related functions ===
 
+    'core_course_get_contents' => array(
+        'classname'   => 'core_course_external',
+        'methodname'  => 'get_course_contents',
+        'classpath'   => 'course/externallib.php',
+        'description' => 'Get course contents',
+        'type'        => 'read',
+        'capabilities'=> 'moodle/course:update,moodle/course:viewhiddencourses',
+    ),
+
     'moodle_course_get_courses' => array(
         'classname'   => 'core_course_external',
         'methodname'  => 'get_courses',
@@ -469,15 +478,6 @@ $functions = array(
         'capabilities'=> 'moodle/course:create,moodle/course:visibility',
     ),
 
-    'core_course_get_contents' => array(
-        'classname'   => 'core_course_external',
-        'methodname'  => 'get_course_contents',
-        'classpath'   => 'course/externallib.php',
-        'description' => 'Get course contents',
-        'type'        => 'read',
-        'capabilities'=> 'moodle/course:update,moodle/course:viewhiddencourses',
-    ),
-
     'core_course_delete_courses' => array(
         'classname'   => 'core_course_external',
         'methodname'  => 'delete_courses',
@@ -496,6 +496,44 @@ $functions = array(
         'capabilities'=> 'moodle/backup:backupcourse,moodle/restore:restorecourse,moodle/course:create',
     ),
 
+    // === course category related functions ===
+
+    'core_course_get_categories' => array(
+        'classname'   => 'core_course_external',
+        'methodname'  => 'get_categories',
+        'classpath'   => 'course/externallib.php',
+        'description' => 'Return category details',
+        'type'        => 'read',
+        'capabilities'=> 'moodle/category:viewhiddencategories',
+    ),
+
+    'core_course_create_categories' => array(
+        'classname'   => 'core_course_external',
+        'methodname'  => 'create_categories',
+        'classpath'   => 'course/externallib.php',
+        'description' => 'Create course categories',
+        'type'        => 'write',
+        'capabilities'=> 'moodle/category:manage',
+    ),
+
+    'core_course_update_categories' => array(
+        'classname'   => 'core_course_external',
+        'methodname'  => 'update_categories',
+        'classpath'   => 'course/externallib.php',
+        'description' => 'Update categories',
+        'type'        => 'write',
+        'capabilities'=> 'moodle:category/manage',
+    ),
+
+    'core_course_delete_categories' => array(
+        'classname'   => 'core_course_external',
+        'methodname'  => 'delete_categories',
+        'classpath'   => 'course/externallib.php',
+        'description' => 'Delete course categories',
+        'type'        => 'write',
+        'capabilities'=> 'moodle/category:manage',
+    ),
+
     // === message related functions ===
 
     'moodle_message_send_instantmessages' => array(
index 718512f..7e8c704 100644 (file)
@@ -3546,20 +3546,38 @@ class settings_navigation extends navigation_node {
 
             if ($this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
                 // We are on the course page, retain the current page params e.g. section.
-                $url = clone($this->page->url);
-                $url->param('sesskey', sesskey());
+                $baseurl = clone($this->page->url);
+                $baseurl->param('sesskey', sesskey());
             } else {
                 // Edit on the main course page.
-                $url = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey()));
+                $baseurl = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey()));
             }
+
+            $editurl = clone($baseurl);
             if ($this->page->user_is_editing()) {
-                $url->param('edit', 'off');
+                $editurl->param('edit', 'off');
                 $editstring = get_string('turneditingoff');
             } else {
-                $url->param('edit', 'on');
+                $editurl->param('edit', 'on');
                 $editstring = get_string('turneditingon');
             }
-            $coursenode->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', ''));
+            $coursenode->add($editstring, $editurl, self::TYPE_SETTING, null, null, new pix_icon('i/edit', ''));
+
+            // Add the module chooser toggle
+            $modchoosertoggleurl = clone($baseurl);
+            if ($this->page->user_is_editing() && course_ajax_enabled($course)) {
+                if ($usemodchooser = get_user_preferences('usemodchooser', 1)) {
+                    $modchoosertogglestring = get_string('modchooserdisable', 'moodle');
+                    $modchoosertoggleurl->param('modchooser', 'off');
+                } else {
+                    $modchoosertogglestring = get_string('modchooserenable', 'moodle');
+                    $modchoosertoggleurl->param('modchooser', 'on');
+                }
+                $modchoosertoggle = $coursenode->add($modchoosertogglestring, $modchoosertoggleurl, self::TYPE_SETTING);
+                $modchoosertoggle->add_class('modchoosertoggle');
+                $modchoosertoggle->add_class('visibleifjs');
+                user_preference_allow_ajax_update('usemodchooser', PARAM_BOOL);
+            }
 
             if ($this->page->user_is_editing()) {
                 // Removed as per MDL-22732
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 97ae95e..2c471f6 100644 (file)
@@ -1097,7 +1097,7 @@ function disable_output_buffering() {
  */
 function redirect_if_major_upgrade_required() {
     global $CFG;
-    $lastmajordbchanges = 2010111700;
+    $lastmajordbchanges = 2012051700;
     if (empty($CFG->version) or (int)$CFG->version < $lastmajordbchanges or
             during_initial_install() or !empty($CFG->adminsetuppending)) {
         try {
diff --git a/lib/yui/chooserdialogue/chooserdialogue.js b/lib/yui/chooserdialogue/chooserdialogue.js
new file mode 100644 (file)
index 0000000..1cbb0fe
--- /dev/null
@@ -0,0 +1,202 @@
+YUI.add('moodle-core-chooserdialogue', function(Y) {
+
+    var CHOOSERDIALOGUE = function() {
+        CHOOSERDIALOGUE.superclass.constructor.apply(this, arguments);
+    }
+
+    Y.extend(CHOOSERDIALOGUE, Y.Base, {
+        // The overlay widget
+        overlay: null,
+
+        // The submit button - we disable this until an element is set
+        submitbutton : null,
+
+        // The chooserdialogue container
+        container : null,
+
+        setup_chooser_dialogue : function(bodycontent, headercontent, config) {
+            // Set Default options
+            var params = {
+                bodyContent : bodycontent.get('innerHTML'),
+                headerContent : headercontent.get('innerHTML'),
+                visible : false, // Hide by default
+                zindex : 100, // Display in front of other items
+                lightbox : true, // This dialogue should be modal
+                shim : true
+            }
+
+            // Override with additional options
+            for (paramkey in config) {
+              params[paramkey] = config[paramkey];
+            }
+
+            // Create the overlay
+            this.overlay = new M.core.dialogue(params);
+
+            // Remove the template for the chooser
+            bodycontent.remove();
+            headercontent.remove();
+
+            // Hide and then render the overlay
+            this.overlay.hide();
+            this.overlay.render();
+
+            // Set useful links
+            this.container = this.overlay.get('boundingBox').one('#choosercontainer');
+            this.options = this.container.all('.option input[type=radio]');
+        },
+        /**
+         * Display the module chooser
+         *
+         * @param e Event Triggering Event
+         * @return void
+         */
+        display_chooser : function (e) {
+            // Stop the default event actions before we proceed
+            e.preventDefault();
+
+            var bb = this.overlay.get('boundingBox');
+            var dialogue = this.container.one('.alloptions');
+
+            // These will trigger a check_options call to display the correct help
+            this.container.on('click', this.check_options, this);
+            this.container.on('key_up', this.check_options, this);
+            this.container.on('dblclick', function(e) {
+                if (e.target.ancestor('div.option')) {
+                    this.check_options();
+                    this.container.one('form').submit();
+                }
+            }, this);
+
+            // Hook onto the cancel button to hide the form
+            this.container.one('#addcancel').on('click', this.cancel_popup, this);
+
+            // Grab global keyup events and handle them
+            Y.one('document').on('keyup', this.handle_key_press, this);
+
+            // Add references to various elements we adjust
+            this.jumplink     = this.container.one('#jump');
+            this.submitbutton = this.container.one('#submitbutton');
+
+            // Disable the submit element until the user makes a selection
+            this.submitbutton.set('disabled', 'true');
+
+            // Display the overlay
+            this.overlay.show();
+
+            // Re-centre the dialogue after we've shown it.
+            this.center_dialogue(dialogue);
+
+            // Finally, focus the first radio element - this enables form selection via the keyboard
+            this.container.one('.option input[type=radio]').focus();
+
+            // Trigger check_options to set the initial jumpurl
+            this.check_options();
+        },
+        /**
+         * Calculate the optimum height of the chooser dialogue
+         *
+         * This tries to set a sensible maximum and minimum to ensure that some options are always shown, and preferably
+         * all, whilst fitting the box within the current viewport
+         *
+         * @param dialogue Y.Node The dialogue
+         * @return void
+         */
+        center_dialogue : function(dialogue) {
+            var bb = this.overlay.get('boundingBox');
+
+            var winheight = bb.get('winHeight');
+            var offsettop = 0;
+
+            // Try and set a sensible max-height -- this must be done before setting the top
+            // Set a default height of 640px
+            var newheight = this.get('maxheight');
+            if (winheight <= newheight) {
+                // Deal with smaller window sizes
+                if (winheight <= this.get('minheight')) {
+                    newheight = this.get('minheight');
+                } else {
+                    newheight = winheight;
+                }
+            }
+
+            // Set a fixed position if the window is large enough
+            if (newheight > this.get('minheight')) {
+                bb.setStyle('position', 'fixed');
+            } else {
+                bb.setStyle('position', 'absolute');
+                offsettop = Y.one('window').get('scrollTop');
+            }
+
+            // Take off 15px top and bottom for borders, plus 40px each for the title and button area before setting the
+            // new max-height
+            var totalheight = newheight;
+            newheight = newheight - (15 + 15 + 40 + 40);
+            dialogue.setStyle('max-height', newheight + 'px');
+            dialogue.setStyle('height', newheight + 'px');
+
+            // Re-calculate the location now that we've changed the size
+            var dialoguetop = Math.max(12, ((winheight - totalheight) / 2)) + offsettop;
+
+            // We need to set the height for the yui3-widget - can't work
+            // out what we're setting at present -- shoud be the boudingBox
+            bb.setStyle('top', dialoguetop + 'px');
+        },
+        handle_key_press : function(e) {
+            if (e.keyCode == 27) {
+                this.cancel_popup(e);
+            }
+        },
+        cancel_popup : function (e) {
+            // Prevent normal form submission before hiding
+            e.preventDefault();
+            this.hide();
+        },
+        hide : function() {
+            // Detach the global keypress handler before hiding
+            Y.one('document').detach('keyup', this.handle_key_press, this);
+            this.container.detachAll();
+            this.overlay.hide();
+        },
+        check_options : function(e) {
+            // Check which options are set, and change the parent class
+            // to show/hide help as required
+            this.options.each(function(thisoption) {
+                var optiondiv = thisoption.get('parentNode').get('parentNode');
+                if (thisoption.get('checked')) {
+                    optiondiv.addClass('selected');
+
+                    // Trigger any events for this option
+                    this.option_selected(thisoption);
+
+                    // Ensure that the form may be submitted
+                    this.submitbutton.removeAttribute('disabled');
+
+                    // Ensure that the radio remains focus so that keyboard navigation is still possible
+                    thisoption.focus();
+                } else {
+                    optiondiv.removeClass('selected');
+                }
+            }, this);
+        },
+        option_selected : function(e) {
+        }
+    },
+    {
+        NAME : 'moodle-core-chooserdialogue',
+        ATTRS : {
+            minheight : {
+                value : 300
+            },
+            maxheight : {
+                value : 660
+            }
+        }
+    });
+    M.core = M.core || {};
+    M.core.chooserdialogue = CHOOSERDIALOGUE;
+},
+'@VERSION@', {
+    requires:['base', 'overlay', 'moodle-enrol-notification']
+}
+);
index 03b11f1..3ab2e07 100644 (file)
@@ -171,6 +171,13 @@ class assign_grading_table extends table_sql implements renderable {
         $columns[] = 'finalgrade';
         $headers[] = get_string('finalgrade', 'grades');
 
+        // load the grading info for all users
+        $this->gradinginfo = grade_get_grades($this->assignment->get_course()->id, 'mod', 'assign', $this->assignment->get_instance()->id, $users);
+
+        if (!empty($CFG->enableoutcomes) && !empty($this->gradinginfo->outcomes)) {
+            $columns[] = 'outcomes';
+            $headers[] = get_string('outcomes', 'grades');
+        }
 
 
         // set the columns
@@ -179,6 +186,8 @@ class assign_grading_table extends table_sql implements renderable {
         $this->no_sorting('finalgrade');
         $this->no_sorting('edit');
         $this->no_sorting('select');
+        $this->no_sorting('outcomes');
+
         foreach ($this->assignment->get_submission_plugins() as $plugin) {
             if ($plugin->is_visible() && $plugin->is_enabled()) {
                 $this->no_sorting('assignsubmission_' . $plugin->get_type());
@@ -190,8 +199,6 @@ class assign_grading_table extends table_sql implements renderable {
             }
         }
 
-        // load the grading info for all users
-        $this->gradinginfo = grade_get_grades($this->assignment->get_course()->id, 'mod', 'assign', $this->assignment->get_instance()->id, $users);
     }
 
     /**
@@ -228,6 +235,25 @@ class assign_grading_table extends table_sql implements renderable {
         return $o;
     }
 
+    /**
+     * Format a list of outcomes
+     *
+     * @param stdClass $row
+     * @return string
+     */
+    function col_outcomes(stdClass $row) {
+        $outcomes = '';
+        foreach($this->gradinginfo->outcomes as $index=>$outcome) {
+            $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');
+        }
+
+        return $outcomes;
+    }
+
+
     /**
      * Format a user picture for display (and update rownum as a sideeffect)
      *
index b279a78..b936ece 100644 (file)
@@ -139,7 +139,12 @@ $string['manageassignfeedbackplugins'] = 'Manage assignment feedback plugins';
 $string['manageassignsubmissionplugins'] = 'Manage assignment submission plugins';
 $string['messageprovider:assign_updates'] = 'Assignment notifications';
 $string['modulename'] = 'Assignment';
-$string['modulename_help'] = 'Assignments enable the teacher to specify a task either on or offline which can then be graded.';
+$string['modulename_help'] = 'The assignment activity module enables a teacher to assess students’ learning by setting work and then reviewing it and providing feedback and grades.
+
+Students can submit any digital content (files), such as word-processed documents, spreadsheets, images, audio and video clips. Alternatively, or in addition, the assignment may require students to type text directly into a field using the text editor. An assignment can also be used to remind students of \'real-world\' assignments they need to complete, such as art work, and thus not require any digital content.
+
+When reviewing assignments, teachers can leave feedback comments and upload files, such as marked-up student submissions, documents with comments or spoken audio feedback. Assignments can be graded using a numerical or custom scale or an advanced grading method such as a rubric. Final grades are recorded in the gradebook.';
+$string['modulename_link'] = 'mod/assignment/view';
 $string['modulenameplural'] = 'Assignments';
 $string['mysubmission'] = 'My submission: ';
 $string['newsubmissions'] = 'Assignments submitted';
index 469d8be..61c9773 100644 (file)
@@ -754,8 +754,8 @@ function assign_get_user_grades($assign, $userid=0) {
     global $CFG;
     require_once($CFG->dirroot . '/mod/assign/locallib.php');
 
-    $context = context_module::instance($assign->cmidnumber);
-    $assignment = new assign($context, null, null);
+    $assignment = new assign(null, null, null);
+    $assignment->set_instance($assign);
     return $assignment->get_user_grades_for_gradebook($userid);
 }
 
index d694b0b..123c9d4 100644 (file)
@@ -560,7 +560,7 @@ class assign {
         if ($this->adminconfig) {
             return $this->adminconfig;
         }
-        $this->adminconfig = get_config('mod_assign');
+        $this->adminconfig = get_config('assign');
         return $this->adminconfig;
     }
 
@@ -2487,8 +2487,30 @@ class assign {
                 }
             }
         }
-        $mform->addElement('static', 'progress', '', get_string('gradingstudentprogress', 'assign', array('index'=>$rownum+1, 'count'=>count($useridlist))));
 
+        $gradinginfo = grade_get_grades($this->get_course()->id,
+                                        'mod',
+                                        'assign',
+                                        $this->get_instance()->id,
+                                        $userid);
+        if (!empty($CFG->enableoutcomes)) {
+            foreach($gradinginfo->outcomes as $index=>$outcome) {
+                $options = make_grades_menu(-$outcome->scaleid);
+                if ($outcome->grades[$userid]->locked) {
+                    $options[0] = get_string('nooutcome', 'grades');
+                    $mform->addElement('static', 'outcome_'.$index.'['.$userid.']', $outcome->name.':',
+                            $options[$outcome->grades[$userid]->grade]);
+                } else {
+                    $options[''] = get_string('nooutcome', 'grades');
+                    $attributes = array('id' => 'menuoutcome_'.$index );
+                    $mform->addElement('select', 'outcome_'.$index.'['.$userid.']', $outcome->name.':', $options, $attributes );
+                    $mform->setType('outcome_'.$index.'['.$userid.']', PARAM_INT);
+                    $mform->setDefault('outcome_'.$index.'['.$userid.']', $outcome->grades[$userid]->grade );
+                }
+            }
+        }
+
+        $mform->addElement('static', 'progress', '', get_string('gradingstudentprogress', 'assign', array('index'=>$rownum+1, 'count'=>count($useridlist))));
 
         // plugins
         $this->add_plugin_grade_elements($grade, $mform, $data);
@@ -2701,6 +2723,43 @@ class assign {
         $this->add_to_log('unlock submission', get_string('unlocksubmissionforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user))));
     }
 
+    /**
+     * save outcomes submitted from grading form
+     *
+     * @param int $userid
+     * @param stdClass $formdata
+     */
+    private function process_outcomes($userid, $formdata) {
+        global $CFG, $USER;
+
+        if (empty($CFG->enableoutcomes)) {
+            return;
+        }
+
+        require_once($CFG->libdir.'/gradelib.php');
+
+        $data = array();
+        $gradinginfo = grade_get_grades($this->get_course()->id,
+                                        'mod',
+                                        'assign',
+                                        $this->get_instance()->id,
+                                        $userid);
+
+        if (!empty($gradinginfo->outcomes)) {
+            foreach($gradinginfo->outcomes as $index=>$oldoutcome) {
+                $name = 'outcome_'.$index;
+                if (isset($formdata->{$name}[$userid]) and $oldoutcome->grades[$userid]->grade != $formdata->{$name}[$userid]) {
+                    $data[$index] = $formdata->{$name}[$userid];
+                }
+            }
+        }
+        if (count($data) > 0) {
+            grade_update_outcomes('mod/assign', $this->course->id, 'mod', 'assign', $this->get_instance()->id, $userid, $data);
+        }
+
+    }
+
+
     /**
      * save grade
      *
@@ -2763,6 +2822,7 @@ class assign {
                     }
                 }
             }
+            $this->process_outcomes($userid, $formdata);
             $this->update_grade($grade);
 
             $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
@@ -2865,7 +2925,8 @@ class assign {
         $grades = array();
         $assignmentid = $this->get_instance()->id;
 
-        $gradebookpluginname = $CFG->assign_feedback_plugin_for_gradebook;
+        $adminconfig = $this->get_admin_config();
+        $gradebookpluginname = $adminconfig->feedback_plugin_for_gradebook;
         $gradebookplugin = null;
 
         // find the gradebook plugin
index d17dc66..36b2036 100644 (file)
@@ -105,6 +105,7 @@ class assign_upgrade_manager {
         $gradingdefinitions = null;
         $gradeidmap = array();
         $completiondone = false;
+        $gradesdone = false;
 
         // from this point we want to rollback on failure
         $rollback = false;
@@ -213,7 +214,9 @@ class assign_upgrade_manager {
             $newassignment->update_gradebook(false,$newcoursemodule->id);
 
             // copy the grades from the old assignment to the new one
-            $this->copy_grades_for_upgrade($oldassignment, $newassignment);
+            $DB->set_field('grade_items', 'itemmodule', 'assign', array('iteminstance'=>$oldassignment->id));
+            $DB->set_field('grade_items', 'iteminstance', $newassignment->get_instance()->id, array('iteminstance'=>$oldassignment->id));
+            $gradesdone = true;
 
         } catch (Exception $exception) {
             $rollback = true;
@@ -221,6 +224,12 @@ class assign_upgrade_manager {
         }
 
         if ($rollback) {
+            // roll back the grades changes
+            if ($gradesdone) {
+                // copy the grades from the old assignment to the new one
+                $DB->set_field('grade_items', 'itemmodule', 'assignment', array('iteminstance'=>$newassignment->get_instance()->id));
+                $DB->set_field('grade_items', 'iteminstance', $oldassignment->id, array('iteminstance'=>$newassignment->get_instance()->id));
+            }
             // roll back the completion changes
             if ($completiondone) {
                 $DB->set_field('course_modules_completion', 'coursemoduleid', $oldcoursemodule->id, array('coursemoduleid'=>$newcoursemodule->id));
@@ -363,44 +372,4 @@ class assign_upgrade_manager {
         return true;
     }
 
-    /**
-     * This function copies the grades from the old assignment module to this one.
-     *
-     * @param stdClass $oldassignment old assignment data record
-     * @param assign $newassignment the new assign class
-     * @return bool true or false
-     */
-    public function copy_grades_for_upgrade($oldassignment, $newassignment) {
-        global $CFG;
-
-        require_once($CFG->libdir.'/gradelib.php');
-
-        // get the old and new grade items
-        $oldgradeitems = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>'assignment', 'iteminstance'=>$oldassignment->id));
-        if (!$oldgradeitems) {
-            return false;
-        }
-        $oldgradeitem = array_pop($oldgradeitems);
-        if (!$oldgradeitem) {
-            return false;
-        }
-        $newgradeitems = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>'assign', 'iteminstance'=>$newassignment->get_instance()->id));
-        if (!$newgradeitems) {
-            return false;
-        }
-        $newgradeitem = array_pop($newgradeitems);
-        if (!$newgradeitem) {
-            return false;
-        }
-
-        $gradegrades = grade_grade::fetch_all(array('itemid'=>$oldgradeitem->id));
-        if ($gradegrades) {
-            foreach ($gradegrades as $gradeid=>$grade) {
-                $grade->itemid = $newgradeitem->id;
-                grade_update('mod/assign', $newassignment->get_course()->id, 'mod', 'assign', $newassignment->get_instance()->id, 0, $grade, NULL);
-            }
-        }
-        return true;
-    }
-
 }
index 353d3d1..879840e 100644 (file)
@@ -141,7 +141,7 @@ $string['maxpublishstate'] = 'Maximum visibility for blog entry before due date'
 $string['messageprovider:assignment_updates'] = 'Assignment (2.2) notifications';
 $string['modulename'] = 'Assignment (2.2)';
 $string['modulename_help'] = 'Assignments enable the teacher to specify a task either on or offline which can then be graded.';
-$string['modulenameplural'] = 'Assignments';
+$string['modulenameplural'] = 'Assignments (2.2)';
 $string['newsubmissions'] = 'Assignments submitted';
 $string['noassignments'] = 'There are no assignments yet';
 $string['noattempts'] = 'No attempts have been made on this assignment';
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 WARRA