$this->find_link(get_string('savecomment'))->click();
// Delay after clicking so that additional comments will have unique time stamps.
// We delay 1 second which is all we need.
- $this->getSession()->wait(1000, false);
+ $this->getSession()->wait(1000);
} else {
);
// Wait for the animation to finish, in theory is just 1 sec, adding 4 just in case.
- $this->getSession()->wait(4 * 1000, false);
+ $this->getSession()->wait(4 * 1000);
}
}
//dtend is better than duration, because it works in Microsoft Outlook and works better in Korganizer
$ev->add_property('dtstart', Bennu::timestamp_to_datetime($event->timestart)); // when event starts.
$ev->add_property('dtend', Bennu::timestamp_to_datetime($event->timestart + $event->timeduration));
+ } else if ($event->timeduration == 0) {
+ // When no duration is present, the event is instantaneous event, ex - Due date of a module.
+ // Moodle doesn't support all day events yet. See MDL-56227.
+ $ev->add_property('dtstart', Bennu::timestamp_to_datetime($event->timestart));
+ $ev->add_property('dtend', Bennu::timestamp_to_datetime($event->timestart));
} else {
- // When no duration is present, ie an all day event, VALUE should be date instead of time and dtend = dtstart + 1 day.
- $ev->add_property('dtstart', Bennu::timestamp_to_date($event->timestart), array('value' => 'DATE')); // All day event.
- $ev->add_property('dtend', Bennu::timestamp_to_date($event->timestart + DAYSECS), array('value' => 'DATE')); // All day event.
+ // This can be used to represent all day events in future.
+ throw new coding_exception("Negative duration is not supported yet.");
}
if ($event->courseid != 0) {
$coursecontext = context_course::instance($event->courseid);
// Check to see if the event started at Midnight on the imported calendar.
date_default_timezone_set($timezone);
if (date('H:i:s', $eventrecord->timestart) === "00:00:00") {
- // This event should be an all day event.
+ // This event should be an all day event. This is not correct, we don't do anything differently for all day events.
+ // See MDL-56227.
$eventrecord->timeduration = 0;
}
\core_date::set_default_server_timezone();
<div class="col-xs-6 span6">
<label class="accesshide" for="modtype_{{id}}">{{#str}}select, core_completion{{/str}} {{formattedname}}</label>
<input id="modtype_{{id}}" type="checkbox" class="m-r-1" name="modids[]" value="{{id}}" aria-label="{{#str}}checkactivity, completion, {{formattedname}}{{/str}}">
- <img src="{{icon}}" alt=" " role="presentation" />
+ <img class="iconlarge activityicon" src="{{icon}}" alt=" " role="presentation" />
<span>{{formattedname}}</span>
</div>
<div class="activity-completionstatus col-xs-6 span6">
$node->click();
// Smooth expansion.
- $this->getSession()->wait(1000, false);
+ $this->getSession()->wait(1000);
}
/**
if (!this.categoriesinit) {
this.get('categorylisting').delegate('click', this.handleCategoryDelegation, 'a[data-action]', this);
this.get('categorylisting').delegate('click', this.handleCategoryDelegation, 'input[name="bcat[]"]', this);
- this.get('categorylisting').delegate('click', this.handleBulkSortByaction, '#menuselectsortby', this);
+ this.get('categorylisting').delegate('change', this.handleBulkSortByaction, '#menuselectsortby', this);
this.categoriesinit = true;
Y.log(count + ' categories being managed', 'info', 'moodle-course-management');
} else {
'captype' => 'read',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
- 'student' => CAP_ALLOW,
- 'manager' => CAP_ALLOW
+ 'user' => CAP_ALLOW
)
)
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Grade overview report upgrade steps.
+ *
+ * @package gradereport_overview
+ * @copyright 2017 Simey Lameze <simey@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Function to upgrade grade overview report.
+ *
+ * @param int $oldversion the version we are upgrading from
+ * @return bool result
+ */
+function xmldb_gradereport_overview_upgrade($oldversion) {
+
+ if ($oldversion < 2017051501) {
+ $context = context_system::instance();
+ $capability = 'gradereport/overview:view';
+
+ // Now allow authenticated user role to access that report.
+ $authenticateduserroles = get_archetype_roles('user');
+ foreach ($authenticateduserroles as $roleid => $notused) {
+ assign_capability($capability, CAP_ALLOW, $roleid, $context->id);
+ }
+
+ upgrade_plugin_savepoint(true, 2017051501, 'gradereport', 'overview');
+ }
+ return true;
+}
$usercontext = context_user::instance($user->id);
$coursecontext = context_course::instance($course->id);
if (grade_report_overview::check_access($systemcontext, $coursecontext, $usercontext, $course, $user->id)) {
- $url = new moodle_url('/grade/report/overview/index.php', array('userid' => $user->id));
+ $url = new moodle_url('/grade/report/overview/index.php', array('userid' => $user->id, 'id' => $course->id));
$node = new core_user\output\myprofile\node('reports', 'grades', get_string('gradesoverview', 'gradereport_overview'),
null, $url);
$tree->add_node($node);
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017051500; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version = 2017051501; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2017050500; // Requires this Moodle version
$plugin->component = 'gradereport_overview'; // Full name of the plugin (used for diagnostics)
$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);
+ $driver = $this->getSession()->getDriver();
+ if ($driver instanceof \Moodle\BehatExtension\Driver\MoodleSelenium2Driver) {
+ $script = "Syn.trigger('change', {}, {{ELEMENT}})";
+ $driver->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.
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
-use Behat\Mink\Exception\ExpectationException as ExpectationException,
+use Behat\Mink\Exception\DriverException,
+ Behat\Mink\Exception\ExpectationException as ExpectationException,
Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
Behat\Mink\Element\NodeElement as NodeElement;
if (!$exception) {
$exception = $e;
}
- // We wait until no exception is thrown or timeout expires.
- continue;
}
if ($this->running_javascript()) {
$pending = '';
try {
$jscode = '
- return function() {
+ return (function() {
if (typeof M === "undefined") {
if (document.readyState === "complete") {
return "";
} else {
return "incomplete"
}
- }();';
+ }());';
$pending = $this->getSession()->evaluateScript($jscode);
} catch (NoSuchWindow $nsw) {
// We catch an exception here, in case we just closed the window we were interacting with.
} catch (NoSuchWindow $e) {
// If we were interacting with a popup window it will not exists after closing it.
+ } catch (DriverException $e) {
+ // Same reason as above.
}
}
}
$this->ensure_node_is_visible($node); // Ensures hidden elements can't be clicked.
$xpath = $node->getXpath();
- $script = "Syn.click({{ELEMENT}})";
- $this->getSession()->getDriver()->triggerSynScript($xpath, $script);
+ $driver = $this->getSession()->getDriver();
+ if ($driver instanceof \Moodle\BehatExtension\Driver\MoodleSelenium2Driver) {
+ $script = "Syn.click({{ELEMENT}})";
+ $driver->triggerSynScript($xpath, $script);
+ } else {
+ $driver->click($xpath);
+ }
}
}
* Trigger on change event.
*/
protected function trigger_on_change() {
- $this->session->getDriver()->triggerSynScript(
- $this->field->getXPath(),
- "Syn.trigger('change', {}, {{ELEMENT}})"
- );
+ $driver = $this->session->getDriver();
+ if ($driver instanceof \Moodle\BehatExtension\Driver\MoodleSelenium2Driver) {
+ $driver->triggerSynScript(
+ $this->field->getXPath(),
+ "Syn.trigger('change', {}, {{ELEMENT}})"
+ );
+ }
}
}
// If the JS handler attached to keydown or keypress destroys the element
// the later events may trigger errors because form element no longer exist
// or is not visible. Ignore such exceptions here.
+ } catch (\Behat\Mink\Exception\ElementNotFoundException $e) {
+ // Other Mink drivers can throw this for the same reason as above.
}
}
if (!$node = $this->session->getDriver()->find($dialoguexpath)) {
$script = "Syn.trigger('change', {}, {{ELEMENT}})";
try {
- $this->session->getDriver()->triggerSynScript($this->field->getXpath(), $script);
- $this->session->getDriver()->click('//body//div[@class="skiplinks"]');
+ $driver = $this->session->getDriver();
+ if ($driver instanceof \Moodle\BehatExtension\Driver\MoodleSelenium2Driver) {
+ $driver->triggerSynScript($this->field->getXpath(), $script);
+ }
+ $driver->click('//body//div[@class="skiplinks"]');
} catch (\Exception $e) {
return;
}
// DTEND must be later than DTSTART
// The standard is not clear on how to hande different value types though
// TODO: handle this correctly even if the value types are different
- if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
+ if($this->properties['DTEND'][0]->value < $this->properties['DTSTART'][0]->value) {
return false;
}
5/ updated DTEND;TZID and DTSTAR;TZID values to support quotations (7 Nov 2014)
6/ MDL-49032: fixed rfc2445_fold() to fix incorrect RFC2445_WSP definition (16 Sep 2015)
7/ added timestamp_to_date function to support zero duration events (16 Sept 2015)
+8/ Updated \iCalendar_event::invariant_holds() to allow for same dtstart and dtend timestamps (13 July 2017)
*/
protected static function guess_image($issuer) {
if (empty($issuer->get('image'))) {
- $baseurl = parse_url($issuer->get('discoveryurl'));
+ $baseurl = parse_url($issuer->get('baseurl'));
$imageurl = $baseurl['scheme'] . '://' . $baseurl['host'] . '/favicon.ico';
$issuer->set('image', $imageurl);
$issuer->update();
return $dir_info;
}
+ /**
+ * Add new file record to database and handle callbacks.
+ *
+ * @param stdClass $newrecord
+ */
+ protected function create_file($newrecord) {
+ global $DB;
+ $newrecord->id = $DB->insert_record('files', $newrecord);
+
+ if ($newrecord->filename !== '.') {
+ // Callback for file created.
+ if ($pluginsfunction = get_plugins_with_function('after_file_created')) {
+ foreach ($pluginsfunction as $plugintype => $plugins) {
+ foreach ($plugins as $pluginfunction) {
+ $pluginfunction($newrecord);
+ }
+ }
+ }
+ }
+ }
+
/**
* Add new local file based on existing local file.
*
}
try {
- $newrecord->id = $DB->insert_record('files', $newrecord);
+ $this->create_file($newrecord);
} catch (dml_exception $e) {
throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid,
$newrecord->filepath, $newrecord->filename, $e->debuginfo);
$newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
try {
- $newrecord->id = $DB->insert_record('files', $newrecord);
+ $this->create_file($newrecord);
} catch (dml_exception $e) {
if ($newfile) {
$this->move_to_trash($newrecord->contenthash);
$newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
try {
- $newrecord->id = $DB->insert_record('files', $newrecord);
+ $this->create_file($newrecord);
} catch (dml_exception $e) {
if ($newfile) {
$this->move_to_trash($newrecord->contenthash);
global $DB;
$updatereferencesneeded = false;
$keys = array_keys((array)$this->file_record);
+ $filepreupdate = clone($this->file_record);
foreach ($dataobject as $field => $value) {
if (in_array($field, $keys)) {
if ($field == 'contextid' and (!is_number($value) or $value < 1)) {
// Either filesize or contenthash of this file have changed. Update all files that reference to it.
$this->fs->update_references_to_storedfile($this);
}
+
+ // Callback for file update.
+ if (!$this->is_directory()) {
+ if ($pluginsfunction = get_plugins_with_function('after_file_updated')) {
+ foreach ($pluginsfunction as $plugintype => $plugins) {
+ foreach ($plugins as $pluginfunction) {
+ $pluginfunction($this->file_record, $filepreupdate);
+ }
+ }
+ }
+ }
}
/**
$DB->delete_records('files', array('id'=>$this->file_record->id));
$transaction->allow_commit();
+
+ if (!$this->is_directory()) {
+ // Callback for file deletion.
+ if ($pluginsfunction = get_plugins_with_function('after_file_deleted')) {
+ foreach ($pluginsfunction as $plugintype => $plugins) {
+ foreach ($plugins as $pluginfunction) {
+ $pluginfunction($this->file_record);
+ }
+ }
+ }
+ }
}
// Move pool file to trash if content not needed any more.
* @return int
*/
public function set_sortorder($sortorder) {
+ $oldorder = $this->file_record->sortorder;
$filerecord = new stdClass;
$filerecord->sortorder = $sortorder;
$this->update($filerecord);
+ if (!$this->is_directory()) {
+ // Callback for file sort order change.
+ if ($pluginsfunction = get_plugins_with_function('after_file_sorted')) {
+ foreach ($pluginsfunction as $plugintype => $plugins) {
+ foreach ($plugins as $pluginfunction) {
+ $pluginfunction($this->file_record, $oldorder, $sortorder);
+ }
+ }
+ }
+ }
}
/**
return;
}
- // Funny thing about this, with findAll() we specify a pattern and each element matching the pattern is added to the array
- // with of xpaths with a [0], [1]... sufix, but when we click on an element it does not matches the specified xpath
- // anymore (now is a "Show less..." link) so [1] becomes [0], that's why we always click on the first XPath match,
- // will be always the next one.
- $iterations = count($showmores);
- for ($i = 0; $i < $iterations; $i++) {
- $showmores[0]->click();
+ if ($this->getSession()->getDriver() instanceof \DMore\ChromeDriver\ChromeDriver) {
+ // Chrome Driver produces unique xpaths for each element.
+ foreach ($showmores as $showmore) {
+ $showmore->click();
+ }
+ } else {
+ // Funny thing about this, with findAll() we specify a pattern and each element matching the pattern
+ // is added to the array with of xpaths with a [0], [1]... sufix, but when we click on an element it
+ // does not matches the specified xpath anymore (now is a "Show less..." link) so [1] becomes [0],
+ // that's why we always click on the first XPath match, will be always the next one.
+ $iterations = count($showmores);
+ for ($i = 0; $i < $iterations; $i++) {
+ $showmores[0]->click();
+ }
}
} catch (ElementNotFoundException $e) {
// Wait until the URL change is executed.
if ($this->running_javascript()) {
- $this->getSession()->wait($waittime * 1000, false);
+ $this->getSession()->wait($waittime * 1000);
} else if (!empty($url)) {
// We redirect directly as we can not wait for an automatic redirection.
*/
public function i_wait_seconds($seconds) {
if ($this->running_javascript()) {
- $this->getSession()->wait($seconds * 1000, false);
+ $this->getSession()->wait($seconds * 1000);
} else {
sleep($seconds);
}
} catch (WebDriver\Exception\NoSuchElement $e) {
// Do nothing just return, as element is no more on page.
return true;
+ } catch (ElementNotFoundException $e) {
+ // Do nothing just return, as element is no more on page.
+ return true;
}
}
}
// Gets the node based on the requested selector type and locator.
$node = $this->get_selected_node($selectortype, $element);
- $this->getSession()->getDriver()->post_key("\xEE\x80\x84", $node->getXpath());
+ $driver = $this->getSession()->getDriver();
+ if ($driver instanceof \Moodle\BehatExtension\Driver\MoodleSelenium2Driver) {
+ $driver->post_key("\xEE\x80\x84", $node->getXpath());
+ } else {
+ $driver->keyDown($node->getXpath(), "\t");
+ }
}
/**
id: message.userid,
});
- var promise = Templates.render('message_popup/message_content_item', message)
- .then(function(html, js) {
- container.append(html);
- Templates.runTemplateJS(js);
- return;
- });
+ var promise = Templates.render('message_popup/message_content_item', message);
promises.push(promise);
}.bind(this));
- return $.when.apply($, promises);
+ return $.when.apply($, promises).then(function() {
+ // Each of the promises in the when will pass its results as an argument to the function.
+ // The order of the arguments will be the order that the promises are passed to when()
+ // i.e. the first promise's results will be in the first argument.
+ $.each(arguments, function(index, argument) {
+ // The promises will return an array containing two values.
+ // The first value is the html that should be attached to the page.
+ // The second will be any JavaScript that needs to be run.
+ container.append(argument[0]);
+ Templates.runTemplateJS(argument[1]);
+ });
+ return;
+ });
};
/**
var promise = Templates.render(TEMPLATES.NOTIFICATION, notification)
.then(function(html, js) {
- container.append(html);
- Templates.runTemplateJS(js);
// Restore it for the cache.
notification.contexturl = contextUrl;
this.setCacheNotification(notification);
- return;
+ // Pass the Rendered content out.
+ return [html, js];
}.bind(this));
promises.push(promise);
}.bind(this));
- return $.when.apply($, promises);
+ return $.when.apply($, promises).then(function() {
+ // Each of the promises in the when will pass its results as an argument to the function.
+ // The order of the arguments will be the order that the promises are passed to when()
+ // i.e. the first promise's results will be in the first argument.
+ $.each(arguments, function(index, argument) {
+ // The promises will return an array containing two values.
+ // The first value is the html that should be attached to the page.
+ // The second will be any JavaScript that needs to be run.
+ container.append(argument[0]);
+ Templates.runTemplateJS(argument[1]);
+ });
+ return;
+ });
};
/**
offset: offset,
});
- var promise = Templates.render('message_popup/notification_content_item', notification)
- .then(function(html, js) {
- container.append(html);
- Templates.runTemplateJS(js);
- return;
- });
+ var promise = Templates.render('message_popup/notification_content_item', notification);
promises.push(promise);
}.bind(this));
- return $.when.apply($, promises);
+ return $.when.apply($, promises).then(function() {
+ // Each of the promises in the when will pass its results as an argument to the function.
+ // The order of the arguments will be the order that the promises are passed to when()
+ // i.e. the first promise's results will be in the first argument.
+ $.each(arguments, function(index, argument) {
+ // The promises will return an array containing two values.
+ // The first value is the html that should be attached to the page.
+ // The second will be any JavaScript that needs to be run.
+ container.append(argument[0]);
+ Templates.runTemplateJS(argument[1]);
+ });
+ return;
+ });
};
/**
$uniqueid = 0;
if ($submission->blindmarking && !$submission->revealidentities) {
if (empty($submission->recordid)) {
- $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id);
+ $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $grader->id);
} else {
$uniqueid = $submission->recordid;
}
$url = new moodle_url('/mod/lesson/view.php', array('id'=>$cm->id, 'pageid'=>$result->newpageid));
if ($lesson->review && !$result->correctanswer && !$result->noanswer && !$result->isessayquestion && !$result->maxattemptsreached) {
- // When the answer is wrong - the result->newpageid points back to the current question.
- $newpageid = $lesson->calculate_new_page_on_jump($page, $page->nextpageid);
- $url = new moodle_url('/mod/lesson/view.php', array('id'=>$cm->id, 'pageid'=>$newpageid));
+ // Button to continue the lesson (the page to go is configured by the teacher).
echo $OUTPUT->single_button($url, get_string('reviewquestioncontinue', 'lesson'));
} else {
// Normal continue button
// Automatically generated Moodle v3.3.0 release upgrade line.
// Put any upgrade step following this.
+ if ($oldversion < 2017051501) {
+
+ // Delete orphaned lesson answer and response files.
+ $sql = "SELECT DISTINCT f.*
+ FROM {files} f
+ LEFT JOIN {lesson_answers} la ON f.itemid = la.id
+ WHERE component = :component
+ AND la.id IS NULL";
+
+ $orphanedfiles = $DB->get_recordset_sql($sql, array('component' => 'mod_lesson'));
+ $fs = get_file_storage();
+ foreach ($orphanedfiles as $file) {
+ $fs->delete_area_files($file->contextid, $file->component, $file->filearea, $file->itemid);
+ }
+ $orphanedfiles->close();
+
+ upgrade_mod_savepoint(true, 2017051501, 'lesson');
+ }
+
return true;
}
$DB->delete_records("lesson_attempts", array("pageid" => $this->properties->id));
$DB->delete_records("lesson_branch", array("pageid" => $this->properties->id));
+
+ // Delete files related to answers and responses.
+ if ($answers = $DB->get_records("lesson_answers", array("pageid" => $this->properties->id))) {
+ foreach ($answers as $answer) {
+ $fs->delete_area_files($context->id, 'mod_lesson', 'page_answers', $answer->id);
+ $fs->delete_area_files($context->id, 'mod_lesson', 'page_responses', $answer->id);
+ }
+ }
+
// ...now delete the answers...
$DB->delete_records("lesson_answers", array("pageid" => $this->properties->id));
// ..and the page itself
// Delete files associated with this page.
$fs->delete_area_files($context->id, 'mod_lesson', 'page_contents', $this->properties->id);
- $fs->delete_area_files($context->id, 'mod_lesson', 'page_answers', $this->properties->id);
- $fs->delete_area_files($context->id, 'mod_lesson', 'page_responses', $this->properties->id);
// repair the hole in the linkage
if (!$this->properties->prevpageid && !$this->properties->nextpageid) {
When I press "Submit"
Then I should see "Maximum number of attempts reached - Moving to next page"
- Scenario: Check that we can move past a question we don't want to re-attempt
- Given I log in as "teacher1"
- And I am on "Course 1" course homepage
- And I follow "Test lesson name"
- And I navigate to "Edit settings" in current page administration
- And I expand all fieldsets
- And I set the field "Provide option to try a question again" to "Yes"
- And I set the field "Maximum number of attempts" to "3"
- And I press "Save and display"
- And I log out
- And I log in as "student1"
- And I am on "Course 1" course homepage
- And I follow "Test lesson name"
- And I should see "First page contents"
- And I press "Next page"
- And I should see "The earth is round"
- And I set the following fields to these values:
- | False| 1 |
- And I press "Submit"
- And I should see "Wrong"
- When I press "No, I just want to go on to the next question"
- Then I should not see "The earth is round"
- And I should see "Kermit is a frog"
- And I set the following fields to these values:
- | False | 1 |
- And I press "Submit"
- And I should see "Wrong"
- And I press "Yes, I'd like to try again"
- And I should see "Kermit is a frog"
- And I set the following fields to these values:
- | False | 1 |
- And I press "Submit"
- And I should see "Wrong"
- And I press "Yes, I'd like to try again"
- And I should see "Kermit is a frog"
- And I set the following fields to these values:
- | False | 1 |
- And I press "Submit"
- And I should not see "Yes, I'd like to try again"
- And I should see "Continue"
-
@javascript @_bug_phantomjs
Scenario: Check that we can not click back on the browser at the last quiz result page and re-attempt the last question to get full marks
Given I log in as "student1"
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2017051500; // The current module version (Date: YYYYMMDDXX)
+$plugin->version = 2017051501; // The current module version (Date: YYYYMMDDXX)
$plugin->requires = 2017050500; // Requires this Moodle version
$plugin->component = 'mod_lesson'; // Full name of the plugin (used for diagnostics)
$plugin->cron = 0;
}
$followingslotnumber = $moveafterslotnumber + 1;
- if ($followingslotnumber == $movingslotnumber) {
+ // Prevent checking against non-existance slot when already at the last slot.
+ if ($followingslotnumber == $movingslotnumber && !$this->is_last_slot_in_quiz($followingslotnumber)) {
$followingslotnumber += 1;
}
), $structure);
}
+ public function test_move_last_slot_to_previous_page_emptying_the_last_page() {
+ $quizobj = $this->create_test_quiz(array(
+ array('TF1', 1, 'truefalse'),
+ array('TF2', 2, 'truefalse'),
+ ));
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+
+ $idtomove = $structure->get_question_in_slot(2)->slotid;
+ $idmoveafter = $structure->get_question_in_slot(1)->slotid;
+ $structure->move_slot($idtomove, $idmoveafter, '1');
+
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assert_quiz_layout(array(
+ array('TF1', 1, 'truefalse'),
+ array('TF2', 1, 'truefalse'),
+ ), $structure);
+ }
+
public function test_end_of_one_section_to_start_of_next() {
$quizobj = $this->create_test_quiz(array(
array('TF1', 1, 'truefalse'),