Merge branch 'MOODLE_25_STABLE' into install_25_STABLE
This commit is contained in:
@@ -49,21 +49,7 @@
|
||||
if ($shibbolethauth->user_login($frm->username, $frm->password)
|
||||
&& $user = authenticate_user_login($frm->username, $frm->password)) {
|
||||
|
||||
enrol_check_plugins($user);
|
||||
session_set_user($user);
|
||||
|
||||
$USER->loggedin = true;
|
||||
$USER->site = $CFG->wwwroot; // for added security, store the site in the
|
||||
|
||||
update_user_login_times();
|
||||
|
||||
// Don't show previous shibboleth username on login page
|
||||
|
||||
set_login_session_preferences();
|
||||
|
||||
unset($SESSION->lang);
|
||||
$SESSION->justloggedin = true;
|
||||
|
||||
complete_user_login($user);
|
||||
add_to_log(SITEID, 'user', 'login', "view.php?id=$USER->id&course=".SITEID, $USER->id, 0, $USER->id);
|
||||
|
||||
if (user_not_fully_set_up($USER)) {
|
||||
|
||||
+15
-14
@@ -41,7 +41,7 @@ class core_badges_renderer extends plugin_renderer_base {
|
||||
$bname = $badge->name;
|
||||
$imageurl = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', 'f1', false);
|
||||
} else {
|
||||
$bname = $badge->assertion->badge->name;
|
||||
$bname = s($badge->assertion->badge->name);
|
||||
$imageurl = $badge->imageUrl;
|
||||
}
|
||||
|
||||
@@ -421,47 +421,48 @@ class core_badges_renderer extends plugin_renderer_base {
|
||||
}
|
||||
|
||||
$datatable->data[] = array($this->output->heading(get_string('issuerdetails', 'badges'), 3), '');
|
||||
$datatable->data[] = array(get_string('issuername', 'badges'), $issuer->name);
|
||||
$datatable->data[] = array(get_string('issuername', 'badges'), s($issuer->name));
|
||||
$datatable->data[] = array(get_string('issuerurl', 'badges'),
|
||||
html_writer::tag('a', $issuer->origin, array('href' => $issuer->origin)));
|
||||
html_writer::tag('a', s($issuer->origin), array('href' => $issuer->origin)));
|
||||
if (isset($issuer->contact)) {
|
||||
$datatable->data[] = array(get_string('contact', 'badges'), obfuscate_mailto($issuer->contact));
|
||||
}
|
||||
$datatable->data[] = array($this->output->heading(get_string('badgedetails', 'badges'), 3), '');
|
||||
$datatable->data[] = array(get_string('name'), $assertion->badge->name);
|
||||
$datatable->data[] = array(get_string('description', 'badges'), $assertion->badge->description);
|
||||
$datatable->data[] = array(get_string('name'), s($assertion->badge->name));
|
||||
$datatable->data[] = array(get_string('description', 'badges'), s($assertion->badge->description));
|
||||
$datatable->data[] = array(get_string('bcriteria', 'badges'),
|
||||
html_writer::tag('a', $assertion->badge->criteria, array('href' => $assertion->badge->criteria)));
|
||||
html_writer::tag('a', s($assertion->badge->criteria), array('href' => $assertion->badge->criteria)));
|
||||
$datatable->data[] = array($this->output->heading(get_string('issuancedetails', 'badges'), 3), '');
|
||||
if (isset($assertion->issued_on)) {
|
||||
$datatable->data[] = array(get_string('dateawarded', 'badges'), $assertion->issued_on);
|
||||
$issuedate = !strtotime($assertion->issued_on) ? s($assertion->issued_on) : strtotime($assertion->issued_on);
|
||||
$datatable->data[] = array(get_string('dateawarded', 'badges'), userdate($issuedate));
|
||||
}
|
||||
if (isset($assertion->badge->expire)) {
|
||||
if (isset($assertion->expires)) {
|
||||
$today_date = date('Y-m-d');
|
||||
$today = strtotime($today_date);
|
||||
$expiration = strtotime($assertion->badge->expire);
|
||||
$expiration = !strtotime($assertion->expires) ? s($assertion->expires) : strtotime($assertion->expires);
|
||||
if ($expiration < $today) {
|
||||
$cell = new html_table_cell($assertion->badge->expire . get_string('warnexpired', 'badges'));
|
||||
$cell = new html_table_cell(userdate($expiration) . get_string('warnexpired', 'badges'));
|
||||
$cell->attributes = array('class' => 'notifyproblem warning');
|
||||
$datatable->data[] = array(get_string('expirydate', 'badges'), $cell);
|
||||
|
||||
$image = html_writer::start_tag('div', array('class' => 'badge'));
|
||||
$image .= html_writer::empty_tag('img', array('src' => $issued['badge']['image']));
|
||||
$image .= html_writer::empty_tag('img', array('src' => $issued->imageUrl));
|
||||
$image .= html_writer::start_tag('span', array('class' => 'expired'))
|
||||
. $this->output->pix_icon('i/expired',
|
||||
get_string('expireddate', 'badges', $assertion->badge->expire),
|
||||
get_string('expireddate', 'badges', userdate($expiration)),
|
||||
'moodle',
|
||||
array('class' => 'expireimage'))
|
||||
. html_writer::end_tag('span');
|
||||
$image .= html_writer::end_tag('div');
|
||||
$imagetable->data[0] = array($image);
|
||||
} else {
|
||||
$datatable->data[] = array(get_string('expirydate', 'badges'), $assertion->badge->expire);
|
||||
$datatable->data[] = array(get_string('expirydate', 'badges'), userdate($expiration));
|
||||
}
|
||||
}
|
||||
if (isset($assertion->evidence)) {
|
||||
$datatable->data[] = array(get_string('evidence', 'badges'),
|
||||
html_writer::tag('a', $assertion->evidence, array('href' => $assertion->evidence)));
|
||||
html_writer::tag('a', s($assertion->evidence), array('href' => $assertion->evidence)));
|
||||
}
|
||||
$table->attributes = array('class' => 'generalbox boxaligncenter issuedbadgebox');
|
||||
$table->data[] = array(html_writer::table($imagetable), html_writer::table($datatable));
|
||||
|
||||
@@ -100,6 +100,7 @@ class moodlequickform_guideeditor extends HTML_QuickForm_input {
|
||||
$mode = gradingform_guide_controller::DISPLAY_EDIT_FULL;
|
||||
$module = array('name'=>'gradingform_guideeditor',
|
||||
'fullpath'=>'/grade/grading/form/guide/js/guideeditor.js',
|
||||
'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'),
|
||||
'strings' => array(
|
||||
array('confirmdeletecriterion', 'gradingform_guide'),
|
||||
array('clicktoedit', 'gradingform_guide'),
|
||||
|
||||
@@ -10,7 +10,7 @@ M.gradingform_guide.init = function(Y, options) {
|
||||
currentfocus = e.currentTarget;
|
||||
});
|
||||
Y.all('.markingguidecomment').on('click', function(e) {
|
||||
currentfocus.set('value', currentfocus.get('value') + '\n' + e.currentTarget.get('innerHTML'));
|
||||
currentfocus.set('value', currentfocus.get('value') + '\n' + e.currentTarget.get('text'));
|
||||
currentfocus.focus();
|
||||
});
|
||||
|
||||
|
||||
@@ -111,9 +111,9 @@ M.gradingform_guideeditor.editmode = function(el, editmode) {
|
||||
value = M.str.gradingform_guide.clicktoedit
|
||||
taplain.addClass('empty')
|
||||
}
|
||||
taplain.one('.textvalue').set('innerHTML', value)
|
||||
taplain.one('.textvalue').set('innerHTML', Y.Escape.html(value))
|
||||
if (tb) {
|
||||
tbplain.one('.textvalue').set('innerHTML', tb.get('value'))
|
||||
tbplain.one('.textvalue').set('innerHTML', Y.Escape.html(tb.get('value')))
|
||||
}
|
||||
// hide/display textarea, textbox and plaintexts
|
||||
taplain.removeClass('hiddenelement')
|
||||
|
||||
@@ -882,7 +882,7 @@ class gradingform_guide_instance extends gradingform_instance {
|
||||
if (!empty($this->validationerrors)) {
|
||||
foreach ($this->validationerrors as $id => $err) {
|
||||
$a = new stdClass();
|
||||
$a->criterianame = $criteria[$id]['shortname'];
|
||||
$a->criterianame = s($criteria[$id]['shortname']);
|
||||
$a->maxscore = $criteria[$id]['maxscore'];
|
||||
$html .= html_writer::tag('div', get_string('err_scoreinvalid', 'gradingform_guide', $a),
|
||||
array('class' => 'gradingform_guide-error'));
|
||||
|
||||
@@ -93,20 +93,20 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
|
||||
|
||||
$shortname = html_writer::empty_tag('input', array('type'=> 'text',
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][shortname]', 'value' => htmlspecialchars($criterion['shortname']),
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][shortname]', 'value' => $criterion['shortname'],
|
||||
'id ' => '{NAME}[criteria][{CRITERION-id}][shortname]'));
|
||||
$shortname = html_writer::tag('div', $shortname, array('class'=>'criterionname'));
|
||||
$description = html_writer::tag('textarea', htmlspecialchars($criterion['description']),
|
||||
$description = html_writer::tag('textarea', s($criterion['description']),
|
||||
array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '65', 'rows' => '5'));
|
||||
$description = html_writer::tag('div', $description, array('class'=>'criteriondesc'));
|
||||
|
||||
$descriptionmarkers = html_writer::tag('textarea', htmlspecialchars($criterion['descriptionmarkers']),
|
||||
$descriptionmarkers = html_writer::tag('textarea', s($criterion['descriptionmarkers']),
|
||||
array('name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]', 'cols' => '65', 'rows' => '5'));
|
||||
$descriptionmarkers = html_writer::tag('div', $descriptionmarkers, array('class'=>'criteriondescmarkers'));
|
||||
|
||||
$maxscore = html_writer::empty_tag('input', array('type'=> 'text',
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]', 'size' => '3',
|
||||
'value' => htmlspecialchars($criterion['maxscore']),
|
||||
'value' => $criterion['maxscore'],
|
||||
'id' => '{NAME}[criteria][{CRITERION-id}][maxscore]'));
|
||||
$maxscore = html_writer::tag('div', $maxscore, array('class'=>'criterionmaxscore'));
|
||||
} else {
|
||||
@@ -125,7 +125,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
$mode == gradingform_guide_controller::DISPLAY_VIEW) {
|
||||
$descriptionclass = 'descriptionreadonly';
|
||||
}
|
||||
$shortname = html_writer::tag('div', $criterion['shortname'],
|
||||
$shortname = html_writer::tag('div', s($criterion['shortname']),
|
||||
array('class'=>'criterionshortname', 'name' => '{NAME}[criteria][{CRITERION-id}][shortname]'));
|
||||
$descmarkerclass = '';
|
||||
$descstudentclass = '';
|
||||
@@ -137,13 +137,13 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
$descstudentclass = ' hide';
|
||||
}
|
||||
}
|
||||
$description = html_writer::tag('div', $criterion['description'],
|
||||
$description = html_writer::tag('div', s($criterion['description']),
|
||||
array('class'=>'criteriondescription'.$descstudentclass,
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]'));
|
||||
$descriptionmarkers = html_writer::tag('div', $criterion['descriptionmarkers'],
|
||||
$descriptionmarkers = html_writer::tag('div', s($criterion['descriptionmarkers']),
|
||||
array('class'=>'criteriondescriptionmarkers'.$descmarkerclass,
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]'));
|
||||
$maxscore = html_writer::tag('div', $criterion['maxscore'],
|
||||
$maxscore = html_writer::tag('div', s($criterion['maxscore']),
|
||||
array('class'=>'criteriondescriptionscore', 'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]'));
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
$scoreclass = 'error';
|
||||
$currentscore = $validationerrors[$criterion['id']]['score']; // Show invalid score in form.
|
||||
}
|
||||
$input = html_writer::tag('textarea', htmlspecialchars($currentremark),
|
||||
$input = html_writer::tag('textarea', s($currentremark),
|
||||
array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '65', 'rows' => '5',
|
||||
'class' => 'markingguideremark'));
|
||||
$criteriontemplate .= html_writer::tag('td', $input, array('class' => 'remark'));
|
||||
@@ -202,7 +202,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
$score .= html_writer::empty_tag('input', array('type'=> 'text',
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][score]', 'class' => $scoreclass,
|
||||
'id' => '{NAME}[criteria][{CRITERION-id}][score]',
|
||||
'size' => '3', 'value' => htmlspecialchars($currentscore)));
|
||||
'size' => '3', 'value' => $currentscore));
|
||||
$score .= '/'.$maxscore;
|
||||
|
||||
$criteriontemplate .= html_writer::tag('td', $score, array('class' => 'score'));
|
||||
@@ -211,9 +211,9 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
|
||||
} else if ($mode == gradingform_guide_controller::DISPLAY_REVIEW ||
|
||||
$mode == gradingform_guide_controller::DISPLAY_VIEW) {
|
||||
$criteriontemplate .= html_writer::tag('td', $currentremark, array('class' => 'remark'));
|
||||
$criteriontemplate .= html_writer::tag('td', s($currentremark), array('class' => 'remark'));
|
||||
if (!empty($options['showmarkspercriterionstudents'])) {
|
||||
$criteriontemplate .= html_writer::tag('td', htmlspecialchars($currentscore). ' / '.$maxscore,
|
||||
$criteriontemplate .= html_writer::tag('td', s($currentscore). ' / '.$maxscore,
|
||||
array('class' => 'score'));
|
||||
}
|
||||
}
|
||||
@@ -272,7 +272,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
$criteriontemplate .= html_writer::end_tag('td'); // Controls.
|
||||
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
|
||||
'name' => '{NAME}[comments][{COMMENT-id}][sortorder]', 'value' => $comment['sortorder']));
|
||||
$description = html_writer::tag('textarea', htmlspecialchars($comment['description']),
|
||||
$description = html_writer::tag('textarea', s($comment['description']),
|
||||
array('name' => '{NAME}[comments][{COMMENT-id}][description]', 'cols' => '65', 'rows' => '5'));
|
||||
$description = html_writer::tag('div', $description, array('class'=>'criteriondesc'));
|
||||
} else {
|
||||
@@ -283,12 +283,12 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
'name' => '{NAME}[comments][{COMMENT-id}][description]', 'value' => $comment['description']));
|
||||
}
|
||||
if ($mode == gradingform_guide_controller::DISPLAY_EVAL) {
|
||||
$description = html_writer::tag('span', htmlspecialchars($comment['description']),
|
||||
$description = html_writer::tag('span', s($comment['description']),
|
||||
array('name' => '{NAME}[comments][{COMMENT-id}][description]',
|
||||
'title' => get_string('clicktocopy', 'gradingform_guide'),
|
||||
'id' => '{NAME}[comments][{COMMENT-id}]', 'class'=>'markingguidecomment'));
|
||||
} else {
|
||||
$description = $comment['description'];
|
||||
$description = s($comment['description']);
|
||||
}
|
||||
}
|
||||
$descriptionclass = 'description';
|
||||
|
||||
@@ -93,8 +93,8 @@ M.gradingform_rubriceditor.editmode = function(el, editmode, focustb) {
|
||||
value = (el.hasClass('level')) ? M.str.gradingform_rubric.levelempty : M.str.gradingform_rubric.criterionempty
|
||||
taplain.addClass('empty')
|
||||
}
|
||||
taplain.one('.textvalue').set('innerHTML', value)
|
||||
if (tb) tbplain.one('.textvalue').set('innerHTML', tb.get('value'))
|
||||
taplain.one('.textvalue').set('innerHTML', Y.Escape.html(value));
|
||||
if (tb) tbplain.one('.textvalue').set('innerHTML', Y.Escape.html(tb.get('value')));
|
||||
// hide/display textarea, textbox and plaintexts
|
||||
taplain.removeClass('hiddenelement')
|
||||
ta.addClass('hiddenelement')
|
||||
|
||||
@@ -79,13 +79,13 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
|
||||
}
|
||||
$criteriontemplate .= html_writer::end_tag('td'); // .controls
|
||||
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
|
||||
$description = html_writer::tag('textarea', htmlspecialchars($criterion['description']), array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '10', 'rows' => '5'));
|
||||
$description = html_writer::tag('textarea', s($criterion['description']), array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '10', 'rows' => '5'));
|
||||
} else {
|
||||
if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
|
||||
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
|
||||
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][description]', 'value' => $criterion['description']));
|
||||
}
|
||||
$description = $criterion['description'];
|
||||
$description = s($criterion['description']);
|
||||
}
|
||||
$descriptionclass = 'description';
|
||||
if (isset($criterion['error_description'])) {
|
||||
@@ -111,12 +111,12 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
|
||||
$currentremark = $value['remark'];
|
||||
}
|
||||
if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
|
||||
$input = html_writer::tag('textarea', htmlspecialchars($currentremark), array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '10', 'rows' => '5'));
|
||||
$input = html_writer::tag('textarea', s($currentremark), array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '10', 'rows' => '5'));
|
||||
$criteriontemplate .= html_writer::tag('td', $input, array('class' => 'remark'));
|
||||
} else if ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN) {
|
||||
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
|
||||
}else if ($mode == gradingform_rubric_controller::DISPLAY_REVIEW || $mode == gradingform_rubric_controller::DISPLAY_VIEW) {
|
||||
$criteriontemplate .= html_writer::tag('td', $currentremark, array('class' => 'remark'));
|
||||
$criteriontemplate .= html_writer::tag('td', s($currentremark), array('class' => 'remark'));
|
||||
}
|
||||
}
|
||||
$criteriontemplate .= html_writer::end_tag('tr'); // .criterion
|
||||
@@ -169,7 +169,7 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
|
||||
$leveltemplate = html_writer::start_tag('td', $tdattributes);
|
||||
$leveltemplate .= html_writer::start_tag('div', array('class' => 'level-wrapper'));
|
||||
if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
|
||||
$definition = html_writer::tag('textarea', htmlspecialchars($level['definition']), array('name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4'));
|
||||
$definition = html_writer::tag('textarea', s($level['definition']), array('name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4'));
|
||||
$score = html_writer::label(get_string('criterionempty', 'gradingform_rubric'), '{NAME}criteria{CRITERION-id}levels{LEVEL-id}', false, array('class' => 'accesshide'));
|
||||
$score .= html_writer::empty_tag('input', array('type' => 'text','id' => '{NAME}criteria{CRITERION-id}levels{LEVEL-id}', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'size' => '3', 'value' => $level['score']));
|
||||
} else {
|
||||
@@ -177,7 +177,7 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
|
||||
$leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'value' => $level['definition']));
|
||||
$leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'value' => $level['score']));
|
||||
}
|
||||
$definition = $level['definition'];
|
||||
$definition = s($level['definition']);
|
||||
$score = $level['score'];
|
||||
}
|
||||
if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
|
||||
|
||||
@@ -106,6 +106,7 @@ class MoodleQuickForm_rubriceditor extends HTML_QuickForm_input {
|
||||
if (!$this->_flagFrozen) {
|
||||
$mode = gradingform_rubric_controller::DISPLAY_EDIT_FULL;
|
||||
$module = array('name'=>'gradingform_rubriceditor', 'fullpath'=>'/grade/grading/form/rubric/js/rubriceditor.js',
|
||||
'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'),
|
||||
'strings' => array(array('confirmdeletecriterion', 'gradingform_rubric'), array('confirmdeletelevel', 'gradingform_rubric'),
|
||||
array('criterionempty', 'gradingform_rubric'), array('levelempty', 'gradingform_rubric')
|
||||
));
|
||||
|
||||
+21
-1
@@ -94,6 +94,25 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
|
||||
editors_head_setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by HTML_QuickForm whenever form event is made on this element
|
||||
*
|
||||
* @param string $event Name of event
|
||||
* @param mixed $arg event arguments
|
||||
* @param object $caller calling object
|
||||
* @return bool
|
||||
*/
|
||||
function onQuickFormEvent($event, $arg, &$caller)
|
||||
{
|
||||
switch ($event) {
|
||||
case 'createElement':
|
||||
$caller->setType($arg[0] . '[format]', PARAM_ALPHANUM);
|
||||
$caller->setType($arg[0] . '[itemid]', PARAM_INT);
|
||||
break;
|
||||
}
|
||||
return parent::onQuickFormEvent($event, $arg, $caller);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets name of editor
|
||||
*
|
||||
@@ -413,7 +432,8 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
|
||||
if (!during_initial_install() && empty($CFG->adminsetuppending)) {
|
||||
// 0 means no files, -1 unlimited
|
||||
if ($maxfiles != 0 ) {
|
||||
$str .= '<input type="hidden" name="'.$elname.'[itemid]" value="'.$draftitemid.'" />';
|
||||
$str .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $elname.'[itemid]',
|
||||
'value' => $draftitemid));
|
||||
|
||||
// used by non js editor only
|
||||
$editorurl = new moodle_url("$CFG->wwwroot/repository/draftfiles_manager.php", array(
|
||||
|
||||
@@ -79,6 +79,24 @@ class MoodleQuickForm_filemanager extends HTML_QuickForm_element {
|
||||
parent::HTML_QuickForm_element($elementName, $elementLabel, $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by HTML_QuickForm whenever form event is made on this element
|
||||
*
|
||||
* @param string $event Name of event
|
||||
* @param mixed $arg event arguments
|
||||
* @param object $caller calling object
|
||||
* @return bool
|
||||
*/
|
||||
function onQuickFormEvent($event, $arg, &$caller)
|
||||
{
|
||||
switch ($event) {
|
||||
case 'createElement':
|
||||
$caller->setType($arg[0], PARAM_INT);
|
||||
break;
|
||||
}
|
||||
return parent::onQuickFormEvent($event, $arg, $caller);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets name of filemanager
|
||||
*
|
||||
@@ -263,9 +281,9 @@ class MoodleQuickForm_filemanager extends HTML_QuickForm_element {
|
||||
$output = $PAGE->get_renderer('core', 'files');
|
||||
$html .= $output->render($fm);
|
||||
|
||||
$html .= '<input value="'.$draftitemid.'" name="'.$elname.'" type="hidden" />';
|
||||
$html .= html_writer::empty_tag('input', array('value' => $draftitemid, 'name' => $elname, 'type' => 'hidden'));
|
||||
// label element needs 'for' attribute work
|
||||
$html .= '<input value="" id="id_'.$elname.'" type="hidden" />';
|
||||
$html .= html_writer::empty_tag('input', array('value' => '', 'id' => 'id_'.$elname, 'type' => 'hidden'));
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
+1
-37
@@ -10500,43 +10500,7 @@ function message_popup_window() {
|
||||
//if we have new messages to notify the user about
|
||||
if (!empty($message_users)) {
|
||||
|
||||
$strmessages = '';
|
||||
if (count($message_users)>1) {
|
||||
$strmessages = get_string('unreadnewmessages', 'message', count($message_users));
|
||||
} else {
|
||||
$message_users = reset($message_users);
|
||||
|
||||
//show who the message is from if its not a notification
|
||||
if (!$message_users->notification) {
|
||||
$strmessages = get_string('unreadnewmessage', 'message', fullname($message_users) );
|
||||
}
|
||||
|
||||
//try to display the small version of the message
|
||||
$smallmessage = null;
|
||||
if (!empty($message_users->smallmessage)) {
|
||||
//display the first 200 chars of the message in the popup
|
||||
$smallmessage = null;
|
||||
if (textlib::strlen($message_users->smallmessage) > 200) {
|
||||
$smallmessage = textlib::substr($message_users->smallmessage,0,200).'...';
|
||||
} else {
|
||||
$smallmessage = $message_users->smallmessage;
|
||||
}
|
||||
|
||||
//prevent html symbols being displayed
|
||||
if ($message_users->fullmessageformat == FORMAT_HTML) {
|
||||
$smallmessage = html_to_text($smallmessage);
|
||||
} else {
|
||||
$smallmessage = s($smallmessage);
|
||||
}
|
||||
} else if ($message_users->notification) {
|
||||
//its a notification with no smallmessage so just say they have a notification
|
||||
$smallmessage = get_string('unreadnewnotification', 'message');
|
||||
}
|
||||
if (!empty($smallmessage)) {
|
||||
$strmessages .= '<div id="usermessage">'.s($smallmessage).'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
$strmessages = get_string('unreadnewmessages', 'message', count($message_users));
|
||||
$strgomessage = get_string('gotomessages', 'message');
|
||||
$strstaymessage = get_string('ignore','admin');
|
||||
|
||||
|
||||
@@ -302,13 +302,13 @@ Y.extend(EXCEPTION, DIALOGUE, {
|
||||
delay = this.get('hideTimeoutDelay');
|
||||
this.get(BASE).addClass('moodle-dialogue-exception');
|
||||
this.setStdModContent(Y.WidgetStdMod.HEADER, '<h1 id="moodle-dialogue-'+this.get('COUNT')+'-header-text">'
|
||||
+ config.name + '</h1>', Y.WidgetStdMod.REPLACE);
|
||||
+ Y.Escape.html(config.name) + '</h1>', Y.WidgetStdMod.REPLACE);
|
||||
content = Y.Node.create('<div class="moodle-exception"></div>')
|
||||
.append(Y.Node.create('<div class="moodle-exception-message">'+this.get('message')+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-message">'+Y.Escape.html(this.get('message'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-filename"><label>File:</label> '
|
||||
+ this.get('fileName')+'</div>'))
|
||||
+ Y.Escape.html(this.get('fileName'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-linenumber"><label>Line:</label> '
|
||||
+ this.get('lineNumber')+'</div>'))
|
||||
+ Y.Escape.html(this.get('lineNumber'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace"><label>Stack trace:</label> <pre>'
|
||||
+ this.get('stack')+'</pre></div>'));
|
||||
if (M.cfg.developerdebug) {
|
||||
@@ -351,7 +351,7 @@ Y.extend(EXCEPTION, DIALOGUE, {
|
||||
},
|
||||
stack : {
|
||||
setter : function(str) {
|
||||
var lines = str.split("\n"),
|
||||
var lines = Y.Escape.html(str).split("\n"),
|
||||
pattern = new RegExp('^(.+)@('+M.cfg.wwwroot+')?(.{0,75}).*:(\\d+)$'),
|
||||
i;
|
||||
for (i in lines) {
|
||||
@@ -382,15 +382,15 @@ Y.extend(AJAXEXCEPTION, DIALOGUE, {
|
||||
delay = this.get('hideTimeoutDelay');
|
||||
this.get(BASE).addClass('moodle-dialogue-exception');
|
||||
this.setStdModContent(Y.WidgetStdMod.HEADER, '<h1 id="moodle-dialogue-'+this.get('COUNT')+'-header-text">'
|
||||
+ config.name + '</h1>', Y.WidgetStdMod.REPLACE);
|
||||
+ Y.Escape.html(config.name) + '</h1>', Y.WidgetStdMod.REPLACE);
|
||||
content = Y.Node.create('<div class="moodle-ajaxexception"></div>')
|
||||
.append(Y.Node.create('<div class="moodle-exception-message">'+this.get('error')+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-message">'+Y.Escape.html(this.get('error'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>URL:</label> '
|
||||
+ this.get('reproductionlink')+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>Debug info:</label> '
|
||||
+ this.get('debuginfo')+'</div>'))
|
||||
+ Y.Escape.html(this.get('debuginfo'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace"><label>Stack trace:</label> <pre>'
|
||||
+ this.get('stacktrace')+'</pre></div>'));
|
||||
+ Y.Escape.html(this.get('stacktrace'))+'</pre></div>'));
|
||||
if (M.cfg.developerdebug) {
|
||||
content.all('.moodle-exception-param').removeClass('hidden');
|
||||
}
|
||||
@@ -427,6 +427,7 @@ Y.extend(AJAXEXCEPTION, DIALOGUE, {
|
||||
reproductionlink : {
|
||||
setter : function(link) {
|
||||
if (link !== null) {
|
||||
link = Y.Escape.html(link);
|
||||
link = '<a href="'+link+'">'+link.replace(M.cfg.wwwroot, '')+'</a>';
|
||||
}
|
||||
return link;
|
||||
@@ -448,4 +449,4 @@ M.core.exception = EXCEPTION;
|
||||
M.core.ajaxException = AJAXEXCEPTION;
|
||||
|
||||
|
||||
}, '@VERSION@', {"requires": ["base", "node", "panel", "event-key", "dd-plugin"]});
|
||||
}, '@VERSION@', {"requires": ["base", "node", "panel", "event-key", "escape", "dd-plugin"]});
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -302,13 +302,13 @@ Y.extend(EXCEPTION, DIALOGUE, {
|
||||
delay = this.get('hideTimeoutDelay');
|
||||
this.get(BASE).addClass('moodle-dialogue-exception');
|
||||
this.setStdModContent(Y.WidgetStdMod.HEADER, '<h1 id="moodle-dialogue-'+this.get('COUNT')+'-header-text">'
|
||||
+ config.name + '</h1>', Y.WidgetStdMod.REPLACE);
|
||||
+ Y.Escape.html(config.name) + '</h1>', Y.WidgetStdMod.REPLACE);
|
||||
content = Y.Node.create('<div class="moodle-exception"></div>')
|
||||
.append(Y.Node.create('<div class="moodle-exception-message">'+this.get('message')+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-message">'+Y.Escape.html(this.get('message'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-filename"><label>File:</label> '
|
||||
+ this.get('fileName')+'</div>'))
|
||||
+ Y.Escape.html(this.get('fileName'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-linenumber"><label>Line:</label> '
|
||||
+ this.get('lineNumber')+'</div>'))
|
||||
+ Y.Escape.html(this.get('lineNumber'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace"><label>Stack trace:</label> <pre>'
|
||||
+ this.get('stack')+'</pre></div>'));
|
||||
if (M.cfg.developerdebug) {
|
||||
@@ -351,7 +351,7 @@ Y.extend(EXCEPTION, DIALOGUE, {
|
||||
},
|
||||
stack : {
|
||||
setter : function(str) {
|
||||
var lines = str.split("\n"),
|
||||
var lines = Y.Escape.html(str).split("\n"),
|
||||
pattern = new RegExp('^(.+)@('+M.cfg.wwwroot+')?(.{0,75}).*:(\\d+)$'),
|
||||
i;
|
||||
for (i in lines) {
|
||||
@@ -382,15 +382,15 @@ Y.extend(AJAXEXCEPTION, DIALOGUE, {
|
||||
delay = this.get('hideTimeoutDelay');
|
||||
this.get(BASE).addClass('moodle-dialogue-exception');
|
||||
this.setStdModContent(Y.WidgetStdMod.HEADER, '<h1 id="moodle-dialogue-'+this.get('COUNT')+'-header-text">'
|
||||
+ config.name + '</h1>', Y.WidgetStdMod.REPLACE);
|
||||
+ Y.Escape.html(config.name) + '</h1>', Y.WidgetStdMod.REPLACE);
|
||||
content = Y.Node.create('<div class="moodle-ajaxexception"></div>')
|
||||
.append(Y.Node.create('<div class="moodle-exception-message">'+this.get('error')+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-message">'+Y.Escape.html(this.get('error'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>URL:</label> '
|
||||
+ this.get('reproductionlink')+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>Debug info:</label> '
|
||||
+ this.get('debuginfo')+'</div>'))
|
||||
+ Y.Escape.html(this.get('debuginfo'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace"><label>Stack trace:</label> <pre>'
|
||||
+ this.get('stacktrace')+'</pre></div>'));
|
||||
+ Y.Escape.html(this.get('stacktrace'))+'</pre></div>'));
|
||||
if (M.cfg.developerdebug) {
|
||||
content.all('.moodle-exception-param').removeClass('hidden');
|
||||
}
|
||||
@@ -427,6 +427,7 @@ Y.extend(AJAXEXCEPTION, DIALOGUE, {
|
||||
reproductionlink : {
|
||||
setter : function(link) {
|
||||
if (link !== null) {
|
||||
link = Y.Escape.html(link);
|
||||
link = '<a href="'+link+'">'+link.replace(M.cfg.wwwroot, '')+'</a>';
|
||||
}
|
||||
return link;
|
||||
@@ -448,4 +449,4 @@ M.core.exception = EXCEPTION;
|
||||
M.core.ajaxException = AJAXEXCEPTION;
|
||||
|
||||
|
||||
}, '@VERSION@', {"requires": ["base", "node", "panel", "event-key", "dd-plugin"]});
|
||||
}, '@VERSION@', {"requires": ["base", "node", "panel", "event-key", "escape", "dd-plugin"]});
|
||||
|
||||
+10
-9
@@ -300,13 +300,13 @@ Y.extend(EXCEPTION, DIALOGUE, {
|
||||
delay = this.get('hideTimeoutDelay');
|
||||
this.get(BASE).addClass('moodle-dialogue-exception');
|
||||
this.setStdModContent(Y.WidgetStdMod.HEADER, '<h1 id="moodle-dialogue-'+this.get('COUNT')+'-header-text">'
|
||||
+ config.name + '</h1>', Y.WidgetStdMod.REPLACE);
|
||||
+ Y.Escape.html(config.name) + '</h1>', Y.WidgetStdMod.REPLACE);
|
||||
content = Y.Node.create('<div class="moodle-exception"></div>')
|
||||
.append(Y.Node.create('<div class="moodle-exception-message">'+this.get('message')+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-message">'+Y.Escape.html(this.get('message'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-filename"><label>File:</label> '
|
||||
+ this.get('fileName')+'</div>'))
|
||||
+ Y.Escape.html(this.get('fileName'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-linenumber"><label>Line:</label> '
|
||||
+ this.get('lineNumber')+'</div>'))
|
||||
+ Y.Escape.html(this.get('lineNumber'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace"><label>Stack trace:</label> <pre>'
|
||||
+ this.get('stack')+'</pre></div>'));
|
||||
if (M.cfg.developerdebug) {
|
||||
@@ -349,7 +349,7 @@ Y.extend(EXCEPTION, DIALOGUE, {
|
||||
},
|
||||
stack : {
|
||||
setter : function(str) {
|
||||
var lines = str.split("\n"),
|
||||
var lines = Y.Escape.html(str).split("\n"),
|
||||
pattern = new RegExp('^(.+)@('+M.cfg.wwwroot+')?(.{0,75}).*:(\\d+)$'),
|
||||
i;
|
||||
for (i in lines) {
|
||||
@@ -380,15 +380,15 @@ Y.extend(AJAXEXCEPTION, DIALOGUE, {
|
||||
delay = this.get('hideTimeoutDelay');
|
||||
this.get(BASE).addClass('moodle-dialogue-exception');
|
||||
this.setStdModContent(Y.WidgetStdMod.HEADER, '<h1 id="moodle-dialogue-'+this.get('COUNT')+'-header-text">'
|
||||
+ config.name + '</h1>', Y.WidgetStdMod.REPLACE);
|
||||
+ Y.Escape.html(config.name) + '</h1>', Y.WidgetStdMod.REPLACE);
|
||||
content = Y.Node.create('<div class="moodle-ajaxexception"></div>')
|
||||
.append(Y.Node.create('<div class="moodle-exception-message">'+this.get('error')+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-message">'+Y.Escape.html(this.get('error'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>URL:</label> '
|
||||
+ this.get('reproductionlink')+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-debuginfo"><label>Debug info:</label> '
|
||||
+ this.get('debuginfo')+'</div>'))
|
||||
+ Y.Escape.html(this.get('debuginfo'))+'</div>'))
|
||||
.append(Y.Node.create('<div class="moodle-exception-param hidden param-stacktrace"><label>Stack trace:</label> <pre>'
|
||||
+ this.get('stacktrace')+'</pre></div>'));
|
||||
+ Y.Escape.html(this.get('stacktrace'))+'</pre></div>'));
|
||||
if (M.cfg.developerdebug) {
|
||||
content.all('.moodle-exception-param').removeClass('hidden');
|
||||
}
|
||||
@@ -425,6 +425,7 @@ Y.extend(AJAXEXCEPTION, DIALOGUE, {
|
||||
reproductionlink : {
|
||||
setter : function(link) {
|
||||
if (link !== null) {
|
||||
link = Y.Escape.html(link);
|
||||
link = '<a href="'+link+'">'+link.replace(M.cfg.wwwroot, '')+'</a>';
|
||||
}
|
||||
return link;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"node",
|
||||
"panel",
|
||||
"event-key",
|
||||
"escape",
|
||||
"dd-plugin"
|
||||
]
|
||||
}
|
||||
|
||||
+11
-3
@@ -147,13 +147,21 @@ class mod_forum_post_form extends moodleform {
|
||||
$mform->setConstants(array('timestart'=> 0, 'timeend'=>0));
|
||||
}
|
||||
|
||||
if (groups_get_activity_groupmode($cm, $course)) { // hack alert
|
||||
if ($groupmode = groups_get_activity_groupmode($cm, $course)) { // hack alert
|
||||
$groupdata = groups_get_activity_allowed_groups($cm);
|
||||
$groupcount = count($groupdata);
|
||||
$groupinfo = array();
|
||||
$modulecontext = context_module::instance($cm->id);
|
||||
$contextcheck = has_capability('mod/forum:movediscussions', $modulecontext) && empty($post->parent) && $groupcount;
|
||||
if ($contextcheck) {
|
||||
|
||||
// Check whether the user has access to all groups in this forum from the accessallgroups cap.
|
||||
if ($groupmode == VISIBLEGROUPS || has_capability('moodle/site:accessallgroups', $modulecontext)) {
|
||||
// Only allow posting to all groups if the user has access to all groups.
|
||||
$groupinfo = array('0' => get_string('allparticipants'));
|
||||
$groupcount++;
|
||||
}
|
||||
|
||||
$contextcheck = has_capability('mod/forum:movediscussions', $modulecontext) && empty($post->parent) && $groupcount > 1;
|
||||
if ($contextcheck) {
|
||||
foreach ($groupdata as $grouptemp) {
|
||||
$groupinfo[$grouptemp->id] = $grouptemp->name;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
@mod @mod_forum
|
||||
Feature: Posting to all groups in a separate group discussion is restricted to users with access to all groups
|
||||
In order to post to all groups in a forum with separate groups
|
||||
As a teacher
|
||||
I need to have the accessallgroups capability or be a member of all of the groups
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@asd.com |
|
||||
| noneditor1 | Non-editing teacher | 1 | noneditor1@asd.com |
|
||||
| noneditor2 | Non-editing teacher | 2 | noneditor2@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 |
|
||||
| noneditor1 | C1 | teacher |
|
||||
| noneditor2 | C1 | teacher |
|
||||
| student1 | C1 | student |
|
||||
And the following "groups" exist:
|
||||
| name | course | idnumber |
|
||||
| Group A | C1 | G1 |
|
||||
| Group B | C1 | G2 |
|
||||
| Group C | C1 | G3 |
|
||||
And the following "group members" exist:
|
||||
| user | group |
|
||||
| teacher1 | G1 |
|
||||
| teacher1 | G2 |
|
||||
| noneditor1 | G1 |
|
||||
| noneditor1 | G2 |
|
||||
| noneditor1 | G3 |
|
||||
| noneditor2 | G1 |
|
||||
| noneditor2 | G2 |
|
||||
| student1 | G1 |
|
||||
| student1 | G2 |
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
And I add a "Forum" to section "1" and I fill the form with:
|
||||
| Forum name | Standard forum name |
|
||||
| Forum type | Standard forum for general use |
|
||||
| Description | Standard forum description |
|
||||
| Group mode | Separate groups |
|
||||
And I log out
|
||||
|
||||
Scenario: Teacher with accessallgroups can post in all groups
|
||||
Given I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I follow "Standard forum name"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
Then the "Group" select box should contain "All participants"
|
||||
And the "Group" select box should contain "Group A"
|
||||
And the "Group" select box should contain "Group B"
|
||||
|
||||
@javascript
|
||||
Scenario: Teacher in all groups but without accessallgroups can only post in their groups
|
||||
And I log in as "admin"
|
||||
And I set the following system permissions of "Non-editing teacher" role:
|
||||
| moodle/site:accessallgroups | Prohibit |
|
||||
And I log out
|
||||
Given I log in as "noneditor1"
|
||||
And I follow "Course 1"
|
||||
And I follow "Standard forum name"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
Then the "Group" select box should not contain "All participants"
|
||||
And the "Group" select box should contain "Group A"
|
||||
And the "Group" select box should contain "Group B"
|
||||
|
||||
@javascript
|
||||
Scenario: Teacher in some groups and without accessallgroups can only post in their groups
|
||||
And I log in as "admin"
|
||||
And I set the following system permissions of "Non-editing teacher" role:
|
||||
| moodle/site:accessallgroups | Prohibit |
|
||||
And I log out
|
||||
Given I log in as "noneditor1"
|
||||
And I follow "Course 1"
|
||||
And I follow "Standard forum name"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
Then the "Group" select box should not contain "All participants"
|
||||
And the "Group" select box should contain "Group A"
|
||||
And the "Group" select box should contain "Group B"
|
||||
@@ -0,0 +1,84 @@
|
||||
@mod @mod_forum
|
||||
Feature: Posting to groups in a separate group discussion when restricted to groupings
|
||||
In order to post to groups in a forum with separate groups and groupings
|
||||
As a teacher
|
||||
I need to have groups configured to post to a group
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | teacher1 | teacher1 | teacher1@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 | teacher |
|
||||
And the following "groups" exist:
|
||||
| name | course | idnumber |
|
||||
| G1G1 | C1 | G1G1 |
|
||||
| G1G2 | C1 | G1G2 |
|
||||
| G2G1 | C1 | G2G1 |
|
||||
And the following "groupings" exist:
|
||||
| name | course | idnumber |
|
||||
| G1 | C1 | G1 |
|
||||
| G2 | C1 | G2 |
|
||||
And the following "group members" exist:
|
||||
| user | group |
|
||||
| teacher1 | G1G1 |
|
||||
| teacher1 | G1G2 |
|
||||
| teacher1 | G2G1 |
|
||||
And the following "grouping groups" exist:
|
||||
| grouping | group |
|
||||
| G1 | G1G1 |
|
||||
| G1 | G1G2 |
|
||||
| G2 | G2G1 |
|
||||
And I log in as "admin"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
And I add a "Forum" to section "1" and I fill the form with:
|
||||
| Forum name | Multiple groups forum |
|
||||
| Forum type | Standard forum for general use |
|
||||
| Description | Standard forum description |
|
||||
| Group mode | Separate groups |
|
||||
| Grouping | G1 |
|
||||
And I add a "Forum" to section "1" and I fill the form with:
|
||||
| Forum name | Single groups forum |
|
||||
| Forum type | Standard forum for general use |
|
||||
| Description | Standard forum description |
|
||||
| Group mode | Separate groups |
|
||||
| Grouping | G2 |
|
||||
And I log out
|
||||
|
||||
Scenario: Teacher with accessallgroups can post in all groups
|
||||
Given I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I follow "Multiple groups forum"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
Then the "Group" select box should contain "All participants"
|
||||
And the "Group" select box should contain "G1G1"
|
||||
And the "Group" select box should contain "G1G2"
|
||||
And I follow "Course 1"
|
||||
And I follow "Single groups forum"
|
||||
And I click on "Add a new discussion topic" "button"
|
||||
And the "Group" select box should contain "All participants"
|
||||
And the "Group" select box should contain "G2G1"
|
||||
|
||||
@javascript
|
||||
Scenario: Teacher in all groups but without accessallgroups can post in either group but not to All Participants
|
||||
And I log in as "admin"
|
||||
And I set the following system permissions of "Non-editing teacher" role:
|
||||
| moodle/site:accessallgroups | Prohibit |
|
||||
And I log out
|
||||
Given I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I follow "Multiple groups forum"
|
||||
When I click on "Add a new discussion topic" "button"
|
||||
Then the "Group" select box should not contain "All participants"
|
||||
And the "Group" select box should contain "G1G1"
|
||||
And the "Group" select box should contain "G1G2"
|
||||
And I follow "Course 1"
|
||||
And I follow "Single groups forum"
|
||||
And I click on "Add a new discussion topic" "button"
|
||||
And I should see "G2G1"
|
||||
And "Group" "select" should not exist
|
||||
@@ -105,9 +105,11 @@ function imscp_parse_structure($imscp, $context) {
|
||||
*/
|
||||
function imscp_parse_manifestfile($manifestfilecontents, $imscp, $context) {
|
||||
$doc = new DOMDocument();
|
||||
$oldentities = libxml_disable_entity_loader(true);
|
||||
if (!$doc->loadXML($manifestfilecontents, LIBXML_NONET)) {
|
||||
return null;
|
||||
}
|
||||
libxml_disable_entity_loader($oldentities);
|
||||
|
||||
// we put this fake URL as base in order to detect path changes caused by xml:base attributes
|
||||
$doc->documentURI = 'http://grrr/';
|
||||
@@ -205,10 +207,14 @@ function imscp_recursive_href($manifestfilename, $imscp, $context) {
|
||||
if (!$manifestfile = $fs->get_file($context->id, 'mod_imscp', 'content', $imscp->revision, $dirname, $filename)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
$oldentities = libxml_disable_entity_loader(true);
|
||||
if (!$doc->loadXML($manifestfile->get_content(), LIBXML_NONET)) {
|
||||
return null;
|
||||
}
|
||||
libxml_disable_entity_loader($oldentities);
|
||||
|
||||
$xmlresources = $doc->getElementsByTagName('resource');
|
||||
foreach ($xmlresources as $res) {
|
||||
if (!$href = $res->attributes->getNamedItem('href')) {
|
||||
|
||||
+8
-1
@@ -53,7 +53,14 @@ if ($sharedsecret === false) {
|
||||
throw new Exception('Message signature not valid');
|
||||
}
|
||||
|
||||
$xml = new SimpleXMLElement($rawbody);
|
||||
// TODO MDL-46023 Replace this code with a call to the new library.
|
||||
$origentity = libxml_disable_entity_loader(true);
|
||||
$xml = simplexml_load_string($rawbody);
|
||||
if (!$xml) {
|
||||
libxml_disable_entity_loader($origentity);
|
||||
throw new Exception('Invalid XML content');
|
||||
}
|
||||
libxml_disable_entity_loader($origentity);
|
||||
|
||||
$body = $xml->imsx_POXBody;
|
||||
foreach ($body->children() as $child) {
|
||||
|
||||
+7
-5
@@ -15,6 +15,10 @@ $userid = optional_param('user', 0, PARAM_INT);
|
||||
$filtertype = optional_param('filtertype', '', PARAM_ALPHA);
|
||||
$filterselect = optional_param('filterselect', 0, PARAM_INT);
|
||||
|
||||
if (empty($CFG->enablenotes)) {
|
||||
print_error('notesdisabled', 'notes');
|
||||
}
|
||||
|
||||
$url = new moodle_url('/notes/index.php');
|
||||
if ($courseid != SITEID) {
|
||||
$url->param('course', $courseid);
|
||||
@@ -61,11 +65,6 @@ if ($userid) {
|
||||
|
||||
/// require login to access notes
|
||||
require_login($course);
|
||||
add_to_log($courseid, 'notes', 'view', 'index.php?course='.$courseid.'&user='.$userid, 'view notes');
|
||||
|
||||
if (empty($CFG->enablenotes)) {
|
||||
print_error('notesdisabled', 'notes');
|
||||
}
|
||||
|
||||
/// output HTML
|
||||
if ($course->id == SITEID) {
|
||||
@@ -73,8 +72,11 @@ if ($course->id == SITEID) {
|
||||
} else {
|
||||
$coursecontext = context_course::instance($course->id); // Course context
|
||||
}
|
||||
require_capability('moodle/notes:view', $coursecontext);
|
||||
$systemcontext = context_system::instance(); // SYSTEM context
|
||||
|
||||
add_to_log($courseid, 'notes', 'view', 'index.php?course='.$courseid.'&user='.$userid, 'view notes');
|
||||
|
||||
$strnotes = get_string('notes', 'notes');
|
||||
if ($userid) {
|
||||
$PAGE->set_context(context_user::instance($user->id));
|
||||
|
||||
@@ -357,6 +357,7 @@ class qformat_default {
|
||||
$count = 0;
|
||||
|
||||
foreach ($questions as $question) { // Process and store each question
|
||||
$transaction = $DB->start_delegated_transaction();
|
||||
|
||||
// reset the php timeout
|
||||
set_time_limit(0);
|
||||
@@ -371,6 +372,7 @@ class qformat_default {
|
||||
$this->category = $newcategory;
|
||||
}
|
||||
}
|
||||
$transaction->allow_commit();
|
||||
continue;
|
||||
}
|
||||
$question->context = $this->importcontext;
|
||||
@@ -429,9 +431,14 @@ class qformat_default {
|
||||
|
||||
if (!empty($result->error)) {
|
||||
echo $OUTPUT->notification($result->error);
|
||||
// Can't use $transaction->rollback(); since it requires an exception,
|
||||
// and I don't want to rewrite this code to change the error handling now.
|
||||
$DB->force_transaction_rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
$transaction->allow_commit();
|
||||
|
||||
if (!empty($result->notice)) {
|
||||
echo $OUTPUT->notification($result->notice);
|
||||
return true;
|
||||
|
||||
@@ -606,12 +606,9 @@ class qformat_webct extends qformat_default {
|
||||
// Calculated Question.
|
||||
$question = $this->defaultquestion();
|
||||
$question->qtype = 'calculated';
|
||||
$question->answers = array(); // No problem as they go as :FORMULA: from webct.
|
||||
$question->answer = array(); // No problem as they go as :FORMULA: from webct.
|
||||
$question->units = array();
|
||||
$question->dataset = array();
|
||||
|
||||
// To make us pass the end-of-question sanity checks.
|
||||
$question->answer = array('dummy');
|
||||
$question->fraction = array('1.0');
|
||||
$question->feedback = array();
|
||||
|
||||
@@ -737,7 +734,7 @@ class qformat_webct extends qformat_default {
|
||||
if (preg_match('~^:FORMULA:(.*)~i', $line, $webctoptions)) {
|
||||
// Answer for a calculated question.
|
||||
++$currentchoice;
|
||||
$question->answers[$currentchoice] =
|
||||
$question->answer[$currentchoice] =
|
||||
qformat_webct_convert_formula($webctoptions[1]);
|
||||
|
||||
// Default settings.
|
||||
|
||||
@@ -780,7 +780,7 @@ class qformat_xml extends qformat_default {
|
||||
|
||||
// get answers array
|
||||
$answers = $question['#']['answer'];
|
||||
$qo->answers = array();
|
||||
$qo->answer = array();
|
||||
$qo->feedback = array();
|
||||
$qo->fraction = array();
|
||||
$qo->tolerance = array();
|
||||
@@ -794,7 +794,7 @@ class qformat_xml extends qformat_default {
|
||||
if (empty($ans->answer['text'])) {
|
||||
$ans->answer['text'] = '*';
|
||||
}
|
||||
$qo->answers[] = $ans->answer;
|
||||
$qo->answer[] = $ans->answer['text'];
|
||||
$qo->feedback[] = $ans->feedback;
|
||||
$qo->tolerance[] = $answer['#']['tolerance'][0]['#'];
|
||||
// fraction as a tag is deprecated
|
||||
|
||||
@@ -212,36 +212,39 @@ class qtype_calculated_edit_form extends qtype_numerical_edit_form {
|
||||
return 'calculated';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the equations in the some question content.
|
||||
* @param array $errors where errors are being accumulated.
|
||||
* @param string $field the field being validated.
|
||||
* @param string $text the content of that field.
|
||||
* @return array the updated $errors array.
|
||||
*/
|
||||
protected function validate_text($errors, $field, $text) {
|
||||
$problems = qtype_calculated_find_formula_errors_in_text($text);
|
||||
if ($problems) {
|
||||
$errors[$field] = $problems;
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function validation($data, $files) {
|
||||
$errors = parent::validation($data, $files);
|
||||
|
||||
// Verifying for errors in {=...} in question text.
|
||||
$qtext = "";
|
||||
$qtextremaining = $data['questiontext']['text'];
|
||||
$possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
|
||||
foreach ($possibledatasets as $name => $value) {
|
||||
$qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining);
|
||||
}
|
||||
while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
|
||||
$qtextsplits = explode($regs1[0], $qtextremaining, 2);
|
||||
$qtext = $qtext.$qtextsplits[0];
|
||||
$qtextremaining = $qtextsplits[1];
|
||||
if (!empty($regs1[1]) && $formulaerrors =
|
||||
qtype_calculated_find_formula_errors($regs1[1])) {
|
||||
if (!isset($errors['questiontext'])) {
|
||||
$errors['questiontext'] = $formulaerrors.':'.$regs1[1];
|
||||
} else {
|
||||
$errors['questiontext'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$errors = parent::validation($data, $files);
|
||||
$errors = $this->validate_text($errors, 'questiontext', $data['questiontext']['text']);
|
||||
$errors = $this->validate_text($errors, 'generalfeedback', $data['generalfeedback']['text']);
|
||||
|
||||
// Check that the answers use datasets.
|
||||
$answers = $data['answer'];
|
||||
$mandatorydatasets = array();
|
||||
foreach ($answers as $key => $answer) {
|
||||
$problems = qtype_calculated_find_formula_errors($answer);
|
||||
if ($problems) {
|
||||
$errors['answeroptions['.$key.']'] = $problems;
|
||||
}
|
||||
$mandatorydatasets += $this->qtypeobj->find_dataset_names($answer);
|
||||
$errors = $this->validate_text($errors, 'feedback[' . $key . ']',
|
||||
$data['feedback'][$key]['text']);
|
||||
}
|
||||
if (empty($mandatorydatasets)) {
|
||||
foreach ($answers as $key => $answer) {
|
||||
|
||||
@@ -259,6 +259,7 @@ class qtype_calculated_dataset_loader {
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class qtype_calculated_variable_substituter {
|
||||
|
||||
/** @var array variable name => value */
|
||||
protected $values;
|
||||
|
||||
@@ -341,6 +342,10 @@ class qtype_calculated_variable_substituter {
|
||||
* @return float the computed result.
|
||||
*/
|
||||
public function calculate($expression) {
|
||||
// Make sure no malicious code is present in the expression. Refer MDL-46148 for details.
|
||||
if ($error = qtype_calculated_find_formula_errors($expression)) {
|
||||
throw new moodle_exception('illegalformulasyntax', 'qtype_calculated', '', $error);
|
||||
}
|
||||
return $this->calculate_raw($this->substitute_values_for_eval($expression));
|
||||
}
|
||||
|
||||
@@ -388,108 +393,11 @@ class qtype_calculated_variable_substituter {
|
||||
* @return string the text with values substituted.
|
||||
*/
|
||||
public function replace_expressions_in_text($text, $length = null, $format = null) {
|
||||
$vs = $this; // Can't see to use $this in a PHP closure.
|
||||
$text = preg_replace_callback('~\{=([^{}]*(?:\{[^{}]+}[^{}]*)*)}~',
|
||||
$vs = $this; // Can't use $this in a PHP closure.
|
||||
$text = preg_replace_callback(qtype_calculated::FORMULAS_IN_TEXT_REGEX,
|
||||
function ($matches) use ($vs, $format, $length) {
|
||||
return $vs->format_float($vs->calculate($matches[1]), $length, $format);
|
||||
}, $text);
|
||||
return $this->substitute_values_pretty($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array describing any problems there are with an expression.
|
||||
* Returns false if the expression is fine.
|
||||
* @param string $formula an expression.
|
||||
* @return array|false list of problems, or false if the exression is OK.
|
||||
*/
|
||||
public function get_formula_errors($formula) {
|
||||
// Validates the formula submitted from the question edit page.
|
||||
// Returns false if everything is alright
|
||||
// otherwise it constructs an error message.
|
||||
// Strip away dataset names.
|
||||
while (preg_match('~\\{[[:alpha:]][^>} <{"\']*\\}~', $formula, $regs)) {
|
||||
$formula = str_replace($regs[0], '1', $formula);
|
||||
}
|
||||
|
||||
// Strip away empty space and lowercase it.
|
||||
$formula = strtolower(str_replace(' ', '', $formula));
|
||||
|
||||
$safeoperatorchar = '-+/*%>:^\~<?=&|!'; /* */
|
||||
$operatorornumber = "[$safeoperatorchar.0-9eE]";
|
||||
|
||||
while (preg_match("~(^|[$safeoperatorchar,(])([a-z0-9_]*)" .
|
||||
"\\(($operatorornumber+(,$operatorornumber+((,$operatorornumber+)+)?)?)?\\)~",
|
||||
$formula, $regs)) {
|
||||
switch ($regs[2]) {
|
||||
// Simple parenthesis.
|
||||
case '':
|
||||
if ((isset($regs[4]) && $regs[4]) || strlen($regs[3]) == 0) {
|
||||
return get_string('illegalformulasyntax', 'qtype_calculated', $regs[0]);
|
||||
}
|
||||
break;
|
||||
|
||||
// Zero argument functions.
|
||||
case 'pi':
|
||||
if ($regs[3]) {
|
||||
return get_string('functiontakesnoargs', 'qtype_calculated', $regs[2]);
|
||||
}
|
||||
break;
|
||||
|
||||
// Single argument functions (the most common case).
|
||||
case 'abs': case 'acos': case 'acosh': case 'asin': case 'asinh':
|
||||
case 'atan': case 'atanh': case 'bindec': case 'ceil': case 'cos':
|
||||
case 'cosh': case 'decbin': case 'decoct': case 'deg2rad':
|
||||
case 'exp': case 'expm1': case 'floor': case 'is_finite':
|
||||
case 'is_infinite': case 'is_nan': case 'log10': case 'log1p':
|
||||
case 'octdec': case 'rad2deg': case 'sin': case 'sinh': case 'sqrt':
|
||||
case 'tan': case 'tanh':
|
||||
if (!empty($regs[4]) || empty($regs[3])) {
|
||||
return get_string('functiontakesonearg', 'qtype_calculated', $regs[2]);
|
||||
}
|
||||
break;
|
||||
|
||||
// Functions that take one or two arguments.
|
||||
case 'log': case 'round':
|
||||
if (!empty($regs[5]) || empty($regs[3])) {
|
||||
return get_string('functiontakesoneortwoargs', 'qtype_calculated',
|
||||
$regs[2]);
|
||||
}
|
||||
break;
|
||||
|
||||
// Functions that must have two arguments.
|
||||
case 'atan2': case 'fmod': case 'pow':
|
||||
if (!empty($regs[5]) || empty($regs[4])) {
|
||||
return get_string('functiontakestwoargs', 'qtype_calculated', $regs[2]);
|
||||
}
|
||||
break;
|
||||
|
||||
// Functions that take two or more arguments.
|
||||
case 'min': case 'max':
|
||||
if (empty($regs[4])) {
|
||||
return get_string('functiontakesatleasttwo', 'qtype_calculated', $regs[2]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return get_string('unsupportedformulafunction', 'qtype_calculated', $regs[2]);
|
||||
}
|
||||
|
||||
// Exchange the function call with '1' and then check for another function call.
|
||||
|
||||
if ($regs[1]) {
|
||||
// The function call is proceeded by an operator.
|
||||
$formula = str_replace($regs[0], $regs[1] . '1', $formula);
|
||||
} else {
|
||||
// The function call starts the formula.
|
||||
$formula = preg_replace("~^$regs[2]\\([^)]*\\)~", '1', $formula);
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match("~[^$safeoperatorchar.0-9eE]+~", $formula, $regs)) {
|
||||
return get_string('illegalformulasyntax', 'qtype_calculated', $regs[0]);
|
||||
} else {
|
||||
// Formula just might be valid.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ require_once($CFG->dirroot . '/question/type/numerical/question.php');
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class qtype_calculated extends question_type {
|
||||
/** Regular expression that finds the formulas in content. */
|
||||
const FORMULAS_IN_TEXT_REGEX = '~\{=([^{}]*(?:\{[^{}]+}[^{}]*)*)\}~';
|
||||
|
||||
const MAX_DATASET_ITEMS = 100;
|
||||
|
||||
public $wizardpagesnumber = 3;
|
||||
@@ -128,11 +131,13 @@ class qtype_calculated extends question_type {
|
||||
|
||||
public function save_question_options($question) {
|
||||
global $CFG, $DB;
|
||||
|
||||
// Make it impossible to save bad formulas anywhere.
|
||||
$this->validate_question_data($question);
|
||||
|
||||
// The code is used for calculated, calculatedsimple and calculatedmulti qtypes.
|
||||
$context = $question->context;
|
||||
if (isset($question->answer) && !isset($question->answers)) {
|
||||
$question->answers = $question->answer;
|
||||
}
|
||||
|
||||
// Calculated options.
|
||||
$update = true;
|
||||
$options = $DB->get_record('question_calculated_options',
|
||||
@@ -182,14 +187,7 @@ class qtype_calculated extends question_type {
|
||||
$units = $result->units;
|
||||
}
|
||||
|
||||
// Insert all the new answers.
|
||||
if (isset($question->answer) && !isset($question->answers)) {
|
||||
$question->answers = $question->answer;
|
||||
}
|
||||
foreach ($question->answers as $key => $answerdata) {
|
||||
if (is_array($answerdata)) {
|
||||
$answerdata = $answerdata['text'];
|
||||
}
|
||||
foreach ($question->answer as $key => $answerdata) {
|
||||
if (trim($answerdata) == '') {
|
||||
continue;
|
||||
}
|
||||
@@ -343,49 +341,6 @@ class qtype_calculated extends question_type {
|
||||
$question->datasetloader = new qtype_calculated_dataset_loader($questiondata->id);
|
||||
}
|
||||
|
||||
public function validate_form($form) {
|
||||
switch($form->wizardpage) {
|
||||
case 'question':
|
||||
$calculatedmessages = array();
|
||||
if (empty($form->name)) {
|
||||
$calculatedmessages[] = get_string('missingname', 'qtype_calculated');
|
||||
}
|
||||
if (empty($form->questiontext)) {
|
||||
$calculatedmessages[] = get_string('missingquestiontext', 'qtype_calculated');
|
||||
}
|
||||
// Verify formulas.
|
||||
foreach ($form->answers as $key => $answer) {
|
||||
if ('' === trim($answer)) {
|
||||
$calculatedmessages[] = get_string(
|
||||
'missingformula', 'qtype_calculated');
|
||||
}
|
||||
if ($formulaerrors = qtype_calculated_find_formula_errors($answer)) {
|
||||
$calculatedmessages[] = $formulaerrors;
|
||||
}
|
||||
if (! isset($form->tolerance[$key])) {
|
||||
$form->tolerance[$key] = 0.0;
|
||||
}
|
||||
if (! is_numeric($form->tolerance[$key])) {
|
||||
$calculatedmessages[] = get_string('xmustbenumeric', 'qtype_numerical',
|
||||
get_string('tolerance', 'qtype_calculated'));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($calculatedmessages)) {
|
||||
$errorstring = "The following errors were found:<br />";
|
||||
foreach ($calculatedmessages as $msg) {
|
||||
$errorstring .= $msg . '<br />';
|
||||
}
|
||||
print_error($errorstring);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
return parent::validate_form($form);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public function finished_edit_wizard($form) {
|
||||
return isset($form->savechanges);
|
||||
}
|
||||
@@ -481,6 +436,55 @@ class qtype_calculated extends question_type {
|
||||
$mform->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the equations in part of the question are OK.
|
||||
* We throw an exception here because this should have already been validated
|
||||
* by the form. This is just a last line of defence to prevent a question
|
||||
* being stored in the database if it has bad formulas. This saves us from,
|
||||
* for example, malicious imports.
|
||||
* @param string $text containing equations.
|
||||
*/
|
||||
protected function validate_text($text) {
|
||||
$error = qtype_calculated_find_formula_errors_in_text($text);
|
||||
if ($error) {
|
||||
throw new coding_exception($error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that an answer is OK.
|
||||
* We throw an exception here because this should have already been validated
|
||||
* by the form. This is just a last line of defence to prevent a question
|
||||
* being stored in the database if it has bad formulas. This saves us from,
|
||||
* for example, malicious imports.
|
||||
* @param string $text containing equations.
|
||||
*/
|
||||
protected function validate_answer($answer) {
|
||||
$error = qtype_calculated_find_formula_errors($answer);
|
||||
if ($error) {
|
||||
throw new coding_exception($error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate data before save.
|
||||
* @param stdClass $question data from the form / import file.
|
||||
*/
|
||||
protected function validate_question_data($question) {
|
||||
$this->validate_text($question->questiontext); // Yes, really no ['text'].
|
||||
|
||||
if (isset($question->generalfeedback['text'])) {
|
||||
$this->validate_text($question->generalfeedback['text']);
|
||||
} else if (isset($question->generalfeedback)) {
|
||||
$this->validate_text($question->generalfeedback); // Because question import is weird.
|
||||
}
|
||||
|
||||
foreach ($question->answer as $key => $answer) {
|
||||
$this->validate_answer($answer);
|
||||
$this->validate_text($question->feedback[$key]['text']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method prepare the $datasets in a format similar to dadatesetdefinitions_form.php
|
||||
* so that they can be saved
|
||||
@@ -493,12 +497,13 @@ class qtype_calculated extends question_type {
|
||||
* @param object $form
|
||||
* @param int $questionfromid default = '0'
|
||||
*/
|
||||
public function preparedatasets($form , $questionfromid = '0') {
|
||||
public function preparedatasets($form, $questionfromid = '0') {
|
||||
|
||||
// The dataset names present in the edit_question_form and edit_calculated_form
|
||||
// are retrieved.
|
||||
$possibledatasets = $this->find_dataset_names($form->questiontext);
|
||||
$mandatorydatasets = array();
|
||||
foreach ($form->answers as $answer) {
|
||||
foreach ($form->answer as $key => $answer) {
|
||||
$mandatorydatasets += $this->find_dataset_names($answer);
|
||||
}
|
||||
// If there are identical datasetdefs already saved in the original question
|
||||
@@ -587,8 +592,9 @@ class qtype_calculated extends question_type {
|
||||
*/
|
||||
public function save_question($question, $form) {
|
||||
global $DB;
|
||||
|
||||
if ($this->wizardpagesnumber() == 1 || $question->qtype == 'calculatedsimple') {
|
||||
$question = parent::save_question($question, $form);
|
||||
$question = parent::save_question($question, $form);
|
||||
return $question;
|
||||
}
|
||||
|
||||
@@ -1058,10 +1064,14 @@ class qtype_calculated extends question_type {
|
||||
}
|
||||
|
||||
$answers = fullclone($answers);
|
||||
$errors = '';
|
||||
$delimiter = ': ';
|
||||
$virtualqtype = $qtypeobj->get_virtual_qtype();
|
||||
foreach ($answers as $key => $answer) {
|
||||
$error = qtype_calculated_find_formula_errors($answer->answer);
|
||||
if ($error) {
|
||||
$comment->stranswers[$key] = $error;
|
||||
continue;
|
||||
}
|
||||
$formula = $this->substitute_variables($answer->answer, $data);
|
||||
$formattedanswer = qtype_calculated_calculate_answer(
|
||||
$answer->answer, $data, $answer->tolerance,
|
||||
@@ -1972,6 +1982,11 @@ function qtype_calculated_calculate_answer($formula, $individualdata,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate a forumula.
|
||||
* @param string $formula the formula to validate.
|
||||
* @return string|boolean false if there are no problems. Otherwise a string error message.
|
||||
*/
|
||||
function qtype_calculated_find_formula_errors($formula) {
|
||||
// Validates the formula submitted from the question edit page.
|
||||
// Returns false if everything is alright
|
||||
@@ -2000,7 +2015,7 @@ function qtype_calculated_find_formula_errors($formula) {
|
||||
|
||||
// Zero argument functions.
|
||||
case 'pi':
|
||||
if ($regs[3]) {
|
||||
if (array_key_exists(3, $regs)) {
|
||||
return get_string('functiontakesnoargs', 'qtype_calculated', $regs[2]);
|
||||
}
|
||||
break;
|
||||
@@ -2061,3 +2076,26 @@ function qtype_calculated_find_formula_errors($formula) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all the forumulas in a bit of text.
|
||||
* @param string $text the text in which to validate the formulas.
|
||||
* @return string|boolean false if there are no problems. Otherwise a string error message.
|
||||
*/
|
||||
function qtype_calculated_find_formula_errors_in_text($text) {
|
||||
preg_match_all(qtype_calculated::FORMULAS_IN_TEXT_REGEX, $text, $matches);
|
||||
|
||||
$errors = array();
|
||||
foreach ($matches[1] as $match) {
|
||||
$error = qtype_calculated_find_formula_errors($match);
|
||||
if ($error) {
|
||||
$errors[] = $error;
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
return implode(' ', $errors);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Unit tests for formula validation code.
|
||||
*
|
||||
* @package qtype_calculated
|
||||
* @copyright 2014 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/question/type/calculated/questiontype.php');
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for formula validation code.
|
||||
*
|
||||
* @copyright 2014 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class qtype_calculated_formula_validation_testcase extends basic_testcase {
|
||||
protected function assert_nonempty_string($actual) {
|
||||
$this->assertInternalType('string', $actual);
|
||||
$this->assertNotEquals('', $actual);
|
||||
}
|
||||
|
||||
public function test_simple_equations_ok() {
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors(1));
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors('1 + 1'));
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors('{x} + {y}'));
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors('{x}*{y}'));
|
||||
}
|
||||
|
||||
public function test_safe_functions_ok() {
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors('abs(-1)'));
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors('tan(pi())'));
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors('log(10)'));
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors('log(64, 2)'));
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors('atan2(1.0, 1.0)'));
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors('max(1.0, 1.0)'));
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors('max(1.0, 1.0, 2.0)'));
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors('max(1.0, 1.0, 2, 3)'));
|
||||
}
|
||||
|
||||
public function test_dangerous_functions_blocked() {
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('eval(1)'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('system(1)'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('base64_decode(1)'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('unserialize(1)'));
|
||||
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('cos(tan(1) + abs(cos(eval)) * pi())'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('eval (CONSTANTREADASSTRING)'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors("eval \t ()"));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('"eval"()'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('?><?php()'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('?><?php+1'));
|
||||
}
|
||||
|
||||
public function test_functions_with_wrong_num_args_caught() {
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('abs(-1, 1)'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('abs()'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('pi(1)'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('log()'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('log(64, 2, 3)'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('atan2(1.0)'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('atan2(1.0, 1.0, 2.0)'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors('max(1.0)'));
|
||||
}
|
||||
|
||||
public function test_validation_of_formulas_in_text_ok() {
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors_in_text(
|
||||
'<p>Look no equations.</p>'));
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors_in_text(
|
||||
'<p>Simple variable: {x}.</p>'));
|
||||
$this->assertFalse(qtype_calculated_find_formula_errors_in_text(
|
||||
'<p>This is an equation: {=1+1}, as is this: {={x}+{y}}.</p>' .
|
||||
'<p>Here is a more complex one: {=sin(2*pi()*{theta})}.</p>'));
|
||||
}
|
||||
|
||||
public function test_validation_of_formulas_in_text_bad_function() {
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors_in_text(
|
||||
'<p>This is an equation: {=eval(1)}.</p>'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors_in_text(
|
||||
'<p>Good: {=1+1}, bad: {=eval(1)}, good: {={x}+{y}}.</p>'));
|
||||
$this->assert_nonempty_string(qtype_calculated_find_formula_errors_in_text(
|
||||
'<p>Bad: {=eval(1)}, bad: {=system(1)}.</p>'));
|
||||
}
|
||||
}
|
||||
@@ -228,30 +228,31 @@ class qtype_calculatedmulti_edit_form extends question_edit_form {
|
||||
return $question;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the equations in the some question content.
|
||||
* @param array $errors where errors are being accumulated.
|
||||
* @param string $field the field being validated.
|
||||
* @param string $text the content of that field.
|
||||
* @return array the updated $errors array.
|
||||
*/
|
||||
protected function validate_text($errors, $field, $text) {
|
||||
$problems = qtype_calculated_find_formula_errors_in_text($text);
|
||||
if ($problems) {
|
||||
$errors[$field] = $problems;
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function validation($data, $files) {
|
||||
$errors = parent::validation($data, $files);
|
||||
|
||||
// Verifying for errors in {=...} in question text.
|
||||
$qtext = '';
|
||||
$qtextremaining = $data['questiontext']['text'];
|
||||
$possibledatasets = $this->qtypeobj->find_dataset_names($data['questiontext']['text']);
|
||||
foreach ($possibledatasets as $name => $value) {
|
||||
$qtextremaining = str_replace('{'.$name.'}', '1', $qtextremaining);
|
||||
}
|
||||
$errors = $this->validate_text($errors, 'questiontext', $data['questiontext']['text']);
|
||||
$errors = $this->validate_text($errors, 'generalfeedback', $data['generalfeedback']['text']);
|
||||
$errors = $this->validate_text($errors, 'correctfeedback', $data['correctfeedback']['text']);
|
||||
$errors = $this->validate_text($errors, 'partiallycorrectfeedback', $data['partiallycorrectfeedback']['text']);
|
||||
$errors = $this->validate_text($errors, 'incorrectfeedback', $data['incorrectfeedback']['text']);
|
||||
|
||||
while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
|
||||
$qtextsplits = explode($regs1[0], $qtextremaining, 2);
|
||||
$qtext = $qtext.$qtextsplits[0];
|
||||
$qtextremaining = $qtextsplits[1];
|
||||
if (!empty($regs1[1]) && $formulaerrors =
|
||||
qtype_calculated_find_formula_errors($regs1[1])) {
|
||||
if (!isset($errors['questiontext'])) {
|
||||
$errors['questiontext'] = $formulaerrors.':'.$regs1[1];
|
||||
} else {
|
||||
$errors['questiontext'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
$answers = $data['answer'];
|
||||
$answercount = 0;
|
||||
$maxgrade = false;
|
||||
@@ -266,6 +267,7 @@ class qtype_calculatedmulti_edit_form extends question_edit_form {
|
||||
get_string('atleastonewildcard', 'qtype_calculated');
|
||||
}
|
||||
}
|
||||
|
||||
$totalfraction = 0;
|
||||
$maxfraction = -1;
|
||||
foreach ($answers as $key => $answer) {
|
||||
@@ -279,27 +281,11 @@ class qtype_calculatedmulti_edit_form extends question_edit_form {
|
||||
}
|
||||
if ($trimmedanswer != '' || $answercount == 0) {
|
||||
// Verifying for errors in {=...} in answer text.
|
||||
$qanswer = '';
|
||||
$qanswerremaining = $trimmedanswer;
|
||||
$possibledatasets = $this->qtypeobj->find_dataset_names($trimmedanswer);
|
||||
foreach ($possibledatasets as $name => $value) {
|
||||
$qanswerremaining = str_replace('{'.$name.'}', '1', $qanswerremaining);
|
||||
}
|
||||
|
||||
while (preg_match('~\{=([^[:space:]}]*)}~', $qanswerremaining, $regs1)) {
|
||||
$qanswersplits = explode($regs1[0], $qanswerremaining, 2);
|
||||
$qanswer = $qanswer . $qanswersplits[0];
|
||||
$qanswerremaining = $qanswersplits[1];
|
||||
if (!empty($regs1[1]) && $formulaerrors =
|
||||
qtype_calculated_find_formula_errors($regs1[1])) {
|
||||
if (!isset($errors['answeroptions['.$key.']'])) {
|
||||
$errors['answeroptions['.$key.']'] = $formulaerrors.':'.$regs1[1];
|
||||
} else {
|
||||
$errors['answeroptions['.$key.']'] .= '<br/>'.$formulaerrors.':'.$regs1[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
$errors = $this->validate_text($errors, 'answeroptions[' . $key . ']', $answer);
|
||||
$errors = $this->validate_text($errors, 'feedback[' . $key . ']',
|
||||
$data['feedback'][$key]['text']);
|
||||
}
|
||||
|
||||
if ($trimmedanswer != '') {
|
||||
if ('2' == $data['correctanswerformat'][$key] &&
|
||||
'0' == $data['correctanswerlength'][$key]) {
|
||||
|
||||
@@ -42,6 +42,9 @@ class qtype_calculatedmulti extends qtype_calculated {
|
||||
global $CFG, $DB;
|
||||
$context = $question->context;
|
||||
|
||||
// Make it impossible to save bad formulas anywhere.
|
||||
$this->validate_question_data($question);
|
||||
|
||||
// Calculated options.
|
||||
$update = true;
|
||||
$options = $DB->get_record('question_calculated_options',
|
||||
@@ -72,10 +75,7 @@ class qtype_calculatedmulti extends qtype_calculated {
|
||||
}
|
||||
|
||||
// Insert all the new answers.
|
||||
if (isset($question->answer) && !isset($question->answers)) {
|
||||
$question->answers = $question->answer;
|
||||
}
|
||||
foreach ($question->answers as $key => $answerdata) {
|
||||
foreach ($question->answer as $key => $answerdata) {
|
||||
if (is_array($answerdata)) {
|
||||
$answerdata = $answerdata['text'];
|
||||
}
|
||||
@@ -156,6 +156,20 @@ class qtype_calculatedmulti extends qtype_calculated {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function validate_answer($answer) {
|
||||
$error = qtype_calculated_find_formula_errors_in_text($answer);
|
||||
if ($error) {
|
||||
throw new coding_exception($error);
|
||||
}
|
||||
}
|
||||
|
||||
protected function validate_question_data($question) {
|
||||
parent::validate_question_data($question);
|
||||
$this->validate_text($question->correctfeedback['text']);
|
||||
$this->validate_text($question->partiallycorrectfeedback['text']);
|
||||
$this->validate_text($question->incorrectfeedback['text']);
|
||||
}
|
||||
|
||||
protected function make_question_instance($questiondata) {
|
||||
question_bank::load_question_definition_classes($this->name());
|
||||
if ($questiondata->options->single) {
|
||||
|
||||
@@ -43,11 +43,9 @@ class qtype_calculatedsimple extends qtype_calculated {
|
||||
public function save_question_options($question) {
|
||||
global $CFG, $DB;
|
||||
$context = $question->context;
|
||||
// Get old answers.
|
||||
|
||||
if (isset($question->answer) && !isset($question->answers)) {
|
||||
$question->answers = $question->answer;
|
||||
}
|
||||
// Make it impossible to save bad formulas anywhere.
|
||||
$this->validate_question_data($question);
|
||||
|
||||
// Get old versions of the objects.
|
||||
if (!$oldanswers = $DB->get_records('question_answers',
|
||||
@@ -69,10 +67,7 @@ class qtype_calculatedsimple extends qtype_calculated {
|
||||
$units = &$result->units;
|
||||
}
|
||||
// Insert all the new answers.
|
||||
if (isset($question->answer) && !isset($question->answers)) {
|
||||
$question->answers = $question->answer;
|
||||
}
|
||||
foreach ($question->answers as $key => $answerdata) {
|
||||
foreach ($question->answer as $key => $answerdata) {
|
||||
if (is_array($answerdata)) {
|
||||
$answerdata = $answerdata['text'];
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class repository_coursefiles extends repository {
|
||||
$browser = get_file_browser();
|
||||
|
||||
if (!empty($encodedpath)) {
|
||||
$params = unserialize(base64_decode($encodedpath));
|
||||
$params = json_decode(base64_decode($encodedpath), true);
|
||||
if (is_array($params)) {
|
||||
$filepath = is_null($params['filepath']) ? NULL : clean_param($params['filepath'], PARAM_PATH);
|
||||
$filename = is_null($params['filename']) ? NULL : clean_param($params['filename'], PARAM_FILE);
|
||||
@@ -80,12 +80,12 @@ class repository_coursefiles extends repository {
|
||||
if ($fileinfo = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename)) {
|
||||
// build path navigation
|
||||
$pathnodes = array();
|
||||
$encodedpath = base64_encode(serialize($fileinfo->get_params()));
|
||||
$encodedpath = base64_encode(json_encode($fileinfo->get_params()));
|
||||
$pathnodes[] = array('name'=>$fileinfo->get_visible_name(), 'path'=>$encodedpath);
|
||||
$level = $fileinfo->get_parent();
|
||||
while ($level) {
|
||||
$params = $level->get_params();
|
||||
$encodedpath = base64_encode(serialize($params));
|
||||
$encodedpath = base64_encode(json_encode($params));
|
||||
if ($params['contextid'] != $context->id) {
|
||||
break;
|
||||
}
|
||||
@@ -102,7 +102,7 @@ class repository_coursefiles extends repository {
|
||||
if ($child->is_directory()) {
|
||||
$params = $child->get_params();
|
||||
$subdir_children = $child->get_children();
|
||||
$encodedpath = base64_encode(serialize($params));
|
||||
$encodedpath = base64_encode(json_encode($params));
|
||||
$node = array(
|
||||
'title' => $child->get_visible_name(),
|
||||
'datemodified' => $child->get_timemodified(),
|
||||
@@ -113,7 +113,7 @@ class repository_coursefiles extends repository {
|
||||
);
|
||||
$list[] = $node;
|
||||
} else {
|
||||
$encodedpath = base64_encode(serialize($child->get_params()));
|
||||
$encodedpath = base64_encode(json_encode($child->get_params()));
|
||||
$node = array(
|
||||
'title' => $child->get_visible_name(),
|
||||
'size' => $child->get_filesize(),
|
||||
|
||||
@@ -55,7 +55,7 @@ if (isset($info->license)) {
|
||||
$license = s(clean_param($info->license, PARAM_ALPHAEXT));
|
||||
}
|
||||
|
||||
$source = base64_encode(serialize((object)array('url'=>$url,'filename'=>$filename)));
|
||||
$source = base64_encode(json_encode(array('url'=>$url,'filename'=>$filename)));
|
||||
|
||||
$js =<<<EOD
|
||||
<html>
|
||||
|
||||
@@ -125,7 +125,11 @@ class repository_equella extends repository {
|
||||
* @return string file referece
|
||||
*/
|
||||
public function get_file_reference($source) {
|
||||
return $source;
|
||||
// Internally we store serialized value but user input is json-encoded for security reasons.
|
||||
$ref = json_decode(base64_decode($source));
|
||||
$filename = clean_param($ref->filename, PARAM_FILE);
|
||||
$url = clean_param($ref->url, PARAM_URL);
|
||||
return base64_encode(serialize((object)array('url' => $url, 'filename' => $filename)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -414,12 +418,13 @@ class repository_equella extends repository {
|
||||
/**
|
||||
* Return the source information
|
||||
*
|
||||
* @param stdClass $url
|
||||
* @param string $source
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_file_source_info($url) {
|
||||
$ref = unserialize(base64_decode($url));
|
||||
return 'EQUELLA: ' . $ref->filename;
|
||||
public function get_file_source_info($source) {
|
||||
$ref = json_decode(base64_decode($source));
|
||||
$filename = clean_param($ref->filename, PARAM_FILE);
|
||||
return 'EQUELLA: ' . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -293,7 +293,7 @@ case 'download':
|
||||
// note that in this case user may not have permission to access the source file directly
|
||||
// so no file_browser/file_info can be used below
|
||||
if ($repo->has_moodle_files()) {
|
||||
$file = repository::get_moodle_file($fileurl);
|
||||
$file = repository::get_moodle_file($reference);
|
||||
if ($file && $file->is_external_file()) {
|
||||
$sourcefield = $file->get_source(); // remember the original source
|
||||
$record->source = $repo::build_source_field($sourcefield);
|
||||
|
||||
+36
-13
@@ -799,13 +799,14 @@ abstract class repository implements cacheable_object {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the 'source' returned by moodle repositories and returns an instance of stored_file
|
||||
* Parses the moodle file reference and returns an instance of stored_file
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $reference reference to the moodle internal file as retruned by
|
||||
* {@link repository::get_file_reference()} or {@link file_storage::pack_reference()}
|
||||
* @return stored_file|null
|
||||
*/
|
||||
public static function get_moodle_file($source) {
|
||||
$params = file_storage::unpack_reference($source, true);
|
||||
public static function get_moodle_file($reference) {
|
||||
$params = file_storage::unpack_reference($reference, true);
|
||||
$fs = get_file_storage();
|
||||
return $fs->get_file($params['contextid'], $params['component'], $params['filearea'],
|
||||
$params['itemid'], $params['filepath'], $params['filename']);
|
||||
@@ -817,13 +818,14 @@ abstract class repository implements cacheable_object {
|
||||
* This is checked when user tries to pick the file from repository to deal with
|
||||
* potential parameter substitutions is request
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned)
|
||||
* @return bool whether the file is accessible by current user
|
||||
*/
|
||||
public function file_is_accessible($source) {
|
||||
if ($this->has_moodle_files()) {
|
||||
$reference = $this->get_file_reference($source);
|
||||
try {
|
||||
$params = file_storage::unpack_reference($source, true);
|
||||
$params = file_storage::unpack_reference($reference, true);
|
||||
} catch (file_reference_exception $e) {
|
||||
return false;
|
||||
}
|
||||
@@ -1401,12 +1403,13 @@ abstract class repository implements cacheable_object {
|
||||
* again to another file area (also as a copy or as a reference), the value of
|
||||
* files.source is copied.
|
||||
*
|
||||
* @param string $source the value that repository returned in listing as 'source'
|
||||
* @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned)
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_file_source_info($source) {
|
||||
if ($this->has_moodle_files()) {
|
||||
return $this->get_reference_details($source, 0);
|
||||
$reference = $this->get_file_reference($source);
|
||||
return $this->get_reference_details($reference, 0);
|
||||
}
|
||||
return $source;
|
||||
}
|
||||
@@ -1687,13 +1690,33 @@ abstract class repository implements cacheable_object {
|
||||
/**
|
||||
* Prepare file reference information
|
||||
*
|
||||
* @param string $source
|
||||
* @return string file referece
|
||||
* @param string $source source of the file, returned by repository as 'source' and received back from user (not cleaned)
|
||||
* @return string file reference, ready to be stored
|
||||
*/
|
||||
public function get_file_reference($source) {
|
||||
if ($this->has_moodle_files() && ($this->supported_returntypes() & FILE_REFERENCE)) {
|
||||
$params = file_storage::unpack_reference($source);
|
||||
if (!is_array($params)) {
|
||||
if ($source && $this->has_moodle_files()) {
|
||||
$params = @json_decode(base64_decode($source), true);
|
||||
if (!$params && !in_array($this->get_typename(), array('recent', 'user', 'local', 'coursefiles'))) {
|
||||
// IMPORTANT! Since default format for moodle files was changed in the minor release as a security fix
|
||||
// we maintain an old code here in order not to break 3rd party repositories that deal
|
||||
// with moodle files. Repositories are strongly encouraged to be upgraded, see MDL-45616.
|
||||
// In Moodle 2.8 this fallback will be removed.
|
||||
$params = file_storage::unpack_reference($source, true);
|
||||
return file_storage::pack_reference($params);
|
||||
}
|
||||
if (!is_array($params) || empty($params['contextid'])) {
|
||||
throw new repository_exception('invalidparams', 'repository');
|
||||
}
|
||||
$params = array(
|
||||
'component' => empty($params['component']) ? '' : clean_param($params['component'], PARAM_COMPONENT),
|
||||
'filearea' => empty($params['filearea']) ? '' : clean_param($params['filearea'], PARAM_AREA),
|
||||
'itemid' => empty($params['itemid']) ? 0 : clean_param($params['itemid'], PARAM_INT),
|
||||
'filename' => empty($params['filename']) ? null : clean_param($params['filename'], PARAM_FILE),
|
||||
'filepath' => empty($params['filepath']) ? null : clean_param($params['filepath'], PARAM_PATH),
|
||||
'contextid' => clean_param($params['contextid'], PARAM_INT)
|
||||
);
|
||||
// Check if context exists.
|
||||
if (!context::instance_by_id($params['contextid'], IGNORE_MISSING)) {
|
||||
throw new repository_exception('invalidparams', 'repository');
|
||||
}
|
||||
return file_storage::pack_reference($params);
|
||||
|
||||
@@ -56,7 +56,7 @@ class repository_local extends repository {
|
||||
$component = null;
|
||||
|
||||
if (!empty($encodedpath)) {
|
||||
$params = unserialize(base64_decode($encodedpath));
|
||||
$params = json_decode(base64_decode($encodedpath), true);
|
||||
if (is_array($params) && isset($params['contextid'])) {
|
||||
$component = is_null($params['component']) ? NULL : clean_param($params['component'], PARAM_COMPONENT);
|
||||
$filearea = is_null($params['filearea']) ? NULL : clean_param($params['filearea'], PARAM_AREA);
|
||||
@@ -216,7 +216,7 @@ class repository_local extends repository {
|
||||
*/
|
||||
private function get_node(file_info $fileinfo) {
|
||||
global $OUTPUT;
|
||||
$encodedpath = base64_encode(serialize($fileinfo->get_params()));
|
||||
$encodedpath = base64_encode(json_encode($fileinfo->get_params()));
|
||||
$node = array(
|
||||
'title' => $fileinfo->get_visible_name(),
|
||||
'datemodified' => $fileinfo->get_timemodified(),
|
||||
@@ -256,7 +256,7 @@ class repository_local extends repository {
|
||||
* @return array
|
||||
*/
|
||||
private function get_node_path(file_info $fileinfo) {
|
||||
$encodedpath = base64_encode(serialize($fileinfo->get_params()));
|
||||
$encodedpath = base64_encode(json_encode($fileinfo->get_params()));
|
||||
return array(
|
||||
'path' => $encodedpath,
|
||||
'name' => $fileinfo->get_visible_name()
|
||||
|
||||
@@ -126,7 +126,7 @@ class repository_recent extends repository {
|
||||
$fileinfo = $browser->get_file_info($context, $file['component'],
|
||||
$file['filearea'], $file['itemid'], $file['filepath'], $file['filename']);
|
||||
if ($fileinfo) {
|
||||
$params = base64_encode(serialize($file));
|
||||
$params = base64_encode(json_encode($file));
|
||||
$node = array(
|
||||
'title' => $fileinfo->get_visible_name(),
|
||||
'size' => $fileinfo->get_filesize(),
|
||||
@@ -192,7 +192,8 @@ class repository_recent extends repository {
|
||||
*/
|
||||
public function file_is_accessible($source) {
|
||||
global $USER;
|
||||
$file = self::get_moodle_file($source);
|
||||
$reference = $this->get_file_reference($source);
|
||||
$file = self::get_moodle_file($reference);
|
||||
return (!empty($file) && $file->get_userid() == $USER->id);
|
||||
}
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ switch ($action) {
|
||||
// note that in this case user may not have permission to access the source file directly
|
||||
// so no file_browser/file_info can be used below
|
||||
if ($repo->has_moodle_files()) {
|
||||
$file = repository::get_moodle_file($source);
|
||||
$file = repository::get_moodle_file($reference);
|
||||
if ($file && $file->is_external_file()) {
|
||||
$sourcefield = $file->get_source(); // remember the original source
|
||||
$record->source = $repo::build_source_field($sourcefield);
|
||||
|
||||
@@ -61,7 +61,7 @@ class repository_user extends repository {
|
||||
$list = array();
|
||||
|
||||
if (!empty($encodedpath)) {
|
||||
$params = unserialize(base64_decode($encodedpath));
|
||||
$params = json_decode(base64_decode($encodedpath), true);
|
||||
if (is_array($params)) {
|
||||
$filepath = clean_param($params['filepath'], PARAM_PATH);
|
||||
$filename = clean_param($params['filename'], PARAM_FILE);
|
||||
@@ -84,7 +84,7 @@ class repository_user extends repository {
|
||||
$level = $fileinfo;
|
||||
$params = $fileinfo->get_params();
|
||||
while ($level && $params['component'] == 'user' && $params['filearea'] == 'private') {
|
||||
$encodedpath = base64_encode(serialize($level->get_params()));
|
||||
$encodedpath = base64_encode(json_encode($level->get_params()));
|
||||
$pathnodes[] = array('name'=>$level->get_visible_name(), 'path'=>$encodedpath);
|
||||
$level = $level->get_parent();
|
||||
$params = $level->get_params();
|
||||
@@ -95,7 +95,7 @@ class repository_user extends repository {
|
||||
$children = $fileinfo->get_children();
|
||||
foreach ($children as $child) {
|
||||
if ($child->is_directory()) {
|
||||
$encodedpath = base64_encode(serialize($child->get_params()));
|
||||
$encodedpath = base64_encode(json_encode($child->get_params()));
|
||||
$node = array(
|
||||
'title' => $child->get_visible_name(),
|
||||
'datemodified' => $child->get_timemodified(),
|
||||
@@ -106,7 +106,7 @@ class repository_user extends repository {
|
||||
);
|
||||
$list[] = $node;
|
||||
} else {
|
||||
$encodedpath = base64_encode(serialize($child->get_params()));
|
||||
$encodedpath = base64_encode(json_encode($child->get_params()));
|
||||
$node = array(
|
||||
'title' => $child->get_visible_name(),
|
||||
'size' => $child->get_filesize(),
|
||||
|
||||
+1
-1
@@ -314,7 +314,7 @@ if ($user->icq && !isset($hiddenfields['icqnumber'])) {
|
||||
|
||||
if ($user->skype && !isset($hiddenfields['skypeid'])) {
|
||||
$imurl = 'skype:'.urlencode($user->skype).'?call';
|
||||
$iconurl = new moodle_url('http://mystatus.skype.com/smallicon/'.$user->skype);
|
||||
$iconurl = new moodle_url('http://mystatus.skype.com/smallicon/'.urlencode($user->skype));
|
||||
if (strpos($CFG->httpswwwroot, 'https:') === 0) {
|
||||
// Bad luck, skype devs are lazy to set up SSL on their servers - see MDL-37233.
|
||||
$statusicon = '';
|
||||
|
||||
Reference in New Issue
Block a user