Merge branch 'master' into install_master

This commit is contained in:
AMOS bot
2014-04-23 00:37:55 +00:00
90 changed files with 875 additions and 387 deletions
-11
View File
@@ -61,15 +61,4 @@ trait reader {
}
return $this->store;
}
/**
* If the current user can access current store or not.
*
* @param \context $context
*
* @return bool
*/
public function can_access(\context $context) {
return has_capability('logstore/' . $this->store . ':read', $context);
}
}
-38
View File
@@ -1,38 +0,0 @@
<?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/>.
/**
* Defines the capabilities used by standard log store.
*
* @package logstore_legacy
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
'logstore/legacy:read' => array(
'riskbitmask' => RISK_PERSONAL,
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'manager' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
),
),
);
@@ -23,7 +23,6 @@
*/
$string['event_legacy_logged'] = 'Legacy event logged';
$string['legacy:read'] = 'Read logs';
$string['loglegacy'] = 'Log legacy data';
$string['loglegacy_help'] = 'This plugin records log data to the legacy log table (mdl_log). This functionality has been replaced by newer, richer and more efficient logging plugins, so you should only run this plugin if you have old custom reports that directly query the old log table. Writing to the legacy logs will increase load, so it is recommended that you disable this plugin for performance reasons when it is not needed.';
$string['pluginname'] = 'Legacy log';
+1 -1
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2014031300; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2014041700; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2014031200; // Requires this Moodle version.
$plugin->component = 'logstore_legacy'; // Full name of the plugin (used for diagnostics).
@@ -1,38 +0,0 @@
<?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/>.
/**
* Defines the capabilities used by standard log store.
*
* @package logstore_standard
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
'logstore/standard:read' => array(
'riskbitmask' => RISK_PERSONAL,
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'manager' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
),
),
);
@@ -25,5 +25,4 @@
$string['buffersize'] = 'Write buffer size';
$string['pluginname'] = 'Standard log';
$string['pluginname_desc'] = 'A log plugin stores log entries in a Moodle database table.';
$string['standard:read'] = 'Read logs';
$string['taskcleanup'] = 'Log table cleanup';
+1 -1
View File
@@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2014041500; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2014041700; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2014031200; // Requires this Moodle version.
$plugin->component = 'logstore_standard'; // Full name of the plugin (used for diagnostics).
+2
View File
@@ -4,7 +4,9 @@
require('../../config.php');
$context = context_system::instance();
$PAGE->set_url('/auth/shibboleth/index.php');
$PAGE->set_context($context);
// Support for WAYFless URLs.
$target = optional_param('target', '', PARAM_LOCALURL);
+1 -1
View File
@@ -172,7 +172,7 @@ abstract class frontend {
$decoded = json_decode($data['availabilityconditionsjson']);
if (!$decoded) {
// This shouldn't be possible.
throw new coding_exception('Invalid JSON from availabilityconditionsjson field');
throw new \coding_exception('Invalid JSON from availabilityconditionsjson field');
}
if (!empty($decoded->errors)) {
$error = '';
+29 -10
View File
@@ -193,12 +193,7 @@ abstract class info {
$tree = $this->get_availability_tree();
$result = $tree->check_available(false, $this, $grabthelot, $userid);
} catch (\coding_exception $e) {
// We catch the message because it causes fatal problems in most of
// the GUI if this exception gets thrown (you can't edit the
// activity to fix it). Obviously it should never happen anyway, but
// just in case.
debugging('Error processing availability data for &lsquo;' .
$this->get_thing_name() . '&rsquo;: ' . s($e->a), DEBUG_DEVELOPER);
$this->warn_about_invalid_availability($e);
$this->modinfo = null;
return false;
}
@@ -237,7 +232,12 @@ abstract class info {
if (is_null($this->availability)) {
return true;
} else {
return $this->get_availability_tree()->is_available_for_all();
try {
return $this->get_availability_tree()->is_available_for_all();
} catch (\coding_exception $e) {
$this->warn_about_invalid_availability($e);
return false;
}
}
}
@@ -273,13 +273,32 @@ abstract class info {
$this->modinfo = null;
return $result;
} catch (\coding_exception $e) {
// Again we catch the message to avoid problems in GUI.
debugging('Error processing availability data for &lsquo;' .
$this->get_thing_name() . '&rsquo;: ' . s($e->a), DEBUG_DEVELOPER);
$this->warn_about_invalid_availability($e);
return false;
}
}
/**
* In some places we catch coding_exception because if a bug happens, it
* would be fatal for the course page GUI; instead we just show a developer
* debug message.
*
* @param \coding_exception $e Exception that occurred
*/
protected function warn_about_invalid_availability(\coding_exception $e) {
$name = $this->get_thing_name();
// If it occurs while building modinfo based on somebody calling $cm->name,
// we can't get $cm->name, and this line will cause a warning.
$htmlname = @$this->format_info($name, $this->course);
if ($htmlname === '') {
// So instead use the numbers (cmid) from the tag.
$htmlname = preg_replace('~[^0-9]~', '', $name);
}
$info = 'Error processing availability data for &lsquo;' . $htmlname
. '&rsquo;: ' . s($e->a);
debugging($info, DEBUG_DEVELOPER);
}
/**
* Called during restore (near end of restore). Updates any necessary ids
* and writes the updated tree to the database. May output warnings if
@@ -79,9 +79,11 @@ class frontend extends \core_availability\frontend {
\section_info $section = null) {
global $CFG;
// Group option can be used on sections, and on modules but only
// if groupmembersonly is turned off. (To avoid confusion.)
if (!is_null($cm) && $CFG->enablegroupmembersonly) {
// If groupmembersonly is turned on, then you can only add group
// restrictions on sections (which don't use groupmembersonly) and
// not on modules. This is to avoid confusion - otherwise
// there would be two ways to add restrictions based on groups.
if (is_null($section) && $CFG->enablegroupmembersonly) {
return false;
}
@@ -74,9 +74,11 @@ class frontend extends \core_availability\frontend {
\section_info $section = null) {
global $CFG, $DB;
// Grouping option can be used on sections, and on modules but only
// if groupmembersonly is turned off. (To avoid confusion.)
if (!is_null($cm) && $CFG->enablegroupmembersonly) {
// If groupmembersonly is turned on, then you can only add group
// restrictions on sections (which don't use groupmembersonly) and
// not on modules. This is to avoid confusion - otherwise
// there would be two ways to add restrictions based on groups.
if (is_null($section) && $CFG->enablegroupmembersonly) {
return false;
}
@@ -123,6 +123,22 @@ Feature: edit_availability
When I click on ".availability-item .availability-delete img" "css_element"
Then I should not see "or" in the "Restrict access" "fieldset"
# Add a nested restriction set with two dates so there will be inner connector.
When I click on "Add restriction..." "button"
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
And I click on "Add restriction..." "button" in the ".availability-children .availability-list" "css_element"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I click on "Add restriction..." "button" in the ".availability-children .availability-list" "css_element"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
Then I should see "and" in the ".availability-children .availability-list .availability-connector" "css_element"
# Check changing the outer one does not affect the inner one.
When I set the field "Required restrictions" to "all"
Then I should not see "or" in the "Restrict access" "fieldset"
When I set the field "Required restrictions" to "any"
Then I should see "or" in the "Restrict access" "fieldset"
And I should not see "or" in the ".availability-children .availability-list .availability-connector" "css_element"
@javascript
Scenario: Edit availability using settings in section form
# Set up.
@@ -96,12 +96,16 @@ M.core_availability.form = {
// Get top-level tree as JSON.
var value = this.field.get('value');
if (value === '') {
this.rootList = new M.core_availability.List(null, true);
} else {
var data = Y.JSON.parse(value);
this.rootList = new M.core_availability.List(data, true);
var data = null;
if (value !== '') {
try {
data = Y.JSON.parse(value);
} catch(x) {
// If the JSON data is not valid, treat it as empty.
this.field.set('value', '');
}
}
this.rootList = new M.core_availability.List(data, true);
this.mainDiv.appendChild(this.rootList.node);
// Update JSON value after loading (to reflect any changes that need
@@ -509,7 +513,7 @@ M.core_availability.List.prototype.updateHtml = function() {
} else {
connectorText = M.str.availability.or;
}
this.inner.all('.availability-connector span.label').each(function(span) {
this.inner.all('> .availability-children > .availability-connector span.label').each(function(span) {
span.set('innerHTML', connectorText);
});
};
File diff suppressed because one or more lines are too long
@@ -96,12 +96,16 @@ M.core_availability.form = {
// Get top-level tree as JSON.
var value = this.field.get('value');
if (value === '') {
this.rootList = new M.core_availability.List(null, true);
} else {
var data = Y.JSON.parse(value);
this.rootList = new M.core_availability.List(data, true);
var data = null;
if (value !== '') {
try {
data = Y.JSON.parse(value);
} catch(x) {
// If the JSON data is not valid, treat it as empty.
this.field.set('value', '');
}
}
this.rootList = new M.core_availability.List(data, true);
this.mainDiv.appendChild(this.rootList.node);
// Update JSON value after loading (to reflect any changes that need
@@ -509,7 +513,7 @@ M.core_availability.List.prototype.updateHtml = function() {
} else {
connectorText = M.str.availability.or;
}
this.inner.all('.availability-connector span.label').each(function(span) {
this.inner.all('> .availability-children > .availability-connector span.label').each(function(span) {
span.set('innerHTML', connectorText);
});
};
+10 -6
View File
@@ -94,12 +94,16 @@ M.core_availability.form = {
// Get top-level tree as JSON.
var value = this.field.get('value');
if (value === '') {
this.rootList = new M.core_availability.List(null, true);
} else {
var data = Y.JSON.parse(value);
this.rootList = new M.core_availability.List(data, true);
var data = null;
if (value !== '') {
try {
data = Y.JSON.parse(value);
} catch(x) {
// If the JSON data is not valid, treat it as empty.
this.field.set('value', '');
}
}
this.rootList = new M.core_availability.List(data, true);
this.mainDiv.appendChild(this.rootList.node);
// Update JSON value after loading (to reflect any changes that need
@@ -507,7 +511,7 @@ M.core_availability.List.prototype.updateHtml = function() {
} else {
connectorText = M.str.availability.or;
}
this.inner.all('.availability-connector span.label').each(function(span) {
this.inner.all('> .availability-children > .availability-connector span.label').each(function(span) {
span.set('innerHTML', connectorText);
});
};
+4 -7
View File
@@ -379,13 +379,7 @@ class backup_section_structure_step extends backup_structure_step {
$section->add_child($formatoptions);
// Define sources.
// The 'availability' field needs to be renamed because it clashes with
// the old nested element structure for availability data.
$section->set_source_sql("
SELECT *, availability AS availabilityjson
FROM {course_sections} WHERE id = ?",
array('id' => backup::VAR_SECTIONID));
$section->set_source_table('course_sections', array('id' => backup::VAR_SECTIONID));
$formatoptions->set_source_sql('SELECT cfo.id, cfo.format, cfo.name, cfo.value
FROM {course} c
JOIN {course_format_options} cfo
@@ -395,6 +389,9 @@ class backup_section_structure_step extends backup_structure_step {
// Aliases
$section->set_source_alias('section', 'number');
// The 'availability' field needs to be renamed because it clashes with
// the old nested element structure for availability data.
$section->set_source_alias('availability', 'availabilityjson');
// Set annotations
$section->annotate_files('course', 'section', 'id');
+4 -4
View File
@@ -221,11 +221,11 @@ class core_backup_renderer extends plugin_renderer_base {
*/
public function backup_details_unknown(moodle_url $nextstageurl) {
$html = html_writer::start_tag('div', array('class' => 'unknownformat'));
$html .= $this->output->heading(get_string('errorinvalidformat', 'backup'), 2, 'notifyproblem');
$html .= html_writer::tag('div', get_string('errorinvalidformatinfo', 'backup'), array('class' => 'notifyproblem'));
$html = html_writer::start_div('unknownformat');
$html .= $this->output->heading(get_string('errorinvalidformat', 'backup'), 2);
$html .= $this->output->notification(get_string('errorinvalidformatinfo', 'backup'), 'notifyproblem');
$html .= $this->output->single_button($nextstageurl, get_string('continue'), 'post');
$html .= html_writer::end_tag('div');
$html .= html_writer::end_div();
return $html;
}
+2 -1
View File
@@ -50,7 +50,8 @@ class block_mentees extends block_base {
$this->content = new stdClass();
// get all the mentees, i.e. users you have a direct assignment to
if ($usercontexts = $DB->get_records_sql("SELECT c.instanceid, c.instanceid, u.firstname, u.lastname
$allusernames = get_all_user_name_fields(true, 'u');
if ($usercontexts = $DB->get_records_sql("SELECT c.instanceid, c.instanceid, $allusernames
FROM {role_assignments} ra, {context} c, {user} u
WHERE ra.userid = ?
AND ra.contextid = c.id
+7 -1
View File
@@ -105,7 +105,13 @@ if (!in_array($mode, $modes)) {
$mode = reset($modes);
}
add_to_log($course->id, "course", "user report", "user.php?id=$course->id&amp;user=$user->id&amp;mode=$mode", "$user->id");
$eventdata = array(
'context' => $coursecontext,
'relateduserid' => $user->id,
'other' => array('mode' => $mode),
);
$event = \core\event\course_user_report_viewed::create($eventdata);
$event->trigger();
$stractivityreport = get_string("activityreport");
+8 -7
View File
@@ -94,11 +94,7 @@
require_once($CFG->dirroot.'/calendar/lib.php'); /// This is after login because it needs $USER
$logparam = 'id='. $course->id;
$loglabel = 'view';
$infoid = $course->id;
if ($section and $section > 0) {
$loglabel = 'view section';
// Get section details and check it exists.
$modinfo = get_fast_modinfo($course);
@@ -111,10 +107,7 @@
// correct error message shown.
require_capability('moodle/course:viewhiddensections', $context);
}
$infoid = $coursesections->id;
$logparam .= '&sectionid='. $infoid;
}
add_to_log($course->id, 'course', $loglabel, "view.php?". $logparam, $infoid);
// Fix course format if it is no longer installed
$course->format = course_get_format($course)->get_format();
@@ -288,6 +281,14 @@
echo html_writer::end_tag('div');
// Trigger course viewed event.
$eventdata = array('context' => $context);
if (!empty($section)) {
$eventdata['other'] = array('coursesectionid' => $section);
}
$event = \core\event\course_viewed::create($eventdata);
$event->trigger();
// Include course AJAX
include_course_ajax($course, $modnamesused);
+1 -1
View File
@@ -82,7 +82,7 @@ function import_xml_grades($text, $course, &$error) {
$newgrade->importer = $USER->id;
// check grade value exists and is a numeric grade
if (isset($result['#']['score'][0]['#'])) {
if (isset($result['#']['score'][0]['#']) && $result['#']['score'][0]['#'] !== '-') {
if (is_numeric($result['#']['score'][0]['#'])) {
$newgrade->finalgrade = $result['#']['score'][0]['#'];
} else {
+3 -3
View File
@@ -75,9 +75,9 @@
}
}
if (isloggedin()) {
add_to_log(SITEID, 'course', 'view', 'view.php?id='.SITEID, SITEID);
}
$eventparams = array('context' => context_course::instance(SITEID));
$event = \core\event\course_viewed::create($eventparams);
$event->trigger();
/// If the hub plugin is installed then we let it take over the homepage here
if (file_exists($CFG->dirroot.'/local/hub/lib.php') and get_config('local_hub', 'hubenabled')) {
+3 -3
View File
@@ -68,15 +68,15 @@ $string['awards'] = 'Recipients';
$string['backpackavailability'] = 'External badge verification';
$string['backpackavailability_help'] = 'For badge recipients to be able to prove they earned their badges from you, an external backpack service should be able to access your site and verify badges issued from it. Your site does not currently appear to be accessible, which means that badges you have already issued or will issue in the future cannot be verified.
##Why am I seeing this message?
**Why am I seeing this message?**
It may be that your firewall prevents access from users outside your network, your site is password protected, or you are running the site on a computer that is not available from the Internet (such as a local development machine).
##Is this a problem?
**Is this a problem?**
You should fix this issue on any production site where you are planning to issue badges, otherwise the recipients will not be able to prove they earned their badges from you. If your site is not yet live you can create and issue test badges, as long as the site is accessible before you go live.
##What if I can\'t make my whole site publicly accessible?
**What if I can\'t make my whole site publicly accessible?**
The only URL required for verification is [your-site-url]/badges/assertion.php so if you are able to modify your firewall to allow external access to that file, badge verification will still work.';
$string['backpackbadges'] = 'You have {$a->totalbadges} badge(s) displayed from {$a->totalcollections} collection(s). <a href="mybackpack.php">Change backpack settings</a>.';
+2
View File
@@ -737,6 +737,8 @@ $string['eventcourserestored'] = 'Course restored';
$string['eventcourseupdated'] = 'Course updated';
$string['eventcoursesectionupdated'] = ' Course section updated';
$string['eventcoursemoduleinstancelistviewed'] = 'Course module instance list viewed';
$string['eventcourseuserreportviewed'] = 'Course user report viewed';
$string['eventcourseviewed'] = 'Course viewed';
$string['eventemailfailed'] = 'Email failed to send';
$string['eventname'] = 'Event name';
$string['eventunknownlogged'] = 'Unknown event';
+2
View File
@@ -34,6 +34,8 @@
- AlfrescoWebService::__soapCall() arguments do not match SoapClient::__soapCall()
- AlfrescoWebService::__doRequest() arguments do not match SoapClient::__soapCall()
6. Apply the changes from MDL-41975 in regard with the timestamp
== Alfresco PHP Library ==
Installation and developer documentation for the Alfresco PHP Library can be found on the Alfresco Wiki.
@@ -79,8 +79,8 @@ class AlfrescoWebService extends SoapClient
// Construct Timestamp Header
$timeStamp = $dom->createElementNS($this->wsUtilityNS, "Timestamp");
$createdDate = date("Y-m-d\TH:i:s\Z", mktime(date("H")+24, date("i"), date("s"), date("m"), date("d"), date("Y")));
$expiresDate = date("Y-m-d\TH:i:s\Z", mktime(date("H")+25, date("i"), date("s"), date("m"), date("d"), date("Y")));
$createdDate = gmdate("Y-m-d\TH:i:s\Z", gmmktime(gmdate("H"), gmdate("i"), gmdate("s"), gmdate("m"), gmdate("d"), gmdate("Y")));
$expiresDate = gmdate("Y-m-d\TH:i:s\Z", gmmktime(gmdate("H")+1, gmdate("i"), gmdate("s"), gmdate("m"), gmdate("d"), gmdate("Y")));
$created = new DOMElement("Created", $createdDate, $this->wsUtilityNS);
$expires = new DOMElement("Expires", $expiresDate, $this->wsUtilityNS);
$timeStamp->appendChild($created);
+6 -1
View File
@@ -481,6 +481,7 @@ class block_manager {
if (!isset($bc->attributes['data-block'])) {
$bc->attributes['data-block'] = '_fake';
}
$bc->attributes['class'] .= ' block_fake';
$this->extracontent[$region][] = $bc;
}
@@ -518,7 +519,11 @@ class block_manager {
$this->check_is_loaded();
$this->ensure_content_created($region, $output);
foreach($this->visibleblockcontent[$region] as $instance) {
if (!$this->region_has_content($region, $output)) {
// If the region has no content then nothing is docked at all of course.
return false;
}
foreach ($this->visibleblockcontent[$region] as $instance) {
if (!empty($instance->content) && !get_user_preferences('docked_block_instance_'.$instance->blockinstanceid, 0)) {
return false;
}
@@ -0,0 +1,112 @@
<?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/>.
/**
* Course user report viewed event.
*
* @package core
* @copyright 2014 Adrian Greeve <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\event;
defined('MOODLE_INTERNAL') || die();
/**
* Course user report viewed event class.
*
* Class for event to be triggered when a course user report is viewed.
* @property-read array $other Extra information about the event.
* -string mode: Mode is used to show the user different data.
*
* @package core
* @since Moodle 2.7
* @copyright 2014 Adrian Greeve <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_user_report_viewed extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "A user with the id '$this->userid' viewed the user report in the course '$this->courseid' for user '$this->relateduserid'";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventcourseuserreportviewed', 'core');
}
/**
* Get URL related to the action.
*
* @return \moodle_url
*/
public function get_url() {
return new \moodle_url("/course/user.php", array('id' => $this->courseid, 'user' => $this->relateduserid,
'mode' => $this->other['mode']));
}
/**
* Return the legacy event log data.
*
* @return array|null
*/
protected function get_legacy_logdata() {
return array($this->courseid, 'course', 'user report', 'user.php?id=' . $this->courseid . '&amp;user='
. $this->relateduserid . '&amp;mode=' . $this->other['mode'], $this->relateduserid);
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if ($this->contextlevel != CONTEXT_COURSE) {
throw new \coding_exception('Context passed must be course context.');
}
if (empty($this->relateduserid)) {
throw new \coding_exception('relateduserid needs to be set.');
}
// Make sure this class is never used without proper object details.
if (!isset($this->other['mode'])) {
throw new \coding_exception('mode needs to be set in $other.');
}
}
}
+118
View File
@@ -0,0 +1,118 @@
<?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/>.
/**
* Course viewed event.
*
* @package core
* @copyright 2014 Adrian Greeve <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core\event;
defined('MOODLE_INTERNAL') || die();
/**
* Course viewed event class.
*
* Class for event to be triggered when a course is viewed.
* @property-read array $other Extra information about the event.
* -int coursesectionid: The course section ID (Optional!).
*
* @package core
* @since Moodle 2.7
* @copyright 2014 Adrian Greeve <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_viewed extends \core\event\base {
/**
* Init method.
*
* @return void
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
}
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
return "A user with the id '$this->userid' viewed the course '$this->courseid'";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('eventcourseviewed', 'core');
}
/**
* Get URL related to the action.
*
* @return \moodle_url|null
*/
public function get_url() {
$sectionid = null;
if (isset($this->other['coursesectionid'])) {
$sectionid = $this->other['coursesectionid'];
}
try {
return \course_get_url($this->courseid, $sectionid);
} catch (\Exception $e) {
return null;
}
}
/**
* Return the legacy event log data.
*
* @return array|null
*/
protected function get_legacy_logdata() {
if ($this->courseid == SITEID and !isloggedin()) {
// We did not log frontpage access in older Moodle versions.
return null;
}
if (isset($this->other['coursesectionid'])) {
return array($this->courseid, 'course', 'view section', 'view.php?id=' . $this->courseid . '&amp;sectionid='
. $this->other['coursesectionid'], $this->other['coursesectionid']);
}
return array($this->courseid, 'course', 'view', 'view.php?id=' . $this->courseid, $this->courseid);
}
/**
* Custom validation.
*
* @throws \coding_exception
* @return void
*/
protected function validate_data() {
parent::validate_data();
if ($this->contextlevel != CONTEXT_COURSE) {
throw new \coding_exception('Context passed must be course context.');
}
}
}
-7
View File
@@ -42,13 +42,6 @@ interface reader {
*/
public function get_description();
/**
* Can the current user access this store?
* @param \context $context
* @return bool
*/
public function can_access(\context $context);
/**
* Are the new events appearing in the reader?
*
@@ -85,6 +85,8 @@ Y.namespace('M.atto_collapse').Button = Y.Base.create('button', Y.M.editor_atto.
} else {
this._setVisibility(button);
}
this.buttons[this.name].focus();
},
/**
@@ -1 +1 @@
YUI.add("moodle-atto_collapse-button",function(e,t){var n="atto_collapse",r="showgroups",i="collapse",s="collapsed",o=".atto_group";e.namespace("M.atto_collapse").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var t=e.Object.size(this.get("host").get("plugins"));if(t<=1+parseInt(this.get(r),10))return;if(this.toolbar.all(o).size()>this.get(r))return;var i=this.addButton({icon:M.util.image_url("icon",n),callback:this._toggle});this.get("host").on("pluginsloaded",function(e,t){this._setVisibility(t);var n=this.toolbar.all(o).item(this.get(r));n.insert('<div class="toolbarbreak"></div>',"before")},this,i)},_toggle:function(e){e.preventDefault();var t=this.buttons[i];t.getData(s)?this._setVisibility(t,!0):this._setVisibility(t)},_setVisibility:function(e,t){var i=this.toolbar.all(o).slice(this.get(r));t?(e.set("title",M.util.get_string("showfewer",n)),i.show(),e.setData(s,!1)):(e.set("title",M.util.get_string("showmore",n)),i.hide(),e.setData(s,!0))}},{ATTRS:{showgroups:{value:3}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
YUI.add("moodle-atto_collapse-button",function(e,t){var n="atto_collapse",r="showgroups",i="collapse",s="collapsed",o=".atto_group";e.namespace("M.atto_collapse").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var t=e.Object.size(this.get("host").get("plugins"));if(t<=1+parseInt(this.get(r),10))return;if(this.toolbar.all(o).size()>this.get(r))return;var i=this.addButton({icon:M.util.image_url("icon",n),callback:this._toggle});this.get("host").on("pluginsloaded",function(e,t){this._setVisibility(t);var n=this.toolbar.all(o).item(this.get(r));n.insert('<div class="toolbarbreak"></div>',"before")},this,i)},_toggle:function(e){e.preventDefault();var t=this.buttons[i];t.getData(s)?this._setVisibility(t,!0):this._setVisibility(t),this.buttons[this.name].focus()},_setVisibility:function(e,t){var i=this.toolbar.all(o).slice(this.get(r));t?(e.set("title",M.util.get_string("showfewer",n)),i.show(),e.setData(s,!1)):(e.set("title",M.util.get_string("showmore",n)),i.hide(),e.setData(s,!0))}},{ATTRS:{showgroups:{value:3}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]});
@@ -81,6 +81,8 @@ Y.namespace('M.atto_collapse').Button = Y.Base.create('button', Y.M.editor_atto.
} else {
this._setVisibility(button);
}
this.buttons[this.name].focus();
},
/**
@@ -83,6 +83,8 @@ Y.namespace('M.atto_collapse').Button = Y.Base.create('button', Y.M.editor_atto.
} else {
this._setVisibility(button);
}
this.buttons[this.name].focus();
},
/**
+3 -2
View File
@@ -28,11 +28,12 @@ define('AJAX_SCRIPT', true);
require_once(dirname(__FILE__) . '/../../../../../config.php');
$contextid = required_param('contextid', PARAM_INT);
$context = context::instance_by_id($contextid, MUST_EXIST);
list($context, $course, $cm) = get_context_info_array($contextid);
$PAGE->set_url('/lib/editor/atto/plugins/equation/ajax.php');
$PAGE->set_context($context);
require_login();
require_login($course, false, $cm);
require_sesskey();
$action = required_param('action', PARAM_ALPHA);
@@ -20,3 +20,7 @@
padding: 5px;
min-width: 206px;
}
.atto_form .atto_equation_preview {
margin-bottom: 0px; /* Turn off the margin-bottom from the class "well" */
}
@@ -62,7 +62,7 @@ var COMPONENTNAME = 'atto_equation',
'<label for="{{elementid}}_{{CSS.EQUATION_TEXT}}">{{{get_string "editequation" component texdocsurl}}}</label>' +
'<textarea class="fullwidth {{CSS.EQUATION_TEXT}}" id="{{elementid}}_{{CSS.EQUATION_TEXT}}" rows="8"></textarea><br/>' +
'<label for="{{elementid}}_{{CSS.EQUATION_PREVIEW}}">{{get_string "preview" component}}</label>' +
'<div class="fullwidth {{CSS.EQUATION_PREVIEW}}" id="{{elementid}}_{{CSS.EQUATION_PREVIEW}}"></div>' +
'<div class="well well-small fullwidth {{CSS.EQUATION_PREVIEW}}" id="{{elementid}}_{{CSS.EQUATION_PREVIEW}}"></div>' +
'<div class="mdl-align">' +
'<br/>' +
'<button class="{{CSS.SUBMIT}}">{{get_string "saveequation" component}}</button>' +
File diff suppressed because one or more lines are too long
@@ -62,7 +62,7 @@ var COMPONENTNAME = 'atto_equation',
'<label for="{{elementid}}_{{CSS.EQUATION_TEXT}}">{{{get_string "editequation" component texdocsurl}}}</label>' +
'<textarea class="fullwidth {{CSS.EQUATION_TEXT}}" id="{{elementid}}_{{CSS.EQUATION_TEXT}}" rows="8"></textarea><br/>' +
'<label for="{{elementid}}_{{CSS.EQUATION_PREVIEW}}">{{get_string "preview" component}}</label>' +
'<div class="fullwidth {{CSS.EQUATION_PREVIEW}}" id="{{elementid}}_{{CSS.EQUATION_PREVIEW}}"></div>' +
'<div class="well well-small fullwidth {{CSS.EQUATION_PREVIEW}}" id="{{elementid}}_{{CSS.EQUATION_PREVIEW}}"></div>' +
'<div class="mdl-align">' +
'<br/>' +
'<button class="{{CSS.SUBMIT}}">{{get_string "saveequation" component}}</button>' +
@@ -60,7 +60,7 @@ var COMPONENTNAME = 'atto_equation',
'<label for="{{elementid}}_{{CSS.EQUATION_TEXT}}">{{{get_string "editequation" component texdocsurl}}}</label>' +
'<textarea class="fullwidth {{CSS.EQUATION_TEXT}}" id="{{elementid}}_{{CSS.EQUATION_TEXT}}" rows="8"></textarea><br/>' +
'<label for="{{elementid}}_{{CSS.EQUATION_PREVIEW}}">{{get_string "preview" component}}</label>' +
'<div class="fullwidth {{CSS.EQUATION_PREVIEW}}" id="{{elementid}}_{{CSS.EQUATION_PREVIEW}}"></div>' +
'<div class="well well-small fullwidth {{CSS.EQUATION_PREVIEW}}" id="{{elementid}}_{{CSS.EQUATION_PREVIEW}}"></div>' +
'<div class="mdl-align">' +
'<br/>' +
'<button class="{{CSS.SUBMIT}}">{{get_string "saveequation" component}}</button>' +
@@ -312,7 +312,6 @@ EditorPluginButtons.prototype = {
* specified, in the class for the button.
* @param {function} config.callback A callback function to call when the button is clicked.
* @param {object} [config.callbackArgs] Any arguments to pass to the callback.
* @param {boolean} [config.wrapCallback=true] Whether to wrap the callback in default functionality.
* @return {Node} The Node representing the newly created button.
*/
addButton: function(config) {
@@ -429,7 +428,6 @@ EditorPluginButtons.prototype = {
* @param {string} [config.title=this.name] The string identifier in the plugin's language file.
* @param {string} [config.buttonName=this.name] The name of the button. This is used in the buttons object, and if
* specified, in the class for the button.
* @param {boolean} [config.wrapCallback=true] Whether to wrap the callback in default functionality.
* @return {Node} The Node representing the newly created button.
*/
addBasicButton: function(config) {
@@ -469,7 +467,6 @@ EditorPluginButtons.prototype = {
* specified, in the class for the button.
* @param {function} config.callback A callback function to call when the button is clicked.
* @param {object} [config.callbackArgs] Any arguments to pass to the callback.
* @param {boolean} [config.wrapCallback=true] Whether to wrap the callback in default functionality.
* @param {array} config.entries List of menu entries with the string (entry.text) and the handlers (entry.handler).
* @param {number} [config.overlayWidth=14] The width of the menu. This will be suffixed with the 'em' unit.
* @param {string} [config.menuColor] menu icon background color
@@ -656,7 +653,6 @@ EditorPluginButtons.prototype = {
* @param {object} config
* @param {function} config.callback A callback function to call when the button is clicked.
* @param {object} [config.callbackArgs] Any arguments to pass to the callback.
* @param {boolean} [config.wrapCallback=true] Whether to wrap the callback in default functionality.
* @param {object} [inheritFrom] A parent configuration that this configuration may inherit from.
* @return {object} The normalized configuration
* @private
@@ -672,18 +668,10 @@ EditorPluginButtons.prototype = {
inheritFrom = {};
}
// By default, we wrap the callback in function to prevent the default action, check whether the editor is
// We wrap the callback in function to prevent the default action, check whether the editor is
// active and focus it, and then mark the field as updated.
if (typeof config.wrapCallback === 'undefined') {
config.wrapCallback = inheritFrom.wrapCallback || true;
}
if (config.wrapCallback) {
config._callback = config.callback || inheritFrom.callback;
config.callback = Y.rbind(this._callbackWrapper, this, config._callback, config.callbackArgs);
} else {
config.callback = config.callback || inheritFrom.callback;
}
config._callback = config.callback || inheritFrom.callback;
config.callback = Y.rbind(this._callbackWrapper, this, config._callback, config.callbackArgs);
config._callbackNormalized = true;
File diff suppressed because one or more lines are too long
@@ -312,7 +312,6 @@ EditorPluginButtons.prototype = {
* specified, in the class for the button.
* @param {function} config.callback A callback function to call when the button is clicked.
* @param {object} [config.callbackArgs] Any arguments to pass to the callback.
* @param {boolean} [config.wrapCallback=true] Whether to wrap the callback in default functionality.
* @return {Node} The Node representing the newly created button.
*/
addButton: function(config) {
@@ -429,7 +428,6 @@ EditorPluginButtons.prototype = {
* @param {string} [config.title=this.name] The string identifier in the plugin's language file.
* @param {string} [config.buttonName=this.name] The name of the button. This is used in the buttons object, and if
* specified, in the class for the button.
* @param {boolean} [config.wrapCallback=true] Whether to wrap the callback in default functionality.
* @return {Node} The Node representing the newly created button.
*/
addBasicButton: function(config) {
@@ -467,7 +465,6 @@ EditorPluginButtons.prototype = {
* specified, in the class for the button.
* @param {function} config.callback A callback function to call when the button is clicked.
* @param {object} [config.callbackArgs] Any arguments to pass to the callback.
* @param {boolean} [config.wrapCallback=true] Whether to wrap the callback in default functionality.
* @param {array} config.entries List of menu entries with the string (entry.text) and the handlers (entry.handler).
* @param {number} [config.overlayWidth=14] The width of the menu. This will be suffixed with the 'em' unit.
* @param {string} [config.menuColor] menu icon background color
@@ -654,7 +651,6 @@ EditorPluginButtons.prototype = {
* @param {object} config
* @param {function} config.callback A callback function to call when the button is clicked.
* @param {object} [config.callbackArgs] Any arguments to pass to the callback.
* @param {boolean} [config.wrapCallback=true] Whether to wrap the callback in default functionality.
* @param {object} [inheritFrom] A parent configuration that this configuration may inherit from.
* @return {object} The normalized configuration
* @private
@@ -670,18 +666,10 @@ EditorPluginButtons.prototype = {
inheritFrom = {};
}
// By default, we wrap the callback in function to prevent the default action, check whether the editor is
// We wrap the callback in function to prevent the default action, check whether the editor is
// active and focus it, and then mark the field as updated.
if (typeof config.wrapCallback === 'undefined') {
config.wrapCallback = inheritFrom.wrapCallback || true;
}
if (config.wrapCallback) {
config._callback = config.callback || inheritFrom.callback;
config.callback = Y.rbind(this._callbackWrapper, this, config._callback, config.callbackArgs);
} else {
config.callback = config.callback || inheritFrom.callback;
}
config._callback = config.callback || inheritFrom.callback;
config.callback = Y.rbind(this._callbackWrapper, this, config._callback, config.callbackArgs);
config._callbackNormalized = true;
+3 -15
View File
@@ -164,7 +164,6 @@ EditorPluginButtons.prototype = {
* specified, in the class for the button.
* @param {function} config.callback A callback function to call when the button is clicked.
* @param {object} [config.callbackArgs] Any arguments to pass to the callback.
* @param {boolean} [config.wrapCallback=true] Whether to wrap the callback in default functionality.
* @return {Node} The Node representing the newly created button.
*/
addButton: function(config) {
@@ -281,7 +280,6 @@ EditorPluginButtons.prototype = {
* @param {string} [config.title=this.name] The string identifier in the plugin's language file.
* @param {string} [config.buttonName=this.name] The name of the button. This is used in the buttons object, and if
* specified, in the class for the button.
* @param {boolean} [config.wrapCallback=true] Whether to wrap the callback in default functionality.
* @return {Node} The Node representing the newly created button.
*/
addBasicButton: function(config) {
@@ -321,7 +319,6 @@ EditorPluginButtons.prototype = {
* specified, in the class for the button.
* @param {function} config.callback A callback function to call when the button is clicked.
* @param {object} [config.callbackArgs] Any arguments to pass to the callback.
* @param {boolean} [config.wrapCallback=true] Whether to wrap the callback in default functionality.
* @param {array} config.entries List of menu entries with the string (entry.text) and the handlers (entry.handler).
* @param {number} [config.overlayWidth=14] The width of the menu. This will be suffixed with the 'em' unit.
* @param {string} [config.menuColor] menu icon background color
@@ -508,7 +505,6 @@ EditorPluginButtons.prototype = {
* @param {object} config
* @param {function} config.callback A callback function to call when the button is clicked.
* @param {object} [config.callbackArgs] Any arguments to pass to the callback.
* @param {boolean} [config.wrapCallback=true] Whether to wrap the callback in default functionality.
* @param {object} [inheritFrom] A parent configuration that this configuration may inherit from.
* @return {object} The normalized configuration
* @private
@@ -524,18 +520,10 @@ EditorPluginButtons.prototype = {
inheritFrom = {};
}
// By default, we wrap the callback in function to prevent the default action, check whether the editor is
// We wrap the callback in function to prevent the default action, check whether the editor is
// active and focus it, and then mark the field as updated.
if (typeof config.wrapCallback === 'undefined') {
config.wrapCallback = inheritFrom.wrapCallback || true;
}
if (config.wrapCallback) {
config._callback = config.callback || inheritFrom.callback;
config.callback = Y.rbind(this._callbackWrapper, this, config._callback, config.callbackArgs);
} else {
config.callback = config.callback || inheritFrom.callback;
}
config._callback = config.callback || inheritFrom.callback;
config.callback = Y.rbind(this._callbackWrapper, this, config._callback, config.callbackArgs);
config._callbackNormalized = true;
+1
View File
@@ -1559,6 +1559,7 @@ function &get_mimetypes_array() {
'webm' => array ('type'=>'video/webm', 'icon'=>'video', 'groups'=>array('video'), 'string'=>'video'),
'wmv' => array ('type'=>'video/x-ms-wmv', 'icon'=>'wmv', 'groups'=>array('video'), 'string'=>'video'),
'asf' => array ('type'=>'video/x-ms-asf', 'icon'=>'wmv', 'groups'=>array('video'), 'string'=>'video'),
'wma' => array ('type'=>'audio/x-ms-wma', 'icon'=>'audio', 'groups'=>array('audio'), 'string'=>'audio'),
'xbk' => array ('type'=>'application/x-smarttech-notebook', 'icon'=>'archive'),
'xdp' => array ('type'=>'application/pdf', 'icon'=>'pdf'),
+4 -1
View File
@@ -355,7 +355,10 @@ M.form_dndupload.init = function(Y, options) {
* @return boolean true if event has files
*/
has_files: function(e) {
var types = e._event.dataTransfer.types;
// In some browsers, dataTransfer.types may be null for a
// 'dragover' event, so ensure a valid Array is always
// inspected.
var types = e._event.dataTransfer.types || [];
for (var i=0; i<types.length; i++) {
if (types[i] == 'Files') {
return true;
+1 -1
View File
@@ -391,7 +391,7 @@ function install_print_footer($config, $reload=false) {
$homelink = '<div class="sitelink">'.
'<a title="Moodle '. $CFG->target_release .'" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">'.
'<img style="width:100px;height:30px" src="pix/moodlelogo.gif" alt="moodlelogo" /></a></div>';
'<img src="pix/moodlelogo.png" alt="moodlelogo" /></a></div>';
echo '</form></div>';
echo '<div id="footer"><hr />'.$homelink.'</div>';
+5
View File
@@ -4933,6 +4933,11 @@ function delete_course($courseorid, $showfeedback = true) {
$DB->delete_records("course", array("id" => $courseid));
$DB->delete_records("course_format_options", array("courseid" => $courseid));
// Reset all course related caches here.
if (class_exists('format_base', false)) {
format_base::reset_course_cache($courseid);
}
// Trigger a course deleted event.
$event = \core\event\course_deleted::create(array(
'objectid' => $course->id,
+2 -2
View File
@@ -698,13 +698,13 @@ class core_renderer extends renderer_base {
// Special case for site home page - please do not remove
return '<div class="sitelink">' .
'<a title="Moodle" href="http://moodle.org/">' .
'<img style="width:100px;height:30px" src="' . $this->pix_url('moodlelogo') . '" alt="moodlelogo" /></a></div>';
'<img src="' . $this->pix_url('moodlelogo') . '" alt="moodlelogo" /></a></div>';
} else if (!empty($CFG->target_release) && $CFG->target_release != $CFG->release) {
// Special case for during install/upgrade.
return '<div class="sitelink">'.
'<a title="Moodle" href="http://docs.moodle.org/en/Administrator_documentation" onclick="this.target=\'_blank\'">' .
'<img style="width:100px;height:30px" src="' . $this->pix_url('moodlelogo') . '" alt="moodlelogo" /></a></div>';
'<img src="' . $this->pix_url('moodlelogo') . '" alt="moodlelogo" /></a></div>';
} else if ($this->page->course->id == $SITE->id || strpos($this->page->pagetype, 'course-view') === 0) {
return '<div class="homelink"><a href="' . $CFG->wwwroot . '/">' .
+84
View File
@@ -178,4 +178,88 @@ class core_events_testcase extends advanced_testcase {
$this->assertEventLegacyLogData($expected, $event);
$this->assertEventContextNotUsed($event);
}
/**
* There is no api involved so the best we can do is test legacy data by triggering event manually.
*/
public function test_course_user_report_viewed() {
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$context = context_course::instance($course->id);
$eventparams = array();
$eventparams['context'] = $context;
$eventparams['relateduserid'] = $user->id;
$eventparams['other'] = array();
$eventparams['other']['mode'] = 'grade';
$event = \core\event\course_user_report_viewed::create($eventparams);
// Trigger and capture the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$event = reset($events);
$this->assertInstanceOf('\core\event\course_user_report_viewed', $event);
$this->assertEquals(context_course::instance($course->id), $event->get_context());
$expected = array($course->id, 'course', 'user report', 'user.php?id=' . $course->id . '&amp;user='
. $user->id . '&amp;mode=grade', $user->id);
$this->assertEventLegacyLogData($expected, $event);
$this->assertEventContextNotUsed($event);
}
/**
* There is no api involved so the best we can do is test legacy data by triggering event manually.
*/
public function test_course_viewed() {
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course();
$context = context_course::instance($course->id);
// First try with no optional parameters.
$eventparams = array();
$eventparams['context'] = $context;
$event = \core\event\course_viewed::create($eventparams);
// Trigger and capture the event.
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$event = reset($events);
$this->assertInstanceOf('\core\event\course_viewed', $event);
$this->assertEquals(context_course::instance($course->id), $event->get_context());
$expected = array($course->id, 'course', 'view', 'view.php?id=' . $course->id, $course->id);
$this->assertEventLegacyLogData($expected, $event);
$this->assertEventContextNotUsed($event);
// Now try with optional parameters.
$sectionid = 34;
$eventparams = array();
$eventparams['context'] = $context;
$eventparams['other'] = array('coursesectionid' => $sectionid);
$event = \core\event\course_viewed::create($eventparams);
// Trigger and capture the event.
$sink = $this->redirectEvents();
$event->trigger();
$loggeddata = $event->get_data();
$events = $sink->get_events();
$event = reset($events);
$this->assertInstanceOf('\core\event\course_viewed', $event);
$this->assertEquals(context_course::instance($course->id), $event->get_context());
$expected = array($course->id, 'course', 'view section', 'view.php?id=' . $course->id . '&amp;sectionid='
. $sectionid, $sectionid);
$this->assertEventLegacyLogData($expected, $event);
$this->assertEventContextNotUsed($event);
delete_course($course->id, false);
$restored = \core\event\base::restore($loggeddata, array('origin' => 'web', 'ip' => '127.0.0.1'));
$this->assertInstanceOf('\core\event\course_viewed', $restored);
$this->assertNull($restored->get_url());
}
}
+1 -1
View File
@@ -61,7 +61,7 @@ if ($getpdf) {
$doc->setPrintHeader(true);
$doc->setHeaderMargin(10);
$doc->setHeaderFont(array($fontfamily, 'b', 10));
$doc->setHeaderData('pix/moodlelogo-med-white.gif', 40, $SITE->fullname, $CFG->wwwroot);
$doc->setHeaderData('pix/moodlelogo-med.png', 40, $SITE->fullname, $CFG->wwwroot);
$doc->setPrintFooter(true);
$doc->setFooterMargin(10);
+2 -2
View File
@@ -2840,7 +2840,7 @@ function debugging($message = '', $level = DEBUG_NORMAL, $backtrace = null) {
if (!$backtrace) {
$backtrace = debug_backtrace();
}
$from = format_backtrace($backtrace, CLI_SCRIPT);
$from = format_backtrace($backtrace, CLI_SCRIPT || NO_DEBUG_DISPLAY);
if (PHPUNIT_TEST) {
if (phpunit_util::debugging_triggered($message, $level, $from)) {
// We are inside test, the debug message was logged.
@@ -2851,7 +2851,7 @@ function debugging($message = '', $level = DEBUG_NORMAL, $backtrace = null) {
if (NO_DEBUG_DISPLAY) {
// Script does not want any errors or debugging in output,
// we send the info to error log instead.
error_log('Debugging: ' . $message . $from);
error_log('Debugging: ' . $message . ' in '. PHP_EOL . $from);
} else if ($forcedebug or $CFG->debugdisplay) {
if (!defined('DEBUGGING_PRINTED')) {
@@ -351,7 +351,7 @@ M.core.blockdraganddrop.is_using_blocks_render_method = function() {
var goodregions = Y.all('.block-region[data-blockregion]').size();
var allregions = Y.all('.block-region').size();
this._isusingnewblocksmethod = (allregions === goodregions);
if (goodregions > 0 && allregions > 0) {
if (goodregions > 0 && allregions > 0 && goodregions !== allregions) {
Y.log('Both core_renderer::blocks and core_renderer::blocks_for_region have been used.', 'warn', 'moodle-core_blocks');
}
}
@@ -443,7 +443,6 @@ MANAGER.prototype = {
i = 0,
region,
regionname,
droptarget,
dragdelegation;
// Evil required by M.core.dragdrop.
@@ -472,7 +471,7 @@ MANAGER.prototype = {
// Setting blockregion as droptarget (the case when it is empty)
// The region-post (the right one)
// is very narrow, so add extra padding on the left to drop block on it.
droptarget = new Y.DD.Drop({
new Y.DD.Drop({
node: region.get_droptarget(),
groups: this.groups,
padding: '40 240 40 240'
@@ -484,7 +483,7 @@ MANAGER.prototype = {
nodes: '.'+CSS.BLOCK,
target: true,
handles: [SELECTOR.DRAGHANDLE],
invalid: '.block-hider-hide, .block-hider-show, .moveto',
invalid: '.block-hider-hide, .block-hider-show, .moveto, .block_fake',
dragConfig: {groups: this.groups}
});
dragdelegation.dd.plug(Y.Plugin.DDProxy, {
@@ -492,9 +491,10 @@ MANAGER.prototype = {
moveOnEnd: false
});
dragdelegation.dd.plug(Y.Plugin.DDWinScroll);
// On the mouse down event we will enable all block regions so that they can be dragged to.
// This is VERY important as without it dnd won't work for empty block regions.
dragdelegation.on('drag:mouseDown', this.enable_all_regions, this);
// On the DD Manager start operation, we enable all block regions so that they can be drop targets. This
// must be done *before* drag:start but after dragging has been initialised.
Y.DD.DDM.on('ddm:start', this.enable_all_regions, this);
region.change_block_move_icons(this);
}
@@ -536,11 +536,24 @@ MANAGER.prototype = {
/**
* Enables all fo the regions so that they are all visible while dragging is occuring.
*
* @method enable_all_regions
*/
enable_all_regions : function() {
var i = 0;
var groups = Y.DD.DDM.activeDrag.get('groups');
// As we're called by Y.DD.DDM, we can't be certain that the call
// relates specifically to a block drag/drop operation. Test
// whether the relevant group applies here.
if (!groups || Y.Array.indexOf(groups, 'block') === -1) {
return;
}
var i;
for (i in this.regionobjects) {
if (!this.regionobjects.hasOwnProperty(i)) {
continue;
}
this.regionobjects[i].enable();
}
},
File diff suppressed because one or more lines are too long
+21 -8
View File
@@ -351,7 +351,7 @@ M.core.blockdraganddrop.is_using_blocks_render_method = function() {
var goodregions = Y.all('.block-region[data-blockregion]').size();
var allregions = Y.all('.block-region').size();
this._isusingnewblocksmethod = (allregions === goodregions);
if (goodregions > 0 && allregions > 0) {
if (goodregions > 0 && allregions > 0 && goodregions !== allregions) {
}
}
return this._isusingnewblocksmethod;
@@ -439,7 +439,6 @@ MANAGER.prototype = {
i = 0,
region,
regionname,
droptarget,
dragdelegation;
// Evil required by M.core.dragdrop.
@@ -468,7 +467,7 @@ MANAGER.prototype = {
// Setting blockregion as droptarget (the case when it is empty)
// The region-post (the right one)
// is very narrow, so add extra padding on the left to drop block on it.
droptarget = new Y.DD.Drop({
new Y.DD.Drop({
node: region.get_droptarget(),
groups: this.groups,
padding: '40 240 40 240'
@@ -480,7 +479,7 @@ MANAGER.prototype = {
nodes: '.'+CSS.BLOCK,
target: true,
handles: [SELECTOR.DRAGHANDLE],
invalid: '.block-hider-hide, .block-hider-show, .moveto',
invalid: '.block-hider-hide, .block-hider-show, .moveto, .block_fake',
dragConfig: {groups: this.groups}
});
dragdelegation.dd.plug(Y.Plugin.DDProxy, {
@@ -488,9 +487,10 @@ MANAGER.prototype = {
moveOnEnd: false
});
dragdelegation.dd.plug(Y.Plugin.DDWinScroll);
// On the mouse down event we will enable all block regions so that they can be dragged to.
// This is VERY important as without it dnd won't work for empty block regions.
dragdelegation.on('drag:mouseDown', this.enable_all_regions, this);
// On the DD Manager start operation, we enable all block regions so that they can be drop targets. This
// must be done *before* drag:start but after dragging has been initialised.
Y.DD.DDM.on('ddm:start', this.enable_all_regions, this);
region.change_block_move_icons(this);
}
@@ -531,11 +531,24 @@ MANAGER.prototype = {
/**
* Enables all fo the regions so that they are all visible while dragging is occuring.
*
* @method enable_all_regions
*/
enable_all_regions : function() {
var i = 0;
var groups = Y.DD.DDM.activeDrag.get('groups');
// As we're called by Y.DD.DDM, we can't be certain that the call
// relates specifically to a block drag/drop operation. Test
// whether the relevant group applies here.
if (!groups || Y.Array.indexOf(groups, 'block') === -1) {
return;
}
var i;
for (i in this.regionobjects) {
if (!this.regionobjects.hasOwnProperty(i)) {
continue;
}
this.regionobjects[i].enable();
}
},
+3 -1
View File
@@ -750,7 +750,9 @@ DOCK.prototype = {
BODY.addClass('used-region-'+regionname);
BODY.removeClass('empty-region-'+regionname);
BODY.removeClass('docked-region-'+regionname);
} else {
} else if (region.all('.block_dock_placeholder').size() > 0) {
// There are no blocks in the region but there are placeholders.
// All blocks in this region have been docked.
BODY.addClass('empty-region-'+regionname);
BODY.addClass('docked-region-'+regionname);
BODY.removeClass('used-region-'+regionname);
File diff suppressed because one or more lines are too long
+3 -1
View File
@@ -743,7 +743,9 @@ DOCK.prototype = {
BODY.addClass('used-region-'+regionname);
BODY.removeClass('empty-region-'+regionname);
BODY.removeClass('docked-region-'+regionname);
} else {
} else if (region.all('.block_dock_placeholder').size() > 0) {
// There are no blocks in the region but there are placeholders.
// All blocks in this region have been docked.
BODY.addClass('empty-region-'+regionname);
BODY.addClass('docked-region-'+regionname);
BODY.removeClass('used-region-'+regionname);
+1 -1
View File
@@ -349,7 +349,7 @@ M.core.blockdraganddrop.is_using_blocks_render_method = function() {
var goodregions = Y.all('.block-region[data-blockregion]').size();
var allregions = Y.all('.block-region').size();
this._isusingnewblocksmethod = (allregions === goodregions);
if (goodregions > 0 && allregions > 0) {
if (goodregions > 0 && allregions > 0 && goodregions !== allregions) {
Y.log('Both core_renderer::blocks and core_renderer::blocks_for_region have been used.', 'warn', 'moodle-core_blocks');
}
}
+20 -7
View File
@@ -58,7 +58,6 @@ MANAGER.prototype = {
i = 0,
region,
regionname,
droptarget,
dragdelegation;
// Evil required by M.core.dragdrop.
@@ -87,7 +86,7 @@ MANAGER.prototype = {
// Setting blockregion as droptarget (the case when it is empty)
// The region-post (the right one)
// is very narrow, so add extra padding on the left to drop block on it.
droptarget = new Y.DD.Drop({
new Y.DD.Drop({
node: region.get_droptarget(),
groups: this.groups,
padding: '40 240 40 240'
@@ -99,7 +98,7 @@ MANAGER.prototype = {
nodes: '.'+CSS.BLOCK,
target: true,
handles: [SELECTOR.DRAGHANDLE],
invalid: '.block-hider-hide, .block-hider-show, .moveto',
invalid: '.block-hider-hide, .block-hider-show, .moveto, .block_fake',
dragConfig: {groups: this.groups}
});
dragdelegation.dd.plug(Y.Plugin.DDProxy, {
@@ -107,9 +106,10 @@ MANAGER.prototype = {
moveOnEnd: false
});
dragdelegation.dd.plug(Y.Plugin.DDWinScroll);
// On the mouse down event we will enable all block regions so that they can be dragged to.
// This is VERY important as without it dnd won't work for empty block regions.
dragdelegation.on('drag:mouseDown', this.enable_all_regions, this);
// On the DD Manager start operation, we enable all block regions so that they can be drop targets. This
// must be done *before* drag:start but after dragging has been initialised.
Y.DD.DDM.on('ddm:start', this.enable_all_regions, this);
region.change_block_move_icons(this);
}
@@ -151,11 +151,24 @@ MANAGER.prototype = {
/**
* Enables all fo the regions so that they are all visible while dragging is occuring.
*
* @method enable_all_regions
*/
enable_all_regions : function() {
var i = 0;
var groups = Y.DD.DDM.activeDrag.get('groups');
// As we're called by Y.DD.DDM, we can't be certain that the call
// relates specifically to a block drag/drop operation. Test
// whether the relevant group applies here.
if (!groups || Y.Array.indexOf(groups, 'block') === -1) {
return;
}
var i;
for (i in this.regionobjects) {
if (!this.regionobjects.hasOwnProperty(i)) {
continue;
}
this.regionobjects[i].enable();
}
},
+3 -1
View File
@@ -748,7 +748,9 @@ DOCK.prototype = {
BODY.addClass('used-region-'+regionname);
BODY.removeClass('empty-region-'+regionname);
BODY.removeClass('docked-region-'+regionname);
} else {
} else if (region.all('.block_dock_placeholder').size() > 0) {
// There are no blocks in the region but there are placeholders.
// All blocks in this region have been docked.
BODY.addClass('empty-region-'+regionname);
BODY.addClass('docked-region-'+regionname);
BODY.removeClass('used-region-'+regionname);
+2 -2
View File
@@ -290,7 +290,7 @@ function chat_print_recent_activity($course, $viewfullnames, $timestart) {
$strftimerecent = get_string('strftimerecent');
if ($past) {
echo $OUTPUT->heading(get_string("pastchats", 'chat').':');
echo $OUTPUT->heading(get_string("pastchats", 'chat').':', 3);
foreach ($past as $cm) {
$link = $CFG->wwwroot.'/mod/chat/view.php?id='.$cm->id;
@@ -301,7 +301,7 @@ function chat_print_recent_activity($course, $viewfullnames, $timestart) {
}
if ($current) {
echo $OUTPUT->heading(get_string("currentchats", 'chat').':');
echo $OUTPUT->heading(get_string("currentchats", 'chat').':', 3);
$oldest = floor((time()-$CFG->chat_old_ping)/10)*10; // better db caching
+5
View File
@@ -372,6 +372,11 @@ function quiz_delete_attempt($attempt, $quiz) {
return;
}
if (!isset($quiz->cmid)) {
$cm = get_coursemodule_from_instance('quiz', $quiz->id, $quiz->course);
$quiz->cmid = $cm->id;
}
question_engine::delete_questions_usage_by_activity($attempt->uniqueid);
$DB->delete_records('quiz_attempts', array('id' => $attempt->id));
+2 -1
View File
@@ -85,8 +85,9 @@ if ($override->groupid) {
$group = $DB->get_record('groups', array('id' => $override->groupid), 'id, name');
$confirmstr = get_string("overridedeletegroupsure", "quiz", $group->name);
} else {
$namefields = get_all_user_name_fields(true);
$user = $DB->get_record('user', array('id' => $override->userid),
'id, firstname, lastname');
'id, ' . $namefields);
$confirmstr = get_string("overridedeleteusersure", "quiz", fullname($user));
}
+2
View File
@@ -318,6 +318,8 @@ abstract class quiz_attempts_report extends quiz_default_report {
continue;
}
// Set the course module id before calling quiz_delete_attempt().
$quiz->cmid = $cm->id;
quiz_delete_attempt($attempt, $quiz);
}
}
@@ -53,7 +53,9 @@ class quiz_responses_options extends mod_quiz_attempts_report_options {
$params['qtext'] = $this->showqtext;
$params['resp'] = $this->showresponses;
$params['right'] = $this->showright;
$params['whichtries'] = $this->whichtries;
if (quiz_allows_multiple_tries($this->quiz)) {
$params['whichtries'] = $this->whichtries;
}
return $params;
}
@@ -62,7 +64,9 @@ class quiz_responses_options extends mod_quiz_attempts_report_options {
$toform->qtext = $this->showqtext;
$toform->resp = $this->showresponses;
$toform->right = $this->showright;
$toform->whichtries = $this->whichtries;
if (quiz_allows_multiple_tries($this->quiz)) {
$toform->whichtries = $this->whichtries;
}
return $toform;
}
@@ -73,7 +77,9 @@ class quiz_responses_options extends mod_quiz_attempts_report_options {
$this->showqtext = $fromform->qtext;
$this->showresponses = $fromform->resp;
$this->showright = $fromform->right;
$this->whichtries = $fromform->whichtries;
if (quiz_allows_multiple_tries($this->quiz)) {
$this->whichtries = $fromform->whichtries;
}
}
public function setup_from_params() {
@@ -82,7 +88,9 @@ class quiz_responses_options extends mod_quiz_attempts_report_options {
$this->showqtext = optional_param('qtext', $this->showqtext, PARAM_BOOL);
$this->showresponses = optional_param('resp', $this->showresponses, PARAM_BOOL);
$this->showright = optional_param('right', $this->showright, PARAM_BOOL);
$this->whichtries = optional_param('whichtries', $this->whichtries, PARAM_ALPHA);
if (quiz_allows_multiple_tries($this->quiz)) {
$this->whichtries = optional_param('whichtries', $this->whichtries, PARAM_ALPHA);
}
}
public function setup_from_user_preferences() {
@@ -91,7 +99,9 @@ class quiz_responses_options extends mod_quiz_attempts_report_options {
$this->showqtext = get_user_preferences('quiz_report_responses_qtext', $this->showqtext);
$this->showresponses = get_user_preferences('quiz_report_responses_resp', $this->showresponses);
$this->showright = get_user_preferences('quiz_report_responses_right', $this->showright);
$this->whichtries = get_user_preferences('quiz_report_responses_which_tries', $this->whichtries);
if (quiz_allows_multiple_tries($this->quiz)) {
$this->whichtries = get_user_preferences('quiz_report_responses_which_tries', $this->whichtries);
}
}
public function update_user_preferences() {
@@ -100,7 +110,9 @@ class quiz_responses_options extends mod_quiz_attempts_report_options {
set_user_preference('quiz_report_responses_qtext', $this->showqtext);
set_user_preference('quiz_report_responses_resp', $this->showresponses);
set_user_preference('quiz_report_responses_right', $this->showright);
set_user_preference('quiz_report_responses_which_tries', $this->whichtries);
if (quiz_allows_multiple_tries($this->quiz)) {
set_user_preference('quiz_report_responses_which_tries', $this->whichtries);
}
}
public function resolve_dependencies() {
@@ -72,48 +72,13 @@ class quiz_report_statistics_from_steps_testcase extends mod_quiz_attempt_walkth
*/
public function test_walkthrough_from_csv($quizsettings, $csvdata) {
// CSV data files for these tests were generated using :
// https://github.com/jamiepratt/moodle-quiz-tools/tree/master/responsegenerator
$this->create_quiz_simulate_attempts_and_check_results($quizsettings, $csvdata);
$this->resetAfterTest(true);
question_bank::get_qtype('random')->clear_caches_before_testing();
$this->create_quiz($quizsettings, $csvdata['questions']);
$attemptids = $this->walkthrough_attempts($csvdata['steps']);
if (isset($csvdata['results'])) {
$this->check_attempts_results($csvdata['results'], $attemptids);
}
$this->report = new quiz_statistics_report();
$whichattempts = QUIZ_GRADEAVERAGE; // All attempts.
$whichtries = question_attempt::ALL_TRIES;
$groupstudents = array();
$questions = $this->report->load_and_initialise_questions_for_calculations($this->quiz);
list($quizstats, $questionstats) = $this->report->get_all_stats_and_analysis($this->quiz,
$whichattempts,
$whichtries,
$groupstudents,
$questions);
$qubaids = quiz_statistics_qubaids_condition($this->quiz->id, $groupstudents, $whichattempts);
// We will create some quiz and question stat calculator instances and some response analyser instances, just in order
// to check the last analysed time then returned.
$quizcalc = new \quiz_statistics\calculator();
// Should not be a delay of more than one second between the calculation of stats above and here.
$this->assertTimeCurrent($quizcalc->get_last_calculated_time($qubaids));
$qcalc = new \core_question\statistics\questions\calculator($questions);
$this->assertTimeCurrent($qcalc->get_last_calculated_time($qubaids));
if (isset($csvdata['responsecounts'])) {
$this->check_response_counts($csvdata['responsecounts'], $qubaids, $questions, $whichtries);
}
if (isset($csvdata['qstats'])) {
$this->check_question_stats($csvdata['qstats'], $questionstats);
}
list($questions, $quizstats, $questionstats, $qubaids) =
$this->check_stats_calculations_and_response_analysis($csvdata, $whichattempts, $whichtries, $groupstudents);
if ($quizsettings['testnumber'] === '00') {
$this->check_variants_count_for_quiz_00($questions, $questionstats, $whichtries, $qubaids);
$this->check_quiz_stats_for_quiz_00($quizstats);
@@ -389,4 +354,44 @@ class quiz_report_statistics_from_steps_testcase extends mod_quiz_attempt_walkth
}
}
/**
* Check the question stats and the response counts used in the statistics report. If the appropriate files exist in fixtures/.
*
* @param PHPUnit_Extensions_Database_DataSet_ITable[] $csvdata Data loaded from csv files for this test.
* @param string $whichattempts
* @param string $whichtries
* @param int[] $groupstudents
* @return array with contents 0 => $questions, 1 => $quizstats, 2=> $questionstats, 3=> $qubaids Might be needed for further
* testing.
*/
protected function check_stats_calculations_and_response_analysis($csvdata, $whichattempts, $whichtries, $groupstudents) {
$this->report = new quiz_statistics_report();
$questions = $this->report->load_and_initialise_questions_for_calculations($this->quiz);
list($quizstats, $questionstats) = $this->report->get_all_stats_and_analysis($this->quiz,
$whichattempts,
$whichtries,
$groupstudents,
$questions);
$qubaids = quiz_statistics_qubaids_condition($this->quiz->id, $groupstudents, $whichattempts);
// We will create some quiz and question stat calculator instances and some response analyser instances, just in order
// to check the last analysed time then returned.
$quizcalc = new \quiz_statistics\calculator();
// Should not be a delay of more than one second between the calculation of stats above and here.
$this->assertTimeCurrent($quizcalc->get_last_calculated_time($qubaids));
$qcalc = new \core_question\statistics\questions\calculator($questions);
$this->assertTimeCurrent($qcalc->get_last_calculated_time($qubaids));
if (isset($csvdata['responsecounts'])) {
$this->check_response_counts($csvdata['responsecounts'], $qubaids, $questions, $whichtries);
}
if (isset($csvdata['qstats'])) {
$this->check_question_stats($csvdata['qstats'], $questionstats);
return array($questions, $quizstats, $questionstats, $qubaids);
}
return array($questions, $quizstats, $questionstats, $qubaids);
}
}
@@ -53,6 +53,23 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
*/
protected $randqids;
/**
* The only test in this class. This is run multiple times depending on how many sets of files there are in fixtures/
* directory.
*
* @param array $quizsettings of settings read from csv file quizzes.csv
* @param PHPUnit_Extensions_Database_DataSet_ITable[] $csvdata of data read from csv file "questionsXX.csv",
* "stepsXX.csv" and "resultsXX.csv".
* @dataProvider get_data_for_walkthrough
*/
public function test_walkthrough_from_csv($quizsettings, $csvdata) {
// CSV data files for these tests were generated using :
// https://github.com/jamiepratt/moodle-quiz-tools/tree/master/responsegenerator
$this->create_quiz_simulate_attempts_and_check_results($quizsettings, $csvdata);
}
public function create_quiz($quizsettings, $qs) {
global $SITE, $DB;
$this->setAdminUser();
@@ -103,7 +120,7 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
// Settings from param override defaults.
$aggregratedsettings = $quizsettings + array('course'=>$SITE->id,
$aggregratedsettings = $quizsettings + array('course' => $SITE->id,
'questionsperpage' => 0,
'grade' => 100.0,
'sumgrades' => $sumofgrades);
@@ -121,6 +138,25 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
}
}
/**
* Create quiz, simulate attempts and check results (if resultsXX.csv exists).
*
* @param array $quizsettings Quiz overrides for this quiz.
* @param PHPUnit_Extensions_Database_DataSet_ITable[] $csvdata Data loaded from csv files for this test.
*/
protected function create_quiz_simulate_attempts_and_check_results($quizsettings, $csvdata) {
$this->resetAfterTest(true);
question_bank::get_qtype('random')->clear_caches_before_testing();
$this->create_quiz($quizsettings, $csvdata['questions']);
$attemptids = $this->walkthrough_attempts($csvdata['steps']);
if (isset($csvdata['results'])) {
$this->check_attempts_results($csvdata['results'], $attemptids);
}
}
/**
* Get full path of CSV file.
*
@@ -192,29 +228,6 @@ class mod_quiz_attempt_walkthrough_from_csv_testcase extends advanced_testcase {
return $datasets;
}
/**
* Create a quiz add questions to it, walk through quiz attempts and then check results.
*
* @param $quizsettings array of settings read from csv file quizzes.csv
* @param $csvdata \PHPUnit_Extensions_Database_DataSet_ITable[] of data read from csv file "questionsXX.csv",
* "stepsXX.csv" and "resultsXX.csv".
* @dataProvider get_data_for_walkthrough
*/
public function test_walkthrough_from_csv($quizsettings, $csvdata) {
// CSV data files for these tests were generated using :
// https://github.com/jamiepratt/moodle-quiz-tools/tree/master/responsegenerator
$this->resetAfterTest(true);
question_bank::get_qtype('random')->clear_caches_before_testing();
$this->create_quiz($quizsettings, $csvdata['questions']);
$attemptids = $this->walkthrough_attempts($csvdata['steps']);
$this->check_attempts_results($csvdata['results'], $attemptids);
}
/**
* @param $steps PHPUnit_Extensions_Database_DataSet_ITable the step data from the csv file.
* @return array attempt no as in csv file => the id of the quiz_attempt as stored in the db.
+3 -2
View File
@@ -232,12 +232,13 @@ function survey_print_recent_activity($course, $viewfullnames, $timestart) {
$slist = implode(',', $ids); // there should not be hundreds of glossaries in one course, right?
$allusernames = user_picture::fields('u');
$rs = $DB->get_recordset_sql("SELECT sa.userid, sa.survey, MAX(sa.time) AS time,
u.firstname, u.lastname, u.email, u.picture
$allusernames
FROM {survey_answers} sa
JOIN {user} u ON u.id = sa.userid
WHERE sa.survey IN ($slist) AND sa.time > ?
GROUP BY sa.userid, sa.survey, u.firstname, u.lastname, u.email, u.picture
GROUP BY sa.userid, sa.survey, $allusernames
ORDER BY time ASC", array($timestart));
if (!$rs->valid()) {
$rs->close(); // Not going to iterate (but exit), close rs
+70
View File
@@ -0,0 +1,70 @@
@mod @mod_wiki
Feature: Edited wiki pages handle tags correctly
In order to get wiki pages properly labelled
As a user
I need to introduce the tags while editing
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@asd.com |
| student1 | Student | 1 | student1@asd.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And I log in as "teacher1"
And I follow "Course 1"
And I turn editing mode on
And I add a "Wiki" to section "1" and I fill the form with:
| Wiki name | Test wiki name |
| Description | Test wiki description |
| First page name | First page |
| Wiki mode | Collaborative wiki |
And I log out
Scenario: Wiki page edition of custom tags works as expected
Given I log in as "student1"
And I follow "Course 1"
And I follow "Test wiki name"
And I press "Create page"
When I set the following fields to these values:
| HTML format | Student page contents to be tagged |
| Other tags (enter tags separated by commas) | Example, Page, Cool |
And I press "Save"
Then I should see "Example" in the ".wiki-tags" "css_element"
And I should see "Page" in the ".wiki-tags" "css_element"
And I should see "Cool" in the ".wiki-tags" "css_element"
And I follow "Edit"
And the field "Other tags (enter tags separated by commas)" matches value "Example, Page, Cool"
And I press "Cancel"
Scenario: Wiki page edition of official tags works as expected
Given I log in as "admin"
And I expand "Site administration" node
And I expand "Appearance" node
And I follow "Manage tags"
And I set the field "otagsadd" to "OT1, OT2, OT3"
And I press "Add official tags"
And I log out
And I log in as "student1"
And I follow "Course 1"
And I follow "Test wiki name"
And I press "Create page"
And the "tags[officialtags][]" select box should contain "OT1"
And the "tags[officialtags][]" select box should contain "OT2"
And the "tags[officialtags][]" select box should contain "OT3"
When I set the following fields to these values:
| HTML format | Student page contents to be tagged |
| tags[officialtags][] | OT1, OT3 |
And I press "Save"
Then I should see "OT1" in the ".wiki-tags" "css_element"
And I should see "OT3" in the ".wiki-tags" "css_element"
And I should not see "OT2" in the ".wiki-tags" "css_element"
And I follow "Edit"
And the field "tags[officialtags][]" matches value "OT1, OT3"
And the field "tags[officialtags][]" does not match value "OT2"
And I press "Cancel"
@@ -0,0 +1,42 @@
@core @core_my
Feature: Restrict which blocks can be added to My home
In order to restrict which blocks can be added
As a student I need to ensure I can add the blocks
As an admin I need to remove the capability to add a blocks
As a student I need to ensure I can't add the blocks any more
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@asd.com |
And the following "courses" exist:
| fullname | shortname | format |
| Course 1 | C1 | topics |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
Scenario: The comments block can be added to My home by default
And I log in as "student1"
And I follow "My home"
And I press "Customise this page"
Then the "Add a block" select box should contain "Comments"
And the "Add a block" select box should contain "Courses"
And the "Add a block" select box should contain "HTML"
And the "Add a block" select box should contain "Tags"
@javascript
Scenario: Remove the ability to add the comments block to My home
When I log in as "admin"
And I set the following system permissions of "Authenticated user" role:
| block/comments:myaddinstance | Prohibit |
| block/course_list:myaddinstance | Prohibit |
| block/html:myaddinstance | Prohibit |
And I log out
And I log in as "student1"
And I follow "My home"
And I press "Customise this page"
Then the "Add a block" select box should not contain "Comments"
And the "Add a block" select box should not contain "Courses"
And the "Add a block" select box should not contain "HTML"
And the "Add a block" select box should contain "Tags"
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+8 -8
View File
@@ -197,7 +197,7 @@ class qtype_multichoice_single_question extends qtype_multichoice_base {
public function prepare_simulated_post_data($simulatedresponse) {
$ansid = 0;
foreach ($this->answers as $answer) {
if ($answer->answer == $simulatedresponse['answer']) {
if (clean_param($answer->answer, PARAM_NOTAGS) == $simulatedresponse['answer']) {
$ansid = $answer->id;
}
}
@@ -360,12 +360,11 @@ class qtype_multichoice_multi_question extends qtype_multichoice_base {
public function prepare_simulated_post_data($simulatedresponse) {
$postdata = array();
foreach ($simulatedresponse as $ans => $checked) {
if ($checked) {
foreach ($this->answers as $ansid => $answer) {
if ($answer->answer == $ans) {
$fieldno = array_search($ansid, $this->order);
$postdata[$this->field($fieldno)] = '1';
}
foreach ($this->answers as $ansid => $answer) {
if (clean_param($answer->answer, PARAM_NOTAGS) == $ans) {
$fieldno = array_search($ansid, $this->order);
$postdata[$this->field($fieldno)] = $checked;
break;
}
}
}
@@ -376,7 +375,8 @@ class qtype_multichoice_multi_question extends qtype_multichoice_base {
$simulatedresponse = array();
foreach ($this->order as $fieldno => $ansid) {
if (isset($postdata[$this->field($fieldno)])) {
$simulatedresponse[$this->answers[$ansid]->answer] = 1;
$checked = $postdata[$this->field($fieldno)];
$simulatedresponse[clean_param($this->answers[$ansid]->answer, PARAM_NOTAGS)] = $checked;
}
}
ksort($simulatedresponse);
@@ -91,27 +91,23 @@ class qtype_multichoice_multi_question_test extends advanced_testcase {
public function test_grading() {
$question = test_question_maker::make_a_multichoice_multi_question();
$question->shuffleanswers = false;
$question->start_attempt(new question_attempt_step(), 1);
$this->assertEquals(array(1, question_state::$gradedright),
$question->grade_response(array('choice0' => '1', 'choice2' => '1')));
$question->grade_response($question->prepare_simulated_post_data(array('A' => 1, 'C' => 1))));
$this->assertEquals(array(0.5, question_state::$gradedpartial),
$question->grade_response(array('choice0' => '1')));
$question->grade_response($question->prepare_simulated_post_data(array('A' => 1))));
$this->assertEquals(array(0, question_state::$gradedwrong),
$question->grade_response(
array('choice0' => '1', 'choice1' => '1', 'choice2' => '1')));
$question->grade_response($question->prepare_simulated_post_data(array('A' => 1, 'B' => 1, 'C' => 1))));
$this->assertEquals(array(0, question_state::$gradedwrong),
$question->grade_response(array('choice1' => '1')));
$question->grade_response($question->prepare_simulated_post_data(array('B' => 1))));
}
public function test_get_correct_response() {
$question = test_question_maker::make_a_multichoice_multi_question();
$question->shuffleanswers = false;
$question->start_attempt(new question_attempt_step(), 1);
$this->assertEquals(array('choice0' => '1', 'choice2' => '1'),
$question->get_correct_response());
$this->assertEquals($question->prepare_simulated_post_data(array('A' => 1, 'C' => 1)), $question->get_correct_response());
}
public function test_get_question_summary() {
@@ -131,7 +127,7 @@ class qtype_multichoice_multi_question_test extends advanced_testcase {
$mc->shuffleanswers = false;
$mc->start_attempt(new question_attempt_step(), 1);
$summary = $mc->summarise_response(array('choice1' => 1, 'choice2' => 1),
$summary = $mc->summarise_response($mc->prepare_simulated_post_data(array('B' => 1, 'C' => 1)),
test_question_maker::get_a_qa($mc));
$this->assertEquals('B; C', $summary);
@@ -139,14 +135,33 @@ class qtype_multichoice_multi_question_test extends advanced_testcase {
public function test_classify_response() {
$mc = test_question_maker::make_a_multichoice_multi_question();
$mc->shuffleanswers = false;
$mc->start_attempt(new question_attempt_step(), 1);
$this->assertEquals(array(
13 => new question_classified_response(13, 'A', 0.5),
14 => new question_classified_response(14, 'B', -1.0),
), $mc->classify_response(array('choice0' => 1, 'choice1' => 1)));
), $mc->classify_response($mc->prepare_simulated_post_data(array('A' => 1, 'B' => 1))));
$this->assertEquals(array(), $mc->classify_response(array()));
}
public function test_prepare_simulated_post_data() {
$mc = test_question_maker::make_a_multichoice_multi_question();
$mc->start_attempt(new question_attempt_step(), 1);
$correctanswers = array(
array(),
array('A' => 1),
array('B' => 1, 'D' => 0),
array('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0),
array('A' => 1, 'B' => 0, 'C' => 1, 'D' => 0),
array('A' => 1, 'B' => 0, 'C' => 1, 'D' => 1),
array('A' => 1, 'B' => 1, 'C' => 1, 'D' => 1)
);
foreach ($correctanswers as $correctanswer) {
$postdata = $mc->prepare_simulated_post_data($correctanswer);
$simulatedreponse = $mc->get_student_response_values_for_simulation($postdata);
$this->assertEquals($correctanswer, $simulatedreponse, '', 0, 10, true);
}
}
}
@@ -84,15 +84,14 @@ class qtype_multichoice_single_question_test extends advanced_testcase {
public function test_grading() {
$question = test_question_maker::make_a_multichoice_single_question();
$question->shuffleanswers = false;
$question->start_attempt(new question_attempt_step(), 1);
$this->assertEquals(array(1, question_state::$gradedright),
$question->grade_response(array('answer' => 0)));
$question->grade_response($question->prepare_simulated_post_data(array('answer' => 'A'))));
$this->assertEquals(array(-0.3333333, question_state::$gradedwrong),
$question->grade_response(array('answer' => 1)));
$question->grade_response($question->prepare_simulated_post_data(array('answer' => 'B'))));
$this->assertEquals(array(-0.3333333, question_state::$gradedwrong),
$question->grade_response(array('answer' => 2)));
$question->grade_response($question->prepare_simulated_post_data(array('answer' => 'C'))));
}
public function test_grading_rounding_three_right() {
@@ -104,7 +103,6 @@ class qtype_multichoice_single_question_test extends advanced_testcase {
$mc->generalfeedback = '1, 3 and 5 are the odd numbers.';
$mc->qtype = question_bank::get_qtype('multichoice');
$mc->shuffleanswers = 0;
$mc->answernumbering = 'abc';
test_question_maker::set_standard_combined_feedback_fields($mc);
@@ -120,40 +118,34 @@ class qtype_multichoice_single_question_test extends advanced_testcase {
$mc->start_attempt(new question_attempt_step(), 1);
list($grade, $state) = $mc->grade_response(
array('choice0' => 1, 'choice2' => 1, 'choice4' => 1));
list($grade, $state) = $mc->grade_response($mc->prepare_simulated_post_data(array('1' => '1', '3' => '1', '5' => '1')));
$this->assertEquals(1, $grade, '', 0.000001);
$this->assertEquals(question_state::$gradedright, $state);
}
public function test_get_correct_response() {
$question = test_question_maker::make_a_multichoice_single_question();
$question->shuffleanswers = false;
$question->start_attempt(new question_attempt_step(), 1);
$this->assertEquals(array('answer' => 0),
$question->get_correct_response());
$this->assertEquals($question->prepare_simulated_post_data(array('answer' => 'A')), $question->get_correct_response());
}
public function test_summarise_response() {
$mc = test_question_maker::make_a_multichoice_single_question();
$mc->shuffleanswers = false;
$mc->start_attempt(new question_attempt_step(), 1);
$summary = $mc->summarise_response(array('answer' => 0),
test_question_maker::get_a_qa($mc));
$summary = $mc->summarise_response($mc->prepare_simulated_post_data(array('answer' => 'A')),
test_question_maker::get_a_qa($mc));
$this->assertEquals('A', $summary);
}
public function test_classify_response() {
$mc = test_question_maker::make_a_multichoice_single_question();
$mc->shuffleanswers = false;
$mc->start_attempt(new question_attempt_step(), 1);
$this->assertEquals(array(
$mc->id => new question_classified_response(14, 'B', -0.3333333),
), $mc->classify_response(array('answer' => 1)));
$this->assertEquals(array($mc->id => new question_classified_response(14, 'B', -0.3333333)),
$mc->classify_response($mc->prepare_simulated_post_data(array('answer' => 'B'))));
$this->assertEquals(array(
$mc->id => question_classified_response::no_response(),
+3
View File
@@ -6,3 +6,6 @@
#page-report-completion-index .export-actions {
text-align: center;
}
#page-report-completion-index.dir-rtl #completion-progress th svg {
direction: ltr;
}
+2 -2
View File
@@ -481,7 +481,7 @@ class report_log_table_log extends table_sql {
$ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
$ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
$courseparams['contextlevel'] = CONTEXT_COURSE;
$sql = "SELECT c.id,c.shortname $ccselect FROM {course} as c
$sql = "SELECT c.id,c.shortname $ccselect FROM {course} c
$ccjoin
WHERE c.id " . $coursesql;
@@ -496,4 +496,4 @@ class report_log_table_log extends table_sql {
}
}
}
}
}
+1 -1
View File
@@ -387,7 +387,7 @@ class report_loglive_table_log extends table_sql {
$ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
$ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
$courseparams['contextlevel'] = CONTEXT_COURSE;
$sql = "SELECT c.id,c.shortname $ccselect FROM {course} as c
$sql = "SELECT c.id,c.shortname $ccselect FROM {course} c
$ccjoin
WHERE c.id " . $coursesql;
+1
View File
@@ -5,3 +5,4 @@
#page-report-progress-index .completion_prev {display:inline;margin-right:2em;}
#page-report-progress-index .completion_pagingbar p {display:inline;margin:0;}
#page-report-progress-index .completion_next {display:inline;margin-left:2em;}
#page-report-progress-index.dir-rtl #completion-progress th svg {direction:ltr;}
@@ -24,7 +24,7 @@
*/
$string['alfresco_url'] = 'Alfresco URL';
$string['alfrescourltext'] = 'Afresco API url should be: http://yoursite.com/alfresco/api';
$string['alfrescourltext'] = 'Afresco API URL should be: http://yoursite.com/alfresco/api or http://yoursite.com/alfresco/soapapi for Alfresco 4.2.d or greater.';
$string['alfresco:view'] = 'View alfresco repository';
$string['configplugin'] = 'Alfresco configuration';
$string['notitle'] = 'notitle';
Binary file not shown.

Before

Width:  |  Height:  |  Size: 894 B

After

Width:  |  Height:  |  Size: 1.1 KiB

+12
View File
@@ -2027,4 +2027,16 @@ body.lockscroll {
.editor_atto_menu .moodle-dialogue-content {
padding: 0px;
border: inherit;
.well {
min-height: 20px;
padding: 19px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0,0,0,0.05);
}
.well-small {
padding: 9px;
}
+1 -1
View File
@@ -71,7 +71,7 @@ class user_filter_courserole extends user_filter_type {
$objs[] = $mform->createElement('select', $this->_name .'_ct', null, $this->get_course_categories());
$objs[] = $mform->createElement('text', $this->_name, null);
$grp =& $mform->addElement('group', $this->_name.'_grp', $this->_label, $objs, '', false);
$mform->setType($this->_name, PARAM_ALPHANUMEXT);
$mform->setType($this->_name, PARAM_TEXT);
if ($this->_advanced) {
$mform->setAdvanced($this->_name.'_grp');
}
+3 -3
View File
@@ -451,9 +451,9 @@ function profile_list_categories() {
* @param string $redirect
*/
function profile_edit_category($id, $redirect) {
global $DB, $OUTPUT;
global $DB, $OUTPUT, $CFG;
require_once('index_category_form.php');
require_once($CFG->dirroot.'/user/profile/index_category_form.php');
$categoryform = new category_form();
if ($category = $DB->get_record('user_info_category', array('id' => $id))) {
@@ -515,7 +515,7 @@ function profile_edit_field($id, $datatype, $redirect) {
$field->description = clean_text($field->description, $field->descriptionformat);
$field->description = array('text' => $field->description, 'format' => $field->descriptionformat, 'itemid' => 0);
require_once('index_field_form.php');
require_once($CFG->dirroot.'/user/profile/index_field_form.php');
$fieldform = new field_form(null, $field->datatype);
// Convert the data format for.
+6 -6
View File
@@ -108,12 +108,11 @@ switch ($action) {
// Normal form.
}
// Print the header.
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('profilefields', 'admin'));
// Show all categories.
$categories = $DB->get_records('user_info_category', null, 'sortorder ASC');
// Check that we have at least one category defined.
if ($DB->count_records('user_info_category') == 0) {
if (empty($categories)) {
$defaultcategory = new stdClass();
$defaultcategory->name = $strdefaultcategory;
$defaultcategory->sortorder = 1;
@@ -121,8 +120,9 @@ if ($DB->count_records('user_info_category') == 0) {
redirect($redirect);
}
// Show all categories.
$categories = $DB->get_records('user_info_category', null, 'sortorder ASC');
// Print the header.
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('profilefields', 'admin'));
foreach ($categories as $category) {
$table = new html_table();
+2 -2
View File
@@ -73,13 +73,13 @@ class category_form extends moodleform {
$data = (object)$data;
$duplicate = $DB->record_exists('user_info_category', array('name' => $data->name));
$duplicate = $DB->get_field('user_info_category', 'id', array('name' => $data->name));
// Check the name is unique.
if (!empty($data->id)) { // We are editing an existing record.
$olddata = $DB->get_record('user_info_category', array('id' => $data->id));
// Name has changed, new name in use, new name in use by another record.
$dupfound = (($olddata->name !== $data->name) && $duplicate && ($data->id != $duplicate->id));
$dupfound = (($olddata->name !== $data->name) && $duplicate && ($data->id != $duplicate));
} else { // New profile category.
$dupfound = $duplicate;
}
+2 -2
View File
@@ -29,11 +29,11 @@
defined('MOODLE_INTERNAL') || die();
$version = 2014041700.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2014042200.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '2.7beta+ (Build: 20140417)'; // Human-friendly version name
$release = '2.7beta+ (Build: 20140422)'; // Human-friendly version name
$branch = '27'; // This version's branch.
$maturity = MATURITY_BETA; // This version's maturity level.