diff --git a/auth/shibboleth/index.php b/auth/shibboleth/index.php index cf9695899ad..9cc3af1b789 100644 --- a/auth/shibboleth/index.php +++ b/auth/shibboleth/index.php @@ -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)) { diff --git a/badges/renderer.php b/badges/renderer.php index 65ed4026348..8e400954f6a 100644 --- a/badges/renderer.php +++ b/badges/renderer.php @@ -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)); diff --git a/grade/grading/form/guide/guideeditor.php b/grade/grading/form/guide/guideeditor.php index aebb9822fca..da430ec9027 100644 --- a/grade/grading/form/guide/guideeditor.php +++ b/grade/grading/form/guide/guideeditor.php @@ -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'), diff --git a/grade/grading/form/guide/js/guide.js b/grade/grading/form/guide/js/guide.js index 1f1cd909d7d..09da8c4512b 100644 --- a/grade/grading/form/guide/js/guide.js +++ b/grade/grading/form/guide/js/guide.js @@ -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(); }); diff --git a/grade/grading/form/guide/js/guideeditor.js b/grade/grading/form/guide/js/guideeditor.js index 3aeb83be91f..94c1e157fab 100644 --- a/grade/grading/form/guide/js/guideeditor.js +++ b/grade/grading/form/guide/js/guideeditor.js @@ -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') diff --git a/grade/grading/form/guide/lib.php b/grade/grading/form/guide/lib.php index 942b19b37fb..d893e0cfe10 100644 --- a/grade/grading/form/guide/lib.php +++ b/grade/grading/form/guide/lib.php @@ -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')); diff --git a/grade/grading/form/guide/renderer.php b/grade/grading/form/guide/renderer.php index 442d24bdf39..87e94de708e 100644 --- a/grade/grading/form/guide/renderer.php +++ b/grade/grading/form/guide/renderer.php @@ -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'; diff --git a/grade/grading/form/rubric/js/rubriceditor.js b/grade/grading/form/rubric/js/rubriceditor.js index bff4d09b2f1..f0dd3aea297 100644 --- a/grade/grading/form/rubric/js/rubriceditor.js +++ b/grade/grading/form/rubric/js/rubriceditor.js @@ -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') diff --git a/grade/grading/form/rubric/renderer.php b/grade/grading/form/rubric/renderer.php index 9132f43e009..b2ba8ff44d7 100644 --- a/grade/grading/form/rubric/renderer.php +++ b/grade/grading/form/rubric/renderer.php @@ -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) { diff --git a/grade/grading/form/rubric/rubriceditor.php b/grade/grading/form/rubric/rubriceditor.php index bb9cee3e42b..b3cc5bfb40e 100644 --- a/grade/grading/form/rubric/rubriceditor.php +++ b/grade/grading/form/rubric/rubriceditor.php @@ -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') )); diff --git a/lib/form/editor.php b/lib/form/editor.php index f2003d2af0b..c82c05c7b58 100644 --- a/lib/form/editor.php +++ b/lib/form/editor.php @@ -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 .= ''; + $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( diff --git a/lib/form/filemanager.php b/lib/form/filemanager.php index 77ee16903e5..8140fec153d 100644 --- a/lib/form/filemanager.php +++ b/lib/form/filemanager.php @@ -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 .= ''; + $html .= html_writer::empty_tag('input', array('value' => $draftitemid, 'name' => $elname, 'type' => 'hidden')); // label element needs 'for' attribute work - $html .= ''; + $html .= html_writer::empty_tag('input', array('value' => '', 'id' => 'id_'.$elname, 'type' => 'hidden')); return $html; } diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 857084bd4c7..020b3685db9 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -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 .= '
'.s($smallmessage).'
'; - } - } - + $strmessages = get_string('unreadnewmessages', 'message', count($message_users)); $strgomessage = get_string('gotomessages', 'message'); $strstaymessage = get_string('ignore','admin'); diff --git a/lib/yui/build/moodle-core-notification/moodle-core-notification-debug.js b/lib/yui/build/moodle-core-notification/moodle-core-notification-debug.js index eb6dbef2744..a5aedd17f2c 100644 --- a/lib/yui/build/moodle-core-notification/moodle-core-notification-debug.js +++ b/lib/yui/build/moodle-core-notification/moodle-core-notification-debug.js @@ -302,13 +302,13 @@ Y.extend(EXCEPTION, DIALOGUE, { delay = this.get('hideTimeoutDelay'); this.get(BASE).addClass('moodle-dialogue-exception'); this.setStdModContent(Y.WidgetStdMod.HEADER, '

' - + config.name + '

', Y.WidgetStdMod.REPLACE); + + Y.Escape.html(config.name) + '', Y.WidgetStdMod.REPLACE); content = Y.Node.create('
') - .append(Y.Node.create('
'+this.get('message')+'
')) + .append(Y.Node.create('
'+Y.Escape.html(this.get('message'))+'
')) .append(Y.Node.create('')) + + Y.Escape.html(this.get('fileName'))+'')) .append(Y.Node.create('')) + + Y.Escape.html(this.get('lineNumber'))+'')) .append(Y.Node.create('')); 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, '

' - + config.name + '

', Y.WidgetStdMod.REPLACE); + + Y.Escape.html(config.name) + '', Y.WidgetStdMod.REPLACE); content = Y.Node.create('
') - .append(Y.Node.create('
'+this.get('error')+'
')) + .append(Y.Node.create('
'+Y.Escape.html(this.get('error'))+'
')) .append(Y.Node.create('')) .append(Y.Node.create('')) + + Y.Escape.html(this.get('debuginfo'))+'')) .append(Y.Node.create('')); + + Y.Escape.html(this.get('stacktrace'))+'')); 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 = ''+link.replace(M.cfg.wwwroot, '')+''; } 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"]}); diff --git a/lib/yui/build/moodle-core-notification/moodle-core-notification-min.js b/lib/yui/build/moodle-core-notification/moodle-core-notification-min.js index 447093cdbcd..dd7992bfaf8 100644 --- a/lib/yui/build/moodle-core-notification/moodle-core-notification-min.js +++ b/lib/yui/build/moodle-core-notification/moodle-core-notification-min.js @@ -1,2 +1,2 @@ YUI.add("moodle-core-notification",function(e,t){var n="Moodle dialogue",r="moodle-dialogue",i="Moodle confirmation dialogue",s="Moodle exception",o="Moodle AJAX exception",u="Moodle alert",a="notificationBase",f="yesLabel",l="noLabel",c="title",h="question",p={BASE:"moodle-dialogue-base",WRAP:"moodle-dialogue-wrap",HEADER:"moodle-dialogue-hd",BODY:"moodle-dialogue-bd",CONTENT:"moodle-dialogue-content",FOOTER:"moodle-dialogue-ft",HIDDEN:"hidden",LIGHTBOX:"moodle-dialogue-lightbox"},d,v,m,g,y;y=function(t){t.count=e.stamp(this);var n="moodle-dialogue-"+t.count;t.notificationBase=e.Node.create('
').append(e.Node.create('').append(e.Node.create('
')).append(e.Node.create('
')).append(e.Node.create('
'))),e.one(document.body).append(t.notificationBase),t.srcNode="#"+n,t.width=t.width||"400px",t.visible=t.visible||!1,t.center=t.centered||!0,t.centered=!1,t.lightbox!==!1&&(t.modal=!0),delete t.lightbox,t.closeButton===!1?t.buttons=null:t.buttons=[{section:e.WidgetStdMod.HEADER,classNames:"closebutton",action:function(){this.hide()}}],y.superclass.constructor.apply(this,[t]),t.closeButton!==!1&&this.get("buttons").header[0].setAttribute("title",this.get("closeButtonTitle"))},e.extend(y,e.Panel,{initializer:function(){this.after("visibleChange",this.visibilityChanged,this),this.render(),this.show(),this.get("boundingBox").setStyle("zIndex",null)},visibilityChanged:function(t){var n;t.attrName==="visible"&&(this.get("maskNode").addClass(p.LIGHTBOX),this.get("center")&&!t.prevVal&&t.newVal&&this.centerDialogue(),this.get("draggable")&&(n="#"+this.get("id")+" ."+p.HEADER,this.plug(e.Plugin.Drag,{handles:[n]}),e.one(n).setStyle("cursor","move")))},hide:function(e){if(e&&e.type==="key"&&e.keyCode===27&&!this.get("focused"))return;return y.superclass.hide.call(this,arguments)},centerDialogue:function(){var t=this.get("boundingBox"),n=t.hasClass(r+"-hidden"),i,s;n&&t.setStyle("top","-1000px").removeClass(r+"-hidden"),i=Math.max(Math.round((t.get("winWidth")-t.get("offsetWidth"))/2),15),s=Math.max(Math.round((t.get("winHeight")-t.get("offsetHeight"))/2),15)+e.one(window).get("scrollTop"),n&&t.addClass(r+"-hidden"),t.setStyle("left",i).setStyle("top",s)}},{NAME:n,CSS_PREFIX:r,ATTRS:{notificationBase:{},lightbox:{validator:e.Lang.isBoolean,value:!0},closeButton:{validator:e.Lang.isBoolean,value:!0},closeButtonTitle:{validator:e.Lang.isString,value:"Close"},center:{validator:e.Lang.isBoolean,value:!0},draggable:{validator:e.Lang.isBoolean,value:!1},COUNT:{value:null}}}),v=function(e){e.closeButton=!1,v.superclass.constructor.apply(this,[e])},e.extend(v,y,{_closeEvents:null,initializer:function(){this._closeEvents=[],this.publish("complete");var t=e.Node.create(''),n=e.Node.create('
').append(e.Node.create('
'+this.get("message")+"
")).append(e.Node.create('
').append(t));this.get(a).addClass("moodle-dialogue-confirm"),this.setStdModContent(e.WidgetStdMod.BODY,n,e.WidgetStdMod.REPLACE),this.setStdModContent(e.WidgetStdMod.HEADER,'

'+this.get(c)+"

",e.WidgetStdMod.REPLACE),this.after("destroyedChange",function(){this.get(a).remove()},this),this._closeEvents.push(e.on("key",this.submit,window,"down:13",this),t.on("click",this.submit,this));var r=this.get("boundingBox").one(".closebutton");r&&this._closeEvents.push(r.on("click",this.submit,this))},submit:function(){(new e.EventHandle(this._closeEvents)).detach(),this.fire("complete"),this.hide(),this.destroy()}},{NAME:u,CSS_PREFIX:r,ATTRS:{title:{validator:e.Lang.isString,value:"Alert"},message:{validator:e.Lang.isString,value:"Confirm"},yesLabel:{validator:e.Lang.isString,setter:function(e){return e||(e="Ok"),e},value:"Ok"}}}),m=function(e){m.superclass.constructor.apply(this,[e])},e.extend(m,y,{_closeEvents:null,initializer:function(){this._closeEvents=[],this.publish("complete"),this.publish("complete-yes"),this.publish("complete-no");var t=e.Node.create(''),n=e.Node.create(''),r=e.Node.create('
').append(e.Node.create('
'+this.get(h)+"
")).append(e.Node.create('
').append(t).append(n));this.get(a).addClass("moodle-dialogue-confirm"),this.setStdModContent(e.WidgetStdMod.BODY,r,e.WidgetStdMod.REPLACE),this.setStdModContent(e.WidgetStdMod.HEADER,'

'+this.get(c)+"

",e.WidgetStdMod.REPLACE),this.after("destroyedChange",function(){this.get(a).remove()},this),this._closeEvents.push(e.on("key",this.submit,window,"down:27",this,!1),t.on("click",this.submit,this,!0),n.on("click",this.submit,this,!1));var i=this.get("boundingBox").one(".closebutton");i&&this._closeEvents.push(i.on("click",this.submit,this))},submit:function(t,n){(new e.EventHandle(this._closeEvents)).detach(),this.fire("complete",n),n?this.fire("complete-yes"):this.fire("complete-no"),this.hide(),this.destroy()}},{NAME:i,CSS_PREFIX:r,ATTRS:{yesLabel:{validator:e.Lang.isString,value:"Yes"},noLabel:{validator:e.Lang.isString,value:"No"},title:{validator:e.Lang.isString,value:"Confirm"},question:{validator:e.Lang.isString,value:"Are you sure?"}}}),e.augment(m,e.EventTarget),d=function(t){t.width=t.width||M.cfg.developerdebug?Math.floor(e.one(document.body).get("winWidth")/3)+"px":null,t.closeButton=!0,d.superclass.constructor -.apply(this,[t])},e.extend(d,y,{_hideTimeout:null,_keypress:null,initializer:function(t){var n,r=this,i=this.get("hideTimeoutDelay");this.get(a).addClass("moodle-dialogue-exception"),this.setStdModContent(e.WidgetStdMod.HEADER,'

'+t.name+"

",e.WidgetStdMod.REPLACE),n=e.Node.create('
').append(e.Node.create('
'+this.get("message")+"
")).append(e.Node.create('")).append(e.Node.create('")).append(e.Node.create('")),M.cfg.developerdebug&&n.all(".moodle-exception-param").removeClass("hidden"),this.setStdModContent(e.WidgetStdMod.BODY,n,e.WidgetStdMod.REPLACE),i&&(this._hideTimeout=setTimeout(function(){r.hide()},i)),this.after("visibleChange",this.visibilityChanged,this),this.after("destroyedChange",function(){this.get(a).remove()},this),this._keypress=e.on("key",this.hide,window,"down:13,27",this),this.centerDialogue()},visibilityChanged:function(e){if(e.attrName==="visible"&&e.prevVal&&!e.newVal){this._keypress&&this._keypress.detach();var t=this;setTimeout(function(){t.destroy()},1e3)}}},{NAME:s,CSS_PREFIX:r,ATTRS:{message:{value:""},name:{value:""},fileName:{value:""},lineNumber:{value:""},stack:{setter:function(e){var t=e.split("\n"),n=new RegExp("^(.+)@("+M.cfg.wwwroot+")?(.{0,75}).*:(\\d+)$"),r;for(r in t)t[r]=t[r].replace(n,"
ln: $4
$3
$1
");return t.join("")},value:""},hideTimeoutDelay:{validator:e.Lang.isNumber,value:null}}}),g=function(e){e.name=e.name||"Error",e.closeButton=!0,g.superclass.constructor.apply(this,[e])},e.extend(g,y,{_keypress:null,initializer:function(t){var n,r=this,i=this.get("hideTimeoutDelay");this.get(a).addClass("moodle-dialogue-exception"),this.setStdModContent(e.WidgetStdMod.HEADER,'

'+t.name+"

",e.WidgetStdMod.REPLACE),n=e.Node.create('
').append(e.Node.create('
'+this.get("error")+"
")).append(e.Node.create('")).append(e.Node.create('")).append(e.Node.create('")),M.cfg.developerdebug&&n.all(".moodle-exception-param").removeClass("hidden"),this.setStdModContent(e.WidgetStdMod.BODY,n,e.WidgetStdMod.REPLACE),i&&(this._hideTimeout=setTimeout(function(){r.hide()},i)),this.after("visibleChange",this.visibilityChanged,this),this._keypress=e.on("key",this.hide,window,"down:13, 27",this),this.centerDialogue()},visibilityChanged:function(e){if(e.attrName==="visible"&&e.prevVal&&!e.newVal){var t=this;this._keypress.detach(),setTimeout(function(){t.destroy()},1e3)}}},{NAME:o,CSS_PREFIX:r,ATTRS:{error:{validator:e.Lang.isString,value:"Unknown error"},debuginfo:{value:null},stacktrace:{value:null},reproductionlink:{setter:function(e){return e!==null&&(e=''+e.replace(M.cfg.wwwroot,"")+""),e},value:null},hideTimeoutDelay:{validator:e.Lang.isNumber,value:null}}}),M.core=M.core||{},M.core.dialogue=y,M.core.alert=v,M.core.confirm=m,M.core.exception=d,M.core.ajaxException=g},"@VERSION@",{requires:["base","node","panel","event-key","dd-plugin"]}); +.apply(this,[t])},e.extend(d,y,{_hideTimeout:null,_keypress:null,initializer:function(t){var n,r=this,i=this.get("hideTimeoutDelay");this.get(a).addClass("moodle-dialogue-exception"),this.setStdModContent(e.WidgetStdMod.HEADER,'

'+e.Escape.html(t.name)+"

",e.WidgetStdMod.REPLACE),n=e.Node.create('
').append(e.Node.create('
'+e.Escape.html(this.get("message"))+"
")).append(e.Node.create('")).append(e.Node.create('")).append(e.Node.create('")),M.cfg.developerdebug&&n.all(".moodle-exception-param").removeClass("hidden"),this.setStdModContent(e.WidgetStdMod.BODY,n,e.WidgetStdMod.REPLACE),i&&(this._hideTimeout=setTimeout(function(){r.hide()},i)),this.after("visibleChange",this.visibilityChanged,this),this.after("destroyedChange",function(){this.get(a).remove()},this),this._keypress=e.on("key",this.hide,window,"down:13,27",this),this.centerDialogue()},visibilityChanged:function(e){if(e.attrName==="visible"&&e.prevVal&&!e.newVal){this._keypress&&this._keypress.detach();var t=this;setTimeout(function(){t.destroy()},1e3)}}},{NAME:s,CSS_PREFIX:r,ATTRS:{message:{value:""},name:{value:""},fileName:{value:""},lineNumber:{value:""},stack:{setter:function(t){var n=e.Escape.html(t).split("\n"),r=new RegExp("^(.+)@("+M.cfg.wwwroot+")?(.{0,75}).*:(\\d+)$"),i;for(i in n)n[i]=n[i].replace(r,"
ln: $4
$3
$1
");return n.join("")},value:""},hideTimeoutDelay:{validator:e.Lang.isNumber,value:null}}}),g=function(e){e.name=e.name||"Error",e.closeButton=!0,g.superclass.constructor.apply(this,[e])},e.extend(g,y,{_keypress:null,initializer:function(t){var n,r=this,i=this.get("hideTimeoutDelay");this.get(a).addClass("moodle-dialogue-exception"),this.setStdModContent(e.WidgetStdMod.HEADER,'

'+e.Escape.html(t.name)+"

",e.WidgetStdMod.REPLACE),n=e.Node.create('
').append(e.Node.create('
'+e.Escape.html(this.get("error"))+"
")).append(e.Node.create('")).append(e.Node.create('")).append(e.Node.create('")),M.cfg.developerdebug&&n.all(".moodle-exception-param").removeClass("hidden"),this.setStdModContent(e.WidgetStdMod.BODY,n,e.WidgetStdMod.REPLACE),i&&(this._hideTimeout=setTimeout(function(){r.hide()},i)),this.after("visibleChange",this.visibilityChanged,this),this._keypress=e.on("key",this.hide,window,"down:13, 27",this),this.centerDialogue()},visibilityChanged:function(e){if(e.attrName==="visible"&&e.prevVal&&!e.newVal){var t=this;this._keypress.detach(),setTimeout(function(){t.destroy()},1e3)}}},{NAME:o,CSS_PREFIX:r,ATTRS:{error:{validator:e.Lang.isString,value:"Unknown error"},debuginfo:{value:null},stacktrace:{value:null},reproductionlink:{setter:function(t){return t!==null&&(t=e.Escape.html(t),t=''+t.replace(M.cfg.wwwroot,"")+""),t},value:null},hideTimeoutDelay:{validator:e.Lang.isNumber,value:null}}}),M.core=M.core||{},M.core.dialogue=y,M.core.alert=v,M.core.confirm=m,M.core.exception=d,M.core.ajaxException=g},"@VERSION@",{requires:["base","node","panel","event-key","escape","dd-plugin"]}); diff --git a/lib/yui/build/moodle-core-notification/moodle-core-notification.js b/lib/yui/build/moodle-core-notification/moodle-core-notification.js index eb6dbef2744..a5aedd17f2c 100644 --- a/lib/yui/build/moodle-core-notification/moodle-core-notification.js +++ b/lib/yui/build/moodle-core-notification/moodle-core-notification.js @@ -302,13 +302,13 @@ Y.extend(EXCEPTION, DIALOGUE, { delay = this.get('hideTimeoutDelay'); this.get(BASE).addClass('moodle-dialogue-exception'); this.setStdModContent(Y.WidgetStdMod.HEADER, '

' - + config.name + '

', Y.WidgetStdMod.REPLACE); + + Y.Escape.html(config.name) + '', Y.WidgetStdMod.REPLACE); content = Y.Node.create('
') - .append(Y.Node.create('
'+this.get('message')+'
')) + .append(Y.Node.create('
'+Y.Escape.html(this.get('message'))+'
')) .append(Y.Node.create('')) + + Y.Escape.html(this.get('fileName'))+'
')) .append(Y.Node.create('')) + + Y.Escape.html(this.get('lineNumber'))+'')) .append(Y.Node.create('')); 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, '

' - + config.name + '

', Y.WidgetStdMod.REPLACE); + + Y.Escape.html(config.name) + '', Y.WidgetStdMod.REPLACE); content = Y.Node.create('
') - .append(Y.Node.create('
'+this.get('error')+'
')) + .append(Y.Node.create('
'+Y.Escape.html(this.get('error'))+'
')) .append(Y.Node.create('')) .append(Y.Node.create('')) + + Y.Escape.html(this.get('debuginfo'))+'')) .append(Y.Node.create('')); + + Y.Escape.html(this.get('stacktrace'))+'')); 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 = ''+link.replace(M.cfg.wwwroot, '')+''; } 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"]}); diff --git a/lib/yui/src/notification/js/notification.js b/lib/yui/src/notification/js/notification.js index 16c6708b2ae..0433e570467 100644 --- a/lib/yui/src/notification/js/notification.js +++ b/lib/yui/src/notification/js/notification.js @@ -300,13 +300,13 @@ Y.extend(EXCEPTION, DIALOGUE, { delay = this.get('hideTimeoutDelay'); this.get(BASE).addClass('moodle-dialogue-exception'); this.setStdModContent(Y.WidgetStdMod.HEADER, '

' - + config.name + '

', Y.WidgetStdMod.REPLACE); + + Y.Escape.html(config.name) + '', Y.WidgetStdMod.REPLACE); content = Y.Node.create('
') - .append(Y.Node.create('
'+this.get('message')+'
')) + .append(Y.Node.create('
'+Y.Escape.html(this.get('message'))+'
')) .append(Y.Node.create('')) + + Y.Escape.html(this.get('fileName'))+'')) .append(Y.Node.create('')) + + Y.Escape.html(this.get('lineNumber'))+'')) .append(Y.Node.create('')); 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, '

' - + config.name + '

', Y.WidgetStdMod.REPLACE); + + Y.Escape.html(config.name) + '', Y.WidgetStdMod.REPLACE); content = Y.Node.create('
') - .append(Y.Node.create('
'+this.get('error')+'
')) + .append(Y.Node.create('
'+Y.Escape.html(this.get('error'))+'
')) .append(Y.Node.create('')) .append(Y.Node.create('')) + + Y.Escape.html(this.get('debuginfo'))+'')) .append(Y.Node.create('')); + + Y.Escape.html(this.get('stacktrace'))+'')); 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 = ''+link.replace(M.cfg.wwwroot, '')+''; } return link; diff --git a/lib/yui/src/notification/meta/notification.json b/lib/yui/src/notification/meta/notification.json index 739c8279ec7..620897294aa 100644 --- a/lib/yui/src/notification/meta/notification.json +++ b/lib/yui/src/notification/meta/notification.json @@ -5,6 +5,7 @@ "node", "panel", "event-key", + "escape", "dd-plugin" ] } diff --git a/mod/forum/post_form.php b/mod/forum/post_form.php index 20d6bf29607..73ea0dae68c 100644 --- a/mod/forum/post_form.php +++ b/mod/forum/post_form.php @@ -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; } diff --git a/mod/forum/tests/behat/separate_group_discussions.feature b/mod/forum/tests/behat/separate_group_discussions.feature new file mode 100644 index 00000000000..3b5978f4441 --- /dev/null +++ b/mod/forum/tests/behat/separate_group_discussions.feature @@ -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" diff --git a/mod/forum/tests/behat/separate_group_single_group_discussions.feature b/mod/forum/tests/behat/separate_group_single_group_discussions.feature new file mode 100644 index 00000000000..d8745d11a77 --- /dev/null +++ b/mod/forum/tests/behat/separate_group_single_group_discussions.feature @@ -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 diff --git a/mod/imscp/locallib.php b/mod/imscp/locallib.php index b3dbafbf9a8..888802a648a 100644 --- a/mod/imscp/locallib.php +++ b/mod/imscp/locallib.php @@ -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')) { diff --git a/mod/lti/service.php b/mod/lti/service.php index 60bcfa2caf8..d3645b4140c 100644 --- a/mod/lti/service.php +++ b/mod/lti/service.php @@ -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) { diff --git a/notes/index.php b/notes/index.php index 62c01b9979d..b2cfe36882b 100644 --- a/notes/index.php +++ b/notes/index.php @@ -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)); diff --git a/question/format.php b/question/format.php index 9520ae17d76..1b5cbf541bf 100644 --- a/question/format.php +++ b/question/format.php @@ -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; diff --git a/question/format/webct/format.php b/question/format/webct/format.php index 9bc4f3a4672..fd6c411b736 100644 --- a/question/format/webct/format.php +++ b/question/format/webct/format.php @@ -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. diff --git a/question/format/xml/format.php b/question/format/xml/format.php index 1005f5a653d..60c0cef552c 100644 --- a/question/format/xml/format.php +++ b/question/format/xml/format.php @@ -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 diff --git a/question/type/calculated/edit_calculated_form.php b/question/type/calculated/edit_calculated_form.php index d6d9f450e90..c3c02a90533 100644 --- a/question/type/calculated/edit_calculated_form.php +++ b/question/type/calculated/edit_calculated_form.php @@ -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'] .= '
'.$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) { diff --git a/question/type/calculated/question.php b/question/type/calculated/question.php index b3246855d70..d4a48726676 100644 --- a/question/type/calculated/question.php +++ b/question/type/calculated/question.php @@ -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 = '-+/*%>:^\~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:
"; - foreach ($calculatedmessages as $msg) { - $errorstring .= $msg . '
'; - } - 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; +} diff --git a/question/type/calculated/tests/formula_validation_test.php b/question/type/calculated/tests/formula_validation_test.php new file mode 100644 index 00000000000..d7ff76ed18e --- /dev/null +++ b/question/type/calculated/tests/formula_validation_test.php @@ -0,0 +1,105 @@ +. + +/** + * 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('?>assert_nonempty_string(qtype_calculated_find_formula_errors('?>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( + '

Look no equations.

')); + $this->assertFalse(qtype_calculated_find_formula_errors_in_text( + '

Simple variable: {x}.

')); + $this->assertFalse(qtype_calculated_find_formula_errors_in_text( + '

This is an equation: {=1+1}, as is this: {={x}+{y}}.

' . + '

Here is a more complex one: {=sin(2*pi()*{theta})}.

')); + } + + public function test_validation_of_formulas_in_text_bad_function() { + $this->assert_nonempty_string(qtype_calculated_find_formula_errors_in_text( + '

This is an equation: {=eval(1)}.

')); + $this->assert_nonempty_string(qtype_calculated_find_formula_errors_in_text( + '

Good: {=1+1}, bad: {=eval(1)}, good: {={x}+{y}}.

')); + $this->assert_nonempty_string(qtype_calculated_find_formula_errors_in_text( + '

Bad: {=eval(1)}, bad: {=system(1)}.

')); + } +} diff --git a/question/type/calculatedmulti/edit_calculatedmulti_form.php b/question/type/calculatedmulti/edit_calculatedmulti_form.php index eee479f1138..56fce17908b 100644 --- a/question/type/calculatedmulti/edit_calculatedmulti_form.php +++ b/question/type/calculatedmulti/edit_calculatedmulti_form.php @@ -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'] .= '
'.$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.']'] .= '
'.$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]) { diff --git a/question/type/calculatedmulti/questiontype.php b/question/type/calculatedmulti/questiontype.php index 2235145855a..62e51571e04 100644 --- a/question/type/calculatedmulti/questiontype.php +++ b/question/type/calculatedmulti/questiontype.php @@ -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) { diff --git a/question/type/calculatedsimple/questiontype.php b/question/type/calculatedsimple/questiontype.php index c98f307dbe8..0b19ca8dc5a 100644 --- a/question/type/calculatedsimple/questiontype.php +++ b/question/type/calculatedsimple/questiontype.php @@ -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']; } diff --git a/repository/coursefiles/lib.php b/repository/coursefiles/lib.php index 30a111de3f6..ccb5dad603e 100644 --- a/repository/coursefiles/lib.php +++ b/repository/coursefiles/lib.php @@ -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(), diff --git a/repository/equella/callback.php b/repository/equella/callback.php index 01fc27dc7f3..f1c044d4001 100644 --- a/repository/equella/callback.php +++ b/repository/equella/callback.php @@ -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 =<< diff --git a/repository/equella/lib.php b/repository/equella/lib.php index 94b575b58b6..35178943816 100644 --- a/repository/equella/lib.php +++ b/repository/equella/lib.php @@ -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; } /** diff --git a/repository/filepicker.php b/repository/filepicker.php index bd19ef7f73b..20d5b872bb6 100644 --- a/repository/filepicker.php +++ b/repository/filepicker.php @@ -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); diff --git a/repository/lib.php b/repository/lib.php index ecbcb4fc149..fbaf167eff6 100644 --- a/repository/lib.php +++ b/repository/lib.php @@ -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); diff --git a/repository/local/lib.php b/repository/local/lib.php index 93bdd2062ec..400392a63aa 100644 --- a/repository/local/lib.php +++ b/repository/local/lib.php @@ -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() diff --git a/repository/recent/lib.php b/repository/recent/lib.php index 2471aa6093c..75b64d2932b 100644 --- a/repository/recent/lib.php +++ b/repository/recent/lib.php @@ -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); } diff --git a/repository/repository_ajax.php b/repository/repository_ajax.php index 993f383a8be..d591a7c3a57 100644 --- a/repository/repository_ajax.php +++ b/repository/repository_ajax.php @@ -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); diff --git a/repository/user/lib.php b/repository/user/lib.php index 4e536d56ab6..1c4be73742d 100644 --- a/repository/user/lib.php +++ b/repository/user/lib.php @@ -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(), diff --git a/user/profile.php b/user/profile.php index 64e0183eb43..fc96b395655 100644 --- a/user/profile.php +++ b/user/profile.php @@ -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 = '';