MDL-13114 tool_uploadcourse: Initial commit
authorPiers Harding <piers@catalyst.net.nz>
Tue, 18 Jun 2013 07:12:09 +0000 (15:12 +0800)
committerFrederic Massart <fred@moodle.com>
Mon, 15 Jul 2013 02:02:54 +0000 (10:02 +0800)
admin/tool/uploadcourse/README.txt [new file with mode: 0644]
admin/tool/uploadcourse/cli/uploadcourse.php [new file with mode: 0644]
admin/tool/uploadcourse/course_form.php [new file with mode: 0644]
admin/tool/uploadcourse/index.php [new file with mode: 0644]
admin/tool/uploadcourse/lang/en/tool_uploadcourse.php [new file with mode: 0644]
admin/tool/uploadcourse/locallib.php [new file with mode: 0644]
admin/tool/uploadcourse/settings.php [new file with mode: 0644]
admin/tool/uploadcourse/version.php [new file with mode: 0644]

diff --git a/admin/tool/uploadcourse/README.txt b/admin/tool/uploadcourse/README.txt
new file mode 100644 (file)
index 0000000..5ee9354
--- /dev/null
@@ -0,0 +1,196 @@
+moodle-tool_uploadcourse
+========================
+
+Is a Moodle admin/tools plugin for uploading course outlines
+in much the same way that admin/tools/uploaduser works for users.
+These plugins became available from Moodle 2.2x and onwards, as
+this is when the admin/tools framework first appeared.
+
+https://gitorious.org/moodle-tool_uploadcourse
+
+There is also a bulk course category upload function available at https://gitorious.org/moodle-tool_uploadcoursecategory
+
+If you need to manage course enrolments via bulk upload then you should look at 
+the core user upload facility - http://docs.moodle.org/22/en/Upload_users
+
+This takes CSV files as input and enables override or augmentation
+with default parameter values.
+
+All the usual add,updated,rename, and delete functions.
+
+
+Thanks to Moshe Golden for help with getting the command line interface going.
+
+
+!!!! NOTE !!!!
+===============
+
+This plugin used to come with the full directory structure
+admin/tool/uploadcourse - this is nolonger the case so the
+installation proceedure has changed!
+
+General installation proceedures are here:
+http://docs.moodle.org/20/en/Installing_contributed_modules_or_plugins
+
+The basic process is:
+Download https://gitorious.org/moodle-tool_uploadcourse/moodle-tool_uploadcourse/archive-tarball/master
+unpack the file (probably called master) with tar -xzvf master
+This will give you a directory called moodle-tool_uploadcourse-moodle-tool_uploadcourse
+Move this directory and rename it into it's final position:
+mv moodle-tool_uploadcourse-moodle-tool_uploadcourse <Moodle dirroot>/admin/tool/uploadcourse
+
+Alternatively you can use git:
+cd <Moodle dirroot>/admin/tool
+git clone git@gitorious.org:moodle-tool_uploadcourse/moodle-tool_uploadcourse.git uploadcourse
+
+Be careful about leaving the .git directory in your live environment.
+
+
+CSV File format
+===============
+
+Possible column names are:
+fullname, shortname, category, idnumber, summary,
+format, showgrades, newsitems, teacher, editingteacher, student, modinfo,
+manager, coursecreator, guest, user, startdate, numsections, maxbytes, visible, groupmode, restrictmodules,
+enablecompletion, completionstartonenrol, completionnotify, hiddensections, groupmodeforce, lang, theme,
+cost, showreports, notifystudents, expirynotify, expirythreshold, requested,
+deleted,     // 1 means delete course
+oldshortname, // for renaming
+backupfile, // for restoring a course template after creation
+templatename, // course to use as a template - the shortname
+reset, // reset the course contents after upload - this resets everything
+          - so you loose groups, roles, logs, grades etc. Be Careful!!!
+
+An example file is:
+
+fullname,shortname,category,idnumber,summary,backupfile
+Computer Science 101,CS101,Cat1,CS101,The first thing you will ever know,/path/to/backup-moodle2-course-cs101-20120213-0748-nu.mbz
+
+As a general rule, the input values for fields are what you find on the data entry form if you inspect the HTML element.
+
+Format
+======
+The options for the format value are 'scorm', 'social', weeks', and 'topics'.
+
+Role Names
+===========
+ 'teacher', 'editingteacher', 'student', 'manager',
+'coursecreator', 'guest', 'user' are - where config permitting - you can
+substitute your own name for these roles (string value).
+
+Category
+========
+For category you must supply the category name as it is in Moodle and this
+field is case sensitive.  If Sub Categories are involved then the full
+category hierarchy needs to be specified as a '/' delimited string eg:
+'Miscellaneous / Sub Cat / Sub Sub Cat'.  The delimiter can be escaped with
+a back slash eg:  'some\/category'.
+
+Course Templating
+=================
+add column backupfile which has the fully qualified path name to a file on
+the server that has a a Moodle course backup in it. 
+
+Add a column templatename which is the shortname of an existing course that 
+will be copied over the top of the new course.
+
+Course Enrolment Methods
+=========================
+
+Enrolment methods need special CSV columns as there can be many per course, and the fields for each 
+method are flexible.  The following is an example with two enrolment methods - manual, and self - firstly you need 
+the column identifying the enrolment method enrolmethod_<n>, and then add the corresponding field values subscripted with _<n>.
+eg:
+fullname,shortname,category,idnumber,summary,enrolmethod_1,status_1,enrolmethod_2,name_2,password_2,customtext1_2
+Parent,Parent,,Parent,Parent,manual,1,self,self1,letmein,this is a custom message 1
+Students,Students,,Students,Students,manual,0,self,self2,letmein,this is a custom message 2
+Teachers,Teachers,,Teachers,Teachers,manual,0,self,self3,letmein,this is a custom message 3
+
+add the special columns for:
+ * delete - delete_<n> with value 1
+ * disable - disable_<n> with value 1
+
+startdate enrol_startdate enrol_enddate
+=======================================
+For startdate enrolstartdate, and enrolenddate the values should be supplied in the form like 31.01.2012 or
+31/01/2012 that can be consumed by strtotime() (http://php.net/manual/en/function.strtotime.php) - check
+your PHP locale settings for the fine tuning eg: m/d/y vs d/m/y.
+
+Enrolment method field 'enrolperiod' must be in seconds.  If this is supplied then enrolenddate will be calculated
+as enrolstartdate + enrolperiod.
+
+enrolperiod should be supplied in multiples of enrolment period measurements - 1 hour = 3600, 1 day = 86400
+and so on. OR - you can pass a text string that php strtotime() can recognise eg: '2 weeks' or '10 days'
+
+Enrolment Method Role
+=====================
+Default Role for an enrolment method is supplied by adding the 'role_<n>' column.  The expected value is the
+descriptive label for the given role eg: 'Student', or "Teacher'.
+
+Enrolment example:
+fullname,shortname,category,idnumber,summary,enrolmethod_1,enrolperiod_1,role_1
+a name,short1,Miscellaneous,id1,a summary,manual,864000,Manager
+
+Update Course:
+=================
+Make sure you have shortname in the csv. After uploading the file, select:
+Upload type: one of the update existing related options
+Existing course details: Overide with file 
+Allow Renames: Yes
+
+Update example:
+fullname,shortname
+new full name,short1
+
+
+Run it in batch mode
+=====================
+Execute Course Upload in batch mode - this must be run as the www-data user (or the equivalent user that the web server runs under).
+
+Options:
+-v, --verbose              Print verbose progress information
+-h, --help                 Print out this help
+-a, --action               Action to perform - addnew, addupdate, update, forceadd
+-m, --mode                 Mode of execution - delete, rename, nochange, file, filedefaults, missing
+-f, --file                 CSV File
+-d, --delimiter            delimiter - colon,semicolon,tab,cfg,comma
+-e, --encoding             File encoding - utf8 etc
+-c, --category             Course category
+-s, --templateshortname    Template course by shortname
+-t, --template             Template course by backup file
+-g, --format               Course format - weeks,scorm,social,topics
+-n, --numsections          Number of sections
+
+
+Example:
+sudo -u www-data /usr/bin/php admin/tool/uploadcourse/cli/uploadcourse.php --action=addupdate \
+                 --mode=delete --file=./courses.csv --delimiter=comma
+
+
+
+Installation
+=================
+git clone this repository into <moodle root>/admin/tools/uploadcourse directory.
+
+Point your browser at Moodle, and login as admin.  This should kick off
+the upgrade so that Moodle can now recognise the new plugin.
+
+This was inspired in part by a need for a complimentary function for uploading
+courses (as for users) for the the NZ MLE tools for Identity and 
+Access Managment (synchronising users with the School SMS):
+https://gitorious.org/pla-udi
+and
+https://gitorious.org/pla-udi/mle_ide_tools
+
+Copyright (C) Piers Harding 2011 and beyond, All rights reserved
+
+moodle-tool_uploadcourse free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This library 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
+Lesser General Public License for more details.
diff --git a/admin/tool/uploadcourse/cli/uploadcourse.php b/admin/tool/uploadcourse/cli/uploadcourse.php
new file mode 100644 (file)
index 0000000..fd5eea3
--- /dev/null
@@ -0,0 +1,260 @@
+<?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/>.
+
+/**
+ * CLI Bulk course registration script from a comma separated file
+ *
+ * @package    tool_uploadcourse
+ * @subpackage uploadcourse
+ * @copyright  2004 onwards Martin Dougiamas (http://dougiamas.com)
+ * @copyright  2012 Piers Harding
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ *
+ * Notes:
+ *   - it is required to use the web server account when executing PHP CLI scripts
+ *   - you need to change the "www-data" to match the apache user account
+ *   - use "su" if "sudo" not available
+ *
+ */
+
+define('CLI_SCRIPT', true);
+
+require(dirname(dirname(dirname(dirname(dirname(__FILE__))))).'/config.php');
+
+require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/csvlib.class.php');
+require_once($CFG->dirroot.'/course/lib.php');
+require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+require_once($CFG->libdir . '/filelib.php');
+require_once($CFG->libdir.'/clilib.php');
+require_once('../locallib.php');
+
+$courseconfig = get_config('moodlecourse');
+
+// Now get cli options.
+list($options, $unrecognized) = cli_get_params(
+                 array('verbose' => false,
+                       'help' => false,
+                       'action' => '',
+                       'mode' => 'nochange',
+                       'file' => '',
+                       'delimiter' => 'comma',
+                       'encoding' => 'UTF-8',
+                       'category' => false,
+                       'templateshortname' => false,
+                       'template' => false,
+                       'format' => $courseconfig->format,
+                       'numsections' => $courseconfig->numsections,
+                       'reset' => false,
+                         ),
+                 array('v' => 'verbose',
+                       'h' => 'help',
+                       'a' => 'action',
+                       'm' => 'mode',
+                       'f' => 'file',
+                       'd' => 'delimiter',
+                       'e' => 'encoding',
+                       'c' => 'category',
+                       's' => 'templateshortname',
+                       't' => 'template',
+                       'g' => 'format',
+                       'n' => 'numsections',
+                       'r' => 'reset',
+                        ));
+
+if ($unrecognized) {
+    $unrecognized = implode("\n  ", $unrecognized);
+    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+$help =
+"Execute Course Upload.
+
+Options:
+-v, --verbose              Print verbose progress information
+-h, --help                 Print out this help
+-a, --action               Action to perform - addnew, addupdate, update, forceadd
+-m, --mode                 Mode of execution - delete, rename, nochange, file, filedefaults, missing
+-f, --file                 CSV File
+-d, --delimiter            delimiter - colon,semicolon,tab,cfg,comma
+-e, --encoding             File encoding - utf8 etc
+-c, --category             Course category
+-s, --templateshortname    Template course by shortname
+-t, --template             Template course by backup file
+-g, --format               Course format - weeks,scorm,social,topics
+-n, --numsections          Number of sections
+-r, --reset                Run the course reset by default after each course import
+
+
+Example:
+\$sudo -u www-data /usr/bin/php admin/tool/uploadcourse/cli/uploadcourse.php --action=addupdate \\
+       --mode=delete --file=./courses.csv --delimiter=comma
+";
+
+if ($options['help']) {
+    echo $help;
+    die;
+}
+echo "Moodle course uploader running ...\n";
+
+$actions = array('addnew' => CC_COURSE_ADDNEW,
+                 'addupdate' => CC_COURSE_ADD_UPDATE,
+                 'update' => CC_COURSE_UPDATE,
+                 'forceadd' => CC_COURSE_ADDINC);
+if (!isset($options['action']) ||
+    !isset($actions[$options['action']]) ||
+    ($options['action'] != 'addnew' && !isset($options['mode']))) {
+    echo get_string('invalidinput', 'tool_uploadcourse')."\n";
+    echo $help;
+    die;
+}
+
+if (!empty($options['verbose']) || $CFG->debug) {
+    define('CC_DEBUG', true);
+}
+
+if (!isset($actions[$options['action']])) {
+    echo get_string('invalidaction', 'tool_uploadcourse')."\n";
+    echo $help;
+    die;
+}
+$options['cctype'] = $actions[$options['action']];
+
+$updatetype = array('nochange' => CC_UPDATE_NOCHANGES,
+                'file' => CC_UPDATE_FILEOVERRIDE,
+                'filedefaults' => CC_UPDATE_ALLOVERRIDE,
+                'missing' => CC_UPDATE_MISSING);
+if ($options['mode'] == 'rename') {
+    $options['ccallowrenames'] = 1;
+    unset($options['mode']);
+} else if ($options['mode'] == 'delete') {
+    $options['ccallowdeletes'] = 1;
+    unset($options['mode']);
+} else if (!isset($updatetype[$options['mode']])) {
+    echo get_string('invalidmode', 'tool_uploadcourse')."\n";
+    echo $help;
+    die;
+}
+if (isset($options['mode'])) {
+    $options['ccupdatetype'] = $updatetype[$options['mode']];
+}
+$options['ccstandardshortnames'] = 1;
+$options['startdate'] = time() + 3600 * 24;
+$options['hiddensections'] = $courseconfig->hiddensections;
+$options['newsitems'] = $courseconfig->newsitems;
+$options['showgrades'] = $courseconfig->showgrades;
+$options['showreports'] = $courseconfig->showreports;
+$options['maxbytes'] = $courseconfig->maxbytes;
+$options['legacyfiles'] = 0;
+$options['groupmode'] = $courseconfig->groupmode;
+$options['groupmodeforce'] = $courseconfig->groupmodeforce;
+$options['visible'] = $courseconfig->visible;
+$options['lang'] =  $courseconfig->lang;
+
+if ($options['category']) {
+    $split = preg_split('|(?<!\\\)/|', $options['category']);
+    $categories = array();
+    foreach ($split as $cat) {
+        $cat = preg_replace('/\\\/', '', $cat);
+        $categories[]= $cat;
+    }
+    $options['category'] = 0;
+    foreach ($categories as $cat) {
+        // Does the category exist - does the category hierachy make sense.
+        $category = $DB->get_record('course_categories', array('name'=>trim($cat), 'parent' => $options['category']));
+        if (empty($category)) {
+            echo get_string('invalidcategory', 'tool_uploadcourse')."\n";
+            echo $help;
+            die;
+        }
+        $options['category'] = $category->id;
+    }
+    $options['cccategory'] = $options['category'];
+} else {
+    $categories = $DB->get_records('course_categories');
+    if (empty($categories)) {
+        echo get_string('invalidcategory', 'tool_uploadcourse')."\n";
+        echo $help;
+        die;
+    }
+    $category = array_shift($categories);
+    $options['cccategory'] = $category->id;
+}
+
+if (isset($options['templateshortname'])) {
+    $options['ccshortname'] = $options['templateshortname'];
+}
+
+$options['file'] = realpath($options['file']);
+if (!file_exists($options['file'])) {
+    echo get_string('invalidcsvfile', 'tool_uploadcourse')."\n";
+    echo $help;
+    die;
+}
+
+$encodings = textlib::get_encodings();
+if (!isset($encodings[$options['encoding']])) {
+    echo get_string('invalidencoding', 'tool_uploadcourse')."\n";
+    echo $help;
+    die;
+}
+
+
+if ($options['template']) {
+    $options['template'] = realpath($options['template']);
+}
+if ($options['template'] && !file_exists($options['template'])) {
+    echo get_string('invalidtemplatefile', 'tool_uploadcourse')."\n";
+    echo $help;
+    die;
+}
+$tmpdir = $CFG->tempdir . '/backup';
+if (!check_dir_exists($tmpdir, true, true)) {
+    throw new restore_controller_exception('cannot_create_backup_temp_dir');
+}
+$filename = restore_controller::get_tempdir_name(SITEID, $USER->id);
+$restorefile = null;
+if ($options['template']) {
+    $restorefile = $options['template'];
+}
+
+$formdata = (object) $options;
+
+
+$returnurl = new moodle_url('/admin/tool/uploadcourse/index.php');
+$bulknurl  = new moodle_url('/admin/tool/uploadcourse/index.php');
+$std_fields = tool_uploadcourse_std_fields();
+
+// Emulate normal session.
+cron_setup_user();
+
+$content = file_get_contents($formdata->file);
+$iid = csv_import_reader::get_new_iid('uploadcourse');
+$cir = new csv_import_reader($iid, 'uploadcourse');
+$readcount = $cir->load_csv_content($content, $formdata->encoding, $formdata->delimiter);
+$filecolumns = tool_uploadcourse_validate_course_upload_columns($cir, $std_fields, $returnurl);
+unset($content);
+if ($readcount === false) {
+    print_error('csvfileerror', 'tool_uploadcourse', $returnurl, $cir->get_error());
+} else if ($readcount == 0) {
+    print_error('csvemptyfile', 'error', $returnurl, $cir->get_error());
+}
+echo "CSV read count: ".$readcount."\n";
+
+$result = tool_uploadcourse_process_course_upload($formdata, $cir, $filecolumns, $restorefile, true);
+
+exit($result);
diff --git a/admin/tool/uploadcourse/course_form.php b/admin/tool/uploadcourse/course_form.php
new file mode 100644 (file)
index 0000000..3f17cc7
--- /dev/null
@@ -0,0 +1,350 @@
+<?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/>.
+
+/**
+ * Bulk course upload forms
+ *
+ * @package    tool_uploadcourse
+ * @subpackage uploadcourse
+ * @copyright  2007 Dan Poltawski
+ * @copyright  2011 Piers Harding
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/formslib.php');
+
+
+/**
+ * Upload a file CVS file with course information.
+ *
+ * @copyright  2007 Petr Skoda  {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class admin_uploadcourse_form1 extends moodleform {
+    /**
+     * The standard form definiton
+     * @return object $form
+     */
+    public function definition () {
+        $mform = $this->_form;
+
+        $mform->addElement('header', 'settingsheader', get_string('upload'));
+
+        $mform->addElement('filepicker', 'coursefile', get_string('file'));
+        $mform->addRule('coursefile', null, 'required');
+
+        $choices = csv_import_reader::get_delimiter_list();
+        $mform->addElement('select', 'delimiter_name', get_string('csvdelimiter', 'tool_uploadcourse'), $choices);
+        if (array_key_exists('cfg', $choices)) {
+            $mform->setDefault('delimiter_name', 'cfg');
+        } else if (get_string('listsep', 'langconfig') == ';') {
+            $mform->setDefault('delimiter_name', 'semicolon');
+        } else {
+            $mform->setDefault('delimiter_name', 'comma');
+        }
+
+        $choices = textlib::get_encodings();
+        $mform->addElement('select', 'encoding', get_string('encoding', 'tool_uploadcourse'), $choices);
+        $mform->setDefault('encoding', 'UTF-8');
+
+        $choices = array('10'=>10, '20'=>20, '100'=>100, '1000'=>1000, '100000'=>100000);
+        $mform->addElement('select', 'previewrows', get_string('rowpreviewnum', 'tool_uploadcourse'), $choices);
+        $mform->setType('previewrows', PARAM_INT);
+
+        $this->add_action_buttons(false, get_string('uploadcourses', 'tool_uploadcourse'));
+    }
+}
+
+
+/**
+ * Specify course upload details
+ *
+ * @copyright  2007 Petr Skoda  {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class admin_uploadcourse_form2 extends moodleform {
+    /**
+     * The standard form definiton
+     * @return object $form
+     */
+    public function definition () {
+        global $CFG, $COURSE, $DB;
+
+        $mform   = $this->_form;
+        $columns = $this->_customdata['columns'];
+        $data    = $this->_customdata['data'];
+        $courseconfig = get_config('moodlecourse');
+
+        // I am the template course, why should it be the administrator? we have roles now, other ppl may use this script ;-).
+        $templatecourse = $COURSE;
+
+        // Upload settings and file.
+        $mform->addElement('header', 'settingsheader', get_string('settings'));
+
+        $choices = array(CC_COURSE_ADDNEW     => get_string('ccoptype_addnew', 'tool_uploadcourse'),
+                         CC_COURSE_ADDINC     => get_string('ccoptype_addinc', 'tool_uploadcourse'),
+                         CC_COURSE_ADD_UPDATE => get_string('ccoptype_addupdate', 'tool_uploadcourse'),
+                         CC_COURSE_UPDATE     => get_string('ccoptype_update', 'tool_uploadcourse'));
+        $mform->addElement('select', 'cctype', get_string('ccoptype', 'tool_uploadcourse'), $choices);
+
+        $choices = array(CC_UPDATE_NOCHANGES    => get_string('nochanges', 'tool_uploadcourse'),
+                         CC_UPDATE_FILEOVERRIDE => get_string('ccupdatefromfile', 'tool_uploadcourse'),
+                         CC_UPDATE_ALLOVERRIDE  => get_string('ccupdateall', 'tool_uploadcourse'),
+                         CC_UPDATE_MISSING      => get_string('ccupdatemissing', 'tool_uploadcourse'));
+        $mform->addElement('select', 'ccupdatetype', get_string('ccupdatetype', 'tool_uploadcourse'), $choices);
+        $mform->setDefault('ccupdatetype', CC_UPDATE_NOCHANGES);
+        $mform->disabledIf('ccupdatetype', 'cctype', 'eq', CC_COURSE_ADDNEW);
+        $mform->disabledIf('ccupdatetype', 'cctype', 'eq', CC_COURSE_ADDINC);
+
+        $mform->addElement('selectyesno', 'ccallowrenames', get_string('allowrenames', 'tool_uploadcourse'));
+        $mform->setDefault('ccallowrenames', 0);
+        $mform->disabledIf('ccallowrenames', 'cctype', 'eq', CC_COURSE_ADDNEW);
+        $mform->disabledIf('ccallowrenames', 'cctype', 'eq', CC_COURSE_ADDINC);
+
+        $mform->addElement('selectyesno', 'ccallowdeletes', get_string('allowdeletes', 'tool_uploadcourse'));
+        $mform->setDefault('ccallowdeletes', 0);
+        $mform->disabledIf('ccallowdeletes', 'cctype', 'eq', CC_COURSE_ADDNEW);
+        $mform->disabledIf('ccallowdeletes', 'cctype', 'eq', CC_COURSE_ADDINC);
+
+        $mform->addElement('selectyesno', 'reset', get_string('reset', 'tool_uploadcourse'));
+        $mform->setDefault('ccallowdeletes', 0);
+        $mform->disabledIf('ccallowdeletes', 'cctype', 'eq', CC_COURSE_ADDNEW);
+        $mform->disabledIf('ccallowdeletes', 'cctype', 'eq', CC_COURSE_ADDINC);
+
+        $mform->addElement('selectyesno', 'ccstandardshortnames', get_string('ccstandardshortnames', 'tool_uploadcourse'));
+        $mform->setDefault('ccstandardshortnames', 1);
+
+        // Default values.
+        $mform->addElement('header', 'defaultheader', get_string('defaultvalues', 'tool_uploadcourse'));
+        $displaylist = array();
+        $parentlist = array();
+        make_categories_list($displaylist, $parentlist, 'moodle/course:create');
+        $mform->addElement('select', 'cccategory', get_string('category'), $displaylist);
+        $mform->addHelpButton('cccategory', 'category');
+
+        $mform->addElement('text', 'ccshortname', get_string('ccshortnametemplate', 'tool_uploadcourse'),
+                           'maxlength="100" size="20"');
+        $mform->addHelpButton('ccshortname', 'shortnamecourse', 'tool_uploadcourse');
+        $mform->disabledIf('ccshortname', 'cctype', 'eq', CC_COURSE_ADD_UPDATE);
+        $mform->disabledIf('ccshortname', 'cctype', 'eq', CC_COURSE_UPDATE);
+
+        $courseformats = get_plugin_list('format');
+        $formcourseformats = array();
+        foreach ($courseformats as $courseformat => $formatdir) {
+            $formcourseformats[$courseformat] = get_string('pluginname', "format_$courseformat");
+        }
+        $mform->addElement('select', 'format', get_string('format'), $formcourseformats);
+        $mform->addHelpButton('format', 'format');
+        $mform->setDefault('format', $courseconfig->format);
+
+        for ($i = 0; $i <= $courseconfig->maxsections; $i++) {
+            $sectionmenu[$i] = "$i";
+        }
+        $mform->addElement('select', 'numsections', get_string('numberweeks'), $sectionmenu);
+        $mform->setDefault('numsections', $courseconfig->numsections);
+
+        $mform->addElement('date_selector', 'startdate', get_string('startdate'));
+        $mform->addHelpButton('startdate', 'startdate');
+        $mform->setDefault('startdate', time() + 3600 * 24);
+
+        $choices = array();
+        $choices['0'] = get_string('hiddensectionscollapsed');
+        $choices['1'] = get_string('hiddensectionsinvisible');
+        $mform->addElement('select', 'hiddensections', get_string('hiddensections'), $choices);
+        $mform->addHelpButton('hiddensections', 'hiddensections');
+        $mform->setDefault('hiddensections', $courseconfig->hiddensections);
+
+        $options = range(0, 10);
+        $mform->addElement('select', 'newsitems', get_string('newsitemsnumber'), $options);
+        $mform->addHelpButton('newsitems', 'newsitemsnumber');
+        $mform->setDefault('newsitems', $courseconfig->newsitems);
+
+        $mform->addElement('selectyesno', 'showgrades', get_string('showgrades'));
+        $mform->addHelpButton('showgrades', 'showgrades');
+        $mform->setDefault('showgrades', $courseconfig->showgrades);
+
+        $mform->addElement('selectyesno', 'showreports', get_string('showreports'));
+        $mform->addHelpButton('showreports', 'showreports');
+        $mform->setDefault('showreports', $courseconfig->showreports);
+
+        $choices = get_max_upload_sizes($CFG->maxbytes);
+        $mform->addElement('select', 'maxbytes', get_string('maximumupload'), $choices);
+        $mform->addHelpButton('maxbytes', 'maximumupload');
+        $mform->setDefault('maxbytes', $courseconfig->maxbytes);
+
+        if (!empty($course->legacyfiles) or !empty($CFG->legacyfilesinnewcourses)) {
+            if (empty($course->legacyfiles)) {
+                // 0 or missing means no legacy files ever used in this course - new course or nobody turned on legacy files yet.
+                $choices = array('0'=>get_string('no'), '2'=>get_string('yes'));
+            } else {
+                $choices = array('1'=>get_string('no'), '2'=>get_string('yes'));
+            }
+            $mform->addElement('select', 'legacyfiles', get_string('courselegacyfiles'), $choices);
+            $mform->addHelpButton('legacyfiles', 'courselegacyfiles');
+            if (!isset($courseconfig->legacyfiles)) {
+                // In case this was not initialised properly due to switching of $CFG->legacyfilesinnewcourses.
+                $courseconfig->legacyfiles = 0;
+            }
+            $mform->setDefault('legacyfiles', $courseconfig->legacyfiles);
+        }
+
+        if (!empty($CFG->allowcoursethemes)) {
+            $themeobjects = get_list_of_themes();
+            $themes=array();
+            $themes[''] = get_string('forceno');
+            foreach ($themeobjects as $key => $theme) {
+                if (empty($theme->hidefromselector)) {
+                    $themes[$key] = get_string('pluginname', 'theme_'.$theme->name);
+                }
+            }
+            $mform->addElement('select', 'theme', get_string('forcetheme'), $themes);
+        }
+        $courseshortnames = $DB->get_records('course', null, $sort='shortname', 'id,shortname,idnumber');
+        $formccourseshortnames = array('none' => get_string('none'));
+        foreach ($courseshortnames as $course) {
+            $formccourseshortnames[$course->shortname] = $course->shortname;
+        }
+        $mform->addElement('select', 'templatename', get_string('coursetemplatename', 'tool_uploadcourse'), $formccourseshortnames);
+        $mform->addHelpButton('templatename', 'coursetemplatename', 'tool_uploadcourse');
+        $mform->setDefault('templatename', 'none');
+
+        $contextid = $this->_customdata['contextid'];
+        $mform->addElement('hidden', 'contextid', $contextid);
+        $mform->addElement('filepicker', 'restorefile', get_string('templatefile', 'tool_uploadcourse'));
+
+        enrol_course_edit_form($mform, null, get_context_instance(CONTEXT_SYSTEM));
+
+        $mform->addElement('header', '', get_string('groups', 'group'));
+
+        $choices = array();
+        $choices[NOGROUPS] = get_string('groupsnone', 'group');
+        $choices[SEPARATEGROUPS] = get_string('groupsseparate', 'group');
+        $choices[VISIBLEGROUPS] = get_string('groupsvisible', 'group');
+        $mform->addElement('select', 'groupmode', get_string('groupmode', 'group'), $choices);
+        $mform->addHelpButton('groupmode', 'groupmode', 'group');
+        $mform->setDefault('groupmode', $courseconfig->groupmode);
+
+        $choices = array();
+        $choices['0'] = get_string('no');
+        $choices['1'] = get_string('yes');
+        $mform->addElement('select', 'groupmodeforce', get_string('groupmodeforce', 'group'), $choices);
+        $mform->addHelpButton('groupmodeforce', 'groupmodeforce', 'group');
+        $mform->setDefault('groupmodeforce', $courseconfig->groupmodeforce);
+
+        // Default groupings selector.
+        $options = array();
+        $options[0] = get_string('none');
+        $mform->addElement('select', 'defaultgroupingid', get_string('defaultgrouping', 'group'), $options);
+
+        $mform->addElement('header', '', get_string('availability'));
+
+        $choices = array();
+        $choices['0'] = get_string('courseavailablenot');
+        $choices['1'] = get_string('courseavailable');
+        $mform->addElement('select', 'visible', get_string('availability'), $choices);
+        $mform->addHelpButton('visible', 'availability');
+        $mform->setDefault('visible', $courseconfig->visible);
+
+        $mform->addElement('header', '', get_string('language'));
+
+        $languages=array();
+        $languages[''] = get_string('forceno');
+        $languages += get_string_manager()->get_list_of_translations();
+        $mform->addElement('select', 'lang', get_string('forcelanguage'), $languages);
+        $mform->setDefault('lang', $courseconfig->lang);
+
+        // Hidden fields.
+        $mform->addElement('hidden', 'iid');
+        $mform->setType('iid', PARAM_INT);
+
+        $mform->addElement('hidden', 'previewrows');
+        $mform->setType('previewrows', PARAM_INT);
+
+        $this->add_action_buttons(true, get_string('uploadcourses', 'tool_uploadcourse'));
+
+        $this->set_data($data);
+    }
+
+    /**
+     * Form tweaks that depend on current data.
+     */
+    public function definition_after_data() {
+        $mform   = $this->_form;
+        $columns = $this->_customdata['columns'];
+
+        foreach ($columns as $column) {
+            if ($mform->elementExists($column)) {
+                $mform->removeElement($column);
+            }
+        }
+
+    }
+
+    /**
+     * Server side validation.
+     * @param array $data - form data
+     * @param object $files  - form files
+     * @return array $errors - form errors
+     */
+    public function validation($data, $files) {
+        global $DB;
+
+        $errors = parent::validation($data, $files);
+        $columns = $this->_customdata['columns'];
+        $optype  = $data['cctype'];
+
+        // Look for other required data.
+        if ($optype != CC_COURSE_UPDATE) {
+            if (!in_array('fullname', $columns)) {
+                if (isset($errors['cctype'])) {
+                    $errors['cctype'] .= ' ';
+                }
+                $errors['cctype'] .= get_string('missingfield', 'error', 'fullname');
+            }
+            if (!in_array('summary', $columns)) {
+                if (isset($errors['cctype'])) {
+                    $errors['cctype'] .= ' ';
+                }
+                $errors['cctype'] .= get_string('missingfield', 'error', 'summary');
+            }
+        }
+        if (!empty($data['templatename']) && $data['templatename'] != 'none') {
+            if (!$template = $DB->get_record('course', array('shortname' => $data['templatename']))) {
+                $errors['templatename'] = get_string('missingtemplate', 'tool_uploadcourse');
+            }
+        }
+
+        return $errors;
+    }
+
+    /**
+     * Used to reformat the data from the editor component
+     *
+     * @return stdClass
+     */
+    public function get_data() {
+        $data = parent::get_data();
+
+        if ($data !== null and isset($data->description)) {
+            $data->descriptionformat = $data->description['format'];
+            $data->description = $data->description['text'];
+        }
+
+        return $data;
+    }
+}
diff --git a/admin/tool/uploadcourse/index.php b/admin/tool/uploadcourse/index.php
new file mode 100644 (file)
index 0000000..e92749f
--- /dev/null
@@ -0,0 +1,184 @@
+<?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/>.
+
+/**
+ * Bulk course registration script from a comma separated file
+ *
+ * @package    tool_uploadcourse
+ * @copyright  2004 onwards Martin Dougiamas (http://dougiamas.com)
+ * @copyright  2011 Piers Harding
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require('../../../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/csvlib.class.php');
+require_once($CFG->dirroot.'/course/lib.php');
+require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+require_once($CFG->libdir . '/filelib.php');
+require_once('locallib.php');
+require_once('course_form.php');
+
+$iid         = optional_param('iid', '', PARAM_INT);
+$previewrows = optional_param('previewrows', 10, PARAM_INT);
+require_login();
+admin_externalpage_setup('tooluploadcourse');
+
+$returnurl = new moodle_url('/admin/tool/uploadcourse/index.php');
+$bulknurl  = new moodle_url('/admin/tool/uploadcourse/index.php');
+$std_fields = tool_uploadcourse_std_fields();
+
+
+if (empty($iid)) {
+    $mform1 = new admin_uploadcourse_form1();
+
+    if ($formdata = $mform1->get_data()) {
+        $iid = csv_import_reader::get_new_iid('uploadcourse');
+        $cir = new csv_import_reader($iid, 'uploadcourse');
+
+        $content = $mform1->get_file_content('coursefile');
+
+        $readcount = $cir->load_csv_content($content, $formdata->encoding, $formdata->delimiter_name);
+        unset($content);
+
+        if ($readcount === false) {
+            print_error('csvfileerror', 'tool_uploadcourse', $returnurl, $cir->get_error());
+        } else if ($readcount == 0) {
+            print_error('csvemptyfile', 'error', $returnurl, $cir->get_error());
+        }
+        // Test if columns ok.
+        $filecolumns = tool_uploadcourse_validate_course_upload_columns($cir, $std_fields, $returnurl);
+        // Continue to form2.
+
+    } else {
+        echo $OUTPUT->header();
+
+        echo $OUTPUT->heading_with_help(get_string('uploadcourses', 'tool_uploadcourse'), 'uploadcourses', 'tool_uploadcourse');
+
+        $mform1->display();
+        echo $OUTPUT->footer();
+        die;
+    }
+} else {
+    $cir = new csv_import_reader($iid, 'uploadcourse');
+    $filecolumns = tool_uploadcourse_validate_course_upload_columns($cir, $std_fields, $returnurl);
+}
+
+$frontpagecontext = context_course::instance(SITEID);
+$mform2 = new admin_uploadcourse_form2(null,
+                                       array('contextid' => $frontpagecontext->id,
+                                             'columns' => $filecolumns,
+                                             'data' => array('iid'=>$iid, 'previewrows'=>$previewrows)));
+
+// If a file has been uploaded, then process it.
+if ($formdata = $mform2->is_cancelled()) {
+    $cir->cleanup(true);
+    redirect($returnurl);
+} else if ($formdata = $mform2->get_data()) {
+    // Print the header.
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(get_string('uploadcoursesresult', 'tool_uploadcourse'));
+
+    $tmpdir = $CFG->tempdir . '/backup';
+    if (!check_dir_exists($tmpdir, true, true)) {
+        throw new restore_controller_exception('cannot_create_backup_temp_dir');
+    }
+    $filename = restore_controller::get_tempdir_name(SITEID, $USER->id);
+    $restorefile = $tmpdir . '/' . $filename;
+    if (!$mform2->save_file('restorefile', $restorefile)) {
+        $restorefile = null;
+    }
+    $bulk              = isset($formdata->ccbulk) ? $formdata->ccbulk : 0;
+
+    tool_uploadcourse_process_course_upload($formdata, $cir, $filecolumns, $restorefile);
+
+    echo $OUTPUT->box_end();
+
+    if ($bulk) {
+        echo $OUTPUT->continue_button($bulknurl);
+    } else {
+        echo $OUTPUT->continue_button($returnurl);
+    }
+    echo $OUTPUT->footer();
+    die;
+}
+
+// Print the header.
+echo $OUTPUT->header();
+
+echo $OUTPUT->heading(get_string('uploadcoursespreview', 'tool_uploadcourse'));
+
+// NOTE: this is JUST csv processing preview, we must not prevent import from here if there is something in the file!!
+//       this was intended for validation of csv formatting and encoding, not filtering the data!!!!
+//       we definitely must not process the whole file!
+
+// Preview table data.
+$data = array();
+$cir->init();
+$linenum = 1; // Column header is first line.
+while ($linenum <= $previewrows and $fields = $cir->next()) {
+    $linenum++;
+    $rowcols = array();
+    $rowcols['line'] = $linenum;
+    foreach ($fields as $key => $field) {
+        $rowcols[$filecolumns[$key]] = s($field);
+    }
+    $rowcols['status'] = array();
+
+    if (isset($rowcols['shortname'])) {
+        $stdshortname = clean_param($rowcols['shortname'], PARAM_MULTILANG);
+        if ($rowcols['shortname'] !== $stdshortname) {
+            $rowcols['status'][] = get_string('invalidshortnameupload');
+        }
+        if ($courseid = $DB->get_field('course', 'id', array('shortname'=>$stdshortname))) {
+            $rowcols['shortname'] = html_writer::link(new moodle_url('/course/view.php',
+                                                                     array('id' => $courseid)),
+                                                                     $rowcols['shortname']);
+        }
+    } else {
+        $rowcols['status'][] = get_string('missingshortname');
+    }
+
+    $rowcols['status'] = implode('<br />', $rowcols['status']);
+    $data[] = $rowcols;
+}
+if ($fields = $cir->next()) {
+    $data[] = array_fill(0, count($fields) + 2, '...');
+}
+$cir->close();
+
+$table = new html_table();
+$table->id = "ccpreview";
+$table->attributes['class'] = 'generaltable';
+$table->tablealign = 'center';
+$table->summary = get_string('uploadcoursespreview', 'tool_uploadcourse');
+$table->head = array();
+$table->data = $data;
+
+$table->head[] = get_string('cccsvline', 'tool_uploadcourse');
+foreach ($filecolumns as $column) {
+    $table->head[] = $column;
+}
+$table->head[] = get_string('status');
+
+echo html_writer::tag('div', html_writer::table($table), array('class'=>'flexible-wrap'));
+
+// Print the form.
+$mform2->display();
+echo $OUTPUT->footer();
+die;
+
diff --git a/admin/tool/uploadcourse/lang/en/tool_uploadcourse.php b/admin/tool/uploadcourse/lang/en/tool_uploadcourse.php
new file mode 100644 (file)
index 0000000..dad6795
--- /dev/null
@@ -0,0 +1,130 @@
+<?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/>.
+
+/**
+ * Strings for component 'tool_uploadcourse', language 'en', branch 'MOODLE_22_STABLE'
+ *
+ * @package    tool_uploadcourse
+ * @subpackage uploadcourse
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @copyright  2011 Piers Harding
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['csvfileerror'] = 'There is something wrong with the format of the CSV file - please check the number of headings and columns match, and that the delimiter and file encoding are correct (don\t use comma-quoted as Moodle does not support it): {$a}';
+$string['allowdeletes'] = 'Allow deletes';
+$string['allowrenames'] = 'Allow renames';
+$string['csvdelimiter'] = 'CSV delimiter';
+$string['defaultvalues'] = 'Default values';
+$string['deleteerrors'] = 'Delete errors';
+$string['encoding'] = 'Encoding';
+$string['errors'] = 'Errors';
+$string['invalidinput'] = 'You must specify a valid combination of --action and --mode';
+$string['nochanges'] = 'No changes';
+$string['pluginname'] = 'Course upload';
+$string['renameerrors'] = 'Rename errors';
+$string['requiredtemplate'] = 'Required. You may use template syntax here (%l = lastname, %f = firstname, %u = coursename). See help for details and examples.';
+$string['rowpreviewnum'] = 'Preview rows';
+$string['uploadpicture_badcoursefield'] = 'The course attribute specified is not valid. Please, try again.';
+$string['uploadpicture_cannotmovezip'] = 'Cannot move zip file to temporary directory.';
+$string['uploadpicture_cannotprocessdir'] = 'Cannot process unzipped files.';
+$string['uploadpicture_cannotsave'] = 'Cannot save picture for course {$a}. Check original picture file.';
+$string['uploadpicture_cannotunzip'] = 'Cannot unzip pictures file.';
+$string['uploadpicture_invalidfilename'] = 'Picture file {$a} has invalid characters in its name. Skipping.';
+$string['uploadpicture_overwrite'] = 'Overwrite existing course pictures?';
+$string['uploadpicture_coursefield'] = 'Course attribute to use to match pictures:';
+$string['uploadpicture_coursenotfound'] = 'Course with a \'{$a->coursefield}\' value of \'{$a->coursevalue}\' does not exist. Skipping.';
+$string['uploadpicture_courseskipped'] = 'Skipping course {$a} (already has a picture).';
+$string['uploadpicture_courseupdated'] = 'Picture updated for course {$a}.';
+$string['uploadpictures'] = 'Upload course pictures';
+$string['uploadpictures_help'] = 'Course pictures can be uploaded as a zip file of image files. The image files should be named chosen-course-attribute.extension, for example course1234.jpg for a course with coursename course1234.';
+$string['uploadcourses'] = 'Upload courses';
+$string['uploadcourses_help'] = 'Courses may be uploaded (and optionally enrolled in courses) via text file. The format of the file should be as follows:
+
+* Each line of the file contains one record
+* Each record is a series of data separated by commas (or other delimiters)
+* The first record contains a list of fieldnames defining the format of the rest of the file
+* Required fieldnames are coursename, password, firstname, lastname, email';
+$string['uploadcoursespreview'] = 'Upload courses preview';
+$string['uploadcoursesresult'] = 'Upload courses results';
+$string['courseupdated'] = 'Course updated';
+$string['courseuptodate'] = 'Course up-to-date';
+$string['coursedeleted'] = 'Course deleted';
+$string['courserenamed'] = 'Course renamed';
+$string['coursescreated'] = 'Courses created';
+$string['coursesdeleted'] = 'Courses deleted';
+$string['coursesrenamed'] = 'Courses renamed';
+$string['coursesskipped'] = 'Courses skipped';
+$string['coursesupdated'] = 'Courses updated';
+$string['coursenotadded'] = 'Course not added - already exists';
+$string['coursenotaddederror'] = 'Course not added - error';
+$string['coursenotdeletederror'] = 'Course not deleted - error';
+$string['coursenotdeletedmissing'] = 'Course not deleted - missing';
+$string['coursenotdeletedoff'] = 'Course not deleted - delete off';
+$string['coursenotdeletedadmin'] = 'Course not deleted - no admin access';
+$string['coursenotupdatederror'] = 'Course not updated - error';
+$string['coursenotupdatednotexists'] = 'Course not updated - does not exist';
+$string['coursenotupdatedadmin'] = 'Course not updated - no admin';
+$string['coursenotrenamedexists'] = 'Course not renamed - target exists';
+$string['coursenotrenamedmissing'] = 'Course not renamed - source missing';
+$string['coursenotrenamedoff'] = 'Course not renamed - renaming off';
+$string['coursenotrenamedadmin'] = 'Course not renamed - no admin';
+$string['invalidvalue'] = 'Invalid value for field {$a}';
+$string['shortnamecourse'] = 'Shortname';
+$string['shortnamecourse_help'] = 'The short name of the course is displayed in the navigation. You may use template syntax here (%f = fullname, %i = idnumber), or enter an initial value that is incremented. See help for details and examples.';
+$string['idnumbernotunique'] = 'idnumber is not unique';
+$string['ccbulk'] = 'Select for bulk operations';
+$string['ccbulkall'] = 'All courses';
+$string['ccbulknew'] = 'New courses';
+$string['ccbulkupdated'] = 'Updated courses';
+$string['cccsvline'] = 'CSV line';
+$string['cclegacy1role'] = '(Original Student) typeN=1';
+$string['cclegacy2role'] = '(Original Teacher) typeN=2';
+$string['cclegacy3role'] = '(Original Non-editing teacher) typeN=3';
+$string['ccnoemailduplicates'] = 'Prevent email address duplicates';
+$string['ccoptype'] = 'Upload type';
+$string['ccoptype_addinc'] = 'Add all, append number to shortnames if needed';
+$string['ccoptype_addnew'] = 'Add new only, skip existing courses';
+$string['ccoptype_addupdate'] = 'Add new and update existing courses';
+$string['ccoptype_update'] = 'Update existing courses only';
+$string['ccpasswordcron'] = 'Generated in cron';
+$string['ccpasswordnew'] = 'New course password';
+$string['ccpasswordold'] = 'Existing course password';
+$string['reset'] = 'Reset course after upload';
+$string['ccstandardshortnames'] = 'Standardise shortnames';
+$string['ccupdateall'] = 'Override with file and defaults';
+$string['ccupdatefromfile'] = 'Override with file';
+$string['ccupdatemissing'] = 'Fill in missing from file and defaults';
+$string['ccupdatetype'] = 'Existing course details';
+$string['ccshortnametemplate'] = 'Shortname template';
+$string['ccfullnametemplate'] = 'Fullname template';
+$string['ccidnumbertemplate'] = 'Idnumber template';
+$string['missingtemplate'] = 'Template not found';
+$string['missing'] = 'missing';
+$string['incorrectformat'] = 'Invalid format specified';
+$string['incorrecttemplatefile'] = 'Template file not found';
+$string['invalidenrolmethod'] = 'Invalid enrolment method';
+$string['invalidcsvfile'] = 'Invalid input CSV file';
+$string['invalidaction'] = 'Invalid action selected';
+$string['invalidmode'] = 'Invalid mode selected';
+$string['invalidtemplatefile'] = 'Invalid template file';
+$string['invalidencoding'] = 'Invalid encoding';
+$string['invalidcategory'] = 'Invalid category';
+$string['coursetemplatename'] = 'Course template shortname';
+$string['coursetemplatename_help'] = 'Select an existing course shortname to use as a template for the creation of all courses.';
+$string['templatefile'] = 'Template backup file';
+$string['invalidbackupfile'] = 'Invalid backup file';
+
diff --git a/admin/tool/uploadcourse/locallib.php b/admin/tool/uploadcourse/locallib.php
new file mode 100644 (file)
index 0000000..79ba08f
--- /dev/null
@@ -0,0 +1,1328 @@
+<?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/>.
+
+/**
+ * Bulk course registration functions
+ *
+ * @package    tool_uploadcourse
+ * @subpackage uploadcourse
+ * @copyright  2004 onwards Martin Dougiamas (http://dougiamas.com)
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+define('CC_COURSE_ADDNEW', 0);
+define('CC_COURSE_ADDINC', 1);
+define('CC_COURSE_ADD_UPDATE', 2);
+define('CC_COURSE_UPDATE', 3);
+
+define('CC_UPDATE_NOCHANGES', 0);
+define('CC_UPDATE_FILEOVERRIDE', 1);
+define('CC_UPDATE_ALLOVERRIDE', 2);
+define('CC_UPDATE_MISSING', 3);
+
+define('CC_BULK_NEW', 1);
+define('CC_BULK_UPDATED', 2);
+define('CC_BULK_ALL', 3);
+
+define('CC_PWRESET_NONE', 0);
+define('CC_PWRESET_WEAK', 1);
+define('CC_PWRESET_ALL', 2);
+define('CC_BULK_NONE', 0);
+
+
+/**
+ * Return the list of stad fields the course upload processes
+ */
+function tool_uploadcourse_std_fields() {
+    // Array of all valid fields for validation.
+    return $std_fields = array('fullname', 'shortname', 'category', 'idnumber', 'summary',
+                    'format', 'showgrades', 'newsitems', 'teacher', 'editingteacher', 'student', 'modinfo',
+                    'manager', 'coursecreator', 'guest', 'user', 'startdate', 'numsections',
+                    'maxbytes', 'visible', 'groupmode', 'restrictmodules',
+                    'enablecompletion', 'completionstartonenrol', 'completionnotify',
+                    'hiddensections', 'groupmodeforce', 'lang', 'theme',
+                    'cost', 'showreports', 'notifystudents', 'expirynotify', 'expirythreshold', 'requested',
+                    'deleted',     // 1 means delete course.
+                    'oldshortname', // For renaming.
+                    'backupfile', // For restoring a course template after creation.
+                    'templatename', // Course to use as a template - the shortname.
+                    'reset',
+                    // There are also the enrolment fields but these are free form as they vary on enrolment type
+                    // eg: enrolmethod_1,status_1,enrolmethod_2,name_2,password_2,customtext1_2
+                    //     manual,       1,       self,         self1, letmein,   this is a custom message 1.
+    );
+}
+
+/**
+ * process the upload
+ *
+ * @param object $formdata - object of the form data
+ * @param object $cir - object of the CSV importer
+ * @param array $filecolumns - file column definitions
+ * @param string $restorefile - file to restore from
+ * @param boolean $plain - plain text output
+ */
+function tool_uploadcourse_process_course_upload($formdata, $cir, $filecolumns, $restorefile=null, $plain=false) {
+    global $CFG, $USER, $OUTPUT, $SESSION, $DB;
+
+    $std_fields = tool_uploadcourse_std_fields();
+
+    @set_time_limit(60*60); // 1 hour should be enough.
+    raise_memory_limit(MEMORY_HUGE);
+
+    require_capability('moodle/course:create', get_context_instance(CONTEXT_SYSTEM));
+    require_capability('moodle/course:update', get_context_instance(CONTEXT_SYSTEM));
+    require_capability('moodle/course:delete', get_context_instance(CONTEXT_SYSTEM));
+
+    $strcourserenamed             = get_string('courserenamed', 'tool_uploadcourse');
+    $strcoursenotrenamedexists    = get_string('coursenotrenamedexists', 'tool_uploadcourse');
+    $strcoursenotrenamedmissing   = get_string('coursenotrenamedmissing', 'tool_uploadcourse');
+    $strcoursenotrenamedoff       = get_string('coursenotrenamedoff', 'tool_uploadcourse');
+
+    $strcourseupdated             = get_string('courseupdated', 'tool_uploadcourse');
+    $strcoursenotupdated          = get_string('coursenotupdatederror', 'tool_uploadcourse');
+    $strcoursenotupdatednotexists = get_string('coursenotupdatednotexists', 'tool_uploadcourse');
+
+    $strcourseuptodate            = get_string('courseuptodate', 'tool_uploadcourse');
+
+    $strcourseadded               = get_string('newcourse');
+    $strcoursenotadded            = get_string('coursenotadded', 'tool_uploadcourse');
+    $strcoursenotaddederror       = get_string('coursenotaddederror', 'tool_uploadcourse');
+
+    $strcoursedeleted             = get_string('coursedeleted', 'tool_uploadcourse');
+    $strcoursenotdeletederror     = get_string('coursenotdeletederror', 'tool_uploadcourse');
+    $strcoursenotdeletedmissing   = get_string('coursenotdeletedmissing', 'tool_uploadcourse');
+    $strcoursenotdeletedoff       = get_string('coursenotdeletedoff', 'tool_uploadcourse');
+    $errorstr                     = get_string('error');
+
+    $returnurl = new moodle_url('/admin/tool/uploadcourse/index.php');
+    $bulknurl  = new moodle_url('/admin/tool/uploadcourse/index.php');
+
+    $today = time();
+    $today = make_timestamp(date('Y', $today), date('m', $today), date('d', $today), 0, 0, 0);
+
+    $optype = $formdata->cctype;
+
+    $updatetype        = isset($formdata->ccupdatetype) ? $formdata->ccupdatetype : 0;
+    $allowrenames      = (!empty($formdata->ccallowrenames) and $optype != CC_COURSE_ADDNEW and $optype != CC_COURSE_ADDINC);
+    $allowdeletes      = (!empty($formdata->ccallowdeletes) and $optype != CC_COURSE_ADDNEW and $optype != CC_COURSE_ADDINC);
+    $bulk              = isset($formdata->ccbulk) ? $formdata->ccbulk : 0;
+    $standardshortnames = $formdata->ccstandardshortnames;
+
+    // Check for the template.
+    $templatepathname = null;
+    if (!empty($formdata->templatename) && $formdata->templatename != 'none') {
+        $template = $DB->get_record('course', array('shortname' => $formdata->templatename));
+
+        // Backup the course template.
+        $bc = new backup_controller(backup::TYPE_1COURSE, $template->id, backup::FORMAT_MOODLE,
+                        backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
+        $backupid       = $bc->get_backupid();
+        $backupbasepath = $bc->get_plan()->get_basepath();
+        $bc->execute_plan();
+        $bc->destroy();
+        $packer = get_file_packer('application/zip');
+        // Check if tmp dir exists.
+        $tmpdir = $CFG->tempdir . '/backup';
+        if (!check_dir_exists($tmpdir, true, true)) {
+            throw new restore_controller_exception('cannot_create_backup_temp_dir');
+        }
+        $filename = restore_controller::get_tempdir_name(SITEID, $USER->id);
+        $templatepathname = $tmpdir . '/' . $filename;
+        // Get the list of files in directory.
+        $filestemp = get_directory_list($backupbasepath, '', false, true, true);
+        $files = array();
+        foreach ($filestemp as $file) {
+            // Add zip paths and fs paths to all them.
+            $files[$file] = $backupbasepath . '/' . $file;
+        }
+        $zippacker = get_file_packer('application/zip');
+        $zippacker->archive_to_pathname($files, $templatepathname);
+        if (empty($CFG->keeptempdirectoriesonbackup)) {
+            fulldelete($backupbasepath);
+        }
+    }
+
+    // Check the uploaded backup file.
+    if (!empty($formdata->restorefile)) {
+        // Check if tmp dir exists.
+        if ($restorefile) {
+            $filepath = restore_controller::get_tempdir_name(SITEID, $USER->id);
+            $packer = get_file_packer('application/zip');
+            $restorepathname = "$CFG->tempdir/backup/$filepath/";
+            $result = $packer->extract_to_pathname($restorefile, $restorepathname);
+            // If not a backup zip file.
+            if (!$result) {
+                if (empty($CFG->keeptempdirectoriesonbackup)) {
+                    fulldelete($restorepathname);
+                    fulldelete($restorefile);
+                }
+                throw new moodle_exception('invalidbackupfile', 'tool_uploadcourse');
+            }
+            if (empty($CFG->keeptempdirectoriesonbackup)) {
+                fulldelete($restorepathname);
+            }
+        } else {
+            $restorefile = null;
+        }
+    }
+
+    // Verification moved to two places: after upload and into form2.
+    $coursesnew      = 0;
+    $coursesupdated  = 0;
+    $coursesuptodate = 0; // Not printed yet anywhere.
+    $courseserrors   = 0;
+    $deletes       = 0;
+    $deleteerrors  = 0;
+    $renames       = 0;
+    $renameerrors  = 0;
+    $coursesskipped  = 0;
+    $enrolmentplugins = enrol_get_plugins(false);
+    $courseformats = array_keys(get_plugin_list('format'));
+
+    // Clear bulk selection.
+    if ($bulk) {
+        $SESSION->bulk_courses = array();
+    }
+
+    // Init csv import helper.
+    $cir->init();
+    $linenum = 1; // Column header is first line.
+
+    // Init upload progress tracker.
+    $upt = new tool_uploadcourse_progress_tracker($plain);
+    $upt->start(); // Start table.
+
+    while ($line = $cir->next()) {
+        $upt->flush();
+        $linenum++;
+
+        $upt->track('line', $linenum);
+
+        $course = new stdClass();
+
+        // Add fields to course object.
+        foreach ($line as $keynum => $value) {
+            if (!isset($filecolumns[$keynum])) {
+                // This should not happen.
+                continue;
+            }
+            $key = $filecolumns[$keynum];
+            $course->$key = $value;
+
+            if (in_array($key, $upt->columns)) {
+                // Default value in progress tracking table, can be changed later.
+                $upt->track($key, s($value), 'normal');
+            }
+        }
+        // Validate category.
+        $error = false;
+        if (!empty($course->category)) {
+            $split = preg_split('|(?<!\\\)/|', $course->category);
+            $categories = array();
+            foreach ($split as $cat) {
+                $cat = preg_replace('/\\\/', '', $cat);
+                $categories[]= $cat;
+            }
+            $course->category = 0;
+            foreach ($categories as $cat) {
+                // Does the category exist - does the category hierachy make sense.
+                $category = $DB->get_record('course_categories', array('name'=>trim($cat), 'parent' => $course->category));
+                if (empty($category)) {
+                    $upt->track('status', get_string('invalidvalue', 'tool_uploadcourse', 'category').' ('.$cat.' '.get_string('missing', 'tool_uploadcourse').')', 'error');
+                    $upt->track('category', $errorstr, 'error');
+                    $error = true;
+                    break;
+                }
+                $course->category = $category->id;
+            }
+        }
+        // Check for category errors.
+        if ($error) {
+            $courseserrors++;
+            continue;
+        }
+
+        if (!isset($course->shortname)) {
+            // Prevent warnings bellow.
+            $course->shortname = '';
+        }
+        if (!empty($course->startdate) && $course->startdate != 0) {
+            $course->startdate = strtotime($course->startdate);
+        }
+        if (!empty($course->enrolstartdate) && $course->enrolstartdate != 0) {
+            $course->enrolstartdate = strtotime($course->enrolstartdate);
+        }
+
+        // Check for enrolment methods.
+        $line_fields = (array) $course;
+        $enrolmethods = array();
+        $enrolments = array();
+        $error = false;
+        foreach ($line_fields as $k => $v) {
+            if (preg_match('/^(\w+)\_(\d+)$/', $k, $matches)) {
+                if (!isset($enrolments[$matches[2]])) {
+                    $enrolments[$matches[2]] = array();
+                }
+                if ($matches[1] == 'enrolmethod') {
+                    if (!isset($enrolmentplugins[$v])) {
+                        $upt->track('status', get_string('invalidenrolmethod', 'tool_uploadcourse', 'category'), 'error');
+                        $upt->track($k, $errorstr, 'error');
+                        $error = true;
+                    }
+                    $enrolmethods[$v] = $matches[2];
+                }
+                $enrolments[$matches[2]][$matches[1]] = $v;
+            }
+        }
+        if ($error) {
+            continue;
+        }
+        foreach ($enrolmethods as $k => $v) {
+            $enrolmethods[$k] = $enrolments[$v];
+        }
+
+        // Roles.
+        $roles = get_all_roles();
+        foreach ($roles as $role) {
+            if (isset($course->{$role->shortname})) {
+                if (in_array($role->shortname, array('teacher', 'editingteacher', 'student',
+                                                     'manager', 'coursecreator', 'guest', 'user'))) {
+                    $course->{'role_'.$role->id} = $course->{$role->shortname};
+                }
+            }
+        }
+
+        // What type of operation is this ?
+        if ($optype == CC_COURSE_ADDNEW or $optype == CC_COURSE_ADDINC) {
+            // Course creation is a special case - the shortname may be constructed from templates using firstname and lastname
+            // better never try this in mixed update types.
+            $error = false;
+            if (!isset($course->fullname) or $course->fullname === '') {
+                $upt->track('status', get_string('missingfield', 'error', 'fullname'), 'error');
+                $upt->track('fullname', $errorstr, 'error');
+                $error = true;
+            }
+            if (!isset($course->summary) or $course->summary === '') {
+                $upt->track('status', get_string('missingfield', 'error', 'summary'), 'error');
+                $upt->track('summary', $errorstr, 'error');
+                $error = true;
+            }
+            if ($error) {
+                $courseserrors++;
+                continue;
+            }
+            // We require shortname too - we might use template for it though.
+            if (empty($course->shortname) and !empty($formdata->ccshortname)) {
+                $course->shortname = tool_uploadcourse_process_template($formdata->ccshortname, $course);
+                $upt->track('shortname', s($course->shortname));
+            }
+        }
+
+        // Normalize shortname.
+        $originalshortname = $course->shortname;
+        if ($standardshortnames) {
+            $course->shortname = clean_param($course->shortname, PARAM_MULTILANG);
+        }
+
+        // Make sure we really have shortname.
+        if (empty($course->shortname)) {
+            $upt->track('status', get_string('missingfield', 'error', 'shortname'), 'error');
+            $upt->track('shortname', $errorstr, 'error');
+            $courseserrors++;
+            continue;
+        }
+
+        if ($existingcourse = $DB->get_record('course', array('shortname' => $course->shortname))) {
+            $upt->track('id', $existingcourse->id, 'normal', false);
+        }
+
+        // Find out in shortname incrementing required.
+        if ($existingcourse and $optype == CC_COURSE_ADDINC) {
+            $course->shortname = tool_uploadcourse_increment_shortname($course->shortname);
+            if (!empty($course->idnumber)) {
+                $oldidnumber = $course->idnumber;
+                $course->idnumber = tool_uploadcourse_increment_idnumber($course->idnumber);
+                if ($course->idnumber !== $oldidnumber) {
+                    $upt->track('idnumber', s($oldidnumber).'-->'.s($course->idnumber), 'info');
+                }
+            }
+            $existingcourse = false;
+        }
+
+        // Check duplicate idnumber.
+        if (!$existingcourse and !empty($course->idnumber)) {
+            if ($DB->record_exists('course', array('idnumber' => $course->idnumber))) {
+                $upt->track('status', get_string('idnumbernotunique', 'tool_uploadcourse'), 'error');
+                $upt->track('idnumber', $errorstr, 'error');
+                $error = true;
+            }
+        }
+
+        // Notify about nay shortname changes.
+        if ($originalshortname !== $course->shortname) {
+            $upt->track('shortname', '', 'normal', false); // Clear previous.
+            $upt->track('shortname', s($originalshortname).'-->'.s($course->shortname), 'info');
+        } else {
+            $upt->track('shortname', s($course->shortname), 'normal', false);
+        }
+
+        // Add default values for remaining fields.
+        $formdefaults = array();
+        foreach ($std_fields as $field) {
+            if (isset($course->$field)) {
+                continue;
+            }
+            // All validation moved to form2.
+            if (isset($formdata->$field)) {
+                $course->$field = $formdata->$field;
+                $formdefaults[$field] = true;
+                if (in_array($field, $upt->columns)) {
+                    $upt->track($field, s($course->$field), 'normal');
+                }
+            } else {
+                // Process templates.
+                if (isset($formdata->{"cc".$field}) && !empty($formdata->{"cc".$field}) && empty($course->$field)) {
+                    $course->$field = tool_uploadcourse_process_template($formdata->{"cc".$field}, $course);
+                }
+            }
+        }
+        // Do we run the reset ?
+        $resetcourse = false;
+        if ($course->reset) {
+            $resetcourse = true;
+            unset($course->reset);
+        }
+
+        // Proof visible flag.
+        $course->visible = (int) $course->visible;
+
+        if (empty($course->category)) {
+            $course->category = $formdata->cccategory;
+        }
+
+        // Delete course.
+        if (!empty($course->deleted)) {
+            if (!$allowdeletes) {
+                $coursesskipped++;
+                $upt->track('status', $strcoursenotdeletedoff, 'warning');
+                continue;
+            }
+            if ($existingcourse) {
+                if (delete_course($existingcourse->id, false)) {
+                    $upt->track('status', $strcoursedeleted);
+                    $deletes++;
+                } else {
+                    $upt->track('status', $strcoursenotdeletederror, 'error');
+                    $deleteerrors++;
+                }
+            } else {
+                $upt->track('status', $strcoursenotdeletedmissing, 'error');
+                $deleteerrors++;
+            }
+            continue;
+        }
+        // We do not need the deleted flag anymore.
+        unset($course->deleted);
+
+        // Renaming requested?
+        if (!empty($course->oldshortname) ) {
+            if (!$allowrenames) {
+                $coursesskipped++;
+                $upt->track('status', $strcoursenotrenamedoff, 'warning');
+                continue;
+            }
+
+            if ($existingcourse) {
+                $upt->track('status', $strcoursenotrenamedexists, 'error');
+                $renameerrors++;
+                continue;
+            }
+
+            if ($standardshortnames) {
+                $oldshortname = clean_param($course->oldshortname, PARAM_MULTILANG);
+            } else {
+                $oldshortname = $course->oldshortname;
+            }
+
+            // No guessing when looking for old shortname, it must be exact match.
+            if ($oldcourse = $DB->get_record('course', array('shortname'=>$oldshortname))) {
+                $upt->track('id', $oldcourse->id, 'normal', false);
+                $DB->set_field('course', 'shortname', $course->shortname, array('id'=>$oldcourse->id));
+                $upt->track('shortname', '', 'normal', false); // Clear previous.
+                $upt->track('shortname', s($oldshortname).'-->'.s($course->shortname), 'info');
+                $upt->track('status', $strcourserenamed);
+                $renames++;
+            } else {
+                $upt->track('status', $strcoursenotrenamedmissing, 'error');
+                $renameerrors++;
+                continue;
+            }
+            $existingcourse = $oldcourse;
+            $existingcourse->shortname = $course->shortname;
+        }
+
+        // Can we process with update or insert?
+        $skip = false;
+        switch ($optype) {
+            case CC_COURSE_ADDNEW:
+                if ($existingcourse) {
+                    $coursesskipped++;
+                    $upt->track('status', $strcoursenotadded, 'warning');
+                    $skip = true;
+                }
+                break;
+
+            case CC_COURSE_ADDINC:
+                if ($existingcourse) {
+                    // This should not happen!
+                    $upt->track('status', $strcoursenotaddederror, 'error');
+                    $courseserrors++;
+                    $skip = true;
+                }
+                break;
+
+            case CC_COURSE_ADD_UPDATE:
+                break;
+
+            case CC_COURSE_UPDATE:
+                if (!$existingcourse) {
+                    $coursesskipped++;
+                    $upt->track('status', $strcoursenotupdatednotexists, 'warning');
+                    $skip = true;
+                }
+                break;
+
+            default:
+                // Unknown type.
+                $skip = true;
+        }
+
+        // Check for the backup file as template.
+        $backupfile = null;
+        if (!empty($course->backupfile)) {
+            if (!is_readable($course->backupfile) || !preg_match('/(\.mbz|\.zip)$/i', $course->backupfile)) {
+                $upt->track('status', get_string('incorrecttemplatefile', 'tool_uploadcourse'), 'error');
+                $courseserrors++;
+                $skip = true;
+            } else {
+                $backupfile = $course->backupfile;
+            }
+        }
+
+        if ($skip) {
+            continue;
+        }
+
+        // check the format
+        if (!empty($course->format) && !in_array($course->format, $courseformats)) {
+            $upt->track('status', get_string('incorrectformat', 'tool_uploadcourse'), 'error');
+            $courseserrors++;
+            continue;
+        }
+
+        $templatename = null;
+        if ($existingcourse) {
+            $course->id = $existingcourse->id;
+
+            $upt->track('shortname', html_writer::link(new moodle_url('/course/view.php',
+                                                                      array('id '=> $existingcourse->id)),
+                                                                      s($existingcourse->shortname)),
+                                                                      'normal', false);
+
+            $existingcourse->timemodified = time();
+            // Do NOT mess with timecreated or firstaccess here!
+            $doupdate = false;
+
+            if ($updatetype != CC_UPDATE_NOCHANGES) {
+                foreach ($std_fields as $column) {
+                    if ($column === 'shortname') {
+                        // These can not be changed here.
+                        continue;
+                    }
+                    if (!property_exists($course, $column) or !property_exists($existingcourse, $column)) {
+                        // This should never happen.
+                        continue;
+                    }
+                    // In the case $updatetype == CC_UPDATE_ALLOVERRIDE we override everything.
+                    if ($updatetype == CC_UPDATE_MISSING) {
+                        if (!is_null($existingcourse->$column) and $existingcourse->$column !== '') {
+                            continue;
+                        }
+
+                    } else if ($updatetype == CC_UPDATE_FILEOVERRIDE) {
+                        if (!empty($formdefaults[$column])) {
+                            // Do not override with form defaults.
+                            continue;
+                        }
+                    }
+                    if ($existingcourse->$column !== $course->$column) {
+                        if (in_array($column, $upt->columns)) {
+                            $upt->track($column, s($existingcourse->$column).'-->'.s($course->$column), 'info', false);
+                        }
+                        $existingcourse->$column = $course->$column;
+                        $doupdate = true;
+                    }
+                }
+            }
+
+            if ($doupdate) {
+                // We want only courses that were really updated.
+                update_course($existingcourse);
+                $upt->track('status', $strcourseupdated);
+                $coursesupdated++;
+
+                events_trigger('course_updated', $existingcourse);
+
+                if ($bulk == CC_BULK_UPDATED or $bulk == CC_BULK_ALL) {
+                    if (!in_array($course->id, $SESSION->bulk_courses)) {
+                        $SESSION->bulk_courses[] = $course->id;
+                    }
+                }
+
+            } else {
+                // No course information changed.
+                $upt->track('status', $strcourseuptodate);
+                $coursesuptodate++;
+
+                if ($bulk == CC_BULK_ALL) {
+                    if (!in_array($course->id, $SESSION->bulk_courses)) {
+                        $SESSION->bulk_courses[] = $course->id;
+                    }
+                }
+            }
+
+        } else {
+            // Save the new course to the database.
+            $course->timemodified = time();
+            $course->timecreated  = time();
+
+            // Create course - insert_record ignores any extra properties.
+            if (isset($course->templatename) && $course->templatename != 'none') {
+                $templatename = $course->templatename;
+            } else {
+                $templatename = null;
+            }
+            try {
+                $course = create_course($course);
+            } catch (moodle_exception $e) {
+                $upt->track('status', $e->getMessage(), 'error');
+                $courseserrors++;
+                $skip = true;
+                continue;
+            }
+            $upt->track('shortname', html_writer::link(new moodle_url('/course/view.php',
+                                                                      array('id' => $course->id)),
+                                                                      s($course->shortname)),
+                                                                      'normal', false);
+
+            $upt->track('status', $strcourseadded);
+            $upt->track('id', $course->id, 'normal', false);
+            $coursesnew++;
+
+            // Make sure course context exists.
+            get_context_instance(CONTEXT_COURSE, $course->id);
+
+            events_trigger('course_created', $course);
+
+            if ($bulk == CC_BULK_NEW or $bulk == CC_BULK_ALL) {
+                if (!in_array($course->id, $SESSION->bulk_courses)) {
+                    $SESSION->bulk_courses[] = $course->id;
+                }
+            }
+        }
+
+        // After creation/update, do we need to copy from template nominated in the CSV file?
+        if (!empty($templatename)) {
+            $coursetemplate = $DB->get_record('course', array('shortname' => $templatename));
+            if (empty($coursetemplate)) {
+                $upt->track('status', get_string('incorrecttemplatefile', 'tool_uploadcourse'), 'error');
+                $courseserrors++;
+                continue;
+            }
+
+            // Backup the course template.
+            $bc = new backup_controller(backup::TYPE_1COURSE, $coursetemplate->id, backup::FORMAT_MOODLE,
+                            backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
+            $backupid       = $bc->get_backupid();
+            $backupbasepath = $bc->get_plan()->get_basepath();
+            $bc->execute_plan();
+            $bc->destroy();
+            $packer = get_file_packer('application/zip');
+            // Check if tmp dir exists.
+            $tmpdir = $CFG->tempdir . '/backup';
+            if (!check_dir_exists($tmpdir, true, true)) {
+                throw new restore_controller_exception('cannot_create_backup_temp_dir');
+            }
+            $filename = restore_controller::get_tempdir_name(SITEID, $USER->id);
+            $temppathname = $tmpdir . '/' . $filename;
+            // Get the list of files in directory.
+            $filestemp = get_directory_list($backupbasepath, '', false, true, true);
+            $files = array();
+            foreach ($filestemp as $file) {
+                // Add zip paths and fs paths to all them.
+                $files[$file] = $backupbasepath . '/' . $file;
+            }
+            $zippacker = get_file_packer('application/zip');
+            $zippacker->archive_to_pathname($files, $temppathname);
+            if (empty($CFG->keeptempdirectoriesonbackup)) {
+                fulldelete($backupbasepath);
+            }
+
+            // Check if tmp dir exists.
+            $tmpdir = $CFG->tempdir . '/backup';
+            $filename = restore_controller::get_tempdir_name($course->id, $USER->id);
+            $pathname = $tmpdir . '/' . $filename;
+            $packer = get_file_packer('application/zip');
+            $packer->extract_to_pathname($temppathname, $pathname);
+
+            // Restore the backup immediately.
+            $rc = new restore_controller($filename, $course->id,
+                            backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING);
+            // Check if the format conversion must happen first.
+            if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) {
+                $rc->convert();
+            }
+            if (!$rc->execute_precheck()) {
+                $precheckresults = $rc->get_precheck_results();
+                if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
+                    if (empty($CFG->keeptempdirectoriesonbackup)) {
+                        fulldelete($pathname);
+                    }
+                    echo $output->precheck_notices($precheckresults);
+                    if (!$plain) {
+                        echo $output->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
+                        echo $output->footer();
+                    }
+                    die();
+                }
+            }
+            $rc->execute_plan();
+            $rc->destroy();
+            if (empty($CFG->keeptempdirectoriesonbackup)) {
+                fulldelete($pathname);
+            }
+        }
+
+        // After creation/update, do we need to copy from template?
+        if (!empty($templatepathname)) {
+            // Check if tmp dir exists.
+            $tmpdir = $CFG->tempdir . '/backup';
+            $filename = restore_controller::get_tempdir_name($course->id, $USER->id);
+            $pathname = $tmpdir . '/' . $filename;
+            $packer = get_file_packer('application/zip');
+            $packer->extract_to_pathname($templatepathname, $pathname);
+
+            // Restore the backup immediately.
+            $rc = new restore_controller($filename, $course->id,
+                            backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING);
+            // Check if the format conversion must happen first.
+            if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) {
+                $rc->convert();
+            }
+            if (!$rc->execute_precheck()) {
+                $precheckresults = $rc->get_precheck_results();
+                if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
+                    if (empty($CFG->keeptempdirectoriesonbackup)) {
+                        fulldelete($pathname);
+                    }
+                    echo $output->precheck_notices($precheckresults);
+                    if (!$plain) {
+                        echo $output->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
+                        echo $output->footer();
+                    }
+                    die();
+                }
+            }
+            $rc->execute_plan();
+            $rc->destroy();
+            if (empty($CFG->keeptempdirectoriesonbackup)) {
+                fulldelete($pathname);
+            }
+        }
+
+        // After creation/update, do we need to copy from template backup file?
+        if (!empty($restorefile)) {
+            // Check if tmp dir exists.
+            $tmpdir = $CFG->tempdir . '/backup';
+            $filename = restore_controller::get_tempdir_name($course->id, $USER->id);
+            $pathname = $tmpdir . '/' . $filename;
+            $packer = get_file_packer('application/zip');
+            $packer->extract_to_pathname($restorefile, $pathname);
+
+            // Restore the backup immediately.
+            $rc = new restore_controller($filename, $course->id,
+                            backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING);
+            // Check if the format conversion must happen first.
+            if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) {
+                $rc->convert();
+            }
+            if (!$rc->execute_precheck()) {
+                $precheckresults = $rc->get_precheck_results();
+                if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
+                    if (empty($CFG->keeptempdirectoriesonbackup)) {
+                        fulldelete($pathname);
+                    }
+                    echo $output->precheck_notices($precheckresults);
+                    if (!$plain) {
+                        echo $output->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
+                        echo $output->footer();
+                    }
+                    die();
+                }
+            }
+            $rc->execute_plan();
+            $rc->destroy();
+            if (empty($CFG->keeptempdirectoriesonbackup)) {
+                fulldelete($pathname);
+            }
+        }
+
+        // After creation/update, do we need to import a Moodle backup?
+        if (!empty($backupfile)) {
+            // Check if tmp dir exists.
+            $tmpdir = $CFG->tempdir . '/backup';
+            if (!check_dir_exists($tmpdir, true, true)) {
+                throw new restore_controller_exception('cannot_create_backup_temp_dir');
+            }
+            $filename = restore_controller::get_tempdir_name($course->id, $USER->id);
+            $pathname = $tmpdir . '/' . $filename;
+            $packer = get_file_packer('application/zip');
+            $packer->extract_to_pathname($backupfile, $pathname);
+
+            // Restore the backup immediately.
+            $rc = new restore_controller($filename, $course->id,
+                            backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING);
+            // Check if the format conversion must happen first.
+            if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) {
+                $rc->convert();
+            }
+            if (!$rc->execute_precheck()) {
+                $precheckresults = $rc->get_precheck_results();
+                if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
+                    if (empty($CFG->keeptempdirectoriesonbackup)) {
+                        fulldelete($pathname);
+                    }
+                    echo $output->precheck_notices($precheckresults);
+                    if (!$plain) {
+                        echo $output->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
+                        echo $output->footer();
+                    }
+                    die();
+                }
+            }
+            $rc->execute_plan();
+            $rc->destroy();
+            if (empty($CFG->keeptempdirectoriesonbackup)) {
+                fulldelete($pathname);
+            }
+        }
+
+        // Handle enrolment methods.
+        $enrol_updated = false;
+        $instances = enrol_get_instances($course->id, false);
+        foreach ($enrolments as $method) {
+            if (isset($method['delete']) && $method['delete']) {
+                // Remove the enrolment method.
+                foreach ($instances as $instance) {
+                    if ($instance->enrol == $method['enrolmethod']) {
+                        $plugin = $enrolmentplugins[$instance->enrol];
+                        $plugin->delete_instance($instance);
+                        $enrol_updated = true;
+                        break;
+                    }
+                }
+            } else if (isset($method['disable']) && $method['disable']) {
+                // Disable the enrolment.
+                foreach ($instances as $instance) {
+                    if ($instance->enrol == $method['enrolmethod']) {
+                        $plugin = $enrolmentplugins[$instance->enrol];
+                        $plugin->update_status($instance, ENROL_INSTANCE_DISABLED);
+                        $enrol_updated = true;
+                        break;
+                    }
+                }
+            } else {
+                // We should have this enrolment method.
+                $instance = null;
+                foreach ($instances as $i) {
+                    if ($i->enrol == $method['enrolmethod']) {
+                        $instance = $i;
+                        break;
+                    }
+                }
+                $plugin = null;
+                if (empty($instance)) {
+                    $plugin = $enrolmentplugins[$method['enrolmethod']];
+                    $instance = new stdClass();
+                    $instance->id = $plugin->add_default_instance($course);
+                    $instance->roleid = $plugin->get_config('roleid');
+                } else {
+                    $plugin = $enrolmentplugins[$instance->enrol];
+                    $plugin->update_status($instance, ENROL_INSTANCE_ENABLED);
+                }
+                // Now update values.
+                foreach ($method as $k => $v) {
+                    $instance->{$k} = $v;
+                }
+
+                // Sort out the start, end and date.
+                $instance->enrolstartdate = (isset($method['startdate']) ? strtotime($method['startdate']) : 0);
+                $instance->enrolenddate = (isset($method['enddate']) ? strtotime($method['enddate']) : 0);
+
+                // Is the enrolment period set?
+                if (isset($method['enrolperiod']) && ! empty($method['enrolperiod'])) {
+                    if (preg_match('/^\d+$/', $method['enrolperiod'])) {
+                        $method['enrolperiod'] = (int) $method['enrolperiod'];
+                    } else {
+                        // Try and convert period to seconds.
+                        $method['enrolperiod'] = strtotime('1970-01-01 GMT + ' . $method['enrolperiod']);
+                    }
+                    $instance->enrolperiod = $method['enrolperiod'];
+                }
+                if ($instance->enrolstartdate > 0 && isset($method['enrolperiod'])) {
+                    $instance->enrolenddate = $instance->enrolstartdate + $method['enrolperiod'];
+                }
+                if ($instance->enrolenddate > 0) {
+                    $instance->enrolperiod = $instance->enrolenddate - $instance->enrolstartdate;
+                }
+                if ($instance->enrolenddate < $instance->enrolstartdate) {
+                    $instance->enrolenddate = $instance->enrolstartdate;
+                }
+                // Sort out the given Role.
+                if (isset($method['role'])) {
+                    $context = context_course::instance($course->id);
+                    $roles = get_default_enrol_roles($context, $plugin->get_config('roleid'));
+                    if (!empty($roles)) {
+                        $roles = array_flip($roles);
+                    }
+                    if (isset($roles[$method['role']])) {
+                        $instance->roleid = $roles[$method['role']];
+                    }
+                }
+                $instance->status = ENROL_INSTANCE_ENABLED;
+                $instance->timemodified = time();
+                $DB->update_record('enrol', $instance);
+                $enrol_updated = true;
+            }
+        }
+
+        // Do the course reset.
+        if ($resetcourse) {
+            $resetdata = new stdClass();
+            $resetdata->reset_start_date = time();
+            $resetdata->id = $course->id;
+            $resetdata->reset_events = true;
+            $resetdata->reset_logs = true;
+            $resetdata->reset_notes = true;
+            $resetdata->reset_comments = true;
+            $resetdata->reset_completion = true;
+            $resetdata->delete_blog_associations = true;
+            $roles = get_assignable_roles(context_course::instance($course->id));
+            $roles[0] = get_string('noroles', 'role');
+            $roles = array_reverse($roles, true);
+            $resetdata->reset_roles_local = true;
+            $resetdata->reset_gradebook_items = true;
+            $resetdata->reset_gradebook_grades = true;
+            $resetdata->reset_gradebook_items = true;
+            $resetdata->reset_groups_remove = true;
+            $resetdata->reset_groups_members = true;
+            $resetdata->reset_groupings_remove = true;
+            $resetdata->reset_groupings_members = true;
+            $resetdata->reset_groups_remove = true;
+            $resetdata->reset_groups_remove = true;
+            $resetdata->reset_start_date_old = $course->startdate;
+            $status = reset_course_userdata($resetdata);
+        }
+
+        if ($enrol_updated) {
+            $coursesupdated++;
+        }
+        // Invalidate all enrol caches.
+        $context = context_course::instance($course->id);
+        $context->mark_dirty();
+    }
+
+    // Clean up backup files.
+    if (!empty($template)) {
+        if (empty($CFG->keeptempdirectoriesonbackup)) {
+            fulldelete($backupbasepath);
+        }
+    }
+    if (!empty($restorefile)) {
+        if (empty($CFG->keeptempdirectoriesonbackup)) {
+            fulldelete($restorefile);
+        }
+    }
+
+    $upt->close(); // Close table.
+
+    $cir->close();
+    $cir->cleanup(true);
+    $systemcontext = context_system::instance();
+    mark_context_dirty($systemcontext->path);
+
+    if (!$plain) {
+        echo $OUTPUT->box_start('boxwidthnarrow boxaligncenter generalbox', 'uploadresults');
+        echo '<p>';
+        if ($optype != CC_COURSE_UPDATE) {
+            echo get_string('coursescreated', 'tool_uploadcourse').': '.$coursesnew.'<br />';
+        }
+        if ($optype == CC_COURSE_UPDATE or $optype == CC_COURSE_ADD_UPDATE) {
+            echo get_string('coursesupdated', 'tool_uploadcourse').': '.$coursesupdated.'<br />';
+        }
+        if ($allowdeletes) {
+            echo get_string('coursesdeleted', 'tool_uploadcourse').': '.$deletes.'<br />';
+            echo get_string('deleteerrors', 'tool_uploadcourse').': '.$deleteerrors.'<br />';
+        }
+        if ($allowrenames) {
+            echo get_string('coursesrenamed', 'tool_uploadcourse').': '.$renames.'<br />';
+            echo get_string('renameerrors', 'tool_uploadcourse').': '.$renameerrors.'<br />';
+        }
+        if ($coursesskipped) {
+            echo get_string('coursesskipped', 'tool_uploadcourse').': '.$coursesskipped.'<br />';
+        }
+        echo get_string('errors', 'tool_uploadcourse').': '.$courseserrors.'</p>';
+    } else {
+        if ($optype != CC_COURSE_UPDATE) {
+            echo get_string('coursescreated', 'tool_uploadcourse').': '.$coursesnew."\n";
+        }
+        if ($optype == CC_COURSE_UPDATE or $optype == CC_COURSE_ADD_UPDATE) {
+            echo get_string('coursesupdated', 'tool_uploadcourse').': '.$coursesupdated."\n";
+        }
+        if ($allowdeletes) {
+            echo get_string('coursesdeleted', 'tool_uploadcourse').': '.$deletes."\n";
+            echo get_string('deleteerrors', 'tool_uploadcourse').': '.$deleteerrors."\n";
+        }
+        if ($allowrenames) {
+            echo get_string('coursesrenamed', 'tool_uploadcourse').': '.$renames."\n";
+            echo get_string('renameerrors', 'tool_uploadcourse').': '.$renameerrors."\n";
+        }
+        if ($coursesskipped) {
+            echo get_string('coursesskipped', 'tool_uploadcourse').': '.$coursesskipped."\n";
+        }
+        echo get_string('errors', 'tool_uploadcourse').': '.$courseserrors."\n";
+        echo "The End.\n";
+    }
+}
+
+
+/**
+ * Tracking of processed courses.
+ *
+ * This class prints course information into a html table.
+ *
+ * @copyright  2007 Petr Skoda  {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_uploadcourse_progress_tracker {
+    /** @var int $_row - row marker */
+    private $_row;
+    /** @var array $columns - output columns */
+    public $columns = array('status', 'line', 'id', 'fullname', 'shortname', 'category', 'idnumber', 'summary', 'deleted');
+    /** @var boolean $_plain - output is text mode */
+    private $_plain;
+
+    /**
+     * Constructor
+     *
+     * @param boolean $type - is this plain text output
+     */
+    public function __construct($type = false) {
+        $this->_plain = $type;
+    }
+
+
+    /**
+     * Print table header.
+     * @return void
+     */
+    public function start() {
+        $ci = 0;
+        if ($this->_plain) {
+            echo "\n\t".get_string('status')."\t".get_string('cccsvline', 'tool_uploadcourse')."\tID\t".
+                 get_string('fullname')."\t".get_string('shortname')."\t".get_string('category')."\t".
+                 get_string('idnumber')."\t".get_string('summary')."\t".get_string('delete')."\n";
+        } else {
+            echo '<table id="ccresults" class="generaltable boxaligncenter flexible-wrap" summary="'.
+                  get_string('uploadcoursesresult', 'tool_uploadcourse').'">';
+            echo '<tr class="heading r0">';
+            echo '<th class="header c'.$ci++.'" scope="col">'.get_string('status').'</th>';
+            echo '<th class="header c'.$ci++.'" scope="col">'.get_string('cccsvline', 'tool_uploadcourse').'</th>';
+            echo '<th class="header c'.$ci++.'" scope="col">ID</th>';
+            echo '<th class="header c'.$ci++.'" scope="col">'.get_string('fullname').'</th>';
+            echo '<th class="header c'.$ci++.'" scope="col">'.get_string('shortname').'</th>';
+            echo '<th class="header c'.$ci++.'" scope="col">'.get_string('category').'</th>';
+            echo '<th class="header c'.$ci++.'" scope="col">'.get_string('idnumber').'</th>';
+            echo '<th class="header c'.$ci++.'" scope="col">'.get_string('summary').'</th>';
+            echo '<th class="header c'.$ci++.'" scope="col">'.get_string('delete').'</th>';
+            echo '</tr>';
+        }
+        $this->_row = null;
+    }
+
+    /**
+     * Flush previous line and start a new one.
+     * @return void
+     */
+    public function flush() {
+        if (empty($this->_row) or empty($this->_row['line']['normal'])) {
+            // Nothing to print - each line has to have at least number.
+            $this->_row = array();
+            foreach ($this->columns as $col) {
+                $this->_row[$col] = array('normal'=>'', 'info'=>'', 'warning'=>'', 'error'=>'');
+            }
+            return;
+        }
+        $ci = 0;
+        $ri = 1;
+        echo $this->_plain ? "" : '<tr class="r'.$ri.'">';
+        foreach ($this->_row as $key => $field) {
+            foreach ($field as $type => $content) {
+                if ($field[$type] !== '') {
+                    $field[$type] = $this->_plain ? $field[$type] : '<span class="cc'.$type.'">'.$field[$type].'</span>';
+                } else {
+                    unset($field[$type]);
+                }
+            }
+            echo $this->_plain ? "\t" : '<td class="cell c'.$ci++.'">';
+            if (!empty($field)) {
+                echo implode(($this->_plain ? "|" : '<br />'), $field);
+            } else {
+                echo $this->_plain ? '' : '&nbsp;';
+            }
+            echo $this->_plain ? '' : '</td>';
+        }
+        echo $this->_plain ? "\n" : '</tr>';
+        foreach ($this->columns as $col) {
+            $this->_row[$col] = array('normal'=>'', 'info'=>'', 'warning'=>'', 'error'=>'');
+        }
+    }
+
+    /**
+     * Add tracking info
+     * @param string $col name of column
+     * @param string $msg message
+     * @param string $level 'normal', 'warning' or 'error'
+     * @param bool $merge true means add as new line, false means override all previous text of the same type
+     * @return void
+     */
+    public function track($col, $msg, $level = 'normal', $merge = true) {
+        if (empty($this->_row)) {
+            $this->flush(); // Init arrays.
+        }
+        if (!in_array($col, $this->columns)) {
+            debugging('Incorrect column:'.$col);
+            return;
+        }
+        if ($merge) {
+            if ($this->_row[$col][$level] != '') {
+                $this->_row[$col][$level] .= $this->_plain ? '' : '<br />';
+            }
+            $this->_row[$col][$level] .= $msg;
+        } else {
+            $this->_row[$col][$level] = $msg;
+        }
+    }
+
+    /**
+     * Print the table end
+     * @return void
+     */
+    public function close() {
+        $this->flush();
+        echo $this->_plain ? "\n" : '</table>';
+    }
+}
+
+/**
+ * Validation callback function - verified the column line of csv file.
+ * Converts standard column names to lowercase.
+ * @param csv_import_reader $cir
+ * @param array $stdfields standard course fields
+ * @param moodle_url $returnurl return url in case of any error
+ * @return array list of fields
+ */
+function tool_uploadcourse_validate_course_upload_columns(csv_import_reader $cir, $stdfields, moodle_url $returnurl) {
+    $columns = $cir->get_columns();
+
+    if (empty($columns)) {
+        $cir->close();
+        $cir->cleanup();
+        print_error('cannotreadtmpfile', 'error', $returnurl);
+    }
+    if (count($columns) < 2) {
+        $cir->close();
+        $cir->cleanup();
+        print_error('csvfewcolumns', 'error', $returnurl);
+    }
+
+    // Test columns.
+    $processed = array();
+    foreach ($columns as $key => $unused) {
+        $field = $columns[$key];
+        $lcfield = textlib::strtolower($field);
+        if (in_array($field, $stdfields) or in_array($lcfield, $stdfields)) {
+            // Standard fields are only lowercase.
+            $newfield = $lcfield;
+
+        } else if (preg_match('/^\w+\_\d+$/', $lcfield)) {
+            // Special fields for enrolments.
+            $newfield = $lcfield;
+
+        } else {
+            $cir->close();
+            $cir->cleanup();
+            print_error('invalidfieldname', 'error', $returnurl, $field);
+        }
+        if (in_array($newfield, $processed)) {
+            $cir->close();
+            $cir->cleanup();
+            print_error('duplicatefieldname', 'error', $returnurl, $newfield);
+        }
+        $processed[$key] = $newfield;
+    }
+
+    return $processed;
+}
+
+/**
+ * Increments shortname - increments trailing number or adds it if not present.
+ * Varifies that the new shortname does not exist yet
+ * @param string $shortname
+ * @return incremented shortname which does not exist yet
+ */
+function tool_uploadcourse_increment_shortname($shortname) {
+    global $DB, $CFG;
+
+    if (!preg_match_all('/(.*?)([0-9]+)$/', $shortname, $matches)) {
+        $shortname = $shortname.'2';
+    } else {
+        $shortname = $matches[1][0].($matches[2][0]+1);
+    }
+
+    if ($DB->record_exists('course', array('shortname'=>$shortname))) {
+        return tool_uploadcourse_increment_shortname($shortname);
+    } else {
+        return $shortname;
+    }
+}
+
+/**
+ * Increments idnumber - increments trailing number or adds it if not present.
+ * Varifies that the new idnumber does not exist yet
+ * @param string $idnumber
+ * @return incremented idnumber which does not exist yet
+ */
+function tool_uploadcourse_increment_idnumber($idnumber) {
+    global $DB, $CFG;
+
+    if (!preg_match_all('/(.*?)([0-9]+)$/', $idnumber, $matches)) {
+        $idnumber = $idnumber.'2';
+    } else {
+        $idnumber = $matches[1][0].($matches[2][0]+1);
+    }
+
+    if ($DB->record_exists('course', array('idnumber'=>$idnumber))) {
+        return tool_uploadcourse_increment_idnumber($idnumber);
+    } else {
+        return $idnumber;
+    }
+}
+
+/**
+ * Check if default field contains templates and apply them.
+ * @param string $template - potential tempalte string
+ * @param object $course - we need coursename, firstname and lastname
+ * @return object $template - course template
+ * @return string $result - field value
+ */
+function tool_uploadcourse_process_template($template, $course) {
+    if (is_array($template)) {
+        // Hack for for support of text editors with format.
+        $t = $template['text'];
+    } else {
+        $t = $template;
+    }
+    if (strpos($t, '%') === false) {
+        return $template;
+    }
+
+    $shortname  = isset($course->shortname) ? $course->shortname  : '';
+    $fullname   = isset($course->fullname) ? $course->fullname : '';
+    $idnumber   = isset($course->idnumber) ? $course->idnumber  : '';
+
+    $callback = partial('tool_uploadcourse_process_template_callback', $shortname, $fullname, $idnumber);
+
+    $result = preg_replace_callback('/(?<!%)%([+-~])?(\d)*([flu])/', $callback, $t);
+
+    if (is_null($result)) {
+        return $template; // Error during regex processing??
+    }
+
+    if (is_array($template)) {
+        $template['text'] = $result;
+        return $t;
+    } else {
+        return $result;
+    }
+}
+
+/**
+ * Internal callback function.
+ * @param string $shortname - course shortname
+ * @param string $fullname - course full name
+ * @param string $idnumber - course idnumber
+ * @param array $block - template parameters
+ * @return string $repl - resolved template
+ */
+function tool_uploadcourse_process_template_callback($shortname, $fullname, $idnumber, $block) {
+
+    switch ($block[3]) {
+        case 's':
+            $repl = $shortname;
+            break;
+        case 'f':
+            $repl = $fullname;
+            break;
+        case 'i':
+            $repl = $idnumber;
+            break;
+        default:
+            return $block[0];
+    }
+
+    switch ($block[1]) {
+        case '+':
+            $repl = textlib::strtoupper($repl);
+            break;
+        case '-':
+            $repl = textlib::strtolower($repl);
+            break;
+        case '~':
+            $repl = textlib::strtotitle($repl);
+            break;
+    }
+
+    if (!empty($block[2])) {
+        $repl = textlib::substr($repl, 0 , $block[2]);
+    }
+
+    return $repl;
+}
+
diff --git a/admin/tool/uploadcourse/settings.php b/admin/tool/uploadcourse/settings.php
new file mode 100644 (file)
index 0000000..55c5a0f
--- /dev/null
@@ -0,0 +1,33 @@
+<?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/>.
+
+/**
+ * Link to CSV course upload
+ *
+ * @package    tool_uploadcourse
+ * @subpackage uploadcourse
+ * @copyright  2010 Petr Skoda {@link http://skodak.org}
+ * @copyright  2011 Piers Harding
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+if (has_capability('moodle/restore:restorecourse', $systemcontext)) {
+    $ADMIN->add('courses', new admin_externalpage('tooluploadcourse',
+                get_string('uploadcourses', 'tool_uploadcourse'),
+                "$CFG->wwwroot/$CFG->admin/tool/uploadcourse/index.php", 'moodle/site:uploadusers'));
+}
diff --git a/admin/tool/uploadcourse/version.php b/admin/tool/uploadcourse/version.php
new file mode 100644 (file)
index 0000000..1139fb2
--- /dev/null
@@ -0,0 +1,31 @@
+<?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/>.
+
+/**
+ * Plugin version info
+ *
+ * @package    tool_uploadcourse
+ * @subpackage uploadcourse
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @copyright  2011 Piers Harding
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die;
+
+$plugin->version   = 2012112301; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2011091600; // Requires this Moodle version
+$plugin->component = 'tool_uploadcourse'; // Full name of the plugin (used for diagnostics).
+