}
public function save_changes() {
- global $DB;
+ global $DB, $CFG;
if (!$this->roleid) {
// Creating role.
// the UI. It would be better to do this only when we know that fields affected are
// updated. But thats getting into the weeds of the coursecat cache and role edits
// should not be that frequent, so here is the ugly brutal approach.
+ require_once($CFG->libdir . '/coursecatlib.php');
coursecat::role_assignment_changed($this->role->id, context_system::instance());
}
$event = \core\event\role_capabilities_updated::create(
array(
'context' => $systemcontext,
- 'objectid' => $roleid
+ 'objectid' => $tableroleid
)
);
$event->set_legacy_logdata(array(SITEID, 'role', $action, 'admin/roles/define.php?action=view&roleid=' . $tableroleid,
// "supportcontact" settingpage
$temp = new admin_settingpage('supportcontact', new lang_string('supportcontact','admin'));
-if (isloggedin()) {
- global $USER;
- $primaryadminemail = $USER->email;
- $primaryadminname = fullname($USER, true);
-
+$primaryadmin = get_admin();
+if ($primaryadmin) {
+ $primaryadminemail = $primaryadmin->email;
+ $primaryadminname = fullname($primaryadmin, true);
} else {
// no defaults during installation - admin user must be created first
$primaryadminemail = NULL;
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
require_once(__DIR__ . '/../../../lib/behat/behat_field_manager.php');
-use Behat\Behat\Context\Step\Given as Given,
- Behat\Gherkin\Node\TableNode as TableNode,
+use Behat\Gherkin\Node\TableNode as TableNode,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
if ($options['profile']) {
$profile = $options['profile'];
- if (!isset($CFG->behat_config[$profile])) {
- echo "Invalid profile passed: " . $profile;
+ if (!isset($CFG->behat_config[$profile]) && !isset($CFG->behat_profiles[$profile])) {
+ echo "Invalid profile passed: " . $profile . PHP_EOL;
exit(1);
}
$extraopts[] = '--profile="' . $profile . '"';
$time = round(microtime(true) - $time, 1);
echo "Finished in " . gmdate("G\h i\m s\s", $time) . PHP_EOL . PHP_EOL;
+ksort($exitcodes);
// Print exit info from each run.
-$status = false;
+// Status bits contains pass/fail status of parallel runs.
+$status = 0;
+$processcounter = 0;
foreach ($exitcodes as $exitcode) {
- $status = (bool)$status || (bool)$exitcode;
+ if ($exitcode) {
+ $status |= (1 << $processcounter);
+ }
+ $processcounter++;
}
// Run finished. Show exit code and output from individual process.
$verbose = empty($options['verbose']) ? false : true;
-$verbose = $verbose || $status;
+$verbose = $verbose || !empty($status);
// Show exit code from each process, if any process failed.
if ($verbose) {
// Echo exit codes.
echo "Exit codes for each behat run: " . PHP_EOL;
- ksort($exitcodes);
foreach ($exitcodes as $run => $exitcode) {
echo $run . ": " . $exitcode . PHP_EOL;
}
// Remove site symlink if necessary.
behat_config_manager::drop_parallel_site_links();
-exit((int) $status);
+exit($status);
/**
* Signal handler for terminal exit.
// This is only displayed once for parallel install.
if (empty($options['run'])) {
+ // Notify user that 2.5 profile has been converted to 3.5.
+ if (behat_config_manager::$autoprofileconversion) {
+ mtrace("2.5 behat profile detected, automatically converted to current 3.x format");
+ }
+
$runtestscommand = behat_command::get_behat_command(true, !empty($options['run']));
$runtestscommand .= ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
When I set the field "First name" to "Field value"
And I set the field "Select a country" to "Japan"
And I set the field "Unmask" to "1"
- And I expand all fieldsets
Then the field "First name" matches value "Field value"
And the "Select a country" select box should contain "Japan"
And the field "Unmask" matches value "1"
// YAML decides when is is necessary to wrap strings between single quotes, so not controlled
// values like paths should not be asserted including the key name as they would depend on the
// directories values.
- $this->assertContains($CFG->dirroot . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . 'features', $contents);
+ $this->assertContains($CFG->dirroot, $contents);
// Not quoted strings.
$this->assertContains('micarro: /me/lo/robaron', $contents);
- $this->assertContains('class: behat_init_context', $contents);
// YAML uses single quotes to wrap URL strings.
$this->assertContains("base_url: '" . $CFG->behat_wwwroot . "'", $contents);
Scenario: Install language pack
Given I log in as "admin"
And I navigate to "Language packs" node in "Site administration > Language"
- When I set the field "Available language packs" to "English - Pirate (en_ar)"
+ When I set the field "Available language packs" to "en_ar"
And I press "Install selected language pack(s)"
Then I should see "Language pack 'en_ar' was successfully installed"
- And the "Installed language packs" select box should contain "English - Pirate (en_ar)"
+ And the "Installed language packs" select box should contain "en_ar"
And I navigate to "Live logs" node in "Site administration > Reports"
And I should see "The language pack 'en_ar' was installed."
And I log out
Scenario: Try to uninstall language pack
Given I log in as "admin"
And I navigate to "Language packs" node in "Site administration > Language"
- And I set the field "Available language packs" to "English - Pirate (en_ar)"
+ And I set the field "Available language packs" to "en_ar"
And I press "Install selected language pack(s)"
- When I set the field "Installed language packs" to "English - Pirate (en_ar)"
+ When I set the field "Installed language packs" to "en_ar"
And I press "Uninstall selected language pack(s)"
And I press "Continue"
Then I should see "Language pack 'en_ar' was uninstalled"
- And the "Installed language packs" select box should not contain "English - Pirate (en_ar)"
- And the "Available language packs" select box should contain "English - Pirate (en_ar)"
+ And the "Installed language packs" select box should not contain "en_ar"
+ And the "Available language packs" select box should contain "en_ar"
And I navigate to "Live logs" node in "Site administration > Reports"
And I should see "The language pack 'en_ar' was removed."
And I should see "Language pack uninstalled"
Scenario: Try to uninstall English language pack
Given I log in as "admin"
And I navigate to "Language packs" node in "Site administration > Language"
- When I set the field "Installed language packs" to "English (en)"
+ When I set the field "Installed language packs" to "en"
And I press "Uninstall selected language pack(s)"
Then I should see "The English language pack cannot be uninstalled."
And I navigate to "Live logs" node in "Site administration > Reports"
if (has_capability('tool/monitor:subscribe', context_system::instance())) {
$options[0] = get_string('site');
}
- if ($courses = get_user_capability_course('tool/monitor:subscribe', null, true, 'fullname', $orderby)) {
+ if ($courses = get_user_capability_course('tool/monitor:subscribe', null, true, 'fullname, visible', $orderby)) {
foreach ($courses as $course) {
- $options[$course->id] = format_string($course->fullname, true,
- array('context' => context_course::instance($course->id)));
+ $coursectx = context_course::instance($course->id);
+ if ($course->visible || has_capability('moodle/course:viewhiddencourses', $coursectx)) {
+ $options[$course->id] = format_string($course->fullname, true, array('context' => $coursectx));
+ }
}
}
// If there are no courses and there is no site permission then return false.
}
if ($existinguser->$column !== $user->$column) {
if ($column === 'email') {
- if ($DB->record_exists('user', array('email'=>$user->email))) {
- if ($noemailduplicates) {
+ $select = $DB->sql_like('email', ':email', false, true, false, '|');
+ $params = array('email' => $DB->sql_like_escape($user->email, '|'));
+ if ($DB->record_exists_select('user', $select , $params)) {
+
+ $changeincase = core_text::strtolower($existinguser->$column) === core_text::strtolower(
+ $user->$column);
+
+ if ($changeincase) {
+ // If only case is different then switch to lower case and carry on.
+ $user->$column = core_text::strtolower($user->$column);
+ continue;
+ } else if ($noemailduplicates) {
$upt->track('email', $stremailduplicate, 'error');
$upt->track('status', $strusernotupdated, 'error');
$userserrors++;
if (!validate_email($rowcols['email'])) {
$rowcols['status'][] = get_string('invalidemail');
}
- if ($DB->record_exists('user', array('email'=>$rowcols['email']))) {
+
+ $select = $DB->sql_like('email', ':email', false, true, false, '|');
+ $params = array('email' => $DB->sql_like_escape($rowcols['email'], '|'));
+ if ($DB->record_exists_select('user', $select , $params)) {
$rowcols['status'][] = $stremailduplicate;
}
}
$updateuser = new stdClass();
$updateuser->id = $user->id;
$updateuser->suspended = 1;
+ $updateuser = $this->clean_data($updateuser);
user_update_user($updateuser, false);
$trace->output(get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
}
$updateuser = new stdClass();
$updateuser->id = $olduser->id;
$updateuser->suspended = 0;
+ $updateuser = $this->clean_data($updateuser);
user_update_user($updateuser);
$trace->output(get_string('auth_dbreviveduser', 'auth_db', array('name' => $username,
'id' => $olduser->id)), 1);
$trace->output(get_string('auth_dbinsertuserduplicate', 'auth_db', array('username'=>$user->username, 'auth'=>$collision->auth)), 1);
continue;
}
+ $user = $this->clean_data($user);
try {
$id = user_create_user($user, false); // It is truly a new user.
$trace->output(get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)), 1);
}
if ($needsupdate) {
require_once($CFG->dirroot . '/user/lib.php');
+ $updateuser = $this->clean_data($updateuser);
user_update_user($updateuser);
}
return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
error_reporting($CFG->debug);
ob_end_flush();
}
+
+ /**
+ * Clean the user data that comes from an external database.
+ *
+ * @param array $user the user data to be validated against properties definition.
+ * @return stdClass $user the cleaned user data.
+ */
+ public function clean_data($user) {
+ if (empty($user)) {
+ return $user;
+ }
+
+ foreach ($user as $field => $value) {
+ // Get the property parameter type and do the cleaning.
+ try {
+ $property = core_user::get_property_definition($field);
+ $user->$field = clean_param($value, $property['type']);
+ } catch (coding_exception $e) {
+ debugging("The property '$field' could not be cleaned.", DEBUG_DEVELOPER);
+ }
+ }
+
+ return $user;
+ }
}
$this->assertEquals("select * from table WHERE column=? AND anothercolumn > ?", $sqlout);
$this->assertEquals(array(1, 'b'), $arrout);
}
+
+ /**
+ * Testing the clean_data() method.
+ */
+ public function test_clean_data() {
+ global $DB;
+
+ $this->resetAfterTest(false);
+ $this->preventResetByRollback();
+ $this->init_auth_database();
+ $auth = get_auth_plugin('db');
+ $auth->db_init();
+
+ // Create users on external table.
+ $extdbuser1 = (object)array('name'=>'u1', 'pass'=>'heslo', 'email'=>'u1@example.com');
+ $extdbuser1->id = $DB->insert_record('auth_db_users', $extdbuser1);
+
+ // User with malicious data on the name.
+ $extdbuser2 = (object)array('name'=>'user<script>alert(1);</script>xss', 'pass'=>'heslo', 'email'=>'xssuser@example.com');
+ $extdbuser2->id = $DB->insert_record('auth_db_users', $extdbuser2);
+
+ $trace = new null_progress_trace();
+
+ // Let's test user sync make sure still works as expected..
+ $auth->sync_users($trace, true);
+
+ // Get the user on moodle user table.
+ $user2 = $DB->get_record('user', array('email'=> $extdbuser2->email, 'auth'=>'db'));
+
+ // The malicious code should be sanitized.
+ $this->assertEquals($user2->username, 'userscriptalert1scriptxss');
+ $this->assertNotEquals($user2->username, $extdbuser2->name);
+
+ // User with correct data, should be equal to external db.
+ $user1 = $DB->get_record('user', array('email'=> $extdbuser1->email, 'auth'=>'db'));
+ $this->assertEquals($extdbuser1->name, $user1->username);
+ $this->assertEquals($extdbuser1->email, $user1->email);
+
+ // Now, let's update the name.
+ $extdbuser2->name = 'user no xss anymore';
+ $DB->update_record('auth_db_users', $extdbuser2);
+
+ // Run sync again to update the user data.
+ $auth->sync_users($trace, true);
+
+ // The user information should be updated.
+ $user2 = $DB->get_record('user', array('username' => 'usernoxssanymore', 'auth' => 'db'));
+ // The spaces should be removed, as it's the username.
+ $this->assertEquals($user2->username, 'usernoxssanymore');
+
+ // Now let's test just the clean_data() method isolated.
+ // Testing PARAM_USERNAME, PARAM_NOTAGS, PARAM_RAW_TRIMMED and others.
+ $user3 = new stdClass();
+ $user3->firstname = 'John <script>alert(1)</script> Doe';
+ $user3->username = 'john%#&~%*_doe';
+ $user3->email = ' john@testing.com ';
+ $user3->deleted = 'no';
+ $user3->description = '<b>A description <script>alert(123)</script>about myself.</b>';
+ $user3cleaned = $auth->clean_data($user3);
+
+ // Expected results.
+ $this->assertEquals($user3cleaned->firstname, 'John alert(1) Doe');
+ $this->assertEquals($user3cleaned->email, 'john@testing.com');
+ $this->assertEquals($user3cleaned->deleted, 0);
+ $this->assertEquals($user3->description, '<b>A description about myself.</b>');
+ $this->assertEquals($user3->username, 'john_doe');
+
+ // Try to clean an invalid property (fullname).
+ $user3->fullname = 'John Doe';
+ $auth->clean_data($user3);
+ $this->assertDebuggingCalled("The property 'fullname' could not be cleaned.");
+ $this->cleanup_auth_database();
+ }
}
--- /dev/null
+@auth @auth_manual
+Feature: Test manual authentication works.
+ In order to check manual authentication
+ As a teacher
+ I need to go on login page and enter username and password.
+
+ Background:
+ Given the following "users" exist:
+ | username |
+ | teacher1 |
+
+ @javascript
+ Scenario: Check login works with javascript.
+ Given I am on homepage
+ And I expand navigation bar
+ And I click on "Log in" "link" in the ".logininfo" "css_element"
+ When I set the field "Username" to "teacher1"
+ And I set the field "Password" to "teacher1"
+ When I press "Log in"
+ Then I should see "You are logged in as"
+
+ Scenario: Check login works without javascript.
+ Given I am on homepage
+ And I click on "Log in" "link" in the ".logininfo" "css_element"
+ When I set the field "Username" to "teacher1"
+ And I set the field "Password" to "teacher1"
+ When I press "Log in"
+ Then I should see "You are logged in as"
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
-use Behat\Behat\Context\Step\When as When;
+use Moodle\BehatExtension\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\When as When;
/**
* Log in log out steps definitions.
* @Given /^I log in as "(?P<username_string>(?:[^"]|\\")*)"$/
*/
public function i_log_in_as($username) {
+ // Visit login page.
+ $this->getSession()->visit($this->locate_path('login/index.php'));
- // Running this step using the API rather than a chained step because
- // we need to see if the 'Log in' link is available or we need to click
- // the dropdown to expand the navigation bar before.
- $this->getSession()->visit($this->locate_path('/'));
+ // Enter username and password.
+ $behatforms = behat_context_helper::get('behat_forms');
+ $behatforms->i_set_the_field_to('Username', $this->escape($username));
+ $behatforms->i_set_the_field_to('Password', $this->escape($username));
- // Generic steps (we will prefix them later expanding the navigation dropdown if necessary).
- $steps = array(
- new Given('I click on "' . get_string('login') . '" "link" in the ".logininfo" "css_element"'),
- new Given('I set the field "' . get_string('username') . '" to "' . $this->escape($username) . '"'),
- new Given('I set the field "' . get_string('password') . '" to "'. $this->escape($username) . '"'),
- new Given('I press "' . get_string('login') . '"')
- );
-
- // If Javascript is disabled we have enough with these steps.
- if (!$this->running_javascript()) {
- return $steps;
- }
-
- // Wait for the homepage to be ready.
- $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
-
- // If it is needed, it expands the navigation bar with the 'Log in' link.
- if ($clicknavbar = $this->get_expand_navbar_step()) {
- array_unshift($steps, $clicknavbar);
- }
-
- return $steps;
+ // Press log in button.
+ $behatforms->press_button(get_string('login'));
}
/**
return $steps;
}
-
- /**
- * Returns a step to open the navigation bar if it is needed.
- *
- * The top log in and log out links are hidden when middle or small
- * size windows (or devices) are used. This step returns a step definition
- * clicking to expand the navbar if it is hidden.
- *
- * @return Given|bool A step definition or false if there is no need to show the navbar.
- */
- protected function get_expand_navbar_step() {
-
- // Checking if we need to click the navbar button to show the navigation menu, it
- // is hidden by default when using clean theme and a medium or small screen size.
-
- // The DOM and the JS should be all ready and loaded. Running without spinning
- // as this is a widely used step and we can not spend time here trying to see
- // a DOM node that is not always there (at the moment clean is not even the
- // default theme...).
- $navbuttonjs = "return (
- Y.one('.btn-navbar') &&
- Y.one('.btn-navbar').getComputedStyle('display') !== 'none'
- )";
-
- // Adding an extra click we need to show the 'Log in' link.
- if (!$this->getSession()->getDriver()->evaluateScript($navbuttonjs)) {
- return false;
- }
-
- return new Given('I click on ".btn-navbar" "css_element"');
- }
}
'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
'availability', 'showdescription'));
+ $tags = new backup_nested_element('tags');
+ $tag = new backup_nested_element('tag', array('id'), array('name', 'rawname'));
+
// attach format plugin structure to $module element, only one allowed
$this->add_plugin_structure('format', $module, false);
// attach local plugin structure to $module, multiple allowed
$this->add_plugin_structure('local', $module, true);
+ $module->add_child($tags);
+ $tags->add_child($tag);
+
// Set the sources
$concat = $DB->sql_concat("'mod_'", 'm.name');
$module->set_source_sql("
JOIN {course_sections} s ON s.id = cm.section
WHERE cm.id = ?", array(backup::VAR_MODID));
+ $tag->set_source_sql("SELECT t.id, t.name, t.rawname
+ FROM {tag} t
+ JOIN {tag_instance} ti ON ti.tagid = t.id
+ WHERE ti.itemtype = 'course_modules'
+ AND ti.component = 'core'
+ AND ti.itemid = ?", array(backup::VAR_MODID));
+
// Define annotations
$module->annotate_ids('grouping', 'groupingid');
$paths[] = new restore_path_element('availability_field', '/module/availability_info/availability_field');
}
+ $paths[] = new restore_path_element('tag', '/module/tags/tag');
+
// Apply for 'format' plugins optional paths at module level
$this->add_plugin_structure('format', $module);
}
}
+ /**
+ * Fetch all the existing because tag_set() deletes them
+ * so everything must be reinserted on each call.
+ *
+ * @param stdClass $data Record data
+ */
+ protected function process_tag($data) {
+ global $CFG;
+
+ $data = (object)$data;
+
+ if (core_tag_tag::is_enabled('core', 'course_modules')) {
+ $modcontext = context::instance_by_id($this->task->get_contextid());
+ $instanceid = $this->task->get_moduleid();
+
+ core_tag_tag::add_item_tag('core', 'course_modules', $instanceid, $modcontext, $data->rawname);
+ }
+ }
+
/**
* Process the legacy availability table record. This table does not exist
* in Moodle 2.7+ but we still support restore.
array('id' => $availfield->coursemoduleid));
}
}
+ /**
+ * This method will be executed after the rest of the restore has been processed.
+ *
+ * Update old tag instance itemid(s).
+ */
+ protected function after_restore() {
+ global $DB;
+
+ $contextid = $this->task->get_contextid();
+ $instanceid = $this->task->get_activityid();
+ $olditemid = $this->task->get_old_activityid();
+
+ $DB->set_field('tag_instance', 'itemid', $instanceid, array('contextid' => $contextid, 'itemid' => $olditemid));
+ }
}
/**
"/descendant::div[@class='restore-course-search']" .
"/descendant::tr[contains(., $existingcourse)]" .
"/descendant::input[@type='radio']");
- $radionode->check();
$radionode->click();
// Pressing the continue button of the restore into an existing course section.
$radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' bcs-new-course ')]" .
"/descendant::div[@class='restore-course-search']" .
"/descendant::input[@type='radio']");
- $radionode->check();
$radionode->click();
// Pressing the continue button of the restore into an existing course section.
// Merge without deleting radio option.
$radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
"/descendant::input[@type='radio'][@name='target'][@value='1']");
- $radionode->check();
$radionode->click();
// Pressing the continue button of the restore merging section.
// Delete contents radio option.
$radionode = $this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), 'bcs-current-course')]" .
"/descendant::input[@type='radio'][@name='target'][@value='0']");
- $radionode->check();
$radionode->click();
// Pressing the continue button of the restore merging section.
return;
}
- $pageoptions = clone $options;
-
$rows = $options->getRows();
$newrows = array();
foreach ($rows as $k => $data) {
$newrows[] = $data;
}
}
- $pageoptions->setRows($newrows);
+ $pageoptions = new TableNode($newrows);
+
return $pageoptions;
}
default:
- paths:
- features: lib/behat/features
- bootstrap: lib/behat/features/bootstrap
- context:
- class: behat_init_context
+ suites:
+ default:
+ paths: { }
+ contexts: { }
extensions:
- Behat\MinkExtension\Extension:
+ Behat\MinkExtension:
base_url: 'http://localhost:8000'
goutte: null
selenium2: null
- Moodle\BehatExtension\Extension:
- features: { }
+ Moodle\BehatExtension:
+ moodledirroot: /Should/Change/To/Moodle/www/dir
steps_definitions: { }
require_once($CFG->dirroot . "/webservice/xmlrpc/lib.php");
$xmlrpcclient = new webservice_xmlrpc_client($serverurl, $token);
try {
- $result = $xmlrpcclient->call($function, $params);
+ $result = $xmlrpcclient->call($function, array_values($params));
$courses = $result['courses'];
$coursetotal = $result['coursetotal'];
} catch (Exception $e) {
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
/**
* Blocks management steps definitions.
$params['options']['timeend'] = PHP_INT_MAX;
}
+ // Event list does not check visibility and permissions, we'll check that later.
$eventlist = calendar_get_events($params['options']['timestart'], $params['options']['timeend'], $funcparam['users'], $funcparam['groups'],
$funcparam['courses'], true, $params['options']['ignorehidden']);
+
// WS expects arrays.
$events = array();
- foreach ($eventlist as $id => $event) {
- $events[$id] = (array) $event;
- }
// We need to get events asked for eventids.
- $eventsbyid = calendar_get_events_by_id($params['events']['eventids']);
- foreach ($eventsbyid as $eventid => $eventobj) {
+ if ($eventsbyid = calendar_get_events_by_id($params['events']['eventids'])) {
+ $eventlist += $eventsbyid;
+ }
+
+ foreach ($eventlist as $eventid => $eventobj) {
$event = (array) $eventobj;
- if (isset($events[$eventid])) {
- continue;
- }
+
if ($hassystemcap) {
// User can see everything, no further check is needed.
$events[$eventid] = $event;
// NOTE: no MOODLE_INTERNAL used, this file may be required by behat before including /config.php.
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
use Behat\Gherkin\Node\TableNode as TableNode;
/**
$events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
$this->assertEquals(1, count($events['events']));
$this->assertEquals(0, count($events['warnings']));
+
+ // Now, create an activity event.
+ $this->setAdminUser();
+ $nexttime = time() + DAYSECS;
+ $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id, 'duedate' => $nexttime));
+
+ $this->setUser($user);
+ $paramevents = array ('courseids' => array($course->id));
+ $options = array ('siteevents' => true, 'userevents' => true, 'timeend' => time() + WEEKSECS);
+ $events = core_calendar_external::get_calendar_events($paramevents, $options);
+ $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
+
+ $this->assertCount(5, $events['events']);
+
+ // Hide the assignment.
+ set_coursemodule_visible($assign->cmid, 0);
+ // Empty all the caches that may be affected by this change.
+ accesslib_clear_all_caches_for_unit_testing();
+ course_modinfo::clear_instance_cache();
+
+ $events = core_calendar_external::get_calendar_events($paramevents, $options);
+ $events = external_api::clean_returnvalue(core_calendar_external::get_calendar_events_returns(), $events);
+ // Expect one less.
+ $this->assertCount(4, $events['events']);
}
/**
if (checkdate($mon, $day, $year)) {
$time = make_timestamp($year, $mon, $day);
} else {
- $time = time();
+ $time = usergetmidnight(time());
}
} else if (empty($time)) {
- $time = time();
+ $time = usergetmidnight(time());
}
$url->param('time', $time);
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given;
+use Moodle\BehatExtension\Context\Step\Given as Given;
/**
* Steps definitions for cohort actions.
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given,
- Behat\Behat\Context\Step\Then,
+use Moodle\BehatExtension\Context\Step\Given,
+ Moodle\BehatExtension\Context\Step\Then,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
"require-dev": {
"phpunit/phpunit": "4.8.*",
"phpunit/dbUnit": "1.4.*",
- "moodlehq/behat-extension": "1.31.0"
+ "moodlehq/behat-extension": "3.31.0"
}
}
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "a957f7332dd1d221be96fb356cb9f03d",
- "content-hash": "463a75022982e6a64bd9fc0513d9b44c",
+ "hash": "769fa23c4b31f60c9fb82d5b23171e0f",
+ "content-hash": "5fca4c69d043cb1f985fc08cd82a64f8",
"packages": [],
"packages-dev": [
{
"name": "behat/behat",
- "version": "v2.5.5",
+ "version": "v3.0.15",
"source": {
"type": "git",
"url": "https://github.com/Behat/Behat.git",
- "reference": "c1e48826b84669c97a1efa78459aedfdcdcf2120"
+ "reference": "b35ae3d45332d80c532af69cc36f780a9397a996"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Behat/zipball/c1e48826b84669c97a1efa78459aedfdcdcf2120",
- "reference": "c1e48826b84669c97a1efa78459aedfdcdcf2120",
+ "url": "https://api.github.com/repos/Behat/Behat/zipball/b35ae3d45332d80c532af69cc36f780a9397a996",
+ "reference": "b35ae3d45332d80c532af69cc36f780a9397a996",
"shasum": ""
},
"require": {
- "behat/gherkin": "~2.3.0",
- "php": ">=5.3.1",
+ "behat/gherkin": "~4.3",
+ "behat/transliterator": "~1.0",
+ "ext-mbstring": "*",
+ "php": ">=5.3.3",
+ "symfony/class-loader": "~2.1",
"symfony/config": "~2.3",
- "symfony/console": "~2.0",
- "symfony/dependency-injection": "~2.0",
- "symfony/event-dispatcher": "~2.0",
- "symfony/finder": "~2.0",
+ "symfony/console": "~2.1",
+ "symfony/dependency-injection": "~2.1",
+ "symfony/event-dispatcher": "~2.1",
"symfony/translation": "~2.3",
- "symfony/yaml": "~2.0"
+ "symfony/yaml": "~2.1"
},
"require-dev": {
- "phpunit/phpunit": "~3.7.19"
+ "phpspec/prophecy-phpunit": "~1.0",
+ "phpunit/phpunit": "~4.0",
+ "symfony/process": "~2.1"
},
"suggest": {
"behat/mink-extension": "for integration with Mink testing framework",
"bin/behat"
],
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
"autoload": {
"psr-0": {
- "Behat\\Behat": "src/"
+ "Behat\\Behat": "src/",
+ "Behat\\Testwork": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"description": "Scenario-oriented BDD framework for PHP 5.3",
"homepage": "http://behat.org/",
"keywords": [
+ "Agile",
"BDD",
- "Behat",
- "Symfony2"
+ "ScenarioBDD",
+ "Scrum",
+ "StoryBDD",
+ "User story",
+ "business",
+ "development",
+ "documentation",
+ "examples",
+ "symfony",
+ "testing"
],
- "time": "2015-06-01 09:37:55"
+ "time": "2015-02-22 14:10:33"
},
{
"name": "behat/gherkin",
- "version": "v2.3.5",
+ "version": "v4.4.1",
"source": {
"type": "git",
"url": "https://github.com/Behat/Gherkin.git",
- "reference": "2b33963da5525400573560c173ab5c9c057e1852"
+ "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Gherkin/zipball/2b33963da5525400573560c173ab5c9c057e1852",
- "reference": "2b33963da5525400573560c173ab5c9c057e1852",
+ "url": "https://api.github.com/repos/Behat/Gherkin/zipball/1576b485c0f92ef6d27da9c4bbfc57ee30cf6911",
+ "reference": "1576b485c0f92ef6d27da9c4bbfc57ee30cf6911",
"shasum": ""
},
"require": {
- "php": ">=5.3.1",
- "symfony/finder": "~2.0"
+ "php": ">=5.3.1"
},
"require-dev": {
- "symfony/config": "~2.0",
- "symfony/translation": "~2.0",
- "symfony/yaml": "~2.0"
+ "phpunit/phpunit": "~4.0",
+ "symfony/yaml": "~2.1"
},
"suggest": {
- "symfony/config": "If you want to use Config component to manage resources",
- "symfony/translation": "If you want to use Symfony2 translations adapter",
"symfony/yaml": "If you want to parse features, represented in YAML files"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-develop": "2.2-dev"
+ "dev-master": "4.4-dev"
}
},
"autoload": {
"keywords": [
"BDD",
"Behat",
+ "Cucumber",
"DSL",
- "Symfony2",
+ "gherkin",
"parser"
],
- "time": "2013-10-15 11:22:17"
+ "time": "2015-12-30 14:47:00"
},
{
"name": "behat/mink",
- "version": "v1.5.0",
+ "version": "v1.7.0",
"source": {
"type": "git",
"url": "https://github.com/minkphp/Mink.git",
- "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe"
+ "reference": "6c129030ec2cc029905cf969a56ca8f087b2dfdf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/Mink/zipball/0769e6d9726c140a54dbf827a438c0f9912749fe",
- "reference": "0769e6d9726c140a54dbf827a438c0f9912749fe",
+ "url": "https://api.github.com/repos/minkphp/Mink/zipball/6c129030ec2cc029905cf969a56ca8f087b2dfdf",
+ "reference": "6c129030ec2cc029905cf969a56ca8f087b2dfdf",
"shasum": ""
},
"require": {
"php": ">=5.3.1",
- "symfony/css-selector": "~2.0"
+ "symfony/css-selector": "~2.1"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
},
"suggest": {
"behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)",
"type": "library",
"extra": {
"branch-alias": {
- "dev-develop": "1.5.x-dev"
+ "dev-master": "1.7.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Mink": "src/"
+ "psr-4": {
+ "Behat\\Mink\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"homepage": "http://everzet.com"
}
],
- "description": "Web acceptance testing framework for PHP 5.3",
+ "description": "Browser controller/emulator abstraction for PHP",
"homepage": "http://mink.behat.org/",
"keywords": [
"browser",
"testing",
"web"
],
- "time": "2013-04-13 23:39:27"
+ "time": "2015-09-20 20:24:03"
},
{
"name": "behat/mink-browserkit-driver",
- "version": "v1.1.0",
+ "version": "v1.3.1",
"source": {
"type": "git",
"url": "https://github.com/minkphp/MinkBrowserKitDriver.git",
- "reference": "63960c8fcad4529faad1ff33e950217980baa64c"
+ "reference": "2650f5420e713e3807c7f09a07370a4f48367bf9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/63960c8fcad4529faad1ff33e950217980baa64c",
- "reference": "63960c8fcad4529faad1ff33e950217980baa64c",
+ "url": "https://api.github.com/repos/minkphp/MinkBrowserKitDriver/zipball/2650f5420e713e3807c7f09a07370a4f48367bf9",
+ "reference": "2650f5420e713e3807c7f09a07370a4f48367bf9",
"shasum": ""
},
"require": {
- "behat/mink": "~1.5.0",
- "php": ">=5.3.1",
- "symfony/browser-kit": "~2.0",
- "symfony/dom-crawler": "~2.0"
+ "behat/mink": "~1.7@dev",
+ "php": ">=5.3.6",
+ "symfony/browser-kit": "~2.3|~3.0",
+ "symfony/dom-crawler": "~2.3|~3.0"
},
"require-dev": {
- "silex/silex": "@dev"
+ "silex/silex": "~1.2",
+ "symfony/phpunit-bridge": "~2.7|~3.0"
},
"type": "mink-driver",
"extra": {
"branch-alias": {
- "dev-master": "1.1.x-dev"
+ "dev-master": "1.3.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Mink\\Driver": "src/"
+ "psr-4": {
+ "Behat\\Mink\\Driver\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"browser",
"testing"
],
- "time": "2013-04-13 23:46:30"
+ "time": "2016-01-19 16:59:07"
},
{
"name": "behat/mink-extension",
- "version": "v1.3.3",
+ "version": "v2.2",
"source": {
"type": "git",
"url": "https://github.com/Behat/MinkExtension.git",
- "reference": "b885b9407cba50a954f72c69ed1b2f8d3bc694f8"
+ "reference": "5b4bda64ff456104564317e212c823e45cad9d59"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/b885b9407cba50a954f72c69ed1b2f8d3bc694f8",
- "reference": "b885b9407cba50a954f72c69ed1b2f8d3bc694f8",
+ "url": "https://api.github.com/repos/Behat/MinkExtension/zipball/5b4bda64ff456104564317e212c823e45cad9d59",
+ "reference": "5b4bda64ff456104564317e212c823e45cad9d59",
"shasum": ""
},
"require": {
- "behat/behat": "~2.5.0",
+ "behat/behat": "~3.0,>=3.0.5",
"behat/mink": "~1.5",
"php": ">=5.3.2",
- "symfony/config": "~2.2"
+ "symfony/config": "~2.2|~3.0"
},
"require-dev": {
- "behat/mink-goutte-driver": "~1.0",
- "fabpot/goutte": "~1.0"
+ "behat/mink-goutte-driver": "~1.1",
+ "phpspec/phpspec": "~2.0"
},
"type": "behat-extension",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1.x-dev"
+ }
+ },
"autoload": {
"psr-0": {
"Behat\\MinkExtension": "src/"
"MIT"
],
"authors": [
+ {
+ "name": "Christophe Coevoet",
+ "email": "stof@notk.org"
+ },
{
"name": "Konstantin Kudryashov",
- "email": "ever.zet@gmail.com",
- "homepage": "http://everzet.com"
+ "email": "ever.zet@gmail.com"
}
],
"description": "Mink extension for Behat",
- "homepage": "http://mink.behat.org",
+ "homepage": "http://extensions.behat.org/mink",
"keywords": [
"browser",
"gui",
"test",
"web"
],
- "time": "2014-05-15 19:27:39"
+ "time": "2016-02-15 07:55:18"
},
{
"name": "behat/mink-goutte-driver",
- "version": "v1.0.9",
+ "version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/minkphp/MinkGoutteDriver.git",
- "reference": "fa1b073b48761464feb0b05e6825da44b20118d8"
+ "reference": "c8e254f127d6f2242b994afd4339fb62d471df3f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/fa1b073b48761464feb0b05e6825da44b20118d8",
- "reference": "fa1b073b48761464feb0b05e6825da44b20118d8",
+ "url": "https://api.github.com/repos/minkphp/MinkGoutteDriver/zipball/c8e254f127d6f2242b994afd4339fb62d471df3f",
+ "reference": "c8e254f127d6f2242b994afd4339fb62d471df3f",
"shasum": ""
},
"require": {
- "behat/mink-browserkit-driver": ">=1.0.5,<1.2.0",
- "fabpot/goutte": "~1.0.1",
+ "behat/mink": "~1.6@dev",
+ "behat/mink-browserkit-driver": "~1.2@dev",
+ "fabpot/goutte": "~1.0.4|~2.0|~3.1",
"php": ">=5.3.1"
},
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
+ },
"type": "mink-driver",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.2.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Mink\\Driver": "src/"
+ "psr-4": {
+ "Behat\\Mink\\Driver\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"headless",
"testing"
],
- "time": "2013-07-03 18:43:54"
+ "time": "2015-09-21 21:31:11"
},
{
"name": "behat/mink-selenium2-driver",
- "version": "v1.1.1",
+ "version": "v1.3.0",
"source": {
"type": "git",
"url": "https://github.com/minkphp/MinkSelenium2Driver.git",
- "reference": "bcf1b537de37db6db0822d9e7bd97e600fd7a476"
+ "reference": "bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/bcf1b537de37db6db0822d9e7bd97e600fd7a476",
- "reference": "bcf1b537de37db6db0822d9e7bd97e600fd7a476",
+ "url": "https://api.github.com/repos/minkphp/MinkSelenium2Driver/zipball/bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9",
+ "reference": "bedbf1999c7ba1bc6921b30ee2eadf383e7ff5c9",
"shasum": ""
},
"require": {
- "behat/mink": "~1.5.0",
- "instaclick/php-webdriver": "~1.0.12",
+ "behat/mink": "~1.7@dev",
+ "instaclick/php-webdriver": "~1.1",
"php": ">=5.3.1"
},
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
+ },
"type": "mink-driver",
"extra": {
"branch-alias": {
- "dev-master": "1.1.x-dev"
+ "dev-master": "1.3.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Mink\\Driver": "src/"
+ "psr-4": {
+ "Behat\\Mink\\Driver\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"testing",
"webdriver"
],
- "time": "2013-06-02 19:09:45"
+ "time": "2015-09-21 21:02:54"
+ },
+ {
+ "name": "behat/transliterator",
+ "version": "v1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Behat/Transliterator.git",
+ "reference": "868e05be3a9f25ba6424c2dd4849567f50715003"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Behat/Transliterator/zipball/868e05be3a9f25ba6424c2dd4849567f50715003",
+ "reference": "868e05be3a9f25ba6424c2dd4849567f50715003",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Behat\\Transliterator": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Artistic-1.0"
+ ],
+ "description": "String transliterator",
+ "keywords": [
+ "i18n",
+ "slug",
+ "transliterator"
+ ],
+ "time": "2015-09-28 16:26:35"
},
{
"name": "doctrine/instantiator",
},
{
"name": "fabpot/goutte",
- "version": "v1.0.7",
+ "version": "v2.0.4",
"source": {
"type": "git",
"url": "https://github.com/FriendsOfPHP/Goutte.git",
- "reference": "794b196e76bdd37b5155cdecbad311f0a3b07625"
+ "reference": "0ad3ee6dc2d0aaa832a80041a1e09bf394e99802"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/794b196e76bdd37b5155cdecbad311f0a3b07625",
- "reference": "794b196e76bdd37b5155cdecbad311f0a3b07625",
+ "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/0ad3ee6dc2d0aaa832a80041a1e09bf394e99802",
+ "reference": "0ad3ee6dc2d0aaa832a80041a1e09bf394e99802",
"shasum": ""
},
"require": {
- "ext-curl": "*",
- "guzzle/http": "~3.1",
- "php": ">=5.3.0",
+ "guzzlehttp/guzzle": ">=4,<6",
+ "php": ">=5.4.0",
"symfony/browser-kit": "~2.1",
"symfony/css-selector": "~2.1",
- "symfony/dom-crawler": "~2.1",
- "symfony/finder": "~2.1",
- "symfony/process": "~2.1"
- },
- "require-dev": {
- "guzzle/plugin-history": "~3.1",
- "guzzle/plugin-mock": "~3.1"
+ "symfony/dom-crawler": "~2.1"
},
"type": "application",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "2.0-dev"
}
},
"autoload": {
- "psr-0": {
- "Goutte": "."
+ "psr-4": {
+ "Goutte\\": "Goutte"
}
},
"notification-url": "https://packagist.org/downloads/",
}
],
"description": "A simple PHP Web Scraper",
- "homepage": "https://github.com/fabpot/Goutte",
+ "homepage": "https://github.com/FriendsOfPHP/Goutte",
"keywords": [
"scraper"
],
- "time": "2014-10-09 15:52:51"
+ "time": "2015-05-05 21:14:57"
},
{
"name": "guzzlehttp/guzzle",
- "version": "v3.8.1",
+ "version": "5.3.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba"
+ "reference": "f3c8c22471cb55475105c14769644a49c3262b93"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/4de0618a01b34aa1c8c33a3f13f396dcd3882eba",
- "reference": "4de0618a01b34aa1c8c33a3f13f396dcd3882eba",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f3c8c22471cb55475105c14769644a49c3262b93",
+ "reference": "f3c8c22471cb55475105c14769644a49c3262b93",
"shasum": ""
},
"require": {
- "ext-curl": "*",
- "php": ">=5.3.3",
- "symfony/event-dispatcher": ">=2.1"
- },
- "replace": {
- "guzzle/batch": "self.version",
- "guzzle/cache": "self.version",
- "guzzle/common": "self.version",
- "guzzle/http": "self.version",
- "guzzle/inflection": "self.version",
- "guzzle/iterator": "self.version",
- "guzzle/log": "self.version",
- "guzzle/parser": "self.version",
- "guzzle/plugin": "self.version",
- "guzzle/plugin-async": "self.version",
- "guzzle/plugin-backoff": "self.version",
- "guzzle/plugin-cache": "self.version",
- "guzzle/plugin-cookie": "self.version",
- "guzzle/plugin-curlauth": "self.version",
- "guzzle/plugin-error-response": "self.version",
- "guzzle/plugin-history": "self.version",
- "guzzle/plugin-log": "self.version",
- "guzzle/plugin-md5": "self.version",
- "guzzle/plugin-mock": "self.version",
- "guzzle/plugin-oauth": "self.version",
- "guzzle/service": "self.version",
- "guzzle/stream": "self.version"
+ "guzzlehttp/ringphp": "^1.1",
+ "php": ">=5.4.0"
},
"require-dev": {
- "doctrine/cache": "*",
- "monolog/monolog": "1.*",
- "phpunit/phpunit": "3.7.*",
- "psr/log": "1.0.*",
- "symfony/class-loader": "*",
- "zendframework/zend-cache": "<2.3",
- "zendframework/zend-log": "<2.3"
+ "ext-curl": "*",
+ "phpunit/phpunit": "^4.0",
+ "psr/log": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.8-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
- "psr-0": {
- "Guzzle": "src/",
- "Guzzle\\Tests": "tests/"
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
- },
- {
- "name": "Guzzle Community",
- "homepage": "https://github.com/guzzle/guzzle/contributors"
}
],
"description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients",
"rest",
"web service"
],
- "time": "2014-01-28 22:29:15"
+ "time": "2015-05-20 03:47:55"
+ },
+ {
+ "name": "guzzlehttp/ringphp",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/RingPHP.git",
+ "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/dbbb91d7f6c191e5e405e900e3102ac7f261bc0b",
+ "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/streams": "~3.0",
+ "php": ">=5.4.0",
+ "react/promise": "~2.0"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "~4.0"
+ },
+ "suggest": {
+ "ext-curl": "Guzzle will use specific adapters if cURL is present"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Ring\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.",
+ "time": "2015-05-20 03:37:09"
+ },
+ {
+ "name": "guzzlehttp/streams",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/streams.git",
+ "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
+ "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Stream\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Provides a simple abstraction over streams of data",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "Guzzle",
+ "stream"
+ ],
+ "time": "2014-10-12 19:18:40"
},
{
"name": "instaclick/php-webdriver",
- "version": "1.0.17",
+ "version": "1.4.3",
"source": {
"type": "git",
"url": "https://github.com/instaclick/php-webdriver.git",
- "reference": "47a6019553a7a5b42d35493276ffc2c9252c53d5"
+ "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/47a6019553a7a5b42d35493276ffc2c9252c53d5",
- "reference": "47a6019553a7a5b42d35493276ffc2c9252c53d5",
+ "url": "https://api.github.com/repos/instaclick/php-webdriver/zipball/0c20707dcf30a32728fd6bdeeab996c887fdb2fb",
+ "reference": "0c20707dcf30a32728fd6bdeeab996c887fdb2fb",
"shasum": ""
},
"require": {
"ext-curl": "*",
"php": ">=5.3.2"
},
- "bin": [
- "bin/webunit"
- ],
+ "require-dev": {
+ "satooshi/php-coveralls": "dev-master"
+ },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.4.x-dev"
}
},
"autoload": {
{
"name": "Anthon Pang",
"email": "apang@softwaredevelopment.ca",
- "role": "developer"
+ "role": "Fork Maintainer"
}
],
"description": "PHP WebDriver for Selenium 2",
"webdriver",
"webtest"
],
- "time": "2013-10-04 15:03:51"
+ "time": "2015-06-15 20:19:33"
},
{
"name": "moodlehq/behat-extension",
- "version": "v1.31.0",
+ "version": "v3.31.0",
"source": {
"type": "git",
"url": "https://github.com/moodlehq/moodle-behat-extension.git",
- "reference": "b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b"
+ "reference": "d985e9da29914b0da90d61c47aadc455586eeee5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b",
- "reference": "b4dd6f7d1ca1c89a44b8b010af8546d7a2808b7b",
+ "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/d985e9da29914b0da90d61c47aadc455586eeee5",
+ "reference": "d985e9da29914b0da90d61c47aadc455586eeee5",
"shasum": ""
},
"require": {
- "behat/behat": "2.5.5",
- "behat/mink": "1.5.0",
- "behat/mink-extension": "1.3.3",
- "behat/mink-goutte-driver": "1.0.9",
- "behat/mink-selenium2-driver": "1.1.1",
- "guzzlehttp/guzzle": "~3.1",
+ "behat/behat": "3.0.*",
+ "behat/mink": "~1.7",
+ "behat/mink-extension": "~2.1",
+ "behat/mink-goutte-driver": "~1.2",
+ "behat/mink-selenium2-driver": "~1.3",
"php": ">=5.4.4",
- "symfony/browser-kit": "2.7.5",
- "symfony/css-selector": "2.7.5",
- "symfony/dom-crawler": "2.7.5",
- "symfony/filesystem": "2.7.5",
- "symfony/finder": "2.7.5"
+ "symfony/process": "2.8.*"
},
"type": "library",
"autoload": {
"Behat",
"moodle"
],
- "time": "2016-01-05 02:55:24"
+ "time": "2016-03-04 07:15:40"
},
{
"name": "phpdocumentor/reflection-docblock",
},
{
"name": "phpspec/prophecy",
- "version": "v1.5.0",
+ "version": "v1.6.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
- "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7"
+ "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7",
- "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
+ "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
+ "php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "~2.0",
- "sebastian/comparator": "~1.1"
+ "sebastian/comparator": "~1.1",
+ "sebastian/recursion-context": "~1.0"
},
"require-dev": {
"phpspec/phpspec": "~2.0"
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.4.x-dev"
+ "dev-master": "1.5.x-dev"
}
},
"autoload": {
"spy",
"stub"
],
- "time": "2015-08-13 10:07:40"
+ "time": "2016-02-15 07:46:21"
},
{
"name": "phpunit/dbunit",
},
{
"name": "phpunit/phpunit",
- "version": "4.8.21",
+ "version": "4.8.23",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "ea76b17bced0500a28098626b84eda12dbcf119c"
+ "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ea76b17bced0500a28098626b84eda12dbcf119c",
- "reference": "ea76b17bced0500a28098626b84eda12dbcf119c",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6e351261f9cd33daf205a131a1ba61c6d33bd483",
+ "reference": "6e351261f9cd33daf205a131a1ba61c6d33bd483",
"shasum": ""
},
"require": {
"testing",
"xunit"
],
- "time": "2015-12-12 07:45:58"
+ "time": "2016-02-11 14:56:33"
},
{
"name": "phpunit/phpunit-mock-objects",
],
"time": "2015-10-02 06:51:40"
},
+ {
+ "name": "react/promise",
+ "version": "v2.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/reactphp/promise.git",
+ "reference": "3aacad8bf10c7d83e6fa2089d413529888c2bedf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/reactphp/promise/zipball/3aacad8bf10c7d83e6fa2089d413529888c2bedf",
+ "reference": "3aacad8bf10c7d83e6fa2089d413529888c2bedf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "React\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com"
+ }
+ ],
+ "description": "A lightweight implementation of CommonJS Promises/A for PHP",
+ "time": "2016-02-26 19:09:02"
+ },
{
"name": "sebastian/comparator",
"version": "1.2.0",
},
{
"name": "sebastian/environment",
- "version": "1.3.3",
+ "version": "1.3.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "6e7133793a8e5a5714a551a8324337374be209df"
+ "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df",
- "reference": "6e7133793a8e5a5714a551a8324337374be209df",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
+ "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
"shasum": ""
},
"require": {
"environment",
"hhvm"
],
- "time": "2015-12-02 08:37:27"
+ "time": "2016-02-26 18:40:46"
},
{
"name": "sebastian/exporter",
},
{
"name": "symfony/browser-kit",
- "version": "v2.7.5",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
- "reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4"
+ "reference": "6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/browser-kit/zipball/277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
- "reference": "277a2457776d4cc25706fbdd9d1e4ab2dac884e4",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2",
+ "reference": "6b2085020b4e86fcb7ae44c3ab8ddb91774b33d2",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
- "symfony/dom-crawler": "~2.0,>=2.0.5"
+ "symfony/dom-crawler": "~2.0,>=2.0.5|~3.0.0"
},
"require-dev": {
- "symfony/css-selector": "~2.0,>=2.0.5",
- "symfony/phpunit-bridge": "~2.7",
- "symfony/process": "~2.0,>=2.0.5"
+ "symfony/css-selector": "~2.0,>=2.0.5|~3.0.0",
+ "symfony/process": "~2.3.34|~2.7,>=2.7.6|~3.0.0"
},
"suggest": {
"symfony/process": ""
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\BrowserKit\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
- "time": "2015-09-06 08:36:38"
+ "time": "2016-01-27 11:34:40"
+ },
+ {
+ "name": "symfony/class-loader",
+ "version": "v2.8.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/class-loader.git",
+ "reference": "517ab0554b6a5744d04480cb06873ffbd9442d73"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/class-loader/zipball/517ab0554b6a5744d04480cb06873ffbd9442d73",
+ "reference": "517ab0554b6a5744d04480cb06873ffbd9442d73",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9",
+ "symfony/polyfill-apcu": "~1.1"
+ },
+ "require-dev": {
+ "symfony/finder": "~2.0,>=2.0.5|~3.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.8-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\ClassLoader\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony ClassLoader Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-01-30 15:58:35"
},
{
"name": "symfony/config",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2"
+ "reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2",
- "reference": "17d4b2e64ce1c6ba7caa040f14469b3c44d7f7d2",
+ "url": "https://api.github.com/repos/symfony/config/zipball/0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
+ "reference": "0f8f94e6a32b5c480024eed5fa5cbd2790d0ad19",
"shasum": ""
},
"require": {
"php": ">=5.3.9",
"symfony/filesystem": "~2.3|~3.0.0"
},
+ "suggest": {
+ "symfony/yaml": "To use the yaml reference dumper"
+ },
"type": "library",
"extra": {
"branch-alias": {
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
- "time": "2015-12-26 13:37:56"
+ "time": "2016-02-22 16:12:45"
},
{
"name": "symfony/console",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a"
+ "reference": "56cc5caf051189720b8de974e4746090aaa10d44"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/2e06a5ccb19dcf9b89f1c6a677a39a8df773635a",
- "reference": "2e06a5ccb19dcf9b89f1c6a677a39a8df773635a",
+ "url": "https://api.github.com/repos/symfony/console/zipball/56cc5caf051189720b8de974e4746090aaa10d44",
+ "reference": "56cc5caf051189720b8de974e4746090aaa10d44",
"shasum": ""
},
"require": {
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2015-12-22 10:25:57"
+ "time": "2016-02-28 16:20:50"
},
{
"name": "symfony/css-selector",
- "version": "v2.7.5",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "abe19cc0429a06be0c133056d1f9859854860970"
+ "reference": "8d83ff9777cdbd83e7f90d9c48f4729823791a5e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/abe19cc0429a06be0c133056d1f9859854860970",
- "reference": "abe19cc0429a06be0c133056d1f9859854860970",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/8d83ff9777cdbd83e7f90d9c48f4729823791a5e",
+ "reference": "8d83ff9777cdbd83e7f90d9c48f4729823791a5e",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
- "require-dev": {
- "symfony/phpunit-bridge": "~2.7"
- },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\CssSelector\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "time": "2015-09-22 13:49:29"
+ "time": "2016-01-27 05:14:19"
},
{
"name": "symfony/dependency-injection",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "c5086d186f538c2711b9af6f727be7b0446979cd"
+ "reference": "62251761a7615435b22ccf562384c588b431be44"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/c5086d186f538c2711b9af6f727be7b0446979cd",
- "reference": "c5086d186f538c2711b9af6f727be7b0446979cd",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/62251761a7615435b22ccf562384c588b431be44",
+ "reference": "62251761a7615435b22ccf562384c588b431be44",
"shasum": ""
},
"require": {
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
- "time": "2015-12-26 13:37:56"
+ "time": "2016-02-28 16:34:46"
},
{
"name": "symfony/dom-crawler",
- "version": "v2.7.5",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "2e185ca136399f902b948694987e62c80099c052"
+ "reference": "e1a4b4c83f5ee6f5902f1d53035e3718909a0c11"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2e185ca136399f902b948694987e62c80099c052",
- "reference": "2e185ca136399f902b948694987e62c80099c052",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/e1a4b4c83f5ee6f5902f1d53035e3718909a0c11",
+ "reference": "e1a4b4c83f5ee6f5902f1d53035e3718909a0c11",
"shasum": ""
},
"require": {
- "php": ">=5.3.9"
+ "php": ">=5.3.9",
+ "symfony/polyfill-mbstring": "~1.0"
},
"require-dev": {
- "symfony/css-selector": "~2.3",
- "symfony/phpunit-bridge": "~2.7"
+ "symfony/css-selector": "~2.8|~3.0.0"
},
"suggest": {
"symfony/css-selector": ""
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\DomCrawler\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
- "time": "2015-09-20 21:13:58"
+ "time": "2016-02-28 16:20:50"
},
{
"name": "symfony/event-dispatcher",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc"
+ "reference": "78c468665c9568c3faaa9c416a7134308f2d85c3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc",
- "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/78c468665c9568c3faaa9c416a7134308f2d85c3",
+ "reference": "78c468665c9568c3faaa9c416a7134308f2d85c3",
"shasum": ""
},
"require": {
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2015-10-30 20:15:42"
+ "time": "2016-01-27 05:14:19"
},
{
"name": "symfony/filesystem",
- "version": "v2.7.5",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab"
+ "reference": "65cb36b6539b1d446527d60457248f30d045464d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
- "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/65cb36b6539b1d446527d60457248f30d045464d",
+ "reference": "65cb36b6539b1d446527d60457248f30d045464d",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
- "require-dev": {
- "symfony/phpunit-bridge": "~2.7"
- },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
- }
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
- "time": "2015-09-09 17:42:36"
+ "time": "2016-02-22 15:02:30"
},
{
- "name": "symfony/finder",
- "version": "v2.7.5",
+ "name": "symfony/polyfill-apcu",
+ "version": "v1.1.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/finder.git",
- "reference": "8262ab605973afbb3ef74b945daabf086f58366f"
+ "url": "https://github.com/symfony/polyfill-apcu.git",
+ "reference": "d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/8262ab605973afbb3ef74b945daabf086f58366f",
- "reference": "8262ab605973afbb3ef74b945daabf086f58366f",
+ "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c",
+ "reference": "d1911e6caeb4b6a4c8e2d5c46b978a66b3745e4c",
"shasum": ""
},
"require": {
- "php": ">=5.3.9"
- },
- "require-dev": {
- "symfony/phpunit-bridge": "~2.7"
+ "php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.7-dev"
+ "dev-master": "1.1-dev"
}
},
"autoload": {
- "psr-4": {
- "Symfony\\Component\\Finder\\": ""
- }
+ "files": [
+ "bootstrap.php"
+ ],
+ "classmap": [
+ "Resources/stubs"
+ ]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
],
"authors": [
{
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony Finder Component",
+ "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions",
"homepage": "https://symfony.com",
- "time": "2015-09-19 19:59:23"
+ "keywords": [
+ "apcu",
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2016-01-20 09:13:37"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.0.1",
+ "version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25"
+ "reference": "1289d16209491b584839022f29257ad859b8532d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/49ff736bd5d41f45240cec77b44967d76e0c3d25",
- "reference": "49ff736bd5d41f45240cec77b44967d76e0c3d25",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/1289d16209491b584839022f29257ad859b8532d",
+ "reference": "1289d16209491b584839022f29257ad859b8532d",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "1.1-dev"
}
},
"autoload": {
"portable",
"shim"
],
- "time": "2015-11-20 09:19:13"
+ "time": "2016-01-20 09:13:37"
},
{
"name": "symfony/process",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "62c254438b5040bc2217156e1570cf2206e8540c"
+ "reference": "7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/62c254438b5040bc2217156e1570cf2206e8540c",
- "reference": "62c254438b5040bc2217156e1570cf2206e8540c",
+ "url": "https://api.github.com/repos/symfony/process/zipball/7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe",
+ "reference": "7dedd5b60550f33dca16dd7e94ef8aca8b67bbfe",
"shasum": ""
},
"require": {
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2015-12-23 11:03:46"
+ "time": "2016-02-02 13:33:15"
},
{
"name": "symfony/translation",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "c1db87c51251167dd91198b9d1edf897773adb4f"
+ "reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/c1db87c51251167dd91198b9d1edf897773adb4f",
- "reference": "c1db87c51251167dd91198b9d1edf897773adb4f",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
+ "reference": "b7b4ebadd2b5e614ff7d2d6fc63e0ed0578909c7",
"shasum": ""
},
"require": {
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
- "time": "2015-12-05 17:37:59"
+ "time": "2016-02-02 09:49:18"
},
{
"name": "symfony/yaml",
- "version": "v2.8.1",
+ "version": "v2.8.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966"
+ "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966",
- "reference": "ac84cbb98b68a6abbc9f5149eb96ccc7b07b8966",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/2a4ee40acb880c56f29fb1b8886e7ffe94f3b995",
+ "reference": "2a4ee40acb880c56f29fb1b8886e7ffe94f3b995",
"shasum": ""
},
"require": {
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2015-12-26 13:37:56"
+ "time": "2016-02-23 07:41:20"
}
],
"aliases": [],
// params hierarchy. More info: http://docs.behat.org/guides/7.config.html
// Example:
// $CFG->behat_config = array(
-// 'default' => array(
-// 'formatter' => array(
-// 'name' => 'pretty',
-// 'parameters' => array(
-// 'decorated' => true,
-// 'verbose' => false
-// )
-// )
-// ),
// 'Mac-Firefox' => array(
+// 'suites' => array (
+// 'default' => array(
+// 'filters' => array(
+// 'tags' => '~@_file_upload'
+// ),
+// ),
+// ),
// 'extensions' => array(
-// 'Behat\MinkExtension\Extension' => array(
+// 'Behat\MinkExtension' => array(
// 'selenium2' => array(
// 'browser' => 'firefox',
// 'capabilities' => array(
// ),
// 'Mac-Safari' => array(
// 'extensions' => array(
-// 'Behat\MinkExtension\Extension' => array(
+// 'Behat\MinkExtension' => array(
// 'selenium2' => array(
// 'browser' => 'safari',
// 'capabilities' => array(
// )
// )
// );
+// You can also use the following config to override default Moodle configuration for Behat.
+// This config is limited to default suite and will be supported in later versions.
+// It will have precedence over $CFG->behat_config.
+// $CFG->behat_profiles = array(
+// 'phantomjs' => array(
+// 'browser' => 'phantomjs',
+// 'tags' => '~@_file_upload&&~@_alert&&~@_bug_phantomjs',
+// 'wd_host' => 'http://127.0.0.1:4443/wd/hub',
+// 'capabilities' => array(
+// 'platform' => 'Linux',
+// 'version' => 2.1
+// )
+// ),
+// );
//
// You can force the browser session (not user's sessions) to restart after N seconds. This could
// be useful if you are using a cloud-based service with time restrictions in the browser side.
(search, modulelist (only admins), blocklist (only admins), tagid)'),
'criteriavalue' => new external_value(PARAM_RAW, 'criteria value'),
'page' => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0),
- 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0)
+ 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
+ 'requiredcapabilities' => new external_multiple_structure(
+ new external_value(PARAM_CAPABILITY, 'Capability string used to filter courses by permission'),
+ VALUE_OPTIONAL
+ )
)
);
}
* @param string $criteriavalue Criteria value
* @param int $page Page number (for pagination)
* @param int $perpage Items per page
+ * @param array $requiredcapabilities Optional list of required capabilities (used to filter the list).
* @return array of course objects and warnings
* @since Moodle 3.0
* @throws moodle_exception
*/
- public static function search_courses($criterianame, $criteriavalue, $page=0, $perpage=0) {
+ public static function search_courses($criterianame,
+ $criteriavalue,
+ $page=0,
+ $perpage=0,
+ $requiredcapabilities=array()) {
global $CFG;
require_once($CFG->libdir . '/coursecatlib.php');
'criterianame' => $criterianame,
'criteriavalue' => $criteriavalue,
'page' => $page,
- 'perpage' => $perpage
+ 'perpage' => $perpage,
+ 'requiredcapabilities' => $requiredcapabilities
);
$params = self::validate_parameters(self::search_courses_parameters(), $parameters);
}
// Search the courses.
- $courses = coursecat::search_courses($searchcriteria, $options);
- $totalcount = coursecat::search_courses_count($searchcriteria);
+ $courses = coursecat::search_courses($searchcriteria, $options, $params['requiredcapabilities']);
+ $totalcount = coursecat::search_courses_count($searchcriteria, $options, $params['requiredcapabilities']);
$finalcourses = array();
$categoriescache = array();
list($summary, $summaryformat) =
external_format_text($course->summary, $course->summaryformat, $coursecontext->id, 'course', 'summary', null);
+ $displayname = get_course_display_name_for_list($course);
$coursereturns = array();
$coursereturns['id'] = $course->id;
- $coursereturns['fullname'] = $course->get_formatted_fullname();
- $coursereturns['shortname'] = $course->get_formatted_shortname();
+ $coursereturns['fullname'] = external_format_string($course->fullname, $coursecontext->id);
+ $coursereturns['displayname'] = external_format_string($displayname, $coursecontext->id);
+ $coursereturns['shortname'] = external_format_string($course->shortname, $coursecontext->id);
$coursereturns['categoryid'] = $course->category;
$coursereturns['categoryname'] = $category->name;
$coursereturns['summary'] = $summary;
array(
'id' => new external_value(PARAM_INT, 'course id'),
'fullname' => new external_value(PARAM_TEXT, 'course full name'),
+ 'displayname' => new external_value(PARAM_TEXT, 'course display name'),
'shortname' => new external_value(PARAM_TEXT, 'course short name'),
'categoryid' => new external_value(PARAM_INT, 'category id'),
'categoryname' => new external_value(PARAM_TEXT, 'category name'),
// Delete all tag instances associated with the instance of this module.
core_tag_tag::delete_instances('mod_' . $modulename, null, $modcontext->id);
+ core_tag_tag::remove_all_item_tags('core', 'course_modules', $cm->id);
// Delete the context.
context_helper::delete_instance(CONTEXT_MODULE, $cm->id);
$data->completionexpected = $cm->completionexpected;
$data->completionusegrade = is_null($cm->completiongradeitemnumber) ? 0 : 1;
$data->showdescription = $cm->showdescription;
+ $data->tags = core_tag_tag::get_item_tags_array('core', 'course_modules', $cm->id);
if (!empty($CFG->enableavailability)) {
$data->availabilityconditionsjson = $cm->availability;
}
$DB->set_field($moduleinfo->modulename, 'intro', $moduleinfo->intro, array('id'=>$moduleinfo->instance));
}
+ // Add module tags.
+ if (core_tag_tag::is_enabled('core', 'course_modules') && isset($moduleinfo->tags)) {
+ core_tag_tag::set_item_tags('core', 'course_modules', $moduleinfo->coursemodule, $modcontext, $moduleinfo->tags);
+ }
+
// Course_modules and course_sections each contain a reference to each other.
// So we have to update one of them twice.
$sectionid = course_add_cm_to_section($course, $moduleinfo->coursemodule, $moduleinfo->section);
set_coursemodule_idnumber($moduleinfo->coursemodule, $moduleinfo->cmidnumber);
}
+ // Update module tags.
+ if (core_tag_tag::is_enabled('core', 'course_modules') && isset($moduleinfo->tags)) {
+ core_tag_tag::set_item_tags('core', 'course_modules', $moduleinfo->coursemodule, $modcontext, $moduleinfo->tags);
+ }
+
// Now that module is fully updated, also update completion data if required.
// (this will wipe all user completion data and recalculate it)
if ($completion->is_enabled() && !empty($moduleinfo->completionunlocked)) {
$mform->disabledIf('completionexpected', 'completion', 'eq', COMPLETION_TRACKING_NONE);
}
+ // Populate module tags.
+ if (core_tag_tag::is_enabled('core', 'course_modules')) {
+ $mform->addElement('header', 'tagshdr', get_string('tags', 'tag'));
+ $mform->addElement('tags', 'tags', get_string('tags'), array('itemtype' => 'course_modules', 'component' => 'core'));
+ if ($this->_cm) {
+ $tags = core_tag_tag::get_item_tags_array('core', 'course_modules', $this->_cm->id);
+ $mform->setDefault('tags', $tags);
+ }
+ }
+
$this->standard_hidden_coursemodule_elements();
}
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode,
Behat\Mink\Exception\ExpectationException as ExpectationException,
Behat\Mink\Exception\DriverException as DriverException,
unset($rows[$key]);
}
}
- $table->setRows($rows);
+ $table = new TableNode($rows);
// Adding a forced wait until editors are loaded as otherwise selenium sometimes tries clicks on the
// format field when the editor is being rendered and the click misses the field coordinates.
// The 'Hide' button should be available.
$nohideexception = new ExpectationException('"' . $activityname . '" don\'t have a "' . get_string('hide') . '" icon', $this->getSession());
- $this->find('named', array('link', get_string('hide')), $nohideexception, $activitynode);
+ $this->find('named_partial', array('link', get_string('hide')), $nohideexception, $activitynode);
}
}
// Also 'Show' icon.
$noshowexception = new ExpectationException('"' . $activityname . '" don\'t have a "' . get_string('show') . '" icon', $this->getSession());
- $this->find('named', array('link', get_string('show')), $noshowexception, $activitynode);
+ $this->find('named_partial', array('link', get_string('show')), $noshowexception, $activitynode);
} else {
And I should see the "Course categories and courses" management page
And I click on <sortby> action for "Master cat" in management category listing
And a new page should have loaded since I started watching
- And I start watching to see if a new page loads
And I should see the "Course categories and courses" management page
And I should see category listing <cat1> before <cat2>
And I should see category listing <cat2> before <cat3>
And I should see "Sort by Course time created descending" in the ".course-listing-actions" "css_element"
And I click on <sortby> "link" in the ".course-listing-actions" "css_element"
And a new page should have loaded since I started watching
- And I start watching to see if a new page loads
And I should see the "Course categories and courses" management page
And I should see course listing <course1> before <course2>
And I should see course listing <course2> before <course3>
case 'assign':
// Add some tags to this assignment.
core_tag_tag::set_item_tags('mod_assign', 'assign', $module->id, $modcontext, array('Tag 1', 'Tag 2', 'Tag 3'));
+ core_tag_tag::set_item_tags('core', 'course_modules', $module->cmid, $modcontext, array('Tag 3', 'Tag 4', 'Tag 5'));
// Confirm the tag instances were added.
- $criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id);
+ $criteria = array('component' => 'mod_assign', 'itemtype' => 'assign', 'contextid' => $modcontext->id);
+ $this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
+ $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id);
$this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
// Verify event assignment event has been generated.
// Verify the tag instances were deleted.
$criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id);
$this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
+
+ $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id);
+ $this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
break;
case 'quiz':
// Verify category deleted.
* Add new instance of enrol plugin.
* @param object $course
* @param array $fields instance fields
- * @return int id of new instance, null if can not be created
+ * @return int id of last instance, null if can not be created
*/
public function add_instance($course, array $fields = null) {
global $CFG;
require_once("$CFG->dirroot/enrol/meta/locallib.php");
- if (!empty($fields['customint2']) && $fields['customint2'] == ENROL_META_CREATE_GROUP) {
- $context = context_course::instance($course->id);
- require_capability('moodle/course:managegroups', $context);
- $groupid = enrol_meta_create_new_group($course->id, $fields['customint1']);
- $fields['customint2'] = $groupid;
+ // Support creating multiple at once.
+ if (is_array($fields['customint1'])) {
+ $courses = array_unique($fields['customint1']);
+ } else {
+ $courses = array($fields['customint1']);
}
+ foreach ($courses as $courseid) {
+ if (!empty($fields['customint2']) && $fields['customint2'] == ENROL_META_CREATE_GROUP) {
+ $context = context_course::instance($course->id);
+ require_capability('moodle/course:managegroups', $context);
+ $groupid = enrol_meta_create_new_group($course->id, $courseid);
+ $fields['customint2'] = $groupid;
+ }
- $result = parent::add_instance($course, $fields);
+ $fields['customint1'] = $courseid;
+ $result = parent::add_instance($course, $fields);
+ }
enrol_meta_sync($course->id);
}
// TODO: this has to be done via ajax or else it will fail very badly on large sites!
- $courses = array('' => get_string('choosedots'));
+ $courses = array();
$select = ', ' . context_helper::get_preload_record_columns_sql('ctx');
$join = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
public function edit_instance_form($instance, MoodleQuickForm $mform, $coursecontext) {
global $DB;
- $courses = $this->get_course_options($instance, $coursecontext);
$groups = $this->get_group_options($coursecontext);
+ $existing = $DB->get_records('enrol', array('enrol' => 'meta', 'courseid' => $coursecontext->instanceid), '', 'customint1, id');
- $mform->addElement('select', 'customint1', get_string('linkedcourse', 'enrol_meta'), $courses);
+ $excludelist = array($coursecontext->instanceid);
+ foreach ($existing as $existinginstance) {
+ $excludelist[] = $existinginstance->customint1;
+ }
+
+ $options = array(
+ 'requiredcapabilities' => array('enrol/meta:selectaslinked'),
+ 'multiple' => true,
+ 'exclude' => $excludelist
+ );
+ $mform->addElement('course', 'customint1', get_string('linkedcourse', 'enrol_meta'), $options);
$mform->addRule('customint1', get_string('required'), 'required', null, 'client');
if (!empty($instance->id)) {
$mform->freeze('customint1');
$c = false;
if (!empty($data['customint1'])) {
- $c = $DB->get_record('course', array('id' => $data['customint1']));
- }
-
- if (!$c) {
- $errors['customint1'] = get_string('required');
- } else {
- $coursecontext = context_course::instance($c->id);
- $existing = $DB->get_records('enrol', array('enrol' => 'meta', 'courseid' => $thiscourseid), '', 'customint1, id');
- if (!$c->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
- $errors['customint1'] = get_string('error');
- } else if (!has_capability('enrol/meta:selectaslinked', $coursecontext)) {
- $errors['customint1'] = get_string('error');
- } else if ($c->id == SITEID or $c->id == $thiscourseid or isset($existing[$c->id])) {
- $errors['customint1'] = get_string('error');
+ foreach ($data['customint1'] as $courseid) {
+ $c = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
+ $coursecontext = context_course::instance($c->id);
+ $existing = $DB->get_records('enrol', array('enrol' => 'meta', 'courseid' => $thiscourseid), '', 'customint1, id');
+ if (!$c->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
+ $errors['customint1'] = get_string('error');
+ } else if (!has_capability('enrol/meta:selectaslinked', $coursecontext)) {
+ $errors['customint1'] = get_string('error');
+ } else if ($c->id == SITEID or $c->id == $thiscourseid or isset($existing[$c->id])) {
+ $errors['customint1'] = get_string('error');
+ }
}
+ } else {
+ $errors['customint1'] = get_string('required');
}
- $validcourses = array_keys($this->get_course_options($instance, $context));
$validgroups = array_keys($this->get_group_options($context));
$tovalidate = array(
- 'customint1' => $validcourses,
'customint2' => $validgroups
);
$typeerrors = $this->validate_param_types($data, $tovalidate);
-@enrol @enrol_meta
+@enrol @enrol_meta @javascript
Feature: Enrolments are synchronised with meta courses
In order to simplify enrolments in parent courses
As a teacher
| student4 | Student | 4 | student4@asd.com |
And the following "courses" exist:
| fullname | shortname |
- | Course 1 | C1 |
- | Course 2 | C2 |
- | Course 3 | C3 |
+ | Course 1 | C1C1 |
+ | Course 2 | C2C2 |
+ | Course 3 | C3C3 |
And the following "groups" exist:
| name | course | idnumber |
- | Groupcourse 1 | C3 | G1 |
- | Groupcourse 2 | C3 | G2 |
+ | Groupcourse 1 | C3C3 | G1 |
+ | Groupcourse 2 | C3C3 | G2 |
And the following "course enrolments" exist:
| user | course | role |
- | student1 | C1 | student |
- | student2 | C1 | student |
- | student3 | C1 | student |
- | student4 | C1 | student |
- | student1 | C2 | student |
- | student2 | C2 | student |
+ | student1 | C1C1 | student |
+ | student2 | C1C1 | student |
+ | student3 | C1C1 | student |
+ | student4 | C1C1 | student |
+ | student1 | C2C2 | student |
+ | student2 | C2C2 | student |
And I log in as "admin"
And I navigate to "Manage enrol plugins" node in "Site administration > Plugins > Enrolments"
And I click on "Enable" "link" in the "Course meta link" "table_row"
Scenario: Add meta enrolment instance without groups
When I follow "Course 3"
And I add "Course meta link" enrolment method with:
- | Link course | Course 1 |
+ | Link course | C1C1 |
And I navigate to "Enrolled users" node in "Course administration > Users"
Then I should see "Student 1"
And I should see "Student 4"
Scenario: Add meta enrolment instance with groups
When I follow "Course 3"
- And I navigate to "Enrolment methods" node in "Course administration > Users"
- And I select "Course meta link" from the "Add method" singleselect
- And I set the following fields to these values:
- | Link course | Course 1 |
+ And I add "Course meta link" enrolment method with:
+ | Link course | C1C1 |
| Add to group | Groupcourse 1 |
- And I press "Add method"
- And I set the field "Add method" to "Course meta link"
- And I press "Go"
- And I set the following fields to these values:
- | Link course | Course 2 |
+ And I follow "Course 3"
+ And I add "Course meta link" enrolment method with:
+ | Link course | C2C2 |
| Add to group | Groupcourse 2 |
- And I press "Add method"
And I navigate to "Enrolled users" node in "Course administration > Users"
Then I should see "Groupcourse 1" in the "Student 1" "table_row"
And I should see "Groupcourse 1" in the "Student 2" "table_row"
Scenario: Add meta enrolment instance with auto-created groups
When I follow "Course 3"
- And I navigate to "Enrolment methods" node in "Course administration > Users"
- And I set the field "Add method" to "Course meta link"
- And I press "Go"
- And I set the following fields to these values:
- | Link course | Course 1 |
+ And I add "Course meta link" enrolment method with:
+ | Link course | C1C1 |
| Add to group | Create new group |
- And I press "Add method"
And I navigate to "Enrolled users" node in "Course administration > Users"
Then I should see "Course 1 course" in the "Student 1" "table_row"
And I should see "Course 1 course" in the "Student 2" "table_row"
Scenario: Backup and restore of meta enrolment instance
When I follow "Course 3"
- And I navigate to "Enrolment methods" node in "Course administration > Users"
- And I set the field "Add method" to "Course meta link"
- And I press "Go"
- And I set the following fields to these values:
- | Link course | Course 1 |
+ And I add "Course meta link" enrolment method with:
+ | Link course | C1C1 |
| Add to group | Groupcourse 1 |
- And I press "Add method"
- And I select "Course meta link" from the "Add method" singleselect
- And I set the following fields to these values:
- | Link course | Course 2 |
- And I press "Add method"
+ And I follow "Course 3"
+ And I add "Course meta link" enrolment method with:
+ | Link course | C2C2 |
When I backup "Course 3" course using this options:
| Confirmation | Filename | test_backup.mbz |
And I click on "Restore" "link" in the "test_backup.mbz" "table_row"
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode;
/**
$grade_item->outcomeid = null;
+ if (!empty($data->grade_item_rescalegrades) && $data->grade_item_rescalegrades == 'yes') {
+ $grade_item->rescale_grades_keep_percentage($grade_item_copy->grademin, $grade_item_copy->grademax, $grade_item->grademin,
+ $grade_item->grademax, 'gradebook');
+ }
+
// update hiding flag
if ($hiddenuntil) {
$grade_item->set_hidden($hiddenuntil, false);
private $aggregation_options = array();
function definition() {
- global $CFG, $COURSE, $DB;
+ global $CFG, $COURSE, $DB, $OUTPUT;
$mform =& $this->_form;
$category = $this->_customdata['current'];
$mform->addHelpButton('grade_item_idnumber', 'idnumbermod');
$mform->setType('grade_item_idnumber', PARAM_RAW);
+ if (!empty($category->id)) {
+ $gradecategory = grade_category::fetch(array('id' => $category->id));
+ $gradeitem = $gradecategory->load_grade_item();
+
+ // If grades exist set a message so the user knows why they can not alter the grade type or scale.
+ // We could never change the grade type for external items, so only need to show this for manual grade items.
+ if ($gradeitem->has_overridden_grades()) {
+ // Set a message so the user knows why the can not alter the grade type or scale.
+ if ($gradeitem->gradetype == GRADE_TYPE_SCALE) {
+ $gradesexistmsg = get_string('modgradecategorycantchangegradetyporscalemsg', 'grades');
+ } else {
+ $gradesexistmsg = get_string('modgradecategorycantchangegradetypemsg', 'grades');
+ }
+ $notification = new \core\output\notification($gradesexistmsg, \core\output\notification::NOTIFY_INFO);
+ $notification->set_show_closebutton(false);
+ $mform->addElement('static', 'gradesexistmsg', '', $OUTPUT->render($notification));
+ }
+ }
+
$options = array(GRADE_TYPE_NONE=>get_string('typenone', 'grades'),
GRADE_TYPE_VALUE=>get_string('typevalue', 'grades'),
GRADE_TYPE_SCALE=>get_string('typescale', 'grades'),
$mform->disabledIf('grade_item_scaleid', 'grade_item_gradetype', 'noteq', GRADE_TYPE_SCALE);
$mform->disabledIf('grade_item_scaleid', 'aggregation', 'eq', GRADE_AGGREGATE_SUM);
+ $choices = array();
+ $choices[''] = get_string('choose');
+ $choices['no'] = get_string('no');
+ $choices['yes'] = get_string('yes');
+ $mform->addElement('select', 'grade_item_rescalegrades', get_string('modgradecategoryrescalegrades', 'grades'), $choices);
+ $mform->addHelpButton('grade_item_rescalegrades', 'modgradecategoryrescalegrades', 'grades');
+ $mform->disabledIf('grade_item_rescalegrades', 'grade_item_gradetype', 'noteq', GRADE_TYPE_VALUE);
+
$mform->addElement('text', 'grade_item_grademax', get_string('grademax', 'grades'));
$mform->setType('grade_item_grademax', PARAM_RAW);
$mform->addHelpButton('grade_item_grademax', 'grademax', 'grades');
}
}
}
+
+ $mform->removeElement('grade_item_rescalegrades');
}
$mform->removeElement('grade_item_display');
$mform->removeElement('grade_item_decimals');
$mform->hardFreeze('grade_item_scaleid');
+ // Only show the option to rescale grades on a category if its corresponding grade_item has overridden grade_grades.
+ } else if ($grade_item->has_overridden_grades()) {
+ // Can't change the grade type or the scale if there are grades.
+ $mform->hardFreeze('grade_item_gradetype, grade_item_scaleid');
+
+ // If we are using scles then remove the unnecessary rescale and grade fields.
+ if ($grade_item->gradetype == GRADE_TYPE_SCALE) {
+ $mform->removeElement('grade_item_rescalegrades');
+ $mform->removeElement('grade_item_grademax');
+ if ($mform->elementExists('grade_item_grademin')) {
+ $mform->removeElement('grade_item_grademin');
+ }
+ } else { // Not using scale, so remove it.
+ $mform->removeElement('grade_item_scaleid');
+ $mform->disabledIf('grade_item_grademax', 'grade_item_rescalegrades', 'eq', '');
+ $mform->disabledIf('grade_item_grademin', 'grade_item_rescalegrades', 'eq', '');
+ }
+ } else { // Remove the rescale element if there are no grades.
+ $mform->removeElement('grade_item_rescalegrades');
}
//remove the aggregation coef element if not needed
/// perform extra validation before submission
function validation($data, $files) {
global $COURSE;
+ $gradeitem = false;
+ if ($data['id']) {
+ $gradecategory = grade_category::fetch(array('id' => $data['id']));
+ $gradeitem = $gradecategory->load_grade_item();
+ }
$errors = parent::validation($data, $files);
}
}
+ if ($data['id'] && $gradeitem->has_overridden_grades()) {
+ if ($gradeitem->gradetype == GRADE_TYPE_VALUE) {
+ if (grade_floats_different($data['grade_item_grademin'], $gradeitem->grademin) ||
+ grade_floats_different($data['grade_item_grademax'], $gradeitem->grademax)) {
+ if (empty($data['grade_item_rescalegrades'])) {
+ $errors['grade_item_rescalegrades'] = get_string('mustchooserescaleyesorno', 'grades');
+ }
+ }
+ }
+ }
return $errors;
}
}
if (!$moving && count($grade_edit_tree->categories) > 1) {
echo '<br /><br />';
echo '<input type="hidden" name="bulkmove" value="0" id="bulkmoveinput" />';
- $attributes = array('id'=>'menumoveafter', 'class' => 'ignoredirty');
+ $attributes = array('id'=>'menumoveafter', 'class' => 'ignoredirty singleselect');
echo html_writer::label(get_string('moveselectedto', 'grades'), 'menumoveafter');
echo html_writer::select($grade_edit_tree->categories, 'moveafter', '', array(''=>'choosedots'), $attributes);
$OUTPUT->add_action_handler(new component_action('change', 'submit_bulk_move'), 'menumoveafter');
$html .= $renderer->display_regrade_confirmation($this->getName(), $this->regradeconfirmation, $data['regrade']);
}
if ($this->validationerrors) {
- $html .= html_writer::div($renderer->notification($this->validationerrors, 'error'), '', array('role' => 'alert'));
+ $html .= html_writer::div($renderer->notification($this->validationerrors));
}
$html .= $renderer->display_guide($data['criteria'], $data['comments'], $data['options'], $mode, $this->getName());
return $html;
require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
use Behat\Gherkin\Node\TableNode as TableNode,
- Behat\Behat\Context\Step\Given as Given,
- Behat\Behat\Context\Step\When as When,
- Behat\Behat\Context\Step\Then as Then,
+ Moodle\BehatExtension\Context\Step\Given as Given,
+ Moodle\BehatExtension\Context\Step\When as When,
+ Moodle\BehatExtension\Context\Step\Then as Then,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
Behat\Mink\Exception\ExpectationException as ExpectationException;
$html .= $renderer->display_regrade_confirmation($this->getName(), $this->regradeconfirmation, $data['regrade']);
}
if ($this->validationerrors) {
- $html .= html_writer::div($renderer->notification($this->validationerrors, 'error'), '', array('role' => 'alert'));
+ $html .= html_writer::div($renderer->notification($this->validationerrors));
}
$html .= $renderer->display_rubric($data['criteria'], $data['options'], $mode, $this->getName());
return $html;
$value = $this->prepare_data($this->_findValue($submitValues));
return $this->_prepareValue($value, $assoc);
}
-}
\ No newline at end of file
+}
require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
use Behat\Gherkin\Node\TableNode as TableNode,
- Behat\Behat\Context\Step\Given as Given,
- Behat\Behat\Context\Step\When as When,
- Behat\Behat\Context\Step\Then as Then,
+ Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
Behat\Mink\Exception\ExpectationException as ExpectationException;
}
}
+ // Remove empty criterion, as TableNode might contain them to make table rows equal size.
+ $newcriterion = array();
+ foreach ($criterion as $k => $c) {
+ if (!empty($c)) {
+ $newcriterion[$k] = $c;
+ }
+ }
+ $criterion = $newcriterion;
+
// Checking the number of cells.
if (count($criterion) % 2 === 0) {
throw new ExpectationException(
require_once(__DIR__ . '/../../../../lib/behat/behat_base.php');
use Behat\Gherkin\Node\TableNode as TableNode,
- Behat\Behat\Context\Step\Given as Given,
- Behat\Behat\Context\Step\When as When;
+ Moodle\BehatExtension\Context\Step\Given as Given,
+ Moodle\BehatExtension\Context\Step\When as When;
/**
* Generic grading methods step definitions.
// Shortcut in case we already are in the grading page.
$usergradetextliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($usergradetext);
- if ($this->getSession()->getPage()->find('named', array('link', $usergradetextliteral))) {
+ if ($this->getSession()->getPage()->find('named_partial', array('link', $usergradetextliteral))) {
return $gradeuserstep;
}
| name | scale |
| Test Scale | Disappointing,Good,Very good,Excellent |
And the following "grade categories" exist:
- | fullname | course |
- | Grade Cat | C1 |
+ | fullname | course |
+ | Grade Cat | C1 |
+ And the following "grade categories" exist:
+ | fullname | course | gradecategory |
+ | Grade Sub Cat | C1 | Grade Cat |
And the following "grade items" exist:
| itemname | course | locked | gradetype | gradecategory |
- | Item 1 | C1 | 0 | value | Grade Cat |
+ | Item 1 | C1 | 0 | value | Grade Cat |
| Item VU | C1 | 0 | value | Grade Cat |
| Item VL | C1 | 1 | value | Grade Cat |
- | Item TU | C1 | 0 | text | Grade Cat |
- | Item TL | C1 | 1 | text | Grade Cat |
- And the following "grade items" exist:
- | itemname | course | locked | gradetype | scale | gradecategory |
- | Item SU | C1 | 0 | scale | Test Scale | Grade Cat |
- | Item SL | C1 | 1 | scale | Test Scale | Grade Cat |
+ | Item TU | C1 | 0 | text | Grade Cat |
+ | Item TL | C1 | 1 | text | Grade Cat |
+ | Item 3 | C1 | 0 | value | Grade Cat |
+ | Calc Item | C1 | 0 | value | Grade Cat |
+ | Item VUSub | C1 | 0 | value | Grade Sub Cat |
And the following "grade items" exist:
- | itemname | course | locked | gradetype | gradecategory |
- | Item 3 | C1 | 0 | value | Grade Cat |
+ | itemname | course | locked | gradetype | scale | gradecategory |
+ | Item SU | C1 | 0 | scale | Test Scale | Grade Cat |
+ | Item SL | C1 | 1 | scale | Test Scale | Grade Cat |
And the following config values are set as admin:
| grade_report_showaverages | 0 |
| grade_report_enableajax | 1 |
And I set the field "ajaxgrade" to "Very good"
And I press key "13" in the field "ajaxgrade"
And the following should exist in the "user-grades" table:
- | -1- | -4- | -5- | -9- | -13- |
+ | -1- | -6- | -7- | -13- | -16- |
| Student 2 | - | 33.00 | - | 33.00 |
| Student 3 | 80.00 | 50.00 | Very good | 133.00 |
And I click on student "Student 3" for grade item "Item VL"
And I set the field "ajaxgrade" to "90"
And I press key "13" in the field "ajaxgrade"
And the following should exist in the "user-grades" table:
- | -1- | -13- |
+ | -1- | -16- |
| Student 1 | 90.00 |
And I navigate to "Grader report" node in "Grade administration"
And the following should exist in the "user-grades" table:
- | -1- | -4- | -5- | -9- | -13- |
- | Student 1 | - | - | - | 90.00 |
- | Student 2 | - | 33.00 | - | 33.00 |
- | Student 3 | 80.00 | 50.00 | Very good | 133.00 |
+ | -1- | -6- | -7- | -13- | -16- |
+ | Student 1 | - | - | - | 90.00 |
+ | Student 2 | - | 33.00 | - | 33.00 |
+ | Student 3 | 80.00 | 50.00 | Very good | 133.00 |
@javascript
Scenario: Use the grader report without editing, with AJAX and quick feedback on
And I press key "13" in the field "ajaxfeedback"
And I navigate to "Grader report" node in "Grade administration"
And the following should exist in the "user-grades" table:
- | -1- | -5- | -9- | -13- |
- | Student 2 | 33.00 | Very good | 36.00 |
+ | -1- | -7- | -13- | -16- |
+ | Student 2 | 33.00 | Very good | 36.00 |
And I click on student "Student 3" for grade item "Item TU"
And the field "ajaxfeedback" matches value "Student 3 TU feedback"
And I click on student "Student 2" for grade item "Item SU"
And I should not see a grade field for "Student 3" and grade item "Course total"
And I should not see a feedback field for "Student 3" and grade item "Course total"
And the following should exist in the "user-grades" table:
- | -1- | -5- | -13- |
- | Student 2 | 33.00 | 33.00 |
+ | -1- | -7- | -16- |
+ | Student 2 | 33.00 | 33.00 |
@javascript
Scenario: Use the grader report with editing, with AJAX and quick feedback on, with category override
And the grade for "Student 2" in grade item "Course total" should match "53.00"
And I turn editing mode off
And the following should exist in the "user-grades" table:
- | -1- | -4- | -5- | -9- | -12- | -13- |
- | Student 2 | 30.00 | 20.00 | Very good | 53.00 | 53.00 |
+ | -1- | -6- | -7- | -13- | -15- | -16- |
+ | Student 2 | 30.00 | 20.00 | Very good | 53.00 | 53.00 |
And I click on student "Student 2" for grade item "Item 1"
And the field "ajaxfeedback" matches value "Some feedback"
And I follow "Course 1"
And I navigate to "Grades" node in "Course administration"
And I turn editing mode on
+ And I change window size to "large"
+ And I set "=[[i1]] + [[i3]] + [[gsc]]" calculation for grade item "Calc Item" with idnumbers:
+ | Item 1 | i1 |
+ | Item 3 | i3 |
+ | Grade Sub Cat | gsc |
Then I should not see a grade field for "Student 2" and grade item "Course total"
And I should not see a feedback field for "Student 2" and grade item "Course total"
And I give the grade "20.00" to the user "Student 2" for the grade item "Item VU"
And I click away from student "Student 2" and grade item "Item VU" value
+ And the following should exist in the "user-grades" table:
+ | -1- | -15- | -16- |
+ | Student 2 | 20.00 | 20.00 |
And I give the grade "30.00" to the user "Student 2" for the grade item "Item 1"
And I click away from student "Student 2" and grade item "Item 1" value
+ And the following should exist in the "user-grades" table:
+ | -1- | -15- | -16- |
+ | Student 2 | 80.00 | 80.00 |
+ And the field "Student 2 Calc Item grade" matches value "30.00"
+ And I give the grade "5.00" to the user "Student 2" for the grade item "Item 3"
+ And I click away from student "Student 2" and grade item "Item 3" value
+ And the following should exist in the "user-grades" table:
+ | -1- | -15- | -16- |
+ | Student 2 | 90.00 | 90.00 |
+ And the field "Student 2 Calc Item grade" matches value "35.00"
+ And I give the grade "10.00" to the user "Student 2" for the grade item "Item VUSub"
+ And I click away from student "Student 2" and grade item "Item VUSub" value
+ And the following should exist in the "user-grades" table:
+ | -1- | -5- | -15- | -16- |
+ | Student 2 | 10.00 | 110.00 | 110.00 |
+ And the field "Student 2 Calc Item grade" matches value "45.00"
And I give the feedback "Some feedback" to the user "Student 2" for the grade item "Item 1"
And I click away from student "Student 2" and grade item "Item 1" feedback
- And the following should exist in the "user-grades" table:
- | -1- | -13- |
- | Student 2 | 50.00 |
And I turn editing mode off
And the following should exist in the "user-grades" table:
- | -1- | -4- | -5- | -13- |
- | Student 2 | 30.00 | 20.00 | 50.00 |
+ | -1- | -4- | -6- | -7- | -11- | -12- | -15- | -16- |
+ | Student 2 | 10.00 | 30.00 | 20.00 | 5.00 | 45.00 | 110.00 | 110.00 |
And I click on student "Student 2" for grade item "Item 1"
And the field "ajaxfeedback" matches value "Some feedback"
require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given,
- Behat\Behat\Context\Step\Then,
+use Moodle\BehatExtension\Context\Step\Given,
+ Moodle\BehatExtension\Context\Step\Then,
Behat\Mink\Exception\ExpectationException as ExpectationException,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
continue;
}
+ // If the user submits Exclude grade elements without the proper.
+ // permissions then we should refuse to update.
+ if ($matches[1] === 'exclude' && !has_capability('moodle/grade:manage', $this->context)){
+ $warnings[] = get_string('nopermissions', 'error', get_string('grade:manage', 'role'));
+ continue;
+ }
+
$msg = $element->set($posted);
// Optional type.
$html .= $this->structure->get_grade_analysis_icon($grade);
}
+ // Singleview users without proper permissions should be presented
+ // disabled checkboxes for the Exclude grade attribute.
+ if ($field == 'exclude' && !has_capability('moodle/grade:manage', $this->context)){
+ $html->disabled = true;
+ }
+
$line[] = $html;
}
return $line;
* @copyright 2014 Moodle Pty Ltd (http://moodle.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class exclude extends grade_attribute_format implements be_checked {
+class exclude extends grade_attribute_format implements be_checked, be_disabled {
/** @var string $name The name of the input */
public $name = 'exclude';
+ /** @var bool $disabled Is the checkbox disabled? */
+ public $disabled = false;
+
/**
* Is it checked?
*
return $this->grade->is_excluded();
}
+ /**
+ * Is it disabled?
+ *
+ * @return bool
+ */
+ public function is_disabled() {
+ return $this->disabled;
+ }
+
/**
* Generate the element used to render the UI
*
return new checkbox_attribute(
$this->get_name(),
$this->get_label(),
- $this->is_checked()
+ $this->is_checked(),
+ $this->is_disabled()
);
}
And the following "users" exist:
| username | firstname | lastname | email | idnumber | alternatename |
| teacher1 | Teacher | 1 | teacher1@example.com | t1 | fred |
+ | teacher2 | No edit | 1 | teacher2@example.com | t2 | nick |
| student1 | Student | 1 | student1@example.com | s1 | james |
| student2 | Student | 2 | student1@example.com | s2 | holly |
| student3 | Student | 3 | student1@example.com | s3 | anna |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
+ | teacher2 | C1 | teacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
And the following "grade items" exist:
| itemname | course | gradetype |
| Test grade item | C1 | Scale |
+ And the following "permission overrides" exist:
+ | capability | permission | role | contextlevel | reference |
+ | moodle/grade:edit | Allow | teacher | Course | C1 |
+ | gradereport/singleview:view | Allow | teacher | Course | C1 |
And I log in as "teacher1"
And I follow "Course 1"
And I navigate to "Grades" node in "Course administration"
And the following should exist in the "generaltable" table:
| First name (Alternate name) Surname | Grade |
| james (Student) 1 | Very good |
+ And I log out
+ And I log in as "teacher2"
+ And I follow "Course 1"
+ And I navigate to "Grades" node in "Course administration"
+ And I click on "Single view" "option"
+ And I click on "Student 4" "option"
+ And the "Exclude for Test assignment one" "checkbox" should be disabled
+ And the "Override for Test assignment one" "checkbox" should be enabled
Scenario: Single view links work on grade report.
Given I follow "Single view for Test assignment one"
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Given as Given,
+use Moodle\BehatExtension\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode;
class behat_grade extends behat_base {
And I follow "Course 1"
And I navigate to "Grades" node in "Course administration"
And I turn editing mode on
+ And I change window size to "large"
And I give the grade "60.00" to the user "Student 1" for the grade item "Test assignment one"
And I give the grade "20.00" to the user "Student 1" for the grade item "Test assignment two"
And I give the grade "40.00" to the user "Student 1" for the grade item "Test assignment three"
| Hidden | 1 |
And I set the following settings for grade item "Test assignment eight":
| Hidden | 1 |
+ And I change window size to "medium"
And I navigate to "Course grade settings" node in "Grade administration > Setup"
And I set the field "Grade display type" to "Real (percentage)"
And I press "Save changes"
--- /dev/null
+@core_grades
+Feature: Editing a grade item
+ In order to ensure validation is provided to the teacher
+ As a teacher
+ I need to know why I can not add/edit values on the grade category form
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | student1 | Student | 1 | student1@example.com |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ And the following "courses" exist:
+ | fullname | shortname | category | groupmode |
+ | Course 1 | C1 | 0 | 1 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ And I log in as "admin"
+ And I navigate to "Scales" node in "Site administration > Grades"
+ And I press "Add a new scale"
+ And I set the following fields to these values:
+ | Name | ABCDEF |
+ | Scale | F,E,D,C,B,A |
+ And I press "Save changes"
+ And I press "Add a new scale"
+ And I set the following fields to these values:
+ | Name | Letter scale |
+ | Scale | Disappointing, Good, Very good, Excellent |
+ And I press "Save changes"
+ And I set the following administration settings values:
+ | grade_aggregations_visible | Mean of grades,Weighted mean of grades,Simple weighted mean of grades,Mean of grades (with extra credits),Median of grades,Lowest grade,Highest grade,Mode of grades,Natural |
+ And I log out
+ And I log in as "teacher1"
+ And I am on site homepage
+ And I follow "Course 1"
+ And I navigate to "Gradebook setup" node in "Course administration"
+ And I press "Add category"
+ And I set the following fields to these values:
+ | Category name | Cat 1 |
+ | Aggregation | Highest grade |
+ And I press "Save changes"
+ And I press "Add grade item"
+ And I set the following fields to these values:
+ | Item name | Item 1 |
+ | Grade category | Cat 1 |
+ And I press "Save changes"
+ And I press "Add grade item"
+ And I set the following fields to these values:
+ | Item name | Item 2 |
+ | Grade category | Cat 1 |
+ And I press "Save changes"
+
+ Scenario: Being able to change the grade type, scale and maximum grade for a grade category when there are no overridden grades
+ Given I click on "Edit" "link" in the "Cat 1" "table_row"
+ When I click on "Edit settings" "link" in the "Cat 1" "table_row"
+ Then I should not see "This category has associated grade items which have been overridden. Therefore some grades have already been awarded"
+ And I set the field "Grade type" to "Scale"
+ And I press "Save changes"
+ And I should see "Scale must be selected"
+ And I set the field "Scale" to "ABCDEF"
+ And I press "Save changes"
+ And I should not see "You cannot change the type, as grades already exist for this item"
+ And I click on "Edit" "link" in the "Cat 1" "table_row"
+ And I click on "Edit settings" "link" in the "Cat 1" "table_row"
+ And I should not see "This category has associated grade items which have been overridden. Therefore some grades have already been awarded"
+ And I set the field "Scale" to "Letter scale"
+ And I press "Save changes"
+ And I should not see "You cannot change the scale, as grades already exist for this item"
+
+ Scenario: Attempting to change a category item's grade type when overridden grades already exist
+ Given I navigate to "Grader report" node in "Grade administration"
+ And I turn editing mode on
+ And I give the grade "20.00" to the user "Student 1" for the grade item "Cat 1 total"
+ And I press "Save changes"
+ And I navigate to "Gradebook setup" node in "Grade administration > Setup"
+ And I click on "Edit" "link" in the "Cat 1" "table_row"
+ When I click on "Edit settings" "link" in the "Cat 1" "table_row"
+ Then I should see "This category has associated grade items which have been overridden. Therefore some grades have already been awarded, so the grade type cannot be changed. If you wish to change the maximum grade, you must first choose whether or not to rescale existing grades."
+ And "//div[contains(concat(' ', normalize-space(@class), ' '), 'fstatic') and contains(text(), 'Value')]" "xpath_element" should exist
+
+ Scenario: Attempting to change a category item's scale when overridden grades already exist
+ Given I click on "Edit" "link" in the "Cat 1" "table_row"
+ And I click on "Edit settings" "link" in the "Cat 1" "table_row"
+ And I set the field "Grade type" to "Scale"
+ And I set the field "Scale" to "ABCDEF"
+ And I press "Save changes"
+ And I navigate to "Grader report" node in "Grade administration"
+ And I turn editing mode on
+ And I give the grade "C" to the user "Student 1" for the grade item "Cat 1 total"
+ And I press "Save changes"
+ And I navigate to "Gradebook setup" node in "Grade administration > Setup"
+ And I click on "Edit" "link" in the "Cat 1" "table_row"
+ When I click on "Edit settings" "link" in the "Cat 1" "table_row"
+ Then I should see "This category has associated grade items which have been overridden. Therefore some grades have already been awarded, so the grade type and scale cannot be changed."
+ And "//div[contains(concat(' ', normalize-space(@class), ' '), 'fstatic') and contains(text(), 'ABCDEF')]" "xpath_element" should exist
+
+ Scenario: Attempting to change a category item's maximum grade when no rescaling option has been chosen
+ Given I navigate to "Grader report" node in "Grade administration"
+ And I turn editing mode on
+ And I give the grade "20.00" to the user "Student 1" for the grade item "Cat 1 total"
+ And I press "Save changes"
+ And I navigate to "Gradebook setup" node in "Grade administration > Setup"
+ And I click on "Edit" "link" in the "Cat 1" "table_row"
+ And I click on "Edit settings" "link" in the "Cat 1" "table_row"
+ And I set the field "Maximum grade" to "50"
+ When I press "Save changes"
+ Then I should see "You must choose whether to rescale existing grades or not."
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
-use Behat\Behat\Context\Step\Then;
+use Moodle\BehatExtension\Context\Step\Then;
use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
/**
$fulloption = $groupoption->getText();
$select->selectOption($fulloption);
+ // This is needed by some drivers to ensure relevant event is triggred and button is enabled.
+ $script = "Syn.trigger('change', {}, {{ELEMENT}})";
+ $this->getSession()->getDriver()->triggerSynScript($select->getXpath(), $script);
+ $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
+
// Here we don't need to wait for the AJAX response.
$this->find_button(get_string('adduserstogroup', 'group'))->click();
defined('MOODLE_INTERNAL') || die();
$string['language'] = 'Dil';
+$string['moodlelogo'] = 'Moodle logo';
$string['next'] = 'Sonraki';
$string['previous'] = 'Önceki';
$string['reload'] = 'Tekrar yükle';
$string['taskdeleteunconfirmedusers'] = 'Delete unconfirmed users';
$string['taskeventscron'] = 'Background processing for events';
$string['taskfiletrashcleanup'] = 'Cleanup files in trash';
-$string['taskglobalsearch'] = 'Global search indexing';
+$string['taskglobalsearchindex'] = 'Global search indexing';
+$string['taskglobalsearchoptimize'] = 'Global search index optimization';
$string['taskgradecron'] = 'Background processing for gradebook';
$string['tasklegacycron'] = 'Legacy cron processing for plugins';
$string['taskmessagingcleanup'] = 'Background processing for messaging';
$string['cachedef_questiondata'] = 'Question definitions';
$string['cachedef_repositories'] = 'Repositories instances data';
$string['cachedef_search_results'] = 'Search results user data';
+$string['cachedef_grade_categories'] = 'Grade category queries';
$string['cachedef_string'] = 'Language string cache';
$string['cachedef_tags'] = 'Tags collections and areas';
$string['cachedef_userselections'] = 'Data used to persist user selections throughout Moodle';
$string['modgradecantchangegradetype'] = 'You cannot change the type, as grades already exist for this item.';
$string['modgradecantchangegradetypemsg'] = 'Some grades have already been awarded, so the grade type cannot be changed. If you wish to change the maximum grade, you must first choose whether or not to rescale existing grades.';
$string['modgradecantchangegradetyporscalemsg'] = 'Some grades have already been awarded, so the grade type and scale cannot be changed.';
+$string['modgradecategorycantchangegradetypemsg'] = 'This category has associated grade items which have been overridden. Therefore some grades have already been awarded, so the grade type cannot be changed. If you wish to change the maximum grade, you must first choose whether or not to rescale existing grades.';
+$string['modgradecategorycantchangegradetyporscalemsg'] = 'This category has associated grade items which have been overridden. Therefore some grades have already been awarded, so the grade type and scale cannot be changed.';
$string['modgradecantchangescale'] = 'You cannot change the scale, as grades already exist for this item.';
$string['modgradecantchangeratingmaxgrade'] = 'You cannot change the maximum grade when grades already exist for an activity with ratings.';
$string['modgradedonotmodify'] = 'Do not modify existing grades';
If this is set to \'Yes\', any existing grades will be rescaled so that the percentage grade remains the same.
For example, if this option is set to \'Yes\', changing the maximum grade on an item from 10 to 20 would cause a grade of 6/10 (60%) to be rescaled to 12/20 (60%). With this option set to \'No\', the grade would change from 6/10 (60%) to 6/20 (30%), requiring manual adjustment of the grade items to ensure correct scores.';
+$string['modgradecategoryrescalegrades'] = 'Rescale overridden grades';
+$string['modgradecategoryrescalegrades_help'] = 'When changing the maximum grades on a gradebook item you need to specify whether or not this will cause existing percentage grades to change as well.
+
+If this is set to \'Yes\', any existing overridden grades will be rescaled so that the percentage grade remains the same.
+
+For example, if this option is set to \'Yes\', changing the maximum grade on an item from 10 to 20 would cause a grade of 6/10 (60%) to be rescaled to 12/20 (60%). With this option set to \'No\', the grade will remain unchanged, requiring manual adjustment of the grade items to ensure correct scores.';
$string['modgradetype'] = 'Type';
$string['modgradetypenone'] = 'None';
$string['modgradetypepoint'] = 'Point';
$string['type_report_plural'] = 'Reports';
$string['type_repository'] = 'Repository';
$string['type_repository_plural'] = 'Repositories';
+$string['type_search'] = 'Search engine';
+$string['type_search_plural'] = 'Search engines';
$string['type_theme'] = 'Theme';
$string['type_theme_plural'] = 'Themes';
$string['type_tool'] = 'Admin tool';
$string['tagarea_post'] = 'Blog posts';
$string['tagarea_user'] = 'User interests';
$string['tagarea_course'] = 'Courses';
+$string['tagarea_course_modules'] = 'Course modules';
$string['tagareaenabled'] = 'Enabled';
$string['tagareaname'] = 'Name';
$string['tagareas'] = 'Tag areas';
* system is more flexible. If you really need, you can to use this
* function but consider has_capability() as a possible substitute.
*
- * The caller function is responsible for including all the
- * $sort fields in $fields param.
+ * All $sort fields are added into $fields if not present there yet.
*
* If $roleid is an array or is empty (all roles) you need to set $fields
* (and $sort by extension) params according to it, as the first field
$params = array_merge($params, $sortparams);
}
+ // Adding the fields from $sort that are not present in $fields.
+ $sortarray = preg_split('/,\s*/', $sort);
+ $fieldsarray = preg_split('/,\s*/', $fields);
+ $addedfields = array();
+ foreach ($sortarray as $sortfield) {
+ if (!in_array($sortfield, $fieldsarray)) {
+ $fieldsarray[] = $sortfield;
+ $addedfields[] = $sortfield;
+ }
+ }
+ $fields = implode(', ', $fieldsarray);
+ if (!empty($addedfields)) {
+ $addedfields = implode(', ', $addedfields);
+ debugging('get_role_users() adding '.$addedfields.' to the query result because they were required by $sort but missing in $fields');
+ }
+
if ($all === null) {
// Previously null was used to indicate that parameter was not used.
$all = true;
$return .= $brtag . get_string('searchsetupdescription', 'search') . $brtag . $brtag;
- // Enable global search.
- $row = array();
- $url = new moodle_url("/admin/search.php?query=enableglobalsearch");
- $row[0] = '1. ' . html_writer::tag('a', get_string('enableglobalsearch', 'admin'),
- array('href' => $url));
- $status = html_writer::tag('span', get_string('no'), array('class' => 'statuscritical'));
- if (\core_search\manager::is_global_search_enabled()) {
- $status = html_writer::tag('span', get_string('yes'), array('class' => 'statusok'));
- }
- $row[1] = $status;
- $table->data[] = $row;
-
// Select a search engine.
$row = array();
$url = new moodle_url('/admin/settings.php?section=manageglobalsearch#admin-searchengine');
- $row[0] = '2. ' . html_writer::tag('a', get_string('selectsearchengine', 'admin'),
+ $row[0] = '1. ' . html_writer::tag('a', get_string('selectsearchengine', 'admin'),
array('href' => $url));
$status = html_writer::tag('span', get_string('no'), array('class' => 'statuscritical'));
// Available areas.
$row = array();
$url = new moodle_url('/admin/settings.php?section=manageglobalsearch#admin-searchengine');
- $row[0] = '3. ' . html_writer::tag('a', get_string('enablesearchareas', 'admin'),
+ $row[0] = '2. ' . html_writer::tag('a', get_string('enablesearchareas', 'admin'),
array('href' => $url));
$status = html_writer::tag('span', get_string('no'), array('class' => 'statuscritical'));
// Setup search engine.
$row = array();
if (empty($CFG->searchengine)) {
- $row[0] = '4. ' . get_string('setupsearchengine', 'admin');
+ $row[0] = '3. ' . get_string('setupsearchengine', 'admin');
$row[1] = html_writer::tag('span', get_string('no'), array('class' => 'statuscritical'));
} else {
$url = new moodle_url('/admin/settings.php?section=search' . $CFG->searchengine);
- $row[0] = '4. ' . html_writer::tag('a', get_string('setupsearchengine', 'admin'),
+ $row[0] = '3. ' . html_writer::tag('a', get_string('setupsearchengine', 'admin'),
array('href' => $url));
// Check the engine status.
$searchengine = \core_search\manager::search_engine_instance();
// Indexed data.
$row = array();
$url = new moodle_url('/report/search/index.php#searchindexform');
- $row[0] = '5. ' . html_writer::tag('a', get_string('indexdata', 'admin'), array('href' => $url));
+ $row[0] = '4. ' . html_writer::tag('a', get_string('indexdata', 'admin'), array('href' => $url));
if ($anyindexed) {
$status = html_writer::tag('span', get_string('yes'), array('class' => 'statusok'));
} else {
$row[1] = $status;
$table->data[] = $row;
+ // Enable global search.
+ $row = array();
+ $url = new moodle_url("/admin/search.php?query=enableglobalsearch");
+ $row[0] = '5. ' . html_writer::tag('a', get_string('enableglobalsearch', 'admin'),
+ array('href' => $url));
+ $status = html_writer::tag('span', get_string('no'), array('class' => 'statuscritical'));
+ if (\core_search\manager::is_global_search_enabled()) {
+ $status = html_writer::tag('span', get_string('yes'), array('class' => 'statusok'));
+ }
+ $row[1] = $status;
+ $table->data[] = $row;
+
$return .= html_writer::table($table);
return highlight($query, $return);
/** Include course lib for its functions */
require_once($CFG->dirroot.'/course/lib.php');
+if (!empty($CFG->forcelogin)) {
+ require_login();
+}
+
try {
// Start buffer capture so that we can `remove` any errors
ob_start();
context: requests,
dataType: 'json',
processData: false,
- async: async
+ async: async,
+ contentType: "application/json"
};
var script = config.wwwroot + '/lib/ajax/service.php?sesskey=' + config.sesskey;
if (!option.prop('selected')) {
option.remove();
} else {
- existingValues.push(option.attr('value'));
+ existingValues.push(String(option.attr('value')));
}
});
+
+ if (!options.multiple && originalSelect.children('option').length === 0) {
+ // If this is a single select - and there are no current options
+ // the first option added will be selected by the browser. This causes a bug!
+ // We need to insert an empty option so that none of the real options are selected.
+ var option = $('<option>');
+ originalSelect.append(option);
+ }
// And add all the new ones returned from ajax.
$.each(processedResults, function(resultIndex, result) {
- if (existingValues.indexOf(result.value) === -1) {
+ if (existingValues.indexOf(String(result.value)) === -1) {
var option = $('<option>');
option.append(result.label);
option.attr('value', result.value);
});
// Handler used to force set the value from behat.
inputElement.on('behat:set-value', function() {
- if (options.tags) {
+ var suggestionsElement = $(document.getElementById(state.suggestionsId));
+ if ((inputElement.attr('aria-expanded') === "true") &&
+ (suggestionsElement.children('[aria-selected=true]').length > 0)) {
+ // If the suggestion list has an active item, select it.
+ selectCurrentItem(options, state, originalSelect);
+ } else if (options.tags) {
+ // If tags are enabled, create a tag.
createItem(options, state, originalSelect);
}
});
// If this field uses ajax, set it up.
if (options.ajax) {
require([options.ajax], function(ajaxHandler) {
+ var throttleTimeout = null;
var handler = function(e) {
updateAjax(e, options, state, originalSelect, ajaxHandler);
};
+
+ // For input events, we do not want to trigger many, many updates.
+ var throttledHandler = function(e) {
+ if (throttleTimeout !== null) {
+ window.clearTimeout(throttleTimeout);
+ throttleTimeout = null;
+ }
+ throttleTimeout = window.setTimeout(handler.bind(this, e), 300);
+ };
// Trigger an ajax update after the text field value changes.
- inputElement.on("input keypress", handler);
+ inputElement.on("input keypress", throttledHandler);
+
var arrowElement = $(document.getElementById(state.downArrowId));
arrowElement.on("click", handler);
});
--- /dev/null
+// 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/>.
+
+/**
+ * Course selector adaptor for auto-complete form element.
+ *
+ * @module core/form-course-selector
+ * @class form-course-selector
+ * @package core
+ * @copyright 2016 Damyon Wiese <damyon@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since 3.1
+ */
+define(['core/ajax', 'jquery'], function(ajax, $) {
+
+ return /** @alias module:core/form-course-selector */ {
+ // Public variables and functions.
+ processResults: function(selector, data) {
+ // Mangle the results into an array of objects.
+ var results = [], i = 0;
+ var excludelist = String($(selector).data('exclude')).split(',');
+
+ for (i = 0; i < data.courses.length; i++) {
+ if (excludelist.indexOf(String(data.courses[i].id)) === -1) {
+ results.push({ value: data.courses[i].id, label: data.courses[i].displayname });
+ }
+ }
+ return results;
+ },
+
+ transport: function(selector, query, success, failure) {
+ // Parse some data-attributes from the form element.
+ var requiredcapabilities = $(selector).data('requiredcapabilities');
+ if (requiredcapabilities.trim() !== "") {
+ requiredcapabilities = requiredcapabilities.split(',');
+ } else {
+ requiredcapabilities = [];
+ }
+ // Build the query.
+ var promise = null;
+
+ if (typeof query === "undefined") {
+ query = '';
+ }
+
+ var searchargs = {
+ criterianame: 'search',
+ criteriavalue: query,
+ page: 0,
+ perpage: 100,
+ requiredcapabilities: requiredcapabilities
+ };
+ // Go go go!
+ promise = ajax.call([{
+ methodname: 'core_course_search_courses', args: searchargs
+ }]);
+
+ promise[0].done(success);
+ promise[0].fail(failure);
+
+ return promise;
+ }
+ };
+});
*/
protected function find($selector, $locator, $exception = false, $node = false, $timeout = false) {
+ // Throw exception, so dev knows it is not supported.
+ if ($selector === 'named') {
+ $exception = 'Using the "named" selector is deprecated as of 3.1. '
+ .' Use the "named_partial" or use the "named_exact" selector instead.';
+ throw new ExpectationException($exception, $this->getSession());
+ }
+
// Returns the first match.
$items = $this->find_all($selector, $locator, $exception, $node, $timeout);
return count($items) ? reset($items) : null;
*/
protected function find_all($selector, $locator, $exception = false, $node = false, $timeout = false) {
+ // Throw exception, so dev knows it is not supported.
+ if ($selector === 'named') {
+ $exception = 'Using the "named" selector is deprecated as of 3.1. '
+ .' Use the "named_partial" or use the "named_exact" selector instead.';
+ throw new ExpectationException($exception, $this->getSession());
+ }
+
// Generic info.
if (!$exception) {
// With named selectors we can be more specific.
- if ($selector == 'named') {
+ if (($selector == 'named_exact') || ($selector == 'named_partial')) {
$exceptiontype = $locator[0];
$exceptionlocator = $locator[1];
// Redirecting execution to the find method with the specified selector.
// It will detect if it's pointing to an unexisting named selector.
- return $this->find('named',
+ return $this->find('named_partial',
array(
$cleanname,
$this->getSession()->getSelectorsHandler()->xpathLiteral($arguments[0])
}
$this->getSession()->getDriver()->resizeWindow($width, $height);
}
+
+ /**
+ * Waits for all the JS to be loaded.
+ *
+ * @throws \Exception
+ * @throws NoSuchWindow
+ * @throws UnknownError
+ * @return bool True or false depending whether all the JS is loaded or not.
+ */
+ public function wait_for_pending_js() {
+ // Waiting for JS is only valid for JS scenarios.
+ if (!$this->running_javascript()) {
+ return;
+ }
+
+ // We don't use behat_base::spin() here as we don't want to end up with an exception
+ // if the page & JSs don't finish loading properly.
+ for ($i = 0; $i < self::EXTENDED_TIMEOUT * 10; $i++) {
+ $pending = '';
+ try {
+ $jscode = '
+ return function() {
+ if (typeof M === "undefined") {
+ if (document.readyState === "complete") {
+ return "";
+ } else {
+ return "incomplete";
+ }
+ } else if (' . self::PAGE_READY_JS . ') {
+ return "";
+ } else {
+ return M.util.pending_js.join(":");
+ }
+ }();';
+ $pending = $this->getSession()->evaluateScript($jscode);
+ } catch (NoSuchWindow $nsw) {
+ // We catch an exception here, in case we just closed the window we were interacting with.
+ // No javascript is running if there is no window right?
+ $pending = '';
+ } catch (UnknownError $e) {
+ // M is not defined when the window or the frame don't exist anymore.
+ if (strstr($e->getMessage(), 'M is not defined') != false) {
+ $pending = '';
+ }
+ }
+
+ // If there are no pending JS we stop waiting.
+ if ($pending === '') {
+ return true;
+ }
+
+ // 0.1 seconds.
+ usleep(100000);
+ }
+
+ // Timeout waiting for JS to complete. It will be catched and forwarded to behat_hooks::i_look_for_exceptions().
+ // It is unlikely that Javascript code of a page or an AJAX request needs more than self::EXTENDED_TIMEOUT seconds
+ // to be loaded, although when pages contains Javascript errors M.util.js_complete() can not be executed, so the
+ // number of JS pending code and JS completed code will not match and we will reach this point.
+ throw new \Exception('Javascript code and/or AJAX requests are not ready after ' . self::EXTENDED_TIMEOUT .
+ ' seconds. There is a Javascript error or the code is extremely slow.');
+ }
}
*/
class behat_config_manager {
+ /**
+ * @var bool Keep track of the automatic profile conversion. So we can notify user.
+ */
+ public static $autoprofileconversion = false;
+
/**
* Updates a config file
*
$CFG->behat_wwwroot = 'http://itwillnotbeused.com';
}
- $basedir = $CFG->dirroot . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'behat';
-
+ // Comments use black color, so failure path is not visible. Using color other then black/white is safer.
+ // https://github.com/Behat/Behat/pull/628.
$config = array(
'default' => array(
- 'paths' => array(
- 'features' => $basedir . DIRECTORY_SEPARATOR . 'features',
- 'bootstrap' => $basedir . DIRECTORY_SEPARATOR . 'features' . DIRECTORY_SEPARATOR . 'bootstrap',
+ 'formatters' => array(
+ 'moodle_progress' => array(
+ 'output_styles' => array(
+ 'comment' => array('magenta'))
+ )
),
- 'context' => array(
- 'class' => 'behat_init_context'
+ 'suites' => array(
+ 'default' => array(
+ 'paths' => $features,
+ 'contexts' => array_keys($stepsdefinitions)
+ )
),
'extensions' => array(
- 'Behat\MinkExtension\Extension' => array(
+ 'Behat\MinkExtension' => array(
'base_url' => $CFG->behat_wwwroot,
'goutte' => null,
'selenium2' => $selenium2wdhost
),
- 'Moodle\BehatExtension\Extension' => array(
- 'formatters' => array(
- 'moodle_progress' => 'Moodle\BehatExtension\Formatter\MoodleProgressFormatter',
- 'moodle_list' => 'Moodle\BehatExtension\Formatter\MoodleListFormatter',
- 'moodle_step_count' => 'Moodle\BehatExtension\Formatter\MoodleStepCountFormatter'
- ),
- 'features' => $features,
+ 'Moodle\BehatExtension' => array(
+ 'moodledirroot' => $CFG->dirroot,
'steps_definitions' => $stepsdefinitions
)
- ),
- 'formatter' => array(
- 'name' => 'moodle_progress'
)
)
);
// In case user defined overrides respect them over our default ones.
if (!empty($CFG->behat_config)) {
- $config = self::merge_config($config, $CFG->behat_config);
+ foreach ($CFG->behat_config as $profile => $values) {
+ $config = self::merge_config($config, self::merge_behat_config($profile, $values));
+ }
+ }
+ // Check for Moodle custom ones.
+ if (!empty($CFG->behat_profiles) && is_array($CFG->behat_profiles)) {
+ foreach ($CFG->behat_profiles as $profile => $values) {
+ $config = self::merge_config($config, self::get_behat_profile($profile, $values));
+ }
}
return Symfony\Component\Yaml\Yaml::dump($config, 10, 2);
}
+ /**
+ * Parse $CFG->behat_config and return the array with required config structure for behat.yml
+ *
+ * @param string $profile profile name
+ * @param array $values values for profile
+ * @return array
+ */
+ protected static function merge_behat_config($profile, $values) {
+ // Only add profile which are compatible with Behat 3.x
+ // Just check if any of Bheat 2.5 config is set. Not checking for 3.x as it might have some other configs
+ // Like : rerun_cache etc.
+ if (!isset($values['filters']['tags']) && !isset($values['extensions']['Behat\MinkExtension\Extension'])) {
+ return array($profile => $values);
+ }
+
+ // Parse 2.5 format and get related values.
+ $oldconfigvalues = array();
+ if (isset($values['extensions']['Behat\MinkExtension\Extension'])) {
+ $extensionvalues = $values['extensions']['Behat\MinkExtension\Extension'];
+ if (isset($extensionvalues['selenium2']['browser'])) {
+ $oldconfigvalues['browser'] = $extensionvalues['selenium2']['browser'];
+ }
+ if (isset($extensionvalues['selenium2']['wd_host'])) {
+ $oldconfigvalues['wd_host'] = $extensionvalues['selenium2']['wd_host'];
+ }
+ if (isset($extensionvalues['capabilities'])) {
+ $oldconfigvalues['capabilities'] = $extensionvalues['capabilities'];
+ }
+ }
+
+ if (isset($values['filters']['tags'])) {
+ $oldconfigvalues['tags'] = $values['filters']['tags'];
+ }
+
+ if (!empty($oldconfigvalues)) {
+ self::$autoprofileconversion = true;
+ return self::get_behat_profile($profile, $oldconfigvalues);
+ }
+
+ // If nothing set above then return empty array.
+ return array();
+ }
+
+ /**
+ * Parse $CFG->behat_profile and return the array with required config structure for behat.yml.
+ *
+ * $CFG->behat_profiles = array(
+ * 'profile' = array(
+ * 'browser' => 'firefox',
+ * 'tags' => '@javascript',
+ * 'wd_host' => 'http://127.0.0.1:4444/wd/hub',
+ * 'capabilities' => array(
+ * 'platform' => 'Linux',
+ * 'version' => 44
+ * )
+ * )
+ * );
+ *
+ * @param string $profile profile name
+ * @param array $values values for profile.
+ * @return array
+ */
+ protected static function get_behat_profile($profile, $values) {
+ // Values should be an array.
+ if (!is_array($values)) {
+ return array();
+ }
+
+ // Check suite values.
+ $behatprofilesuites = array();
+ // Fill tags information.
+ if (isset($values['tags'])) {
+ $behatprofilesuites = array(
+ 'suites' => array(
+ 'default' => array(
+ 'filters' => array(
+ 'tags' => $values['tags'],
+ )
+ )
+ )
+ );
+ }
+
+ // Selenium2 config values.
+ $behatprofileextension = array();
+ $seleniumconfig = array();
+ if (isset($values['browser'])) {
+ $seleniumconfig['browser'] = $values['browser'];
+ }
+ &nb