Merge branch 'wip-MDL-35468-master' of git://github.com/marinaglancy/moodle

This commit is contained in:
Damyon Wiese 2014-09-15 13:16:59 +08:00
commit 7214b77a51
11 changed files with 875 additions and 11 deletions

View File

@ -25,7 +25,6 @@
require('../config.php');
require($CFG->dirroot.'/cohort/lib.php');
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->libdir.'/coursecatlib.php');
$contextid = optional_param('contextid', 0, PARAM_INT);
$page = optional_param('page', 0, PARAM_INT);
@ -124,10 +123,10 @@ foreach($cohorts['cohorts'] as $cohort) {
$cohortcontext = context::instance_by_id($cohort->contextid);
if ($showall) {
if ($cohortcontext->contextlevel == CONTEXT_COURSECAT) {
$cat = coursecat::get($cohortcontext->instanceid);
$line[] = html_writer::link(new moodle_url('/cohort/index.php' , array('contextid' => $cohort->contextid)), $cat->get_formatted_name());
$line[] = html_writer::link(new moodle_url('/cohort/index.php' ,
array('contextid' => $cohort->contextid)), $cohortcontext->get_context_name(false));
} else {
$line[] = get_string('coresystem');
$line[] = $cohortcontext->get_context_name(false);
}
}
$line[] = format_string($cohort->name);

View File

@ -418,6 +418,12 @@ function cohort_edit_controls(context $context, moodle_url $currenturl) {
if ($currenturl->get_path() === $addurl->get_path() && !$currenturl->param('id')) {
$currenttab = 'addcohort';
}
$uploadurl = new moodle_url('/cohort/upload.php', array('contextid' => $context->id));
$tabs[] = new tabobject('uploadcohorts', $uploadurl, get_string('uploadcohorts', 'cohort'));
if ($currenturl->get_path() === $uploadurl->get_path()) {
$currenttab = 'uploadcohorts';
}
}
if (count($tabs) > 1) {
return new tabtree($tabs, $currenttab);

View File

@ -0,0 +1,156 @@
@core @core_cohort @_file_upload
Feature: A privileged user can create cohorts using a CSV file
In order to create cohorts using a CSV file
As an admin
I need to be able to upload a CSV file and navigate through the upload process
Background:
Given the following "categories" exist:
| name | category | idnumber |
| Cat 1 | 0 | CAT1 |
| Cat 2 | 0 | CAT2 |
| Cat 3 | CAT1 | CAT3 |
@javascript
Scenario: Upload cohorts with default System context as admin
When I log in as "admin"
And I navigate to "Cohorts" node in "Site administration > Users > Accounts"
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts1.csv" file to "File" filemanager
And I click on "Preview" "button"
Then the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | Status |
| cohort name 1 | cohortid1 | first description | System | |
| cohort name 2 | cohortid2 | | System | |
| cohort name 3 | cohortid3 | | Miscellaneous | |
| cohort name 4 | cohortid4 | | Cat 1 | |
| cohort name 5 | cohortid5 | | Cat 2 | |
| cohort name 6 | cohortid6 | | Cat 3 | |
And I press "Upload cohorts"
And I should see "Uploaded 6 cohorts"
And I press "Continue"
And the following should exist in the "cohorts" table:
| Name | Cohort ID | Description | Cohort size | Source |
| cohort name 1 | cohortid1 | first description | 0 | Created manually |
| cohort name 2 | cohortid2 | | 0 | Created manually |
And I follow "All cohorts"
And the following should exist in the "cohorts" table:
| Category | Name | Cohort ID | Description | Cohort size | Source |
| System | cohort name 1 | cohortid1 | first description | 0 | Created manually |
| System | cohort name 2 | cohortid2 | | 0 | Created manually |
| Miscellaneous | cohort name 3 | cohortid3 | | 0 | Created manually |
| Cat 1 | cohort name 4 | cohortid4 | | 0 | Created manually |
| Cat 2 | cohort name 5 | cohortid5 | | 0 | Created manually |
| Cat 3 | cohort name 6 | cohortid6 | | 0 | Created manually |
@javascript
Scenario: Upload cohorts with default category context as admin
When I log in as "admin"
And I navigate to "Cohorts" node in "Site administration > Users > Accounts"
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts1.csv" file to "File" filemanager
And I set the field "Default context" to "Cat 1 / Cat 3"
And I click on "Preview" "button"
Then the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | Status |
| cohort name 1 | cohortid1 | first description | Cat 3 | |
| cohort name 2 | cohortid2 | | Cat 3 | |
| cohort name 3 | cohortid3 | | Miscellaneous | |
| cohort name 4 | cohortid4 | | Cat 1 | |
| cohort name 5 | cohortid5 | | Cat 2 | |
| cohort name 6 | cohortid6 | | Cat 3 | |
And I press "Upload cohorts"
And I should see "Uploaded 6 cohorts"
And I press "Continue"
And I should see "Category: Cat 3: available cohorts (3)"
And I navigate to "Cohorts" node in "Site administration > Users > Accounts"
And I follow "All cohorts"
And the following should exist in the "cohorts" table:
| Category | Name | Cohort ID | Description | Cohort size | Source |
| Cat 3 | cohort name 1 | cohortid1 | first description | 0 | Created manually |
| Cat 3 | cohort name 2 | cohortid2 | | 0 | Created manually |
| Miscellaneous | cohort name 3 | cohortid3 | | 0 | Created manually |
| Cat 1 | cohort name 4 | cohortid4 | | 0 | Created manually |
| Cat 2 | cohort name 5 | cohortid5 | | 0 | Created manually |
| Cat 3 | cohort name 6 | cohortid6 | | 0 | Created manually |
@javascript
Scenario: Upload cohorts with default category context as manager
Given the following "users" exist:
| username | firstname | lastname | email |
| user1 | User | 1 | user1@moodlemoodle.com |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user1 | manager | Category | CAT1 |
When I log in as "user1"
And I follow "Courses"
And I follow "Cat 1"
And I navigate to "Cohorts" node in "Category: Cat 1"
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts1.csv" file to "File" filemanager
And I click on "Preview" "button"
Then the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | Status |
| cohort name 1 | cohortid1 | first description | Cat 1 | |
| cohort name 2 | cohortid2 | | Cat 1 | |
| cohort name 3 | cohortid3 | | Cat 1 | Category Miscellaneous not found or you don't have permission to create a cohort there. The default context will be used. |
| cohort name 4 | cohortid4 | | Cat 1 | |
| cohort name 5 | cohortid5 | | Cat 1 | Category CAT2 not found or you don't have permission to create a cohort there. The default context will be used. |
| cohort name 6 | cohortid6 | | Cat 3 | |
And I press "Upload cohorts"
And I should see "Uploaded 6 cohorts"
@javascript
Scenario: Upload cohorts with conflicting id number
Given the following "cohorts" exist:
| name | idnumber |
| Cohort | cohortid2 |
When I log in as "admin"
And I navigate to "Cohorts" node in "Site administration > Users > Accounts"
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts1.csv" file to "File" filemanager
And I click on "Preview" "button"
Then I should see "Errors were found in CSV data. See details below."
Then the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | Status |
| cohort name 1 | cohortid1 | first description | System | |
| cohort name 2 | cohortid2 | | System | Cohort with the same ID number already exists |
| cohort name 3 | cohortid3 | | Miscellaneous | |
| cohort name 4 | cohortid4 | | Cat 1 | |
| cohort name 5 | cohortid5 | | Cat 2 | |
| cohort name 6 | cohortid6 | | Cat 3 | |
And "Upload cohorts" "button" should not exist
@javascript
Scenario: Upload cohorts with different ways of specifying context
When I log in as "admin"
And I navigate to "Cohorts" node in "Site administration > Users > Accounts"
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts2.csv" file to "File" filemanager
And I click on "Preview" "button"
Then the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | Status |
| Specify category as name | cohortid1 | | Miscellaneous | |
| Specify category as idnumber | cohortid2 | | Cat 1 | |
| Specify category as id | cohortid3 | | Miscellaneous | |
| Specify category as path | cohortid4 | | Cat 3 | |
| Specify category_id | cohortid5 | | Miscellaneous | |
| Specify category_idnumber | cohortid6 | | Cat 1 | |
| Specify category_path | cohortid7 | | Cat 3 | |
And I should not see "not found or you"
And I press "Upload cohorts"
And I should see "Uploaded 7 cohorts"
And I press "Continue"
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts3.csv" file to "File" filemanager
And I click on "Preview" "button"
And the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | Status |
| Specify context as id (system) | cohortid8 | | System | |
| Specify context as name (system) | cohortid9 | | System | |
| Specify context as category name only | cohortid10 | | Cat 1 | |
| Specify context as category path | cohortid12 | | Cat 3 | |
| Specify context as category idnumber | cohortid13 | | Cat 2 | |
And I should not see "not found or you"
And I press "Upload cohorts"
And I should see "Uploaded 5 cohorts"

View File

@ -0,0 +1,7 @@
name,idnumber,description,category
cohort name 1,cohortid1,first description,
cohort name 2,cohortid2,,
cohort name 3,cohortid3,,Miscellaneous
cohort name 4,cohortid4,,CAT1
cohort name 5,cohortid5,,CAT2
cohort name 6,cohortid6,,CAT3
1 name idnumber description category
2 cohort name 1 cohortid1 first description
3 cohort name 2 cohortid2
4 cohort name 3 cohortid3 Miscellaneous
5 cohort name 4 cohortid4 CAT1
6 cohort name 5 cohortid5 CAT2
7 cohort name 6 cohortid6 CAT3

View File

@ -0,0 +1,8 @@
name,idnumber,description,category,category_id,category_idnumber,category_path
Specify category as name,cohortid1,,Miscellaneous,,,
Specify category as idnumber,cohortid2,,CAT1,,,
Specify category as id,cohortid3,,1,,,
Specify category as path,cohortid4,,Cat 1 / Cat 3,,,
Specify category_id,cohortid5,,,1,,
Specify category_idnumber,cohortid6,,,,CAT1,
Specify category_path,cohortid7,,,,,Cat 1 / Cat 3
1 name idnumber description category category_id category_idnumber category_path
2 Specify category as name cohortid1 Miscellaneous
3 Specify category as idnumber cohortid2 CAT1
4 Specify category as id cohortid3 1
5 Specify category as path cohortid4 Cat 1 / Cat 3
6 Specify category_id cohortid5 1
7 Specify category_idnumber cohortid6 CAT1
8 Specify category_path cohortid7 Cat 1 / Cat 3

View File

@ -0,0 +1,6 @@
name,idnumber,description,context
Specify context as id (system),cohortid8,,1
Specify context as name (system),cohortid9,,System
Specify context as category name only,cohortid10,,Cat 1
Specify context as category path,cohortid12,,Cat 1 / Cat 3
Specify context as category idnumber,cohortid13,,CAT2
1 name idnumber description context
2 Specify context as id (system) cohortid8 1
3 Specify context as name (system) cohortid9 System
4 Specify context as category name only cohortid10 Cat 1
5 Specify context as category path cohortid12 Cat 1 / Cat 3
6 Specify context as category idnumber cohortid13 CAT2

View File

@ -0,0 +1,13 @@
name,idnumber,description,category
cohort name 1,cid1,first description,
cohort name 2,cid2,,
cohort name 3,cid3,,Miscellaneous
cohort name 4,cid4,,CAT1
cohort name 5,cid5,,CAT2
cohort name 6,cid6,,CAT3
cohort name 7,cid7,,CAT1
cohort name 8,cid8,,CAT2
cohort name 9,cid9,,CAT3
cohort name 10,cid10,,CAT1
cohort name 11,cid11,,CAT2
cohort name 12,cid1,,CAT3
1 name idnumber description category
2 cohort name 1 cid1 first description
3 cohort name 2 cid2
4 cohort name 3 cid3 Miscellaneous
5 cohort name 4 cid4 CAT1
6 cohort name 5 cid5 CAT2
7 cohort name 6 cid6 CAT3
8 cohort name 7 cid7 CAT1
9 cohort name 8 cid8 CAT2
10 cohort name 9 cid9 CAT3
11 cohort name 10 cid10 CAT1
12 cohort name 11 cid11 CAT2
13 cohort name 12 cid1 CAT3

93
cohort/upload.php Normal file
View File

@ -0,0 +1,93 @@
<?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/>.
/**
* A form for cohort upload.
*
* @package core_cohort
* @copyright 2014 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../config.php');
require_once($CFG->dirroot.'/cohort/lib.php');
require_once($CFG->dirroot.'/cohort/upload_form.php');
require_once($CFG->libdir . '/csvlib.class.php');
$contextid = optional_param('contextid', 0, PARAM_INT);
$returnurl = optional_param('returnurl', '', PARAM_URL);
require_login();
if ($contextid) {
$context = context::instance_by_id($contextid, MUST_EXIST);
} else {
$context = context_system::instance();
}
if ($context->contextlevel != CONTEXT_COURSECAT && $context->contextlevel != CONTEXT_SYSTEM) {
print_error('invalidcontext');
}
require_capability('moodle/cohort:manage', $context);
$PAGE->set_context($context);
$baseurl = new moodle_url('/cohort/upload.php', array('contextid' => $context->id));
$PAGE->set_url($baseurl);
$PAGE->set_heading($COURSE->fullname);
$PAGE->set_pagelayout('admin');
if ($context->contextlevel == CONTEXT_COURSECAT) {
$PAGE->set_category_by_id($context->instanceid);
navigation_node::override_active_url(new moodle_url('/cohort/index.php', array('contextid' => $context->id)));
} else {
navigation_node::override_active_url(new moodle_url('/cohort/index.php', array()));
}
$uploadform = new cohort_upload_form(null, array('contextid' => $context->id, 'returnurl' => $returnurl));
if ($returnurl) {
$returnurl = new moodle_url($returnurl);
} else {
$returnurl = new moodle_url('/cohort/index.php', array('contextid' => $context->id));
}
if ($uploadform->is_cancelled()) {
redirect($returnurl);
}
$strheading = get_string('uploadcohorts', 'cohort');
$PAGE->navbar->add($strheading);
echo $OUTPUT->header();
echo $OUTPUT->heading_with_help($strheading, 'uploadcohorts', 'cohort');
if ($editcontrols = cohort_edit_controls($context, $baseurl)) {
echo $OUTPUT->render($editcontrols);
}
if ($data = $uploadform->get_data()) {
$cohortsdata = $uploadform->get_cohorts_data();
foreach ($cohortsdata as $cohort) {
cohort_add_cohort($cohort);
}
echo $OUTPUT->notification(get_string('uploadedcohorts', 'cohort', count($cohortsdata)), 'notifysuccess');
echo $OUTPUT->continue_button($returnurl);
} else {
$uploadform->display();
}
echo $OUTPUT->footer();

552
cohort/upload_form.php Normal file
View File

@ -0,0 +1,552 @@
<?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/>.
/**
* A form for cohort upload.
*
* @package core_cohort
* @copyright 2014 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* Cohort upload form class
*
* @package core_cohort
* @copyright 2014 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort_upload_form extends moodleform {
/** @var array new cohorts that need to be created */
public $processeddata = null;
/** @var array cached list of available contexts */
protected $contextoptions = null;
/** @var array temporary cache for retrieved categories */
protected $categoriescache = array();
/**
* Form definition
*/
public function definition() {
$mform = $this->_form;
$data = (object)$this->_customdata;
$mform->addElement('hidden', 'returnurl');
$mform->setType('returnurl', PARAM_URL);
$mform->addElement('header', 'cohortfileuploadform', get_string('uploadafile'));
$filepickeroptions = array();
$filepickeroptions['filetypes'] = '*';
$filepickeroptions['maxbytes'] = get_max_upload_file_size();
$mform->addElement('filepicker', 'cohortfile', get_string('file'), null, $filepickeroptions);
$choices = csv_import_reader::get_delimiter_list();
$mform->addElement('select', 'delimiter', get_string('csvdelimiter', 'tool_uploadcourse'), $choices);
if (array_key_exists('cfg', $choices)) {
$mform->setDefault('delimiter', 'cfg');
} else if (get_string('listsep', 'langconfig') == ';') {
$mform->setDefault('delimiter', 'semicolon');
} else {
$mform->setDefault('delimiter', 'comma');
}
$mform->addHelpButton('delimiter', 'csvdelimiter', 'tool_uploadcourse');
$choices = core_text::get_encodings();
$mform->addElement('select', 'encoding', get_string('encoding', 'tool_uploadcourse'), $choices);
$mform->setDefault('encoding', 'UTF-8');
$mform->addHelpButton('encoding', 'encoding', 'tool_uploadcourse');
$options = $this->get_context_options();
$mform->addElement('select', 'contextid', get_string('defaultcontext', 'cohort'), $options);
$this->add_cohort_upload_buttons(true);
$this->set_data($data);
}
/**
* Add buttons to the form ("Upload cohorts", "Preview", "Cancel")
*/
protected function add_cohort_upload_buttons() {
$mform = $this->_form;
$buttonarray = array();
$submitlabel = get_string('uploadcohorts', 'cohort');
$buttonarray[] = $mform->createElement('submit', 'submitbutton', $submitlabel);
$previewlabel = get_string('preview', 'cohort');
$buttonarray[] = $mform->createElement('submit', 'previewbutton', $previewlabel);
$mform->registerNoSubmitButton('previewbutton');
$buttonarray[] = $mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$mform->closeHeaderBefore('buttonar');
}
/**
* Process the uploaded file and allow the submit button only if it doest not have errors.
*/
public function definition_after_data() {
$mform = $this->_form;
$cohortfile = $mform->getElementValue('cohortfile');
$allowsubmitform = false;
if ($cohortfile && ($file = $this->get_cohort_file($cohortfile))) {
// File was uploaded. Parse it.
$encoding = $mform->getElementValue('encoding')[0];
$delimiter = $mform->getElementValue('delimiter')[0];
$contextid = $mform->getElementValue('contextid')[0];
if (!empty($contextid) && ($context = context::instance_by_id($contextid, IGNORE_MISSING))) {
$this->processeddata = $this->process_upload_file($file, $encoding, $delimiter, $context);
if ($this->processeddata && count($this->processeddata) > 1 && !$this->processeddata[0]['errors']) {
$allowsubmitform = true;
}
}
}
if (!$allowsubmitform) {
// Hide submit button.
$el = $mform->getElement('buttonar')->getElements()[0];
$el->setValue('');
$el->freeze();
} else {
$mform->setExpanded('cohortfileuploadform', false);
}
}
/**
* Returns the list of contexts where current user can create cohorts.
*
* @return array
*/
protected function get_context_options() {
global $CFG;
require_once($CFG->libdir. '/coursecatlib.php');
if ($this->contextoptions === null) {
$this->contextoptions = array();
$displaylist = coursecat::make_categories_list('moodle/cohort:manage');
// We need to index the options array by context id instead of category id and add option for system context.
$syscontext = context_system::instance();
if (has_capability('moodle/cohort:manage', $syscontext)) {
$this->contextoptions[$syscontext->id] = $syscontext->get_context_name();
}
foreach ($displaylist as $cid => $name) {
$context = context_coursecat::instance($cid);
$this->contextoptions[$context->id] = $name;
}
}
return $this->contextoptions;
}
public function validation($data, $files) {
$errors = parent::validation($data, $files);
if (empty($errors)) {
if (empty($data['cohortfile']) || !($file = $this->get_cohort_file($data['cohortfile']))) {
$errors['cohortfile'] = get_string('required');
} else {
if (!empty($this->processeddata[0]['errors'])) {
// Any value in $errors will notify that validation did not pass. The detailed errors will be shown in preview.
$errors['dummy'] = '';
}
}
}
return $errors;
}
/**
* Returns the uploaded file if it is present.
*
* @param int $draftid
* @return stored_file|null
*/
protected function get_cohort_file($draftid) {
global $USER;
// We can not use moodleform::get_file_content() method because we need the content before the form is validated.
if (!$draftid) {
return null;
}
$fs = get_file_storage();
$context = context_user::instance($USER->id);
if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
return null;
}
$file = reset($files);
return $file;
}
/**
* Returns the list of prepared objects to be added as cohorts
*
* @return array of stdClass objects, each can be passed to {@link cohort_add_cohort()}
*/
public function get_cohorts_data() {
$cohorts = array();
if ($this->processeddata) {
foreach ($this->processeddata as $idx => $line) {
if ($idx && !empty($line['data'])) {
$cohorts[] = (object)$line['data'];
}
}
}
return $cohorts;
}
/**
* Displays the preview of the uploaded file
*/
protected function preview_uploaded_cohorts() {
global $OUTPUT;
if (empty($this->processeddata)) {
return;
}
foreach ($this->processeddata[0]['errors'] as $error) {
echo $OUTPUT->notification($error);
}
foreach ($this->processeddata[0]['warnings'] as $warning) {
echo $OUTPUT->notification($warning, 'notifymessage');
}
$table = new html_table();
$table->id = 'previewuploadedcohorts';
$columns = $this->processeddata[0]['data'];
$columns['contextid'] = get_string('context', 'role');
// Add column names to the preview table.
$table->head = array('');
foreach ($columns as $key => $value) {
$table->head[] = $value;
}
$table->head[] = get_string('status');
// Add (some) rows to the preview table.
$previewdrows = $this->get_previewed_rows();
foreach ($previewdrows as $idx) {
$line = $this->processeddata[$idx];
$cells = array(new html_table_cell($idx));
$context = context::instance_by_id($line['data']['contextid']);
foreach ($columns as $key => $value) {
if ($key === 'contextid') {
$text = html_writer::link(new moodle_url('/cohort/index.php', array('contextid' => $context->id)),
$context->get_context_name(false));
} else {
$text = s($line['data'][$key]);
}
$cells[] = new html_table_cell($text);
}
$text = '';
if ($line['errors']) {
$text .= html_writer::div(join('<br>', $line['errors']), 'notifyproblem');
}
if ($line['warnings']) {
$text .= html_writer::div(join('<br>', $line['warnings']));
}
$cells[] = new html_table_cell($text);
$table->data[] = new html_table_row($cells);
}
if ($notdisplayed = count($this->processeddata) - count($previewdrows) - 1) {
$cell = new html_table_cell(get_string('displayedrows', 'cohort',
(object)array('displayed' => count($previewdrows), 'total' => count($this->processeddata) - 1)));
$cell->colspan = count($columns) + 2;
$table->data[] = new html_table_row(array($cell));
}
echo html_writer::table($table);
}
/**
* Find up rows to show in preview
*
* Number of previewed rows is limited but rows with errors and warnings have priority.
*
* @return array
*/
protected function get_previewed_rows() {
$previewlimit = 10;
if (count($this->processeddata) <= 1) {
$rows = array();
} else if (count($this->processeddata) < $previewlimit + 1) {
// Return all rows.
$rows = range(1, count($this->processeddata) - 1);
} else {
// First find rows with errors and warnings (no more than 10 of each).
$errorrows = $warningrows = array();
foreach ($this->processeddata as $rownum => $line) {
if ($rownum && $line['errors']) {
$errorrows[] = $rownum;
if (count($errorrows) >= $previewlimit) {
return $errorrows;
}
} else if ($rownum && $line['warnings']) {
if (count($warningrows) + count($errorrows) < $previewlimit) {
$warningrows[] = $rownum;
}
}
}
// Include as many error rows as possible and top them up with warning rows.
$rows = array_merge($errorrows, array_slice($warningrows, 0, $previewlimit - count($errorrows)));
// Keep adding good rows until we reach limit.
for ($rownum = 1; count($rows) < $previewlimit; $rownum++) {
if (!in_array($rownum, $rows)) {
$rows[] = $rownum;
}
}
asort($rows);
}
return $rows;
}
public function display() {
// Finalize the form definition if not yet done.
if (!$this->_definition_finalized) {
$this->_definition_finalized = true;
$this->definition_after_data();
}
// Difference from the parent display() method is that we want to show preview above the form if applicable.
$this->preview_uploaded_cohorts();
$this->_form->display();
}
/**
* @param stored_file $file
* @param string $encoding
* @param string $delimiter
* @param context $defaultcontext
* @return array
*/
protected function process_upload_file($file, $encoding, $delimiter, $defaultcontext) {
global $CFG, $DB;
require_once($CFG->libdir . '/csvlib.class.php');
$cohorts = array(
0 => array('errors' => array(), 'warnings' => array(), 'data' => array())
);
// Read and parse the CSV file using csv library.
$content = $file->get_content();
if (!$content) {
$cohorts[0]['errors'][] = new lang_string('csvemptyfile', 'error');
return $cohorts;
}
$uploadid = csv_import_reader::get_new_iid('uploadcohort');
$cir = new csv_import_reader($uploadid, 'uploadcohort');
$readcount = $cir->load_csv_content($content, $encoding, $delimiter);
unset($content);
if (!$readcount) {
$cohorts[0]['errors'][] = get_string('csvloaderror', 'error', $cir->get_error());
return $cohorts;
}
$columns = $cir->get_columns();
// Check that columns include 'name' and warn about extra columns.
$allowedcolumns = array('contextid', 'name', 'idnumber', 'description', 'descriptionformat');
$additionalcolumns = array('context', 'category', 'category_id', 'category_idnumber', 'category_path');
$displaycolumns = array();
$extracolumns = array();
$columnsmapping = array();
foreach ($columns as $i => $columnname) {
$columnnamelower = preg_replace('/ /', '', core_text::strtolower($columnname));
$columnsmapping[$i] = null;
if (in_array($columnnamelower, $allowedcolumns)) {
$displaycolumns[$columnnamelower] = $columnname;
$columnsmapping[$i] = $columnnamelower;
} else if (in_array($columnnamelower, $additionalcolumns)) {
$columnsmapping[$i] = $columnnamelower;
} else {
$extracolumns[] = $columnname;
}
}
if (!in_array('name', $columnsmapping)) {
$cohorts[0]['errors'][] = new lang_string('namecolumnmissing', 'cohort');
return $cohorts;
}
if ($extracolumns) {
$cohorts[0]['warnings'][] = new lang_string('csvextracolumns', 'cohort', s(join(', ', $extracolumns)));
}
if (!isset($displaycolumns['contextid'])) {
$displaycolumns['contextid'] = 'contextid';
}
$cohorts[0]['data'] = $displaycolumns;
// Parse data rows.
$cir->init();
$rownum = 0;
$idnumbers = array();
$haserrors = false;
$haswarnings = false;
while ($row = $cir->next()) {
$rownum++;
$cohorts[$rownum] = array(
'errors' => array(),
'warnings' => array(),
'data' => array(),
);
$hash = array();
foreach ($row as $i => $value) {
if ($columnsmapping[$i]) {
$hash[$columnsmapping[$i]] = $value;
}
}
$this->clean_cohort_data($hash);
$warnings = $this->resolve_context($hash, $defaultcontext);
$cohorts[$rownum]['warnings'] = array_merge($cohorts[$rownum]['warnings'], $warnings);
if (!empty($hash['idnumber'])) {
if (isset($idnumbers[$hash['idnumber']]) || $DB->record_exists('cohort', array('idnumber' => $hash['idnumber']))) {
$cohorts[$rownum]['errors'][] = new lang_string('duplicateidnumber', 'cohort');
}
$idnumbers[$hash['idnumber']] = true;
}
if (empty($hash['name'])) {
$cohorts[$rownum]['errors'][] = new lang_string('namefieldempty', 'cohort');
}
$cohorts[$rownum]['data'] = array_intersect_key($hash, $cohorts[0]['data']);
$haserrors = $haserrors || !empty($cohorts[$rownum]['errors']);
$haswarnings = $haswarnings || !empty($cohorts[$rownum]['warnings']);
}
if ($haserrors) {
$cohorts[0]['errors'][] = new lang_string('csvcontainserrors', 'cohort');
}
if ($haswarnings) {
$cohorts[0]['warnings'][] = new lang_string('csvcontainswarnings', 'cohort');
}
return $cohorts;
}
/**
* Cleans input data about one cohort.
*
* @param array $hash
*/
protected function clean_cohort_data(&$hash) {
foreach ($hash as $key => $value) {
switch ($key) {
case 'contextid': $hash[$key] = clean_param($value, PARAM_INT); break;
case 'name': $hash[$key] = core_text::substr(clean_param($value, PARAM_TEXT), 0, 254); break;
case 'idnumber': $hash[$key] = core_text::substr(clean_param($value, PARAM_RAW), 0, 254); break;
case 'description': $hash[$key] = clean_param($value, PARAM_RAW); break;
case 'descriptionformat': $hash[$key] = clean_param($value, PARAM_INT); break;
}
}
}
/**
* Determines in which context the particular cohort will be created
*
* @param array $hash
* @param context $defaultcontext
* @return array array of warning strings
*/
protected function resolve_context(&$hash, $defaultcontext) {
global $DB;
$warnings = array();
if (!empty($hash['contextid'])) {
// Contextid was specified, verify we can post there.
$contextoptions = $this->get_context_options();
if (!isset($contextoptions[$hash['contextid']])) {
$warnings[] = new lang_string('contextnotfound', 'cohort', $hash['contextid']);
$hash['contextid'] = $defaultcontext->id;
}
return $warnings;
}
if (!empty($hash['context'])) {
$systemcontext = context_system::instance();
if ((core_text::strtolower(trim($hash['context'])) ===
core_text::strtolower($systemcontext->get_context_name())) ||
('' . $hash['context'] === '' . $systemcontext->id)) {
// User meant system context.
$hash['contextid'] = $systemcontext->id;
$contextoptions = $this->get_context_options();
if (!isset($contextoptions[$hash['contextid']])) {
$warnings[] = new lang_string('contextnotfound', 'cohort', $hash['context']);
$hash['contextid'] = $defaultcontext->id;
}
} else {
// Assume it is a category.
$hash['category'] = trim($hash['context']);
}
}
if (!empty($hash['category_path'])) {
// We already have array with available categories, look up the value.
$contextoptions = $this->get_context_options();
if (!$hash['contextid'] = array_search($hash['category_path'], $contextoptions)) {
$warnings[] = new lang_string('categorynotfound', 'cohort', s($hash['category_path']));
$hash['contextid'] = $defaultcontext->id;
}
return $warnings;
}
if (!empty($hash['category'])) {
// Quick search by category path first.
// Do not issue warnings or return here, further we'll try to search by id or idnumber.
$contextoptions = $this->get_context_options();
if ($hash['contextid'] = array_search($hash['category'], $contextoptions)) {
return $warnings;
}
}
// Now search by category id or category idnumber.
if (!empty($hash['category_id'])) {
$field = 'id';
$value = clean_param($hash['category_id'], PARAM_INT);
} else if (!empty($hash['category_idnumber'])) {
$field = 'idnumber';
$value = $hash['category_idnumber'];
} else if (!empty($hash['category'])) {
$field = is_numeric($hash['category']) ? 'id' : 'idnumber';
$value = $hash['category'];
} else {
// No category field was specified, assume default category.
$hash['contextid'] = $defaultcontext->id;
return $warnings;
}
if (empty($this->categoriescache[$field][$value])) {
$record = $DB->get_record_sql("SELECT c.id, ctx.id contextid
FROM {context} ctx JOIN {course_categories} c ON ctx.contextlevel = ? AND ctx.instanceid = c.id
WHERE c.$field = ?", array(CONTEXT_COURSECAT, $value));
if ($record && ($contextoptions = $this->get_context_options()) && isset($contextoptions[$record->contextid])) {
$contextid = $record->contextid;
} else {
$warnings[] = new lang_string('categorynotfound', 'cohort', s($value));
$contextid = $defaultcontext->id;
}
// Next time when we can look up and don't search by this value again.
$this->categoriescache[$field][$value] = $contextid;
}
$hash['contextid'] = $this->categoriescache[$field][$value];
return $warnings;
}
}

View File

@ -32,6 +32,7 @@ $string['assignto'] = 'Cohort \'{$a}\' members';
$string['backtocohorts'] = 'Back to cohorts';
$string['bulkadd'] = 'Add to cohort';
$string['bulknocohort'] = 'No available cohorts found';
$string['categorynotfound'] = 'Category <b>{$a}</b> not found or you don\'t have permission to create a cohort there. The default context will be used.';
$string['cohort'] = 'Cohort';
$string['cohorts'] = 'Cohorts';
$string['cohortsin'] = '{$a}: available cohorts';
@ -39,11 +40,17 @@ $string['cohort:assign'] = 'Assign cohort members';
$string['cohort:manage'] = 'Manage cohorts';
$string['cohort:view'] = 'Use cohorts and view members';
$string['component'] = 'Source';
$string['contextnotfound'] = 'Context <b>{$a}</b> not found or you don\'t have permission to create a cohort there. The default context will be used.';
$string['csvcontainserrors'] = 'Errors were found in CSV data. See details below.';
$string['csvcontainswarnings'] = 'Warnings were found in CSV data. See details below.';
$string['csvextracolumns'] = 'Column(s) <b>{$a}</b> will be ignored.';
$string['currentusers'] = 'Current users';
$string['currentusersmatching'] = 'Current users matching';
$string['defaultcontext'] = 'Default context';
$string['delcohort'] = 'Delete cohort';
$string['delconfirm'] = 'Do you really want to delete cohort \'{$a}\'?';
$string['description'] = 'Description';
$string['displayedrows'] = '{$a->displayed} rows displayed out of {$a->total}.';
$string['duplicateidnumber'] = 'Cohort with the same ID number already exists';
$string['editcohort'] = 'Edit cohort';
$string['eventcohortcreated'] = 'Cohort created';
@ -55,13 +62,26 @@ $string['external'] = 'External cohort';
$string['idnumber'] = 'Cohort ID';
$string['memberscount'] = 'Cohort size';
$string['name'] = 'Name';
$string['namecolumnmissing'] = 'There is something wrong with the format of the CSV file. Please check that it includes column names.';
$string['namefieldempty'] = 'Field name can not be empty';
$string['nocomponent'] = 'Created manually';
$string['potusers'] = 'Potential users';
$string['potusersmatching'] = 'Potential matching users';
$string['preview'] = 'Preview';
$string['removeuserwarning'] = 'Removing users from a cohort may result in unenrolling of users from multiple courses which includes deleting of user settings, grades, group membership and other user information from affected courses.';
$string['selectfromcohort'] = 'Select members from cohort';
$string['systemcohorts'] = 'System cohorts';
$string['unknowncohort'] = 'Unknown cohort ({$a})!';
$string['uploadcohorts'] = 'Upload cohorts';
$string['uploadedcohorts'] = 'Uploaded {$a} cohorts';
$string['useradded'] = 'User added to cohort "{$a}"';
$string['search'] = 'Search';
$string['searchcohort'] = 'Search cohort';
$string['uploadcohorts_help'] = 'Cohorts may be uploaded 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 fieldname is name
* Optional fieldnames are idnumber, description, descriptionformat, context, category, category_id, category_idnumber, category_path
';

View File

@ -1000,11 +1000,15 @@ class behat_general extends behat_base {
$tablenode = $this->get_selected_node('table', $table);
$tablexpath = $tablenode->getXpath();
$rowliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($row);
$valueliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($value);
$columnliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($column);
// Header can be in thead or tbody (first row), following xpath should work.
$theadheaderxpath = "thead/tr[1]/th[(normalize-space(.)='" . $column . "' or a[normalize-space(text())='" .
$column . "'])]";
$tbodyheaderxpath = "tbody/tr[1]/td[(normalize-space(.)='" . $column . "' or a[normalize-space(text())='" .
$column . "'])]";
$theadheaderxpath = "thead/tr[1]/th[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" .
$columnliteral . "])]";
$tbodyheaderxpath = "tbody/tr[1]/td[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" .
$columnliteral . "])]";
// Check if column exists.
$columnheaderxpath = $tablexpath . "[" . $theadheaderxpath . " | " . $tbodyheaderxpath . "]";
@ -1016,21 +1020,21 @@ class behat_general extends behat_base {
// Check if value exists in specific row/column.
// Get row xpath.
$rowxpath = $tablexpath."/tbody/tr[th[normalize-space(.)='" . $row . "'] | td[normalize-space(.)='" . $row . "']]";
$rowxpath = $tablexpath."/tbody/tr[th[normalize-space(.)=" . $rowliteral . "] | td[normalize-space(.)=" . $rowliteral . "]]";
// Following conditions were considered before finding column count.
// 1. Table header can be in thead/tr/th or tbody/tr/td[1].
// 2. First column can have th (Gradebook -> user report), so having lenient sibling check.
$columnpositionxpath = "/child::*[position() = count(" . $tablexpath . "/" . $theadheaderxpath .
"/preceding-sibling::*) + 1]";
$columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.),'" . $value . "')]";
$columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.)," . $valueliteral . ")]";
// Looks for the requested node inside the container node.
$coumnnode = $this->getSession()->getDriver()->find($columnvaluexpath);
if (empty($coumnnode)) {
// Check if tbody/tr[1] contains header selector.
$columnpositionxpath = "/child::*[position() = count(" . $tablexpath . "/" . $tbodyheaderxpath .
"/preceding-sibling::*) + 1]";
$columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.),'" . $value . "')]";
$columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.)," . $valueliteral . ")]";
$coumnnode = $this->getSession()->getDriver()->find($columnvaluexpath);
if (empty($coumnnode)) {
$locatorexceptionmsg = $value . '" in "' . $row . '" row with column "' . $column;