first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Advanced checkbox type form element
*
* Contains HTML class for an advcheckbox type form element
*
* @package core_form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('HTML/QuickForm/advcheckbox.php');
require_once('templatable_form_element.php');
/**
* HTML class for an advcheckbox type element
*
* Overloaded {@link HTML_QuickForm_advcheckbox} with default behavior modified for Moodle.
* This will return '0' if not checked and '1' if checked.
*
* @package core_form
* @category form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_advcheckbox extends HTML_QuickForm_advcheckbox implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var string html for help button, if empty then no help will icon will be dispalyed. */
var $_helpbutton='';
/** @var string Group to which this checkbox belongs (for select all/select none button) */
var $_group;
/**
* constructor
*
* @param string $elementName (optional) name of the checkbox
* @param string $elementLabel (optional) checkbox label
* @param string $text (optional) Text to put after the checkbox
* @param mixed $attributes (optional) Either a typical HTML attribute string
* or an associative array
* @param mixed $values (optional) Values to pass if checked or not checked
*/
public function __construct($elementName=null, $elementLabel=null, $text=null, $attributes=null, $values=null)
{
if ($values === null){
$values = array(0, 1);
}
if (!empty($attributes['group'])) {
$this->_group = 'checkboxgroup' . $attributes['group'];
unset($attributes['group']);
if (is_null($attributes)) {
$attributes = array();
$attributes['class'] .= " $this->_group";
} elseif (is_array($attributes)) {
if (isset($attributes['class'])) {
$attributes['class'] .= " $this->_group";
} else {
$attributes['class'] = $this->_group;
}
} elseif ($strpos = stripos($attributes, 'class="')) {
$attributes = str_ireplace('class="', 'class="' . $this->_group . ' ', $attributes);
} else {
$attributes .= ' class="' . $this->_group . '"';
}
}
parent::__construct($elementName, $elementLabel, $text, $attributes, $values);
$this->_type = 'advcheckbox';
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_advcheckbox($elementName=null, $elementLabel=null, $text=null, $attributes=null, $values=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $text, $attributes, $values);
}
/**
* get html for help button
*
* @return string html for help button
*/
function getHelpButton(){
return $this->_helpbutton;
}
/**
* Returns HTML for advchecbox form element.
*
* @return string
*/
function toHtml()
{
return '<span>' . parent::toHtml() . '</span>';
}
/**
* Returns the disabled field. Accessibility: the return "[ ]" from parent
* class is not acceptable for screenreader users, and we DO want a label.
*
* @return string
*/
function getFrozenHtml()
{
//$this->_generateId();
$output = '<input type="checkbox" disabled="disabled" id="'.$this->getAttribute('id').'" ';
if ($this->getChecked()) {
$output .= 'checked="checked" />'.$this->_getPersistantData();
} else {
$output .= '/>';
}
return $output;
}
public function export_for_template(renderer_base $output) {
$context = $this->export_for_template_base($output);
$context['selectedvalue'] = $this->_values[1];
$context['deselectedvalue'] = $this->_values[0];
$context['frozenvalue'] = $this->getValue();
return $context;
}
}
+53
View File
@@ -0,0 +1,53 @@
define("core_form/changechecker",["exports","core_editor/events","core/str"],(function(_exports,_events,_str){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.watchFormById=_exports.watchForm=_exports.unWatchForm=_exports.startWatching=_exports.resetFormDirtyStateById=_exports.resetFormDirtyState=_exports.resetAllFormDirtyStates=_exports.markFormSubmitted=_exports.markFormChangedFromNode=_exports.markFormAsDirtyById=_exports.markFormAsDirty=_exports.markAllFormsSubmitted=_exports.markAllFormsAsDirty=_exports.isAnyWatchedFormDirty=_exports.disableAllChecks=void 0;
/**
* This module provides change detection to forms, allowing a browser to warn the user before navigating away if changes
* have been made.
*
* Two flags are stored for each form:
* * a 'dirty' flag; and
* * a 'submitted' flag.
*
* When the page is unloaded each watched form is checked. If the 'dirty' flag is set for any form, and the 'submitted'
* flag is not set for any form, then a warning is shown.
*
* The 'dirty' flag is set when any form element is modified within a watched form.
* The flag can also be set programatically. This may be required for custom form elements.
*
* It is not possible to customise the warning message in any modern browser.
*
* Please note that some browsers have controls on when these alerts may or may not be shown.
* See {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload} for browser-specific
* notes and references.
*
* @module core_form/changechecker
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @example <caption>Usage where the FormElement is already held</caption>
*
* import {watchForm} from 'core_form/changechecker';
*
* // Fetch the form element somehow.
* watchForm(formElement);
*
* @example <caption>Usage from the child of a form - i.e. an input, button, div, etc.</caption>
*
* import {watchForm} from 'core_form/changechecker';
*
* // Watch the form by using a child of it.
* watchForm(document.querySelector('input[data-foo="bar"]'););
*
* @example <caption>Usage from within a template</caption>
* <form id="mod_example-entry-{{uniqid}}" ...>
* <!--
*
* -->
* </form>
* {{#js}}
* require(['core_form/changechecker'], function(changeChecker) {
* watchFormById('mod_example-entry-{{uniqid}}');
* });
* {{/js}}
*/
let warningString,watchedForms=[],formChangeCheckerDisabled=!1;const getFormFromChild=formChild=>formChild.closest("form"),watchForm=formNode=>{(formNode=getFormFromChild(formNode))&&(isWatchingForm(formNode)||watchedForms.push(formNode))};_exports.watchForm=watchForm;_exports.unWatchForm=formNode=>{watchedForms=watchedForms.filter((watchedForm=>!!watchedForm.contains(formNode)))};const resetAllFormDirtyStates=()=>{watchedForms.forEach((watchedForm=>{watchedForm.dataset.formSubmitted="false",watchedForm.dataset.formDirty="false"}))};_exports.resetAllFormDirtyStates=resetAllFormDirtyStates;const resetFormDirtyState=formNode=>{(formNode=getFormFromChild(formNode))&&(formNode.dataset.formSubmitted="false",formNode.dataset.formDirty="false")};_exports.resetFormDirtyState=resetFormDirtyState;_exports.markAllFormsAsDirty=()=>{watchedForms.forEach((watchedForm=>{watchedForm.dataset.formDirty="true"}))};const markFormAsDirty=formNode=>{(formNode=getFormFromChild(formNode))&&(formNode.dataset.formDirty="true")};_exports.markFormAsDirty=markFormAsDirty;const disableAllChecks=()=>{formChangeCheckerDisabled=!0};_exports.disableAllChecks=disableAllChecks;const isAnyWatchedFormDirty=()=>{if(formChangeCheckerDisabled)return!1;if(watchedForms.some((watchedForm=>"true"===watchedForm.dataset.formSubmitted)))return!1;return!!watchedForms.some((watchedForm=>{if(!watchedForm.isConnected)return!1;if("true"===watchedForm.dataset.formDirty)return!0;if(document.activeElement&&document.activeElement.dataset.propertyIsEnumerable("initialValue")){const isActiveElementWatched=isWatchingForm(document.activeElement)&&!shouldIgnoreChangesForNode(document.activeElement),hasValueChanged=document.activeElement.dataset.initialValue!==document.activeElement.value;if(isActiveElementWatched&&hasValueChanged)return!0}return!1}))||!(void 0===window.tinyMCE||!window.tinyMCE.editors||!window.tinyMCE.editors.some((editor=>editor.isDirty())))};_exports.isAnyWatchedFormDirty=isAnyWatchedFormDirty;const isWatchingForm=target=>watchedForms.some((watchedForm=>watchedForm.contains(target))),shouldIgnoreChangesForNode=target=>!!target.closest(".ignoredirty"),markFormChangedFromNode=changedNode=>{if(changedNode.dataset.formChangeCheckerOverride)return void disableAllChecks();if(!isWatchingForm(changedNode))return;if(shouldIgnoreChangesForNode(changedNode))return;var target;(target=changedNode,watchedForms.find((watchedForm=>watchedForm.contains(target)))).dataset.formDirty="true"};_exports.markFormChangedFromNode=markFormChangedFromNode;const markFormSubmitted=formNode=>{(formNode=getFormFromChild(formNode))&&(formNode.dataset.formSubmitted="true")};_exports.markFormSubmitted=markFormSubmitted;_exports.markAllFormsSubmitted=()=>{watchedForms.forEach((watchedForm=>markFormSubmitted(watchedForm)))};const beforeUnloadHandler=e=>isAnyWatchedFormDirty()&&!M.cfg.behatsiterunning?(e.preventDefault(),e.returnValue=warningString,e.returnValue):(window.removeEventListener("beforeunload",beforeUnloadHandler),null),startWatching=()=>{document.addEventListener("change",(e=>{isWatchingForm(e.target)&&markFormChangedFromNode(e.target)})),document.addEventListener("click",(e=>{if(!e.target.closest("[data-formchangechecker-ignore-submit]"))return;const ownerForm=getFormFromChild(e.target);ownerForm&&(ownerForm.dataset.ignoreSubmission="true")})),document.addEventListener("focusin",(e=>{if(e.target.matches("input, textarea, select")){if(e.target.dataset.propertyIsEnumerable("initialValue"))return;e.target.dataset.initialValue=e.target.value}})),document.addEventListener("submit",(e=>{const formNode=getFormFromChild(e.target);formNode&&(formNode.dataset.ignoreSubmission?formNode.dataset.ignoreSubmission="false":markFormSubmitted(formNode))})),document.addEventListener(_events.eventTypes.editorContentRestored,(e=>{e.target!=document?resetFormDirtyState(e.target):resetAllFormDirtyStates()})),(0,_str.getString)("changesmadereallygoaway","moodle").then((changesMadeString=>{warningString=changesMadeString})).catch(),window.addEventListener("beforeunload",beforeUnloadHandler)};_exports.startWatching=startWatching;_exports.watchFormById=formId=>{watchForm(document.getElementById(formId))};_exports.resetFormDirtyStateById=formId=>{resetFormDirtyState(document.getElementById(formId))};_exports.markFormAsDirtyById=formId=>{markFormAsDirty(document.getElementById(formId))},startWatching()}));
//# sourceMappingURL=changechecker.min.js.map
File diff suppressed because one or more lines are too long
+19
View File
@@ -0,0 +1,19 @@
define("core_form/choicedropdown",["exports","core/local/dropdown/status","core_form/changechecker"],(function(_exports,_status,_changechecker){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;
/**
* Field controller for choicedropdown field.
*
* @module core_form/choicedropdown
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const Classes_hidden="d-none";
/**
* Internal form element class.
*
* @private
* @class FieldController
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/class FieldController{constructor(elementId){this.elementId=elementId,this.mainSelect=document.getElementById(this.elementId),this.dropdown=(0,_status.getDropdownStatus)('[data-form-controls="'.concat(this.elementId,'"]')),this.dropdown.getElement().classList.remove(Classes_hidden)}addEventListeners(){this.dropdown.getElement().addEventListener("change",this.updateSelect.bind(this)),this.dropdown.getElement().addEventListener("click",(event=>event.preventDefault())),this.mainSelect.addEventListener("change",this.updateDropdown.bind(this));new MutationObserver((mutations=>{mutations.forEach((mutation=>{"attributes"===mutation.type&&"disabled"===mutation.attributeName&&this.updateDropdown()}))})).observe(this.mainSelect,{attributeFilter:["disabled"]})}isDisabled(){var _this$mainSelect;return null===(_this$mainSelect=this.mainSelect)||void 0===_this$mainSelect?void 0:_this$mainSelect.hasAttribute("disabled")}async updateDropdown(){this.dropdown.setButtonDisabled(this.isDisabled()),this.dropdown.getSelectedValue()!=this.mainSelect.value&&this.dropdown.setSelectedValue(this.mainSelect.value)}async updateSelect(){this.dropdown.getSelectedValue()!=this.mainSelect.value&&(this.mainSelect.value=this.dropdown.getSelectedValue(),(0,_changechecker.markFormAsDirty)(this.mainSelect.closest("form")),this.mainSelect.dispatchEvent(new Event("change")))}disableInteractiveDialog(){var _this$mainSelect2;null===(_this$mainSelect2=this.mainSelect)||void 0===_this$mainSelect2||_this$mainSelect2.classList.remove(Classes_hidden);this.dropdown.getElement().classList.add(Classes_hidden)}hasForceDialog(){var _this$mainSelect3;return!(null===(_this$mainSelect3=this.mainSelect)||void 0===_this$mainSelect3||!_this$mainSelect3.dataset.forceDialog)}}_exports.init=elementId=>{const field=new FieldController(elementId);!document.body.classList.contains("behat-site")||field.hasForceDialog()?field.addEventListeners():field.disableInteractiveDialog()}}));
//# sourceMappingURL=choicedropdown.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
define("core_form/collapsesections",["exports","jquery","core/pending"],(function(_exports,_jquery,_pending){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Collapse or expand all form sections on clicking the expand all / collapse al link.
*
* @module core_form/collapsesections
* @copyright 2021 Bas Brands
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.0
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=_interopRequireDefault(_jquery),_pending=_interopRequireDefault(_pending);const SELECTORS_FORM=".mform",SELECTORS_FORMHEADER=".fheader",SELECTORS_FORMCONTAINER="fieldset > .fcontainer",CLASSES_SHOW="show",CLASSES_COLLAPSED="collapsed",CLASSES_HIDDEN="d-none";_exports.init=collapsesections=>{const pendingPromise=new _pending.default("core_form/collapsesections"),collapsemenu=document.querySelector(collapsesections),formParent=collapsemenu.closest(SELECTORS_FORM),formContainers=formParent.querySelectorAll(SELECTORS_FORMCONTAINER);collapsemenu.addEventListener("keydown",(e=>{"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),collapsemenu.click())}));let formcontainercount=0,expandedcount=0;formContainers.forEach((container=>{container.parentElement.classList.contains(CLASSES_HIDDEN)||formcontainercount++,container.classList.contains(CLASSES_SHOW)&&expandedcount++})),formcontainercount===expandedcount&&(collapsemenu.classList.remove(CLASSES_COLLAPSED),collapsemenu.setAttribute("aria-expanded",!0)),collapsemenu.addEventListener("click",(()=>{let action="hide";collapsemenu.classList.contains(CLASSES_COLLAPSED)&&(action="show"),formContainers.forEach((container=>(0,_jquery.default)(container).collapse(action)))}));const collapseElementIds=[...formParent.querySelectorAll(SELECTORS_FORMHEADER)].map(((element,index)=>(element.id=element.id||"collapseElement-".concat(index),element.id)));collapsemenu.setAttribute("aria-controls",collapseElementIds.join(" ")),(0,_jquery.default)(SELECTORS_FORMCONTAINER).on("hidden.bs.collapse",(()=>{[...formContainers].every((container=>!container.classList.contains(CLASSES_SHOW)))&&(collapsemenu.classList.add(CLASSES_COLLAPSED),collapsemenu.setAttribute("aria-expanded",!1))})),(0,_jquery.default)(SELECTORS_FORMCONTAINER).on("shown.bs.collapse",(()=>{[...formContainers].every((container=>container.classList.contains(CLASSES_SHOW)))&&(collapsemenu.classList.remove(CLASSES_COLLAPSED),collapsemenu.setAttribute("aria-expanded",!0))})),pendingPromise.resolve()}}));
//# sourceMappingURL=collapsesections.min.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
define("core_form/configtext_maxlength",["exports","core/str","core/templates","core/notification","core/prefetch"],(function(_exports,_str,_templates,_notification,_prefetch){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_notification=_interopRequireDefault(_notification);let registered=!1;_exports.init=()=>{registered||((0,_prefetch.prefetchStrings)("core",["maximumchars"]),(0,_prefetch.prefetchTemplates)(["core_form/setting_validation_failure"]),registered=!0,document.addEventListener("input",(e=>{const maxLengthField=e.target.closest("[data-validation-max-length]");if(maxLengthField)if(maxLengthField.value.length>maxLengthField.dataset.validationMaxLength)maxLengthField.form.addEventListener("submit",submissionCheck),(0,_str.get_string)("maximumchars","core",maxLengthField.dataset.validationMaxLength).then((errorMessage=>_templates.default.renderForPromise("core_form/setting_validation_failure",{fieldid:maxLengthField.id,message:errorMessage}))).then((errorTemplate=>{if(!maxLengthField.dataset.validationFailureId){const formWrapper=maxLengthField.closest(".form-text");_templates.default.prependNodeContents(formWrapper,errorTemplate.html,errorTemplate.js),maxLengthField.dataset.validationFailureId="maxlength_error_".concat(maxLengthField.id),updateSubmitButton()}})).then((()=>{maxLengthField.setAttribute("aria-invalid",!0);const errorField=document.getElementById(maxLengthField.dataset.validationFailureId);errorField&&errorField.setAttribute("aria-describedby",maxLengthField.id)})).catch(_notification.default.exception);else{const validationMessage=document.getElementById(maxLengthField.dataset.validationFailureId);validationMessage&&(validationMessage.parentElement.remove(),delete maxLengthField.dataset.validationFailureId,maxLengthField.removeAttribute("aria-invalid"),updateSubmitButton())}})))};const submissionCheck=e=>{const maxLengthFields=e.target.querySelectorAll("[data-validation-max-length]");Array.from(maxLengthFields).some((maxLengthField=>maxLengthField.value.length>maxLengthField.dataset.validationMaxLength&&(e.preventDefault(),maxLengthField.focus(),!0)))},updateSubmitButton=()=>{const shouldDisable=document.querySelector("form#adminsettings .error");document.querySelector('form#adminsettings button[type="submit"]').disabled=!!shouldDisable}}));
//# sourceMappingURL=configtext_maxlength.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
/**
* Functionality for the form element defaultcustom
*
* @module core_form/defaultcustom
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.3
*/
define("core_form/defaultcustom",["jquery"],(function($){$("body").on("change","input[data-defaultcustom=true]",(function(event){var element=$(event.target),defaultvalue=JSON.parse(element.attr("data-defaultvalue")),customvalue=JSON.parse(element.attr("data-customvalue")),type=element.attr("data-type"),form=element.closest("form"),elementName=element.attr("name").replace(/\[customize\]$/,"[value]"),newvalue=element.prop("checked")?customvalue:defaultvalue;"text"===type?form.find('[name="'+elementName+'"]').val(newvalue):"date_selector"===type?(form.find('[name="'+elementName+'[day]"]').val(newvalue.day),form.find('[name="'+elementName+'[month]"]').val(newvalue.month),form.find('[name="'+elementName+'[year]"]').val(newvalue.year)):"date_time_selector"===type&&(form.find('[name="'+elementName+'[day]"]').val(newvalue.day),form.find('[name="'+elementName+'[month]"]').val(newvalue.month),form.find('[name="'+elementName+'[year]"]').val(newvalue.year),form.find('[name="'+elementName+'[hour]"]').val(newvalue.hour),form.find('[name="'+elementName+'[minute]"]').val(newvalue.minute))}))}));
//# sourceMappingURL=defaultcustom.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"defaultcustom.min.js","sources":["../src/defaultcustom.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Functionality for the form element defaultcustom\n *\n * @module core_form/defaultcustom\n * @copyright 2017 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.3\n */\ndefine(['jquery'], function($) {\n var onChangeSelect = function(event) {\n var element = $(event.target),\n defaultvalue = JSON.parse(element.attr('data-defaultvalue')),\n customvalue = JSON.parse(element.attr('data-customvalue')),\n type = element.attr('data-type'),\n form = element.closest('form'),\n elementName = element.attr('name').replace(/\\[customize\\]$/, '[value]'),\n newvalue = element.prop('checked') ? customvalue : defaultvalue;\n\n if (type === 'text') {\n form.find('[name=\"' + elementName + '\"]').val(newvalue);\n } else if (type === 'date_selector') {\n form.find('[name=\"' + elementName + '[day]\"]').val(newvalue.day);\n form.find('[name=\"' + elementName + '[month]\"]').val(newvalue.month);\n form.find('[name=\"' + elementName + '[year]\"]').val(newvalue.year);\n } else if (type === 'date_time_selector') {\n form.find('[name=\"' + elementName + '[day]\"]').val(newvalue.day);\n form.find('[name=\"' + elementName + '[month]\"]').val(newvalue.month);\n form.find('[name=\"' + elementName + '[year]\"]').val(newvalue.year);\n form.find('[name=\"' + elementName + '[hour]\"]').val(newvalue.hour);\n form.find('[name=\"' + elementName + '[minute]\"]').val(newvalue.minute);\n }\n };\n\n var selector = 'input[data-defaultcustom=true]';\n $('body').on('change', selector, onChangeSelect);\n});\n"],"names":["define","$","on","event","element","target","defaultvalue","JSON","parse","attr","customvalue","type","form","closest","elementName","replace","newvalue","prop","find","val","day","month","year","hour","minute"],"mappings":";;;;;;;;AAuBAA,iCAAO,CAAC,WAAW,SAASC,GA0BxBA,EAAE,QAAQC,GAAG,SADE,kCAxBM,SAASC,WACtBC,QAAUH,EAAEE,MAAME,QAClBC,aAAeC,KAAKC,MAAMJ,QAAQK,KAAK,sBACvCC,YAAcH,KAAKC,MAAMJ,QAAQK,KAAK,qBACtCE,KAAOP,QAAQK,KAAK,aACpBG,KAAOR,QAAQS,QAAQ,QACvBC,YAAcV,QAAQK,KAAK,QAAQM,QAAQ,iBAAkB,WAC7DC,SAAWZ,QAAQa,KAAK,WAAaP,YAAcJ,aAE1C,SAATK,KACAC,KAAKM,KAAK,UAAYJ,YAAc,MAAMK,IAAIH,UAC9B,kBAATL,MACPC,KAAKM,KAAK,UAAYJ,YAAc,WAAWK,IAAIH,SAASI,KAC5DR,KAAKM,KAAK,UAAYJ,YAAc,aAAaK,IAAIH,SAASK,OAC9DT,KAAKM,KAAK,UAAYJ,YAAc,YAAYK,IAAIH,SAASM,OAC7C,uBAATX,OACPC,KAAKM,KAAK,UAAYJ,YAAc,WAAWK,IAAIH,SAASI,KAC5DR,KAAKM,KAAK,UAAYJ,YAAc,aAAaK,IAAIH,SAASK,OAC9DT,KAAKM,KAAK,UAAYJ,YAAc,YAAYK,IAAIH,SAASM,MAC7DV,KAAKM,KAAK,UAAYJ,YAAc,YAAYK,IAAIH,SAASO,MAC7DX,KAAKM,KAAK,UAAYJ,YAAc,cAAcK,IAAIH,SAASQ"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
define("core_form/encryptedpassword",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.EncryptedPassword=void 0;
/**
* Encrypted password functionality.
*
* @module core_form/encryptedpassword
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const EncryptedPassword=function(elementId){const wrapper=document.querySelector('div[data-encryptedpasswordid="'+elementId+'"]');this.spanOrLink=wrapper.querySelector("span, a"),this.input=wrapper.querySelector("input"),this.editButtonOrLink=wrapper.querySelector("button[data-editbutton], a"),this.cancelButton=wrapper.querySelector("button[data-cancelbutton]");var editHandler=e=>{e.stopImmediatePropagation(),e.preventDefault(),this.startEditing(!0)};this.editButtonOrLink.addEventListener("click",editHandler),"A"===this.editButtonOrLink.nodeName&&wrapper.parentElement.previousElementSibling.querySelector("label").addEventListener("click",editHandler),this.cancelButton.addEventListener("click",(e=>{e.stopImmediatePropagation(),e.preventDefault(),this.cancelEditing()})),"y"===wrapper.dataset.novalue&&(this.startEditing(!1),this.cancelButton.style.display="none")};_exports.EncryptedPassword=EncryptedPassword,EncryptedPassword.prototype.startEditing=function(moveFocus){this.input.style.display="inline",this.input.disabled=!1,this.spanOrLink.style.display="none",this.editButtonOrLink.style.display="none",this.cancelButton.style.display="inline";const id=this.editButtonOrLink.id;this.editButtonOrLink.removeAttribute("id"),this.input.id=id,moveFocus&&this.input.focus()},EncryptedPassword.prototype.cancelEditing=function(){this.input.style.display="none",this.input.value="",this.input.disabled=!0,this.spanOrLink.style.display="inline",this.editButtonOrLink.style.display="inline",this.cancelButton.style.display="none";const id=this.input.id;this.input.removeAttribute("id"),this.editButtonOrLink.id=id}}));
//# sourceMappingURL=encryptedpassword.min.js.map
File diff suppressed because one or more lines are too long
+19
View File
@@ -0,0 +1,19 @@
define("core_form/events",["exports","core/str","core/event_dispatcher","jquery","core/yui"],(function(_exports,_str,_event_dispatcher,_jquery,_yui){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Javascript events for the `core_form` subsystem.
*
* @module core_form/events
* @copyright 2021 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.10
*
* @example <caption>Example of listening to a form event.</caption>
* import {eventTypes as formEventTypes} from 'core_form/events';
*
* document.addEventListener(formEventTypes.formSubmittedByJavascript, e => {
* window.console.log(e.target); // The form that was submitted.
* window.console.log(e.detail.skipValidation); // Whether form validation was skipped.
* });
*/let changesMadeString;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.types=_exports.triggerUploadStarted=_exports.triggerUploadCompleted=_exports.notifyUploadStarted=_exports.notifyUploadCompleted=_exports.notifyUploadChanged=_exports.notifyFormSubmittedByJavascript=_exports.notifyFormError=_exports.notifyFieldValidationFailure=_exports.notifyFieldStructureChanged=_exports.eventTypes=void 0,_jquery=_interopRequireDefault(_jquery),_yui=_interopRequireDefault(_yui);const changesMadeCheck=e=>{e&&(e.returnValue=changesMadeString)},eventTypes={formError:"core_form/error",formSubmittedByJavascript:"core_form/submittedByJavascript",formFieldValidationFailed:"core_form/fieldValidationFailed",uploadStarted:"core_form/uploadStarted",uploadCompleted:"core_form/uploadCompleted",uploadChanged:"core_form/uploadChanged",fieldStructureChanged:"core_form/fieldStructureChanged"};_exports.eventTypes=eventTypes;_exports.notifyFormError=field=>(0,_event_dispatcher.dispatchEvent)(eventTypes.formError,{},field);_exports.notifyFormSubmittedByJavascript=function(form){let skipValidation=arguments.length>1&&void 0!==arguments[1]&&arguments[1],fallbackHandled=arguments.length>2&&void 0!==arguments[2]&&arguments[2];skipValidation&&(window.skipClientValidation=!0);const customEvent=(0,_event_dispatcher.dispatchEvent)(eventTypes.formSubmittedByJavascript,{skipValidation:skipValidation,fallbackHandled:fallbackHandled},form);return skipValidation&&(window.skipClientValidation=!1),customEvent};_exports.notifyFieldValidationFailure=(field,message)=>(0,_event_dispatcher.dispatchEvent)(eventTypes.formFieldValidationFailed,{message:message},field,{cancelable:!0});const notifyUploadStarted=async elementId=>(changesMadeString=await(0,_str.getString)("changesmadereallygoaway","moodle"),window.addEventListener("beforeunload",changesMadeCheck),(0,_event_dispatcher.dispatchEvent)(eventTypes.uploadStarted,{},document.getElementById(elementId),{bubbles:!0,cancellable:!1}));_exports.notifyUploadStarted=notifyUploadStarted;const notifyUploadCompleted=elementId=>(window.removeEventListener("beforeunload",changesMadeCheck),(0,_event_dispatcher.dispatchEvent)(eventTypes.uploadCompleted,{},document.getElementById(elementId),{bubbles:!0,cancellable:!1}));_exports.notifyUploadCompleted=notifyUploadCompleted;const triggerUploadStarted=notifyUploadStarted;_exports.triggerUploadStarted=triggerUploadStarted;const triggerUploadCompleted=notifyUploadCompleted;_exports.triggerUploadCompleted=triggerUploadCompleted;_exports.types={uploadStarted:"core_form/uploadStarted",uploadCompleted:"core_form/uploadCompleted"};let legacyEventsRegistered=!1;legacyEventsRegistered||(_yui.default.use("event","moodle-core-event",(()=>{document.addEventListener(eventTypes.formError,(e=>{const element=_yui.default.one(e.target),formElement=_yui.default.one(e.target.closest("form"));_yui.default.Global.fire(M.core.globalEvents.FORM_ERROR,{formid:formElement.generateID(),elementid:element.generateID()})})),document.addEventListener(eventTypes.formSubmittedByJavascript,(e=>{if(e.detail.fallbackHandled)return;e.skipValidation&&(window.skipClientValidation=!0);const form=_yui.default.one(e.target);form.fire(M.core.event.FORM_SUBMIT_AJAX,{currentTarget:form,fallbackHandled:!0}),e.skipValidation&&(window.skipClientValidation=!1)}))})),document.addEventListener(eventTypes.formFieldValidationFailed,(e=>{const legacyEvent=_jquery.default.Event("core_form-field-validation");(0,_jquery.default)(e.target).trigger(legacyEvent,e.detail.message)})),legacyEventsRegistered=!0);_exports.notifyUploadChanged=elementId=>(0,_event_dispatcher.dispatchEvent)(eventTypes.uploadChanged,{},document.getElementById(elementId),{bubbles:!0,cancellable:!1});_exports.notifyFieldStructureChanged=elementId=>(0,_event_dispatcher.dispatchEvent)(eventTypes.fieldStructureChanged,{},document.getElementById(elementId),{bubbles:!0,cancellable:!1})}));
//# sourceMappingURL=events.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
/**
* Password Unmask functionality.
*
* @module core_form/passwordunmask
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.2
*/
define("core_form/passwordunmask",["jquery","core/templates"],(function($,Template){var PasswordUnmask=function(elementid){this.wrapperSelector='[data-passwordunmask="wrapper"][data-passwordunmaskid="'+elementid+'"]',this.wrapper=$(this.wrapperSelector),this.editorSpace=this.wrapper.find('[data-passwordunmask="editor"]'),this.editLink=this.wrapper.find('a[data-passwordunmask="edit"]'),this.editInstructions=this.wrapper.find('[data-passwordunmask="instructions"]'),this.displayValue=this.wrapper.find('[data-passwordunmask="displayvalue"]'),this.inputFieldLabel=$('label[for="'+elementid+'"]'),this.inputField=this.editorSpace.find(document.getElementById(elementid)),this.inputField.addClass("d-none"),this.inputField.removeClass("hiddenifjs"),this.editInstructions.attr("id")||this.editInstructions.attr("id",elementid+"_instructions"),this.editInstructions.hide(),this.setDisplayValue(),this.addListeners()};return PasswordUnmask.prototype.addListeners=function(){return this.wrapper.on("click keypress",'[data-passwordunmask="edit"]',$.proxy((function(e){"keypress"===e.type&&13!==e.keyCode||(e.stopImmediatePropagation(),e.preventDefault(),this.isEditing()?"click"===e.type||$(e.relatedTarget).is(":input")?this.turnEditingOff(!1):this.turnEditingOff(!0):this.turnEditingOn())}),this)),this.wrapper.on("click keypress",'[data-passwordunmask="unmask"]',$.proxy((function(e){"keypress"===e.type&&13!==e.keyCode||(e.stopImmediatePropagation(),e.preventDefault(),this.wrapper.data("unmasked",!this.wrapper.data("unmasked")),this.setDisplayValue())}),this)),this.wrapper.on("keydown","input",$.proxy((function(e){"keydown"===e.type&&13!==e.keyCode||(e.stopImmediatePropagation(),e.preventDefault(),this.turnEditingOff(!0))}),this)),this.inputFieldLabel.on("click",$.proxy((function(e){e.preventDefault(),this.turnEditingOn()}),this)),this},PasswordUnmask.prototype.checkFocusOut=function(e){this.isEditing()&&window.setTimeout($.proxy((function(){var relatedTarget=e.relatedTarget||document.activeElement;this.wrapper.has($(relatedTarget)).length||this.turnEditingOff(!$(relatedTarget).is(":input,a"))}),this),100)},PasswordUnmask.prototype.passwordVisible=function(){return!!this.wrapper.data("unmasked")},PasswordUnmask.prototype.isEditing=function(){return this.inputField.hasClass("d-inline-block")},PasswordUnmask.prototype.turnEditingOn=function(){var value=this.getDisplayValue();return this.passwordVisible()?this.inputField.attr("type","text"):this.inputField.attr("type","password"),this.inputField.val(value),this.inputField.attr("size",this.inputField.attr("data-size")),this.inputField.addClass("d-inline-block"),this.editInstructions.length&&(this.inputField.attr("aria-describedby",this.editInstructions.attr("id")),this.editInstructions.show()),this.wrapper.attr("data-passwordunmask-visible",1),this.editLink.hide(),this.inputField.focus().select(),$("body").on("focusout",this.wrapperSelector,$.proxy(this.checkFocusOut,this)),this},PasswordUnmask.prototype.turnEditingOff=function(focusOnEditLink){$("body").off("focusout",this.wrapperSelector,this.checkFocusOut);var value=this.getDisplayValue();return this.inputField.attr("aria-describedby",null),this.inputField.val(value),this.inputField.removeClass("d-inline-block"),this.editInstructions.hide(),this.wrapper.removeAttr("data-passwordunmask-visible"),this.inputField.removeAttr("size"),this.editLink.show(),this.setDisplayValue(),focusOnEditLink&&this.editLink.focus(),this},PasswordUnmask.prototype.getDisplayValue=function(){return this.inputField.val()},PasswordUnmask.prototype.setDisplayValue=function(){var value=this.getDisplayValue();return this.isEditing()&&(this.wrapper.data("unmasked")?this.inputField.attr("type","text"):this.inputField.attr("type","password"),this.inputField.val(value)),value&&this.wrapper.data("unmasked")?this.displayValue.text(value):(value||(value=""),Template.render("core_form/element-passwordunmask-fill",{element:{frozen:this.inputField.is("[readonly]"),value:value,valuechars:value.split("")}}).done($.proxy((function(html,js){this.displayValue.html(html),Template.runTemplateJS(js)}),this))),this},PasswordUnmask}));
//# sourceMappingURL=passwordunmask.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* A class to help show and hide advanced form content.
*
* @module core_form/showadvanced
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_form/showadvanced",["jquery","core/log","core/str","core/notification"],(function($,Log,Strings,Notification){var SELECTORS_FIELDSETCONTAINSADVANCED="fieldset.containsadvancedelements",SELECTORS_DIVFITEMADVANCED="div.fitem.advanced",SELECTORS_DIVADVANCEDSECTION="div#form-advanced-div",SELECTORS_MORELESSLINK="fieldset.containsadvancedelements .moreless-toggler",CSS_SHOW="show",CSS_MORELESSACTIONS="moreless-actions",CSS_MORELESSTOGGLER="moreless-toggler",CSS_SHOWLESS="moreless-less",WRAPPERS_FITEM='<div class="fitem"></div>',WRAPPERS_FELEMENT='<div class="felement"></div>',WRAPPERS_ADVANCEDDIV='<div id="form-advanced-div"></div>',uniqIdSeed=0,ShowAdvanced=function(id){this.id=id;var form=$(document.getElementById(id));this.enhanceForm(form)};return ShowAdvanced.prototype.id="",ShowAdvanced.prototype.enhanceForm=function(form){return form.find(SELECTORS_FIELDSETCONTAINSADVANCED).each(function(index,item){this.enhanceFieldset($(item))}.bind(this)),form.on("click",SELECTORS_MORELESSLINK,this.switchState),form.on("keydown",SELECTORS_MORELESSLINK,function(e){return 13!=e.which&&32!=e.which||this.switchState(e)}.bind(this)),this},ShowAdvanced.prototype.generateId=function(node){var id=node.prop("id");return void 0===id&&(id="showadvancedid-"+uniqIdSeed++,node.prop("id",id)),id},ShowAdvanced.prototype.enhanceFieldset=function(fieldset){var statuselement=$("input[name=mform_showmore_"+fieldset.prop("id")+"]");return statuselement.length?(Strings.get_strings([{key:"showmore",component:"core_form"},{key:"showless",component:"core_form"}]).then(function(results){var showmore=results[0],showless=results[1],morelesslink=$('<a href="#"></a>');morelesslink.addClass(CSS_MORELESSTOGGLER),"0"===statuselement.val()?(morelesslink.html(showmore),morelesslink.attr("aria-expanded","false")):(morelesslink.html(showless),morelesslink.attr("aria-expanded","true"),morelesslink.addClass(CSS_SHOWLESS),fieldset.find(SELECTORS_DIVFITEMADVANCED).addClass(CSS_SHOW));var idlist=[];fieldset.find(SELECTORS_DIVFITEMADVANCED).each(function(index,node){idlist[idlist.length]=this.generateId($(node))}.bind(this)),morelesslink.attr("role","button"),morelesslink.attr("aria-controls","form-advanced-div");var formadvancedsection=$(WRAPPERS_ADVANCEDDIV);fieldset.find(SELECTORS_DIVFITEMADVANCED).wrapAll(formadvancedsection);var fitem=$(WRAPPERS_FITEM);fitem.addClass(CSS_MORELESSACTIONS);var felement=$(WRAPPERS_FELEMENT);return felement.append(morelesslink),fitem.append(felement),fieldset.find(SELECTORS_DIVADVANCEDSECTION).before(fitem),!0}.bind(this)).fail(Notification.exception),this):(Log.debug("M.form.showadvanced::processFieldset was called on an fieldset without a status field: '"+fieldset.prop("id")+"'"),this)},ShowAdvanced.prototype.switchState=function(e){return e.preventDefault(),Strings.get_strings([{key:"showmore",component:"core_form"},{key:"showless",component:"core_form"}]).then((function(results){var showmore=results[0],showless=results[1],fieldset=$(e.target).closest(SELECTORS_FIELDSETCONTAINSADVANCED);fieldset.find(SELECTORS_DIVFITEMADVANCED).toggleClass(CSS_SHOW);var statuselement=$("input[name=mform_showmore_"+fieldset.prop("id")+"]");return"0"===statuselement.val()?(statuselement.val(1),$(e.target).addClass(CSS_SHOWLESS),$(e.target).html(showless),$(e.target).attr("aria-expanded","true")):(statuselement.val(0),$(e.target).removeClass(CSS_SHOWLESS),$(e.target).html(showmore),$(e.target).attr("aria-expanded","false")),!0})).fail(Notification.exception),this},{init:function(formid){return new ShowAdvanced(formid)}}}));
//# sourceMappingURL=showadvanced.min.js.map
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
define("core_form/submit",["exports","core_form/events"],(function(_exports,_events){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;
/**
* Submit button JavaScript. All submit buttons will be automatically disabled once the form is
* submitted, unless that submission results in an error/cancelling the submit.
*
* @module core_form/submit
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.8
*/
let cookieListener=0;const cookieListeningButtons=[];let currentUploadCount=0;const uploadListeningButtons=[];let uploadListenersRegistered=!1;const getCookieName=()=>"moodledownload_"+M.cfg.sesskey,clearDownloadCookie=()=>{document.cookie=encodeURIComponent(getCookieName())+"=deleted; expires="+new Date(0).toUTCString()},checkUploadCount=()=>{currentUploadCount?uploadListeningButtons.forEach((button=>{button.disabled=!0})):uploadListeningButtons.forEach((button=>{button.disabled=!1}))};_exports.init=elementId=>{const button=document.getElementById(elementId);null!==button&&(button.disabled||uploadListeningButtons.push(button),uploadListenersRegistered||(document.addEventListener(_events.eventTypes.uploadStarted,(()=>{currentUploadCount++,checkUploadCount()})),document.addEventListener(_events.eventTypes.uploadCompleted,(()=>{currentUploadCount--,checkUploadCount()})),uploadListenersRegistered=!0),"off"!==button.form.dataset.doubleSubmitProtection&&button.form.addEventListener("submit",(function(event){const disableAction=function(){event.defaultPrevented||button.disabled||(button.disabled=!0,clearDownloadCookie(),(button=>{cookieListeningButtons.push(button),cookieListener||(cookieListener=setInterval((()=>{2==document.cookie.split(getCookieName()+"=").length&&(clearDownloadCookie(),clearInterval(cookieListener),cookieListener=0,cookieListeningButtons.forEach((button=>{button.disabled=!1})))}),500))})(button))};window.addEventListener("beforeunload",disableAction),setTimeout((function(){window.removeEventListener("beforeunload",disableAction)}),1)}),!1))}}));
//# sourceMappingURL=submit.min.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
define("core_form/util",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.serialize=void 0;const serialize=function(data){let prefix=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return[...Object.entries(data).map((_ref=>{let[index,value]=_ref;const key=prefix?"".concat(prefix,"[").concat(index,"]"):index;return null!==value&&"object"==typeof value?serialize(value,key):"".concat(key,"=").concat(encodeURIComponent(value))}))].join("&")};_exports.serialize=serialize}));
//# sourceMappingURL=util.min.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"util.min.js","sources":["../src/util.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Serialize form values into a string.\n *\n * This must be used instead of URLSearchParams, which does not correctly encode nested values such as arrays.\n *\n * @param {Object} data The form values to serialize\n * @param {string} prefix The prefix to use for key names\n * @returns {string}\n */\nexport const serialize = (data, prefix = '') => [\n ...Object.entries(data).map(([index, value]) => {\n const key = prefix ? `${prefix}[${index}]` : index;\n return (value !== null && typeof value === \"object\") ? serialize(value, key) : `${key}=${encodeURIComponent(value)}`;\n })\n].join(\"&\");\n"],"names":["serialize","data","prefix","Object","entries","map","_ref","index","value","key","encodeURIComponent","join"],"mappings":"gJAwBaA,UAAY,SAACC,UAAMC,8DAAS,SAAO,IACzCC,OAAOC,QAAQH,MAAMI,KAAIC,WAAEC,MAAOC,kBAC3BC,IAAMP,iBAAYA,mBAAUK,WAAWA,aAC3B,OAAVC,OAAmC,iBAAVA,MAAsBR,UAAUQ,MAAOC,eAAUA,gBAAOC,mBAAmBF,YAElHG,KAAK"}
+516
View File
@@ -0,0 +1,516 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module provides change detection to forms, allowing a browser to warn the user before navigating away if changes
* have been made.
*
* Two flags are stored for each form:
* * a 'dirty' flag; and
* * a 'submitted' flag.
*
* When the page is unloaded each watched form is checked. If the 'dirty' flag is set for any form, and the 'submitted'
* flag is not set for any form, then a warning is shown.
*
* The 'dirty' flag is set when any form element is modified within a watched form.
* The flag can also be set programatically. This may be required for custom form elements.
*
* It is not possible to customise the warning message in any modern browser.
*
* Please note that some browsers have controls on when these alerts may or may not be shown.
* See {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload} for browser-specific
* notes and references.
*
* @module core_form/changechecker
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @example <caption>Usage where the FormElement is already held</caption>
*
* import {watchForm} from 'core_form/changechecker';
*
* // Fetch the form element somehow.
* watchForm(formElement);
*
* @example <caption>Usage from the child of a form - i.e. an input, button, div, etc.</caption>
*
* import {watchForm} from 'core_form/changechecker';
*
* // Watch the form by using a child of it.
* watchForm(document.querySelector('input[data-foo="bar"]'););
*
* @example <caption>Usage from within a template</caption>
* <form id="mod_example-entry-{{uniqid}}" ...>
* <!--
*
* -->
* </form>
* {{#js}}
* require(['core_form/changechecker'], function(changeChecker) {
* watchFormById('mod_example-entry-{{uniqid}}');
* });
* {{/js}}
*/
import {eventTypes} from 'core_editor/events';
import {getString} from 'core/str';
/**
* @property {Bool} initialised Whether the change checker has been initialised
* @private
*/
let initialised = false;
/**
* @property {String} warningString The warning string to show on form change failure
* @private
*/
let warningString;
/**
* @property {Array} watchedForms The list of watched forms
* @private
*/
let watchedForms = [];
/**
* @property {Bool} formChangeCheckerDisabled Whether the form change checker has been actively disabled
* @private
*/
let formChangeCheckerDisabled = false;
/**
* Get the nearest form element from a child element.
*
* @param {HTMLElement} formChild
* @returns {HTMLFormElement|null}
* @private
*/
const getFormFromChild = formChild => formChild.closest('form');
/**
* Watch the specified form for changes.
*
* @method
* @param {HTMLElement} formNode
*/
export const watchForm = formNode => {
// Normalise the formNode.
formNode = getFormFromChild(formNode);
if (!formNode) {
// No form found.
return;
}
if (isWatchingForm(formNode)) {
// This form is already watched.
return;
}
watchedForms.push(formNode);
};
/**
* Stop watching the specified form for changes.
*
* If the form was not watched, then no change is made.
*
* A child of the form may be passed instead.
*
* @method
* @param {HTMLElement} formNode
* @example <caption>Stop watching a form for changes</caption>
* import {unWatchForm} from 'core_form/changechecker';
*
* // ...
* document.addEventListener('click', e => {
* if (e.target.closest('[data-action="changePage"]')) {
* unWatchForm(e.target);
* }
* });
*/
export const unWatchForm = formNode => {
watchedForms = watchedForms.filter(watchedForm => !!watchedForm.contains(formNode));
};
/**
* Reset the 'dirty' flag for all watched forms.
*
* If a form was previously marked as 'dirty', then this flag will be cleared and when the page is unloaded no warning
* will be shown.
*
* @method
*/
export const resetAllFormDirtyStates = () => {
watchedForms.forEach(watchedForm => {
watchedForm.dataset.formSubmitted = "false";
watchedForm.dataset.formDirty = "false";
});
};
/**
* Reset the 'dirty' flag of the specified form.
*
* @method
* @param {HTMLElement} formNode
*/
export const resetFormDirtyState = formNode => {
formNode = getFormFromChild(formNode);
if (!formNode) {
return;
}
formNode.dataset.formSubmitted = "false";
formNode.dataset.formDirty = "false";
};
/**
* Mark all forms as dirty.
*
* This function is only for backwards-compliance with the old YUI module and should not be used in any other situation.
* It will be removed in Moodle 4.4.
*
* @method
*/
export const markAllFormsAsDirty = () => {
watchedForms.forEach(watchedForm => {
watchedForm.dataset.formDirty = "true";
});
};
/**
* Mark a specific form as dirty.
*
* This behaviour may be required for custom form elements which are not caught by the standard change listeners.
*
* @method
* @param {HTMLElement} formNode
*/
export const markFormAsDirty = formNode => {
formNode = getFormFromChild(formNode);
if (!formNode) {
return;
}
// Mark it as dirty.
formNode.dataset.formDirty = "true";
};
/**
* Actively disable the form change checker.
*
* Please note that it cannot be re-enabled once disabled.
*
* @method
*/
export const disableAllChecks = () => {
formChangeCheckerDisabled = true;
};
/**
* Check whether any watched from is dirty.
*
* @method
* @returns {Bool}
*/
export const isAnyWatchedFormDirty = () => {
if (formChangeCheckerDisabled) {
// The form change checker is disabled.
return false;
}
const hasSubmittedForm = watchedForms.some(watchedForm => watchedForm.dataset.formSubmitted === "true");
if (hasSubmittedForm) {
// Do not warn about submitted forms, ever.
return false;
}
const hasDirtyForm = watchedForms.some(watchedForm => {
if (!watchedForm.isConnected) {
// The watched form is not connected to the DOM.
return false;
}
if (watchedForm.dataset.formDirty === "true") {
// The form has been marked as dirty.
return true;
}
// Elements currently holding focus will not have triggered change detection.
// Check whether the value matches the original value upon form load.
if (document.activeElement && document.activeElement.dataset.propertyIsEnumerable('initialValue')) {
const isActiveElementWatched = isWatchingForm(document.activeElement)
&& !shouldIgnoreChangesForNode(document.activeElement);
const hasValueChanged = document.activeElement.dataset.initialValue !== document.activeElement.value;
if (isActiveElementWatched && hasValueChanged) {
return true;
}
}
return false;
});
if (hasDirtyForm) {
// At least one form is dirty.
return true;
}
// Handle TinyMCE editor instances.
// TinyMCE forms may not have been initialised at the time that startWatching is called.
// Check whether any tinyMCE editor is dirty.
if (typeof window.tinyMCE !== 'undefined' && window.tinyMCE.editors) {
if (window.tinyMCE.editors.some(editor => editor.isDirty())) {
return true;
}
}
// No dirty forms detected.
return false;
};
/**
* Get the watched form for the specified target.
*
* @method
* @param {HTMLNode} target
* @returns {HTMLFormElement}
* @private
*/
const getFormForNode = target => watchedForms.find(watchedForm => watchedForm.contains(target));
/**
* Whether the specified target is a watched form.
*
* @method
* @param {HTMLNode} target
* @returns {Bool}
* @private
*/
const isWatchingForm = target => watchedForms.some(watchedForm => watchedForm.contains(target));
/**
* Whether the specified target should ignore changes or not.
*
* @method
* @param {HTMLNode} target
* @returns {Bool}
* @private
*/
const shouldIgnoreChangesForNode = target => !!target.closest('.ignoredirty');
/**
* Mark a form as changed.
*
* @method
* @param {HTMLElement} changedNode An element in the form which was changed
*/
export const markFormChangedFromNode = changedNode => {
if (changedNode.dataset.formChangeCheckerOverride) {
// Changes to this form node disable the form change checker entirely.
// This is intended for select fields which cause an immediate redirect.
disableAllChecks();
return;
}
if (!isWatchingForm(changedNode)) {
return;
}
if (shouldIgnoreChangesForNode(changedNode)) {
return;
}
// Mark the form as dirty.
const formNode = getFormForNode(changedNode);
formNode.dataset.formDirty = "true";
};
/**
* Mark a form as submitted.
*
* @method
* @param {HTMLElement} formNode An element in the form to mark as submitted
*/
export const markFormSubmitted = formNode => {
formNode = getFormFromChild(formNode);
if (!formNode) {
return;
}
formNode.dataset.formSubmitted = "true";
};
/**
* Mark all forms as submitted.
*
* This function is only for backwards-compliance with the old YUI module and should not be used in any other situation.
* It will be removed in Moodle 4.4.
*
* @method
*/
export const markAllFormsSubmitted = () => {
watchedForms.forEach(watchedForm => markFormSubmitted(watchedForm));
};
/**
* Handle the beforeunload event.
*
* @method
* @param {Event} e
* @returns {string|null}
* @private
*/
const beforeUnloadHandler = e => {
// Please note: The use of Promises in this function is forbidden.
// This is an event handler and _cannot_ be asynchronous.
let warnBeforeUnload = isAnyWatchedFormDirty() && !M.cfg.behatsiterunning;
if (warnBeforeUnload) {
// According to the specification, to show the confirmation dialog an event handler should call preventDefault()
// on the event.
e.preventDefault();
// However note that not all browsers support this method, and some instead require the event handler to
// implement one of two legacy methods:
// * assigning a string to the event's returnValue property; and
// * returning a string from the event handler.
// Assigning a string to the event's returnValue property.
e.returnValue = warningString;
// Returning a string from the event handler.
return e.returnValue;
}
// Attaching an event handler/listener to window or document's beforeunload event prevents browsers from using
// in-memory page navigation caches, like Firefox's Back-Forward cache or WebKit's Page Cache.
// Remove the handler.
window.removeEventListener('beforeunload', beforeUnloadHandler);
return null;
};
/**
* Start watching for form changes.
*
* This function is called on module load, and should not normally be called.
*
* @method
* @protected
*/
export const startWatching = () => {
if (initialised) {
return;
}
document.addEventListener('change', e => {
if (!isWatchingForm(e.target)) {
return;
}
markFormChangedFromNode(e.target);
});
document.addEventListener('click', e => {
const ignoredButton = e.target.closest('[data-formchangechecker-ignore-submit]');
if (!ignoredButton) {
return;
}
const ownerForm = getFormFromChild(e.target);
if (ownerForm) {
ownerForm.dataset.ignoreSubmission = "true";
}
});
document.addEventListener('focusin', e => {
if (e.target.matches('input, textarea, select')) {
if (e.target.dataset.propertyIsEnumerable('initialValue')) {
// The initial value has already been set.
return;
}
e.target.dataset.initialValue = e.target.value;
}
});
document.addEventListener('submit', e => {
const formNode = getFormFromChild(e.target);
if (!formNode) {
// Weird, but watch for this anyway.
return;
}
if (formNode.dataset.ignoreSubmission) {
// This form was submitted by a button which requested that the form checked should not mark it as submitted.
formNode.dataset.ignoreSubmission = "false";
return;
}
markFormSubmitted(formNode);
});
document.addEventListener(eventTypes.editorContentRestored, e => {
if (e.target != document) {
resetFormDirtyState(e.target);
} else {
resetAllFormDirtyStates();
}
});
getString('changesmadereallygoaway', 'moodle')
.then(changesMadeString => {
warningString = changesMadeString;
return;
})
.catch();
window.addEventListener('beforeunload', beforeUnloadHandler);
};
/**
* Watch the form matching the specified ID for changes.
*
* @method
* @param {String} formId
*/
export const watchFormById = formId => {
watchForm(document.getElementById(formId));
};
/**
* Reset the dirty state of the form matching the specified ID..
*
* @method
* @param {String} formId
*/
export const resetFormDirtyStateById = formId => {
resetFormDirtyState(document.getElementById(formId));
};
/**
* Mark the form matching the specified ID as dirty.
*
* @method
* @param {String} formId
*/
export const markFormAsDirtyById = formId => {
markFormAsDirty(document.getElementById(formId));
};
// Configure all event listeners.
startWatching();
+158
View File
@@ -0,0 +1,158 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Field controller for choicedropdown field.
*
* @module core_form/choicedropdown
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {getDropdownStatus} from 'core/local/dropdown/status';
import {markFormAsDirty} from 'core_form/changechecker';
const Classes = {
notClickable: 'not-clickable',
hidden: 'd-none',
};
/**
* Internal form element class.
*
* @private
* @class FieldController
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class FieldController {
/**
* Class constructor.
*
* @param {String} elementId Form element id
*/
constructor(elementId) {
this.elementId = elementId;
this.mainSelect = document.getElementById(this.elementId);
this.dropdown = getDropdownStatus(`[data-form-controls="${this.elementId}"]`);
this.dropdown.getElement().classList.remove(Classes.hidden);
}
/**
* Add form element event listener.
*/
addEventListeners() {
this.dropdown.getElement().addEventListener(
'change',
this.updateSelect.bind(this)
);
// Click on a dropdown link can trigger a wrong dirty form reload warning.
this.dropdown.getElement().addEventListener(
'click',
(event) => event.preventDefault()
);
this.mainSelect.addEventListener(
'change',
this.updateDropdown.bind(this)
);
// Enabling or disabling the select does not trigger any JS event.
const observerCallback = (mutations) => {
mutations.forEach((mutation) => {
if (mutation.type !== 'attributes' || mutation.attributeName !== 'disabled') {
return;
}
this.updateDropdown();
});
};
new MutationObserver(observerCallback).observe(
this.mainSelect,
{attributeFilter: ['disabled']}
);
}
/**
* Check if the field is disabled.
* @returns {Boolean}
*/
isDisabled() {
return this.mainSelect?.hasAttribute('disabled');
}
/**
* Update selected option preview in form.
*/
async updateDropdown() {
this.dropdown.setButtonDisabled(this.isDisabled());
if (this.dropdown.getSelectedValue() == this.mainSelect.value) {
return;
}
this.dropdown.setSelectedValue(this.mainSelect.value);
}
/**
* Update selected option preview in form.
*/
async updateSelect() {
if (this.dropdown.getSelectedValue() == this.mainSelect.value) {
return;
}
this.mainSelect.value = this.dropdown.getSelectedValue();
markFormAsDirty(this.mainSelect.closest('form'));
// Change the select element via JS does not trigger the standard change event.
this.mainSelect.dispatchEvent(new Event('change'));
}
/**
* Disable the choice dialog and convert it into a regular select field.
*/
disableInteractiveDialog() {
this.mainSelect?.classList.remove(Classes.hidden);
const dropdownElement = this.dropdown.getElement();
dropdownElement.classList.add(Classes.hidden);
}
/**
* Check if the field has a force dialog attribute.
// *
* The force dialog is a setting to force the javascript control even in
* behat test.
*
* @returns {Boolean} if the dialog modal should be forced or not
*/
hasForceDialog() {
return !!this.mainSelect?.dataset.forceDialog;
}
}
/**
* Initialises a choice dialog field.
*
* @method init
* @param {String} elementId Form element id
* @listens event:uploadStarted
* @listens event:uploadCompleted
*/
export const init = (elementId) => {
const field = new FieldController(elementId);
// This field is just a select wrapper. To optimize tests, we don't want to keep behat
// waiting for extra loadings in this case. The set field steps are about testing other
// stuff, not to test fancy javascript form fields. However, we keep the possibility of
// testing the javascript part using behat when necessary.
if (document.body.classList.contains('behat-site') && !field.hasForceDialog()) {
field.disableInteractiveDialog();
return;
}
field.addEventListeners();
};
+112
View File
@@ -0,0 +1,112 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Collapse or expand all form sections on clicking the expand all / collapse al link.
*
* @module core_form/collapsesections
* @copyright 2021 Bas Brands
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.0
*/
import $ from 'jquery';
import Pending from 'core/pending';
const SELECTORS = {
FORM: '.mform',
FORMHEADER: '.fheader',
FORMCONTAINER: 'fieldset > .fcontainer',
};
const CLASSES = {
SHOW: 'show',
COLLAPSED: 'collapsed',
HIDDEN: 'd-none'
};
/**
* Initialises the form section collapse / expand action.
*
* @param {string} collapsesections the collapse/expand link id.
*/
export const init = collapsesections => {
// All jQuery in this code can be replaced when MDL-71979 is integrated (move to Bootstrap 5).
const pendingPromise = new Pending('core_form/collapsesections');
const collapsemenu = document.querySelector(collapsesections);
const formParent = collapsemenu.closest(SELECTORS.FORM);
const formContainers = formParent.querySelectorAll(SELECTORS.FORMCONTAINER);
collapsemenu.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
collapsemenu.click();
}
});
// Override default collapse class if all visible containers are expanded on page load
let formcontainercount = 0;
let expandedcount = 0;
formContainers.forEach(container => {
const parentFieldset = container.parentElement;
if (!parentFieldset.classList.contains(CLASSES.HIDDEN)) {
formcontainercount++;
}
if (container.classList.contains(CLASSES.SHOW)) {
expandedcount++;
}
});
if (formcontainercount === expandedcount) {
collapsemenu.classList.remove(CLASSES.COLLAPSED);
collapsemenu.setAttribute('aria-expanded', true);
}
// When the collapse menu is toggled, update each form container to match.
collapsemenu.addEventListener('click', () => {
let action = 'hide';
if (collapsemenu.classList.contains(CLASSES.COLLAPSED)) {
action = 'show';
}
formContainers.forEach(container => $(container).collapse(action));
});
// Ensure collapse menu button adds aria-controls attribute referring to each collapsible element.
const collapseElements = formParent.querySelectorAll(SELECTORS.FORMHEADER);
const collapseElementIds = [...collapseElements].map((element, index) => {
element.id = element.id || `collapseElement-${index}`;
return element.id;
});
collapsemenu.setAttribute('aria-controls', collapseElementIds.join(' '));
// When any form container is toggled, re-calculate collapse menu state.
$(SELECTORS.FORMCONTAINER).on('hidden.bs.collapse', () => {
const allCollapsed = [...formContainers].every(container => !container.classList.contains(CLASSES.SHOW));
if (allCollapsed) {
collapsemenu.classList.add(CLASSES.COLLAPSED);
collapsemenu.setAttribute('aria-expanded', false);
}
});
$(SELECTORS.FORMCONTAINER).on('shown.bs.collapse', () => {
const allExpanded = [...formContainers].every(container => container.classList.contains(CLASSES.SHOW));
if (allExpanded) {
collapsemenu.classList.remove(CLASSES.COLLAPSED);
collapsemenu.setAttribute('aria-expanded', true);
}
});
pendingPromise.resolve();
};
+121
View File
@@ -0,0 +1,121 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Validation for configtext_maxlength.
*
* @module core_form/configtext-maxlength
* @copyright 2021 The Open University
*/
import {get_string as getString} from 'core/str';
import Templates from 'core/templates';
import Notification from 'core/notification';
import {prefetchStrings, prefetchTemplates} from 'core/prefetch';
let registered = false;
/**
* Initialisation function.
*/
export const init = () => {
if (registered) {
return;
}
prefetchStrings('core', [
'maximumchars',
]);
prefetchTemplates([
'core_form/setting_validation_failure',
]);
registered = true;
document.addEventListener('input', e => {
const maxLengthField = e.target.closest('[data-validation-max-length]');
if (!maxLengthField) {
return;
}
if (maxLengthField.value.length > maxLengthField.dataset.validationMaxLength) {
// Disable the form for this field.
maxLengthField.form.addEventListener('submit', submissionCheck);
// Display an error.
getString('maximumchars', 'core', maxLengthField.dataset.validationMaxLength)
.then(errorMessage => {
return Templates.renderForPromise('core_form/setting_validation_failure', {
fieldid: maxLengthField.id,
message: errorMessage,
});
})
.then(errorTemplate => {
if (!maxLengthField.dataset.validationFailureId) {
const formWrapper = maxLengthField.closest('.form-text');
Templates.prependNodeContents(formWrapper, errorTemplate.html, errorTemplate.js);
maxLengthField.dataset.validationFailureId = `maxlength_error_${maxLengthField.id}`;
// Disable submit button when the message is displayed.
updateSubmitButton();
}
return;
})
.then(() => {
maxLengthField.setAttribute('aria-invalid', true);
const errorField = document.getElementById(maxLengthField.dataset.validationFailureId);
if (errorField) {
errorField.setAttribute('aria-describedby', maxLengthField.id);
}
return;
})
.catch(Notification.exception);
} else {
// Remove the old message.
const validationMessage = document.getElementById(maxLengthField.dataset.validationFailureId);
if (validationMessage) {
validationMessage.parentElement.remove();
delete maxLengthField.dataset.validationFailureId;
maxLengthField.removeAttribute('aria-invalid');
// Enable submit button when the message was removed.
updateSubmitButton();
}
}
});
};
/**
* Handle form submission.
*
* @param {Event} e The event.
*/
const submissionCheck = e => {
const maxLengthFields = e.target.querySelectorAll('[data-validation-max-length]');
const maxLengthFieldsArray = Array.from(maxLengthFields);
maxLengthFieldsArray.some(maxLengthField => {
// Focus on the first validation failure.
if (maxLengthField.value.length > maxLengthField.dataset.validationMaxLength) {
e.preventDefault();
maxLengthField.focus();
return true;
}
return false;
});
};
/**
* Update submit button.
*/
const updateSubmitButton = () => {
const shouldDisable = document.querySelector('form#adminsettings .error');
document.querySelector('form#adminsettings button[type="submit"]').disabled = !!shouldDisable;
};
+51
View File
@@ -0,0 +1,51 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Functionality for the form element defaultcustom
*
* @module core_form/defaultcustom
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.3
*/
define(['jquery'], function($) {
var onChangeSelect = function(event) {
var element = $(event.target),
defaultvalue = JSON.parse(element.attr('data-defaultvalue')),
customvalue = JSON.parse(element.attr('data-customvalue')),
type = element.attr('data-type'),
form = element.closest('form'),
elementName = element.attr('name').replace(/\[customize\]$/, '[value]'),
newvalue = element.prop('checked') ? customvalue : defaultvalue;
if (type === 'text') {
form.find('[name="' + elementName + '"]').val(newvalue);
} else if (type === 'date_selector') {
form.find('[name="' + elementName + '[day]"]').val(newvalue.day);
form.find('[name="' + elementName + '[month]"]').val(newvalue.month);
form.find('[name="' + elementName + '[year]"]').val(newvalue.year);
} else if (type === 'date_time_selector') {
form.find('[name="' + elementName + '[day]"]').val(newvalue.day);
form.find('[name="' + elementName + '[month]"]').val(newvalue.month);
form.find('[name="' + elementName + '[year]"]').val(newvalue.year);
form.find('[name="' + elementName + '[hour]"]').val(newvalue.hour);
form.find('[name="' + elementName + '[minute]"]').val(newvalue.minute);
}
};
var selector = 'input[data-defaultcustom=true]';
$('body').on('change', selector, onChangeSelect);
});
+377
View File
@@ -0,0 +1,377 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Display an embedded form, it is only loaded and reloaded inside its container
*
*
* @module core_form/dynamicform
* @copyright 2019 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* See also https://docs.moodle.org/dev/Modal_and_AJAX_forms
*
* @example
* import DynamicForm from 'core_form/dynamicform';
*
* const dynamicForm = new DynamicForm(document.querySelector('#mycontainer', 'pluginname\\form\\formname');
* dynamicForm.addEventListener(dynamicForm.events.FORM_SUBMITTED, e => {
* e.preventDefault();
* window.console.log(e.detail);
* dynamicForm.container.innerHTML = 'Thank you, your form is submitted!';
* });
* dynamicForm.load();
*
*/
import * as FormChangeChecker from 'core_form/changechecker';
import * as FormEvents from 'core_form/events';
import Ajax from 'core/ajax';
import Fragment from 'core/fragment';
import Notification from 'core/notification';
import Pending from 'core/pending';
import Templates from 'core/templates';
import {getStrings} from 'core/str';
import {serialize} from './util';
/**
* @class core_form/dynamicform
*/
export default class DynamicForm {
/**
* Various events that can be observed.
*
* @type {Object}
*/
events = {
// Form was successfully submitted - the response is passed to the event listener.
// Cancellable (in order to prevent default behavior to clear the container).
FORM_SUBMITTED: 'core_form_dynamicform_formsubmitted',
// Cancel button was pressed.
// Cancellable (in order to prevent default behavior to clear the container).
FORM_CANCELLED: 'core_form_dynamicform_formcancelled',
// User attempted to submit the form but there was client-side validation error.
CLIENT_VALIDATION_ERROR: 'core_form_dynamicform_clientvalidationerror',
// User attempted to submit the form but server returned validation error.
SERVER_VALIDATION_ERROR: 'core_form_dynamicform_validationerror',
// Error occurred while performing request to the server.
// Cancellable (by default calls Notification.exception).
ERROR: 'core_form_dynamicform_error',
// Right after user pressed no-submit button,
// listen to this event if you want to add JS validation or processing for no-submit button.
// Cancellable.
NOSUBMIT_BUTTON_PRESSED: 'core_form_dynamicform_nosubmitbutton',
// Right after user pressed submit button,
// listen to this event if you want to add additional JS validation or confirmation dialog.
// Cancellable.
SUBMIT_BUTTON_PRESSED: 'core_form_dynamicform_submitbutton',
// Right after user pressed cancel button,
// listen to this event if you want to add confirmation dialog.
// Cancellable.
CANCEL_BUTTON_PRESSED: 'core_form_dynamicform_cancelbutton',
};
/**
* Constructor
*
* Creates an instance
*
* @param {Element} container - the parent element for the form
* @param {string} formClass full name of the php class that extends \core_form\modal , must be in autoloaded location
*/
constructor(container, formClass) {
this.formClass = formClass;
this.container = container;
// Ensure strings required for shortforms are always available.
getStrings([
{key: 'collapseall', component: 'moodle'},
{key: 'expandall', component: 'moodle'}
]).catch(Notification.exception);
// Register delegated events handlers in vanilla JS.
this.container.addEventListener('click', e => {
if (e.target.matches('form input[type=submit][data-cancel]')) {
e.preventDefault();
const event = this.trigger(this.events.CANCEL_BUTTON_PRESSED, e.target);
if (!event.defaultPrevented) {
this.processCancelButton();
}
} else if (e.target.matches('form input[type=submit][data-no-submit="1"]')) {
e.preventDefault();
const event = this.trigger(this.events.NOSUBMIT_BUTTON_PRESSED, e.target);
if (!event.defaultPrevented) {
this.processNoSubmitButton(e.target);
}
}
});
this.container.addEventListener('submit', e => {
if (e.target.matches('form')) {
e.preventDefault();
const event = this.trigger(this.events.SUBMIT_BUTTON_PRESSED);
if (!event.defaultPrevented) {
this.submitFormAjax();
}
}
});
}
/**
* Loads the form via AJAX and shows it inside a given container
*
* @param {Object} args
* @return {Promise}
* @public
*/
load(args = null) {
const formData = serialize(args || {});
const pendingPromise = new Pending('core_form/dynamicform:load');
return this.getBody(formData)
.then((resp) => this.updateForm(resp))
.then(pendingPromise.resolve);
}
/**
* Triggers a custom event
*
* @private
* @param {String} eventName
* @param {*} detail
* @param {Boolean} cancelable
* @return {CustomEvent<unknown>}
*/
trigger(eventName, detail = null, cancelable = true) {
const e = new CustomEvent(eventName, {detail, cancelable});
this.container.dispatchEvent(e);
return e;
}
/**
* Add listener for an event
*
* @param {array} args
* @example:
* const dynamicForm = new DynamicForm(...);
* dynamicForm.addEventListener(dynamicForm.events.FORM_SUBMITTED, e => {
* e.preventDefault();
* window.console.log(e.detail);
* dynamicForm.container.innerHTML = 'Thank you, your form is submitted!';
* });
*/
addEventListener(...args) {
this.container.addEventListener(...args);
}
/**
* Get form body
*
* @param {String} formDataString form data in format of a query string
* @private
* @return {Promise}
*/
getBody(formDataString) {
return Ajax.call([{
methodname: 'core_form_dynamic_form',
args: {
formdata: formDataString,
form: this.formClass,
}
}])[0]
.then(response => {
return {html: response.html, js: Fragment.processCollectedJavascript(response.javascript)};
});
}
/**
* On form submit
*
* @param {*} response Response received from the form's "process" method
*/
onSubmitSuccess(response) {
const event = this.trigger(this.events.FORM_SUBMITTED, response);
if (event.defaultPrevented) {
return;
}
// Default implementation is to remove the form. Event listener should either remove or reload the form
// since its contents is no longer correct. For example, if an element was created as a result of
// form submission, the "id" in the form would be still zero. Also the server-side validation
// errors from the previous submission may still be present.
this.container.innerHTML = '';
}
/**
* On exception during form processing
*
* @private
* @param {Object} exception
*/
onSubmitError(exception) {
const event = this.trigger(this.events.ERROR, exception);
if (event.defaultPrevented) {
return;
}
Notification.exception(exception);
}
/**
* Click on a "submit" button that is marked in the form as registerNoSubmitButton()
*
* @method submitButtonPressed
* @param {Element} button that was pressed
* @fires event:formSubmittedByJavascript
*/
processNoSubmitButton(button) {
const pendingPromise = new Pending('core_form/dynamicform:nosubmit');
const form = this.getFormNode();
const formData = new URLSearchParams([...(new FormData(form)).entries()]);
formData.append(button.getAttribute('name'), button.getAttribute('value'));
FormEvents.notifyFormSubmittedByJavascript(form, true);
// Add the button name to the form data and submit it.
this.disableButtons();
this.getBody(formData.toString())
.then(resp => this.updateForm(resp))
.then(pendingPromise.resolve)
.catch(exception => this.onSubmitError(exception));
}
/**
* Get the form node from the Dialogue.
*
* @returns {HTMLFormElement}
*/
getFormNode() {
return this.container.querySelector('form');
}
/**
* Notifies listeners that form dirty state should be reset.
*
* @fires event:formSubmittedByJavascript
*/
notifyResetFormChanges() {
FormEvents.notifyFormSubmittedByJavascript(this.getFormNode(), true);
FormChangeChecker.resetFormDirtyState(this.getFormNode());
}
/**
* Click on a "cancel" button
*/
processCancelButton() {
// Notify listeners that the form is about to be submitted (this will reset atto autosave).
this.notifyResetFormChanges();
const event = this.trigger(this.events.FORM_CANCELLED);
if (!event.defaultPrevented) {
// By default removes the form from the DOM.
this.container.innerHTML = '';
}
}
/**
* Update form contents
*
* @param {object} param
* @param {string} param.html
* @param {string} param.js
* @returns {Promise}
*/
updateForm({html, js}) {
return Templates.replaceNodeContents(this.container, html, js);
}
/**
* Validate form elements
* @return {Boolean} Whether client-side validation has passed, false if there are errors
* @fires event:formSubmittedByJavascript
*/
validateElements() {
// Notify listeners that the form is about to be submitted (this will reset atto autosave).
FormEvents.notifyFormSubmittedByJavascript(this.getFormNode());
// Now the change events have run, see if there are any "invalid" form fields.
const invalid = [...this.container.querySelectorAll('[aria-invalid="true"], .error')];
// If we found invalid fields, focus on the first one and do not submit via ajax.
if (invalid.length) {
invalid[0].focus();
return false;
}
return true;
}
/**
* Disable buttons during form submission
*/
disableButtons() {
this.container.querySelectorAll('form input[type="submit"]')
.forEach(el => el.setAttribute('disabled', true));
}
/**
* Enable buttons after form submission (on validation error)
*/
enableButtons() {
this.container.querySelectorAll('form input[type="submit"]')
.forEach(el => el.removeAttribute('disabled'));
}
/**
* Submit the form via AJAX call to the core_form_dynamic_form WS
*/
async submitFormAjax() {
// If we found invalid fields, focus on the first one and do not submit via ajax.
if (!(await this.validateElements())) {
this.trigger(this.events.CLIENT_VALIDATION_ERROR, null, false);
return;
}
this.disableButtons();
// Convert all the form elements values to a serialised string.
const form = this.container.querySelector('form');
const formData = new URLSearchParams([...(new FormData(form)).entries()]);
// Now we can continue...
Ajax.call([{
methodname: 'core_form_dynamic_form',
args: {
formdata: formData.toString(),
form: this.formClass
}
}])[0]
.then((response) => {
if (!response.submitted) {
// Form was not submitted, it could be either because validation failed or because no-submit button was pressed.
this.updateForm({html: response.html, js: Fragment.processCollectedJavascript(response.javascript)});
this.enableButtons();
this.trigger(this.events.SERVER_VALIDATION_ERROR, null, false);
} else {
// Form was submitted properly.
const data = JSON.parse(response.data);
this.enableButtons();
this.notifyResetFormChanges();
this.onSubmitSuccess(data);
}
return null;
})
.catch(exception => this.onSubmitError(exception));
}
}
+103
View File
@@ -0,0 +1,103 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Encrypted password functionality.
*
* @module core_form/encryptedpassword
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Constructor for EncryptedPassword.
*
* @class core_form/encryptedpassword
* @param {String} elementId The element to apply the encrypted password JS to
*/
export const EncryptedPassword = function(elementId) {
const wrapper = document.querySelector('div[data-encryptedpasswordid="' + elementId + '"]');
this.spanOrLink = wrapper.querySelector('span, a');
this.input = wrapper.querySelector('input');
this.editButtonOrLink = wrapper.querySelector('button[data-editbutton], a');
this.cancelButton = wrapper.querySelector('button[data-cancelbutton]');
// Edit button action.
var editHandler = (e) => {
e.stopImmediatePropagation();
e.preventDefault();
this.startEditing(true);
};
this.editButtonOrLink.addEventListener('click', editHandler);
// When it's a link, do some magic to make the label work as well.
if (this.editButtonOrLink.nodeName === 'A') {
wrapper.parentElement.previousElementSibling.querySelector('label').addEventListener('click', editHandler);
}
// Cancel button action.
this.cancelButton.addEventListener('click', (e) => {
e.stopImmediatePropagation();
e.preventDefault();
this.cancelEditing();
});
// If the value is not set yet, start editing and remove the cancel option - so that
// it saves something in the config table and doesn't keep repeat showing it as a new
// admin setting...
if (wrapper.dataset.novalue === 'y') {
this.startEditing(false);
this.cancelButton.style.display = 'none';
}
};
/**
* Starts editing.
*
* @param {Boolean} moveFocus If true, sets focus to the edit box
*/
EncryptedPassword.prototype.startEditing = function(moveFocus) {
this.input.style.display = 'inline';
this.input.disabled = false;
this.spanOrLink.style.display = 'none';
this.editButtonOrLink.style.display = 'none';
this.cancelButton.style.display = 'inline';
// Move the id around, which changes what happens when you click the label.
const id = this.editButtonOrLink.id;
this.editButtonOrLink.removeAttribute('id');
this.input.id = id;
if (moveFocus) {
this.input.focus();
}
};
/**
* Cancels editing.
*/
EncryptedPassword.prototype.cancelEditing = function() {
this.input.style.display = 'none';
this.input.value = '';
this.input.disabled = true;
this.spanOrLink.style.display = 'inline';
this.editButtonOrLink.style.display = 'inline';
this.cancelButton.style.display = 'none';
// Move the id around, which changes what happens when you click the label.
const id = this.input.id;
this.input.removeAttribute('id');
this.editButtonOrLink.id = id;
};
+367
View File
@@ -0,0 +1,367 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Javascript events for the `core_form` subsystem.
*
* @module core_form/events
* @copyright 2021 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.10
*
* @example <caption>Example of listening to a form event.</caption>
* import {eventTypes as formEventTypes} from 'core_form/events';
*
* document.addEventListener(formEventTypes.formSubmittedByJavascript, e => {
* window.console.log(e.target); // The form that was submitted.
* window.console.log(e.detail.skipValidation); // Whether form validation was skipped.
* });
*/
import {getString} from 'core/str';
import {dispatchEvent} from 'core/event_dispatcher';
let changesMadeString;
/**
* Prevent user navigate away when upload progress still running.
* @param {Event} e The event
*/
const changesMadeCheck = e => {
if (e) {
e.returnValue = changesMadeString;
}
};
/**
* Events for `core_form`.
*
* @constant
* @property {String} formError See {@link event:core_form/error}
* @property {String} formFieldValidationFailed See {@link event:core_form/fieldValidationFailed}
* @property {String} formSubmittedByJavascript See {@link event:core_form/submittedByJavascript}
* @property {String} uploadChanged See {@link event:core_form/uploadChanged}
* @property {String} fieldStructureChanged See {@link event:core_form/fieldStructureChanged}
*/
export const eventTypes = {
/**
* An event triggered when a form contains an error
*
* @event formError
* @type {CustomEvent}
* @property {HTMLElement} target The form field which errored
*/
formError: 'core_form/error',
/**
* An event triggered when an mform is about to be submitted via javascript.
*
* @event core_form/submittedByJavascript
* @type {CustomEvent}
* @property {HTMLElement} target The form that was submitted
* @property {object} detail
* @property {boolean} detail.skipValidation Whether the form was submitted without validation (i.e. via a Cancel button)
* @property {boolean} detail.fallbackHandled Whether the legacy YUI event has been handled
*/
formSubmittedByJavascript: 'core_form/submittedByJavascript',
/**
* An event triggered upon form field validation failure.
*
* @event core_form/fieldValidationFailed
* @type {CustomEvent}
* @property {HTMLElement} target The field that failed validation
* @property {object} detail
* @property {String} detail.message The message displayed upon failure
*/
formFieldValidationFailed: 'core_form/fieldValidationFailed',
/**
* An event triggered when an upload is started
*
* @event core_form/uploadStarted
* @type {CustomEvent}
* @property {HTMLElement} target The location where the upload began
*/
uploadStarted: 'core_form/uploadStarted',
/**
* An event triggered when an upload completes
*
* @event core_form/uploadCompleted
* @type {CustomEvent}
* @property {HTMLElement} target The location where the upload completed
*/
uploadCompleted: 'core_form/uploadCompleted',
/**
* An event triggered when a file upload field has been changed.
*
* @event core_form/uploadChanged
* @type {CustomEvent}
* @property {HTMLElement} target The form field which was changed
*/
uploadChanged: 'core_form/uploadChanged',
/**
* An event triggered when a form field structure has changed.
*
* @event core_form/fieldStructureChanged
* @type {CustomEvent}
* @property {HTMLElement} target The form field that has changed
*/
fieldStructureChanged: 'core_form/fieldStructureChanged',
};
// These are only imported for legacy.
import jQuery from 'jquery';
import Y from 'core/yui';
/**
* Trigger an event to indicate that a form field contained an error.
*
* @method notifyFormError
* @param {HTMLElement} field The form field causing the error
* @returns {CustomEvent}
* @fires formError
*/
export const notifyFormError = field => dispatchEvent(eventTypes.formError, {}, field);
/**
* Trigger an event to indiciate that a form was submitted by Javascript.
*
* @method
* @param {HTMLElement} form The form that was submitted
* @param {Boolean} skipValidation Submit the form without validation. E.g. "Cancel".
* @param {Boolean} fallbackHandled The legacy YUI event has been handled
* @returns {CustomEvent}
* @fires formSubmittedByJavascript
*/
export const notifyFormSubmittedByJavascript = (form, skipValidation = false, fallbackHandled = false) => {
if (skipValidation) {
window.skipClientValidation = true;
}
const customEvent = dispatchEvent(
eventTypes.formSubmittedByJavascript,
{
skipValidation,
fallbackHandled,
},
form
);
if (skipValidation) {
window.skipClientValidation = false;
}
return customEvent;
};
/**
* Trigger an event to indicate that a form field contained an error.
*
* @method notifyFieldValidationFailure
* @param {HTMLElement} field The field which failed validation
* @param {String} message The message displayed
* @returns {CustomEvent}
* @fires formFieldValidationFailed
*/
export const notifyFieldValidationFailure = (field, message) => dispatchEvent(
eventTypes.formFieldValidationFailed,
{
message,
},
field,
{
cancelable: true
}
);
/**
* Trigger an event to indicate that an upload was started.
*
* @method
* @param {String} elementId The element which was uploaded to
* @returns {CustomEvent}
* @fires uploadStarted
*/
export const notifyUploadStarted = async elementId => {
// Add an additional check for changes made.
changesMadeString = await getString('changesmadereallygoaway', 'moodle');
window.addEventListener('beforeunload', changesMadeCheck);
return dispatchEvent(
eventTypes.uploadStarted,
{},
document.getElementById(elementId),
{
bubbles: true,
cancellable: false,
}
);
};
/**
* Trigger an event to indicate that an upload was completed.
*
* @method
* @param {String} elementId The element which was uploaded to
* @returns {CustomEvent}
* @fires uploadCompleted
*/
export const notifyUploadCompleted = elementId => {
// Remove the additional check for changes made.
window.removeEventListener('beforeunload', changesMadeCheck);
return dispatchEvent(
eventTypes.uploadCompleted,
{},
document.getElementById(elementId),
{
bubbles: true,
cancellable: false,
}
);
};
/**
* Trigger upload start event.
*
* @method
* @param {String} elementId
* @returns {CustomEvent}
* @fires uploadStarted
* @deprecated Since Moodle 4.0 See {@link module:core_form/events.notifyUploadStarted notifyUploadStarted}
*/
export const triggerUploadStarted = notifyUploadStarted;
/**
* Trigger upload complete event.
*
* @method
* @param {String} elementId
* @returns {CustomEvent}
* @fires uploadCompleted
* @deprecated Since Moodle 4.0 See {@link module:core_form/events.notifyUploadCompleted notifyUploadCompleted}
*/
export const triggerUploadCompleted = notifyUploadCompleted;
/**
* List of the events.
*
* @deprecated since Moodle 4.0. See {@link module:core_form/events.eventTypes eventTypes} instead.
**/
export const types = {
uploadStarted: 'core_form/uploadStarted',
uploadCompleted: 'core_form/uploadCompleted',
};
let legacyEventsRegistered = false;
if (!legacyEventsRegistered) {
// The following event triggers are legacy and will be removed in the future.
// The following approach provides a backwards-compatability layer for the new events.
// Code should be updated to make use of native events.
Y.use('event', 'moodle-core-event', () => {
// Watch for the new native formError event, and trigger the legacy YUI event.
document.addEventListener(eventTypes.formError, e => {
const element = Y.one(e.target);
const formElement = Y.one(e.target.closest('form'));
Y.Global.fire(
M.core.globalEvents.FORM_ERROR,
{
formid: formElement.generateID(),
elementid: element.generateID(),
}
);
});
// Watch for the new native formSubmittedByJavascript event, and trigger the legacy YUI event.
document.addEventListener(eventTypes.formSubmittedByJavascript, e => {
if (e.detail.fallbackHandled) {
// This event was originally generated by a YUI event.
// Do not generate another as this will recurse.
return;
}
if (e.skipValidation) {
window.skipClientValidation = true;
}
// Trigger the legacy YUI event.
const form = Y.one(e.target);
form.fire(
M.core.event.FORM_SUBMIT_AJAX,
{
currentTarget: form,
fallbackHandled: true,
}
);
if (e.skipValidation) {
window.skipClientValidation = false;
}
});
});
// Watch for the new native formFieldValidationFailed event, and trigger the legacy jQuery event.
document.addEventListener(eventTypes.formFieldValidationFailed, e => {
// Note: The "core_form-field-validation" event is hard-coded in core/event.
// This is not included to prevent cyclic module dependencies.
const legacyEvent = jQuery.Event("core_form-field-validation");
jQuery(e.target).trigger(legacyEvent, e.detail.message);
});
legacyEventsRegistered = true;
}
/**
* Trigger an event to notify the file upload field has been changed.
*
* @method
* @param {string} elementId The element which was changed
* @returns {CustomEvent}
* @fires uploadChanged
*/
export const notifyUploadChanged = elementId => dispatchEvent(
eventTypes.uploadChanged,
{},
document.getElementById(elementId),
{
bubbles: true,
cancellable: false,
}
);
/**
* Trigger an event to notify the field structure has changed.
*
* @method
* @param {string} elementId The element which was changed
* @returns {CustomEvent}
* @fires fieldStructureChanged
*/
export const notifyFieldStructureChanged = elementId => dispatchEvent(
eventTypes.fieldStructureChanged,
{},
document.getElementById(elementId),
{
bubbles: true,
cancellable: false,
}
);
+304
View File
@@ -0,0 +1,304 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module allows to enhance the form elements MoodleQuickForm_filetypes
*
* @module core_form/filetypes
* @copyright 2017 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.3
*/
define(['jquery', 'core/log', 'core/modal_events', 'core/modal_save_cancel', 'core/ajax',
'core/templates', 'core/tree'],
function($, Log, ModalEvents, ModalSaveCancel, Ajax, Templates, Tree) {
"use strict";
/**
* Constructor of the FileTypes instances.
*
* @constructor
* @param {String} elementId The id of the form element to enhance
* @param {String} elementLabel The label of the form element used as the modal selector title
* @param {String} onlyTypes Limit the list of offered types to this
* @param {Bool} allowAll Allow presence of the "All file types" item
*/
var FileTypes = function(elementId, elementLabel, onlyTypes, allowAll) {
this.elementId = elementId;
this.elementLabel = elementLabel;
this.onlyTypes = onlyTypes;
this.allowAll = allowAll;
this.inputField = $('#' + elementId);
this.wrapperBrowserTrigger = $('[data-filetypesbrowser="' + elementId + '"]');
this.wrapperDescriptions = $('[data-filetypesdescriptions="' + elementId + '"]');
if (!this.wrapperBrowserTrigger.length) {
// This is a valid case. Most probably the element is frozen and
// the filetypes browser should not be available.
return;
}
if (!this.inputField.length || !this.wrapperDescriptions.length) {
Log.error('core_form/filetypes: Unexpected DOM structure, unable to enhance filetypes field ' + elementId);
return;
}
this.prepareBrowserTrigger()
.then(function() {
return this.prepareBrowserModal();
}.bind(this))
.then(function() {
return this.prepareBrowserTree();
}.bind(this));
};
/**
* Create and set the browser trigger widget (this.browserTrigger).
*
* @method prepareBrowserTrigger
* @returns {Promise}
*/
FileTypes.prototype.prepareBrowserTrigger = function() {
return Templates.render('core_form/filetypes-trigger', {})
.then(function(html) {
this.wrapperBrowserTrigger.html(html);
this.browserTrigger = this.wrapperBrowserTrigger.find('[data-filetypeswidget="browsertrigger"]');
}.bind(this));
};
/**
* Create and set the modal for displaying the browser (this.browserModal).
*
* @method prepareBrowserModal
* @returns {Promise}
*/
FileTypes.prototype.prepareBrowserModal = function() {
return ModalSaveCancel.create({
title: this.elementLabel,
})
.then(function(modal) {
this.browserModal = modal;
return modal;
}.bind(this))
.then(function() {
// Because we have custom conditional modal trigger, we need to
// handle the focus after closing ourselves, too.
this.browserModal.getRoot().on(ModalEvents.hidden, function() {
this.browserTrigger.focus();
}.bind(this));
this.browserModal.getRoot().on(ModalEvents.save, function() {
this.saveBrowserModal();
}.bind(this));
}.bind(this));
};
/**
* Create and set the tree in the browser modal's body.
*
* @method prepareBrowserTree
* @returns {Promise}
*/
FileTypes.prototype.prepareBrowserTree = function() {
this.browserTrigger.on('click', function(e) {
e.preventDefault();
// We want to display the browser modal only when the associated input
// field is not frozen (disabled).
if (this.inputField.is('[disabled]')) {
return;
}
var bodyContent = this.loadBrowserModalBody();
bodyContent.then(function() {
// Turn the list of groups and extensions into the tree.
this.browserTree = new Tree(this.browserModal.getBody());
// Override the behaviour of the Enter and Space keys to toggle our checkbox,
// rather than toggle the tree node expansion status.
this.browserTree.handleKeyDown = function(item, e) {
if (e.keyCode == this.browserTree.keys.enter || e.keyCode == this.browserTree.keys.space) {
e.preventDefault();
e.stopPropagation();
this.toggleCheckbox(item.attr('data-filetypesbrowserkey'));
} else {
Tree.prototype.handleKeyDown.call(this.browserTree, item, e);
}
}.bind(this);
if (this.allowAll) {
// Hide all other items if "All file types" is enabled.
this.hideOrShowItemsDependingOnAllowAll(this.browserModal.getRoot()
.find('input[type="checkbox"][data-filetypesbrowserkey="*"]').first());
// And do the same whenever we click that checkbox.
this.browserModal.getRoot().on('change', 'input[type="checkbox"][data-filetypesbrowserkey="*"]', function(e) {
this.hideOrShowItemsDependingOnAllowAll($(e.currentTarget));
}.bind(this));
}
// Synchronize checked status if the file extension is present in multiple groups.
this.browserModal.getRoot().on('change', 'input[type="checkbox"][data-filetypesbrowserkey]', function(e) {
var checkbox = $(e.currentTarget);
var key = checkbox.attr('data-filetypesbrowserkey');
this.browserModal.getRoot().find('input[type="checkbox"][data-filetypesbrowserkey="' + key + '"]')
.prop('checked', checkbox.prop('checked'));
}.bind(this));
}.bind(this))
.then(function() {
this.browserModal.show();
}.bind(this));
this.browserModal.setBody(bodyContent);
}.bind(this));
// Return a resolved promise.
return $.when();
};
/**
* Load the browser modal body contents.
*
* @returns {Promise}
*/
FileTypes.prototype.loadBrowserModalBody = function() {
var args = {
onlytypes: this.onlyTypes.join(),
allowall: this.allowAll,
current: this.inputField.val()
};
return Ajax.call([{
methodname: 'core_form_get_filetypes_browser_data',
args: args
}])[0].then(function(browserData) {
return Templates.render('core_form/filetypes-browser', {
elementid: this.elementId,
groups: browserData.groups
});
}.bind(this));
};
/**
* Change the checked status of the given file type (group or extension).
*
* @method toggleCheckbox
* @param {String} key
*/
FileTypes.prototype.toggleCheckbox = function(key) {
var checkbox = this.browserModal.getRoot().find('input[type="checkbox"][data-filetypesbrowserkey="' + key + '"]').first();
checkbox.prop('checked', !checkbox.prop('checked'));
};
/**
* Update the associated input field with selected file types.
*
* @method saveBrowserModal
*/
FileTypes.prototype.saveBrowserModal = function() {
// Check the "All file types" first.
if (this.allowAll) {
var allcheckbox = this.browserModal.getRoot().find('input[type="checkbox"][data-filetypesbrowserkey="*"]');
if (allcheckbox.length && allcheckbox.prop('checked')) {
this.inputField.val('*');
this.updateDescriptions(['*']);
return;
}
}
// Iterate over all checked boxes and populate the list.
var newvalue = [];
this.browserModal.getRoot().find('input[type="checkbox"]').each(/** @this represents the checkbox */ function() {
var checkbox = $(this);
var key = checkbox.attr('data-filetypesbrowserkey');
if (checkbox.prop('checked')) {
newvalue.push(key);
}
});
// Remove duplicates (e.g. file types present in multiple groups).
newvalue = newvalue.filter(function(x, i, a) {
return a.indexOf(x) == i;
});
this.inputField.val(newvalue.join(' '));
this.updateDescriptions(newvalue);
};
/**
* Describe the selected filetypes in the form when saving the browser.
*
* @param {Array} keys List of keys to describe
* @returns {Promise}
*/
FileTypes.prototype.updateDescriptions = function(keys) {
var descriptions = [];
keys.forEach(function(key) {
descriptions.push({
description: this.browserModal.getRoot().find('[data-filetypesname="' + key + '"]').first().text().trim(),
extensions: this.browserModal.getRoot().find('[data-filetypesextensions="' + key + '"]').first().text().trim()
});
}.bind(this));
var templatedata = {
hasdescriptions: (descriptions.length > 0),
descriptions: descriptions
};
return Templates.render('core_form/filetypes-descriptions', templatedata)
.then(function(html) {
this.wrapperDescriptions.html(html);
}.bind(this));
};
/**
* If "All file types" is checked, all other browser items are made hidden, and vice versa.
*
* @param {jQuery} allcheckbox The "All file types" checkbox.
*/
FileTypes.prototype.hideOrShowItemsDependingOnAllowAll = function(allcheckbox) {
var others = this.browserModal.getRoot().find('[role="treeitem"][data-filetypesbrowserkey!="*"]');
if (allcheckbox.prop('checked')) {
others.hide();
} else {
others.show();
}
};
return {
init: function(elementId, elementLabel, onlyTypes, allowAll) {
new FileTypes(elementId, elementLabel, onlyTypes, allowAll);
}
};
});
+432
View File
@@ -0,0 +1,432 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Display a form in a modal dialogue
*
* Example:
* import ModalForm from 'core_form/modalform';
*
* const modalForm = new ModalForm({
* formClass: 'pluginname\\form\\formname',
* modalConfig: {title: 'Here comes the title'},
* args: {categoryid: 123},
* returnFocus: e.target,
* });
* modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, (c) => window.console.log(c.detail));
* modalForm.show();
*
* See also https://docs.moodle.org/dev/Modal_and_AJAX_forms
*
* @module core_form/modalform
* @copyright 2018 Mitxel Moriana <mitxel@tresipunt.>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Ajax from 'core/ajax';
import * as FormChangeChecker from 'core_form/changechecker';
import * as FormEvents from 'core_form/events';
import Fragment from 'core/fragment';
import ModalEvents from 'core/modal_events';
import Notification from 'core/notification';
import Pending from 'core/pending';
import {serialize} from './util';
export default class ModalForm {
/**
* Various events that can be observed.
*
* @type {Object}
*/
events = {
// Form was successfully submitted - the response is passed to the event listener.
// Cancellable (but it's hardly ever needed to cancel this event).
FORM_SUBMITTED: 'core_form_modalform_formsubmitted',
// Cancel button was pressed.
// Cancellable (but it's hardly ever needed to cancel this event).
FORM_CANCELLED: 'core_form_modalform_formcancelled',
// User attempted to submit the form but there was client-side validation error.
CLIENT_VALIDATION_ERROR: 'core_form_modalform_clientvalidationerror',
// User attempted to submit the form but server returned validation error.
SERVER_VALIDATION_ERROR: 'core_form_modalform_validationerror',
// Error occurred while performing request to the server.
// Cancellable (by default calls Notification.exception).
ERROR: 'core_form_modalform_error',
// Right after user pressed no-submit button,
// listen to this event if you want to add JS validation or processing for no-submit button.
// Cancellable.
NOSUBMIT_BUTTON_PRESSED: 'core_form_modalform_nosubmitbutton',
// Right after user pressed submit button,
// listen to this event if you want to add additional JS validation or confirmation dialog.
// Cancellable.
SUBMIT_BUTTON_PRESSED: 'core_form_modalform_submitbutton',
// Right after user pressed cancel button,
// listen to this event if you want to add confirmation dialog.
// Cancellable.
CANCEL_BUTTON_PRESSED: 'core_form_modalform_cancelbutton',
// Modal was loaded and this.modal is available (but the form content may not be loaded yet).
LOADED: 'core_form_modalform_loaded',
};
/**
* Constructor
*
* Shows the required form inside a modal dialogue
*
* @param {Object} config parameters for the form and modal dialogue:
* @paramy {String} config.formClass PHP class name that handles the form (should extend \core_form\modal )
* @paramy {String} config.moduleName module name to use if different to core/modal_save_cancel (optional)
* @paramy {Object} config.modalConfig modal config - title, header, footer, etc.
* Default: {removeOnClose: true, large: true}
* @paramy {Object} config.args Arguments for the initial form rendering (for example, id of the edited entity)
* @paramy {String} config.saveButtonText the text to display on the Modal "Save" button (optional)
* @paramy {String} config.saveButtonClasses additional CSS classes for the Modal "Save" button
* @paramy {HTMLElement} config.returnFocus element to return focus to after the dialogue is closed
*/
constructor(config) {
this.modal = null;
this.config = config;
this.config.modalConfig = {
removeOnClose: true,
large: true,
...(this.config.modalConfig || {}),
};
this.config.args = this.config.args || {};
this.futureListeners = [];
}
/**
* Loads the modal module and creates an instance
*
* @returns {Promise}
*/
getModalModule() {
if (!this.config.moduleName && this.config.modalConfig.type && this.config.modalConfig.type !== 'SAVE_CANCEL') {
// Legacy loader for plugins that were not updated with Moodle 4.3 changes.
window.console.warn(
'Passing config.modalConfig.type to ModalForm has been deprecated since Moodle 4.3. ' +
'Please pass config.modalName instead with the full module name.',
);
return import('core/modal_factory')
.then((ModalFactory) => ModalFactory.create(this.config.modalConfig));
} else {
// New loader for Moodle 4.3 and above.
const moduleName = this.config.moduleName ?? 'core/modal_save_cancel';
return import(moduleName)
.then((module) => module.create(this.config.modalConfig));
}
}
/**
* Initialise the modal and shows it
*
* @return {Promise}
*/
show() {
const pendingPromise = new Pending('core_form/modalform:init');
return this.getModalModule()
.then((modal) => {
this.modal = modal;
// Retrieve the form and set the modal body. We can not set the body in the modalConfig,
// we need to make sure that the modal already exists when we render the form. Some form elements
// such as date_selector inspect the existing elements on the page to find the highest z-index.
const formParams = serialize(this.config.args || {});
const bodyContent = this.getBody(formParams);
this.modal.setBodyContent(bodyContent);
bodyContent.catch(Notification.exception);
// After successfull submit, when we press "Cancel" or close the dialogue by clicking on X in the top right corner.
this.modal.getRoot().on(ModalEvents.hidden, () => {
this.notifyResetFormChanges();
this.modal.destroy();
// Focus on the element that actually launched the modal.
if (this.config.returnFocus) {
this.config.returnFocus.focus();
}
});
// Add the class to the modal dialogue.
this.modal.getModal().addClass('modal-form-dialogue');
// We catch the press on submit buttons in the forms.
this.modal.getRoot().on('click', 'form input[type=submit][data-no-submit]',
(e) => {
e.preventDefault();
const event = this.trigger(this.events.NOSUBMIT_BUTTON_PRESSED, e.target);
if (!event.defaultPrevented) {
this.processNoSubmitButton(e.target);
}
});
// We catch the form submit event and use it to submit the form with ajax.
this.modal.getRoot().on('submit', 'form', (e) => {
e.preventDefault();
const event = this.trigger(this.events.SUBMIT_BUTTON_PRESSED);
if (!event.defaultPrevented) {
this.submitFormAjax();
}
});
// Change the text for the save button.
if (typeof this.config.saveButtonText !== 'undefined' &&
typeof this.modal.setSaveButtonText !== 'undefined') {
this.modal.setSaveButtonText(this.config.saveButtonText);
}
// Set classes for the save button.
if (typeof this.config.saveButtonClasses !== 'undefined') {
this.setSaveButtonClasses(this.config.saveButtonClasses);
}
// When Save button is pressed - submit the form.
this.modal.getRoot().on(ModalEvents.save, (e) => {
e.preventDefault();
this.modal.getRoot().find('form').submit();
});
// When Cancel button is pressed - allow to intercept.
this.modal.getRoot().on(ModalEvents.cancel, (e) => {
const event = this.trigger(this.events.CANCEL_BUTTON_PRESSED);
if (event.defaultPrevented) {
e.preventDefault();
}
});
this.futureListeners.forEach(args => this.modal.getRoot()[0].addEventListener(...args));
this.futureListeners = [];
this.trigger(this.events.LOADED, null, false);
return this.modal.show();
})
.then(pendingPromise.resolve);
}
/**
* Triggers a custom event
*
* @private
* @param {String} eventName
* @param {*} detail
* @param {Boolean} cancelable
* @return {CustomEvent<unknown>}
*/
trigger(eventName, detail = null, cancelable = true) {
const e = new CustomEvent(eventName, {detail, cancelable});
this.modal.getRoot()[0].dispatchEvent(e);
return e;
}
/**
* Add listener for an event
*
* @param {array} args
* @example:
* const modalForm = new ModalForm(...);
* dynamicForm.addEventListener(modalForm.events.FORM_SUBMITTED, e => {
* window.console.log(e.detail);
* });
*/
addEventListener(...args) {
if (!this.modal) {
this.futureListeners.push(args);
} else {
this.modal.getRoot()[0].addEventListener(...args);
}
}
/**
* Get form contents (to be used in ModalForm.setBodyContent())
*
* @param {String} formDataString form data in format of a query string
* @method getBody
* @private
* @return {Promise}
*/
getBody(formDataString) {
const params = {
formdata: formDataString,
form: this.config.formClass
};
const pendingPromise = new Pending('core_form/modalform:form_body');
return Ajax.call([{
methodname: 'core_form_dynamic_form',
args: params
}])[0]
.then(response => {
pendingPromise.resolve();
return {html: response.html, js: Fragment.processCollectedJavascript(response.javascript)};
})
.catch(exception => this.onSubmitError(exception));
}
/**
* On exception during form processing or initial rendering. Caller may override.
*
* @param {Object} exception
*/
onSubmitError(exception) {
const event = this.trigger(this.events.ERROR, exception);
if (event.defaultPrevented) {
return;
}
Notification.exception(exception);
}
/**
* Notifies listeners that form dirty state should be reset.
*
* @fires event:formSubmittedByJavascript
*/
notifyResetFormChanges() {
const form = this.getFormNode();
if (!form) {
return;
}
FormEvents.notifyFormSubmittedByJavascript(form, true);
FormChangeChecker.resetFormDirtyState(form);
}
/**
* Get the form node from the Dialogue.
*
* @returns {HTMLFormElement}
*/
getFormNode() {
return this.modal.getRoot().find('form')[0];
}
/**
* Click on a "submit" button that is marked in the form as registerNoSubmitButton()
*
* @param {Element} button button that was pressed
* @fires event:formSubmittedByJavascript
*/
processNoSubmitButton(button) {
const form = this.getFormNode();
if (!form) {
return;
}
FormEvents.notifyFormSubmittedByJavascript(form, true);
// Add the button name to the form data and submit it.
let formData = this.modal.getRoot().find('form').serialize();
formData = formData + '&' + encodeURIComponent(button.getAttribute('name')) + '=' +
encodeURIComponent(button.getAttribute('value'));
const bodyContent = this.getBody(formData);
this.modal.setBodyContent(bodyContent);
bodyContent.catch(Notification.exception);
}
/**
* Validate form elements
* @return {Boolean} Whether client-side validation has passed, false if there are errors
* @fires event:formSubmittedByJavascript
*/
validateElements() {
FormEvents.notifyFormSubmittedByJavascript(this.getFormNode());
// Now the change events have run, see if there are any "invalid" form fields.
/** @var {jQuery} list of elements with errors */
const invalid = this.modal.getRoot().find('[aria-invalid="true"], .error');
// If we found invalid fields, focus on the first one and do not submit via ajax.
if (invalid.length) {
invalid.first().focus();
return false;
}
return true;
}
/**
* Disable buttons during form submission
*/
disableButtons() {
this.modal.getFooter().find('[data-action]').attr('disabled', true);
}
/**
* Enable buttons after form submission (on validation error)
*/
enableButtons() {
this.modal.getFooter().find('[data-action]').removeAttr('disabled');
}
/**
* Submit the form via AJAX call to the core_form_dynamic_form WS
*/
async submitFormAjax() {
// If we found invalid fields, focus on the first one and do not submit via ajax.
if (!this.validateElements()) {
this.trigger(this.events.CLIENT_VALIDATION_ERROR, null, false);
return;
}
this.disableButtons();
// Convert all the form elements values to a serialised string.
const form = this.modal.getRoot().find('form');
const formData = form.serialize();
// Now we can continue...
Ajax.call([{
methodname: 'core_form_dynamic_form',
args: {
formdata: formData,
form: this.config.formClass
}
}])[0]
.then((response) => {
if (!response.submitted) {
// Form was not submitted because validation failed.
const promise = new Promise(
resolve => resolve({html: response.html, js: Fragment.processCollectedJavascript(response.javascript)}));
this.modal.setBodyContent(promise);
this.enableButtons();
this.trigger(this.events.SERVER_VALIDATION_ERROR);
} else {
// Form was submitted properly. Hide the modal and execute callback.
const data = JSON.parse(response.data);
FormChangeChecker.markFormSubmitted(form[0]);
const event = this.trigger(this.events.FORM_SUBMITTED, data);
if (!event.defaultPrevented) {
this.modal.hide();
}
}
return null;
})
.catch(exception => {
this.enableButtons();
this.onSubmitError(exception);
});
}
/**
* Set the classes for the 'save' button.
*
* @method setSaveButtonClasses
* @param {(String)} value The 'save' button classes.
*/
setSaveButtonClasses(value) {
const button = this.modal.getFooter().find("[data-action='save']");
if (!button) {
throw new Error("Unable to find the 'save' button");
}
button.removeClass().addClass(value);
}
}
+299
View File
@@ -0,0 +1,299 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Password Unmask functionality.
*
* @module core_form/passwordunmask
* @copyright 2016 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.2
*/
define(['jquery', 'core/templates'], function($, Template) {
/**
* Constructor for PasswordUnmask.
*
* @class core_form/passwordunmask
* @param {String} elementid The element to apply the PasswordUnmask to
*/
var PasswordUnmask = function(elementid) {
// Setup variables.
this.wrapperSelector = '[data-passwordunmask="wrapper"][data-passwordunmaskid="' + elementid + '"]';
this.wrapper = $(this.wrapperSelector);
this.editorSpace = this.wrapper.find('[data-passwordunmask="editor"]');
this.editLink = this.wrapper.find('a[data-passwordunmask="edit"]');
this.editInstructions = this.wrapper.find('[data-passwordunmask="instructions"]');
this.displayValue = this.wrapper.find('[data-passwordunmask="displayvalue"]');
this.inputFieldLabel = $('label[for="' + elementid + '"]');
this.inputField = this.editorSpace.find(document.getElementById(elementid));
// Hide the field.
this.inputField.addClass('d-none');
this.inputField.removeClass('hiddenifjs');
if (!this.editInstructions.attr('id')) {
this.editInstructions.attr('id', elementid + '_instructions');
}
this.editInstructions.hide();
this.setDisplayValue();
// Add the listeners.
this.addListeners();
};
/**
* Add the event listeners required for PasswordUnmask.
*
* @method addListeners
* @return {PasswordUnmask}
* @chainable
*/
PasswordUnmask.prototype.addListeners = function() {
this.wrapper.on('click keypress', '[data-passwordunmask="edit"]', $.proxy(function(e) {
if (e.type === 'keypress' && e.keyCode !== 13) {
return;
}
e.stopImmediatePropagation();
e.preventDefault();
if (this.isEditing()) {
// Only focus on the edit link if the event was not a click, and the new target is not an input field.
if (e.type !== 'click' && !$(e.relatedTarget).is(':input')) {
this.turnEditingOff(true);
} else {
this.turnEditingOff(false);
}
} else {
this.turnEditingOn();
}
}, this));
this.wrapper.on('click keypress', '[data-passwordunmask="unmask"]', $.proxy(function(e) {
if (e.type === 'keypress' && e.keyCode !== 13) {
return;
}
e.stopImmediatePropagation();
e.preventDefault();
// Toggle the data attribute.
this.wrapper.data('unmasked', !this.wrapper.data('unmasked'));
this.setDisplayValue();
}, this));
this.wrapper.on('keydown', 'input', $.proxy(function(e) {
if (e.type === 'keydown' && e.keyCode !== 13) {
return;
}
e.stopImmediatePropagation();
e.preventDefault();
this.turnEditingOff(true);
}, this));
this.inputFieldLabel.on('click', $.proxy(function(e) {
e.preventDefault();
this.turnEditingOn();
}, this));
return this;
};
/**
* Check whether focus was lost from the PasswordUnmask and turn editing off if required.
*
* @method checkFocusOut
* @param {EventFacade} e The EventFacade generating the suspsected Focus Out
*/
PasswordUnmask.prototype.checkFocusOut = function(e) {
if (!this.isEditing()) {
// Ignore - not editing.
return;
}
window.setTimeout($.proxy(function() {
// Firefox does not have the focusout event. Instead jQuery falls back to the 'blur' event.
// The blur event does not have a relatedTarget, so instead we use a timeout and the new activeElement.
var relatedTarget = e.relatedTarget || document.activeElement;
if (this.wrapper.has($(relatedTarget)).length) {
// Ignore, some part of the element is still active.
return;
}
// Only focus on the edit link if the new related target is not an input field or anchor.
this.turnEditingOff(!$(relatedTarget).is(':input,a'));
}, this), 100);
};
/**
* Whether the password is currently visible (unmasked).
*
* @method passwordVisible
* @return {Boolean} True if the password is unmasked
*/
PasswordUnmask.prototype.passwordVisible = function() {
return !!this.wrapper.data('unmasked');
};
/**
* Whether the user is currently editing the field.
*
* @method isEditing
* @return {Boolean} True if edit mode is enabled
*/
PasswordUnmask.prototype.isEditing = function() {
return this.inputField.hasClass('d-inline-block');
};
/**
* Enable the editing functionality.
*
* @method turnEditingOn
* @return {PasswordUnmask}
* @chainable
*/
PasswordUnmask.prototype.turnEditingOn = function() {
var value = this.getDisplayValue();
if (this.passwordVisible()) {
this.inputField.attr('type', 'text');
} else {
this.inputField.attr('type', 'password');
}
this.inputField.val(value);
this.inputField.attr('size', this.inputField.attr('data-size'));
// Show the field.
this.inputField.addClass('d-inline-block');
if (this.editInstructions.length) {
this.inputField.attr('aria-describedby', this.editInstructions.attr('id'));
this.editInstructions.show();
}
this.wrapper.attr('data-passwordunmask-visible', 1);
this.editLink.hide();
this.inputField
.focus()
.select();
// Note, this cannot be added as a delegated listener on init because Firefox does not support the FocusOut
// event (https://bugzilla.mozilla.org/show_bug.cgi?id=687787) and the blur event does not identify the
// relatedTarget.
// The act of focusing the this.inputField means that in Firefox the focusout will be triggered on blur of the edit
// link anchor.
$('body').on('focusout', this.wrapperSelector, $.proxy(this.checkFocusOut, this));
return this;
};
/**
* Disable the editing functionality, optionally focusing on the edit link.
*
* @method turnEditingOff
* @param {Boolean} focusOnEditLink Whether to focus on the edit link after disabling the editor
* @return {PasswordUnmask}
* @chainable
*/
PasswordUnmask.prototype.turnEditingOff = function(focusOnEditLink) {
$('body').off('focusout', this.wrapperSelector, this.checkFocusOut);
var value = this.getDisplayValue();
this.inputField
// Ensure that the aria-describedby is removed.
.attr('aria-describedby', null);
this.inputField.val(value);
// Hide the field again.
this.inputField.removeClass('d-inline-block');
this.editInstructions.hide();
// Remove the visible attr.
this.wrapper.removeAttr('data-passwordunmask-visible');
// Remove the size attr.
this.inputField.removeAttr('size');
this.editLink.show();
this.setDisplayValue();
if (focusOnEditLink) {
this.editLink.focus();
}
return this;
};
/**
* Get the currently value.
*
* @method getDisplayValue
* @return {String}
*/
PasswordUnmask.prototype.getDisplayValue = function() {
return this.inputField.val();
};
/**
* Set the currently value in the display, taking into account the current settings.
*
* @method setDisplayValue
* @return {PasswordUnmask}
* @chainable
*/
PasswordUnmask.prototype.setDisplayValue = function() {
var value = this.getDisplayValue();
if (this.isEditing()) {
if (this.wrapper.data('unmasked')) {
this.inputField.attr('type', 'text');
} else {
this.inputField.attr('type', 'password');
}
this.inputField.val(value);
}
// Update the display value.
// Note: This must always be updated.
// The unmask value can be changed whilst editing and the editing can then be disabled.
if (value && this.wrapper.data('unmasked')) {
// There is a value, and we will show it.
this.displayValue.text(value);
} else {
if (!value) {
value = "";
}
// There is a value, but it will be disguised.
// We use the passwordunmask-fill to allow modification of the fill and to ensure that the display does not
// change as the page loads the JS.
Template.render('core_form/element-passwordunmask-fill', {
element: {
frozen: this.inputField.is('[readonly]'),
value: value,
valuechars: value.split(''),
},
}).done($.proxy(function(html, js) {
this.displayValue.html(html);
Template.runTemplateJS(js);
}, this));
}
return this;
};
return PasswordUnmask;
});
+227
View File
@@ -0,0 +1,227 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A class to help show and hide advanced form content.
*
* @module core_form/showadvanced
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/log', 'core/str', 'core/notification'], function($, Log, Strings, Notification) {
var SELECTORS = {
FIELDSETCONTAINSADVANCED: 'fieldset.containsadvancedelements',
DIVFITEMADVANCED: 'div.fitem.advanced',
DIVADVANCEDSECTION: 'div#form-advanced-div',
DIVFCONTAINER: 'div.fcontainer',
MORELESSLINK: 'fieldset.containsadvancedelements .moreless-toggler'
},
CSS = {
SHOW: 'show',
MORELESSACTIONS: 'moreless-actions',
MORELESSTOGGLER: 'moreless-toggler',
SHOWLESS: 'moreless-less'
},
WRAPPERS = {
FITEM: '<div class="fitem"></div>',
FELEMENT: '<div class="felement"></div>',
ADVANCEDDIV: '<div id="form-advanced-div"></div>'
},
IDPREFIX = 'showadvancedid-';
/** @property {Integer} uniqIdSeed Auto incrementing number used to generate ids. */
var uniqIdSeed = 0;
/**
* ShowAdvanced behaviour class.
*
* @class core_form/showadvanced
* @param {String} id The id of the form.
*/
var ShowAdvanced = function(id) {
this.id = id;
var form = $(document.getElementById(id));
this.enhanceForm(form);
};
/** @property {String} id The form id to enhance. */
ShowAdvanced.prototype.id = '';
/**
* @method enhanceForm
* @param {JQuery} form JQuery selector representing the form
* @return {ShowAdvanced}
*/
ShowAdvanced.prototype.enhanceForm = function(form) {
var fieldsets = form.find(SELECTORS.FIELDSETCONTAINSADVANCED);
// Enhance each fieldset in the form matching the selector.
fieldsets.each(function(index, item) {
this.enhanceFieldset($(item));
}.bind(this));
// Attach some event listeners.
// Subscribe more/less links to click event.
form.on('click', SELECTORS.MORELESSLINK, this.switchState);
// Subscribe to key events but filter for space or enter.
form.on('keydown', SELECTORS.MORELESSLINK, function(e) {
// Enter or space.
if (e.which == 13 || e.which == 32) {
return this.switchState(e);
}
return true;
}.bind(this));
return this;
};
/**
* Generates a uniq id for the dom element it's called on unless the element already has an id.
* The id is set on the dom node before being returned.
*
* @method generateId
* @param {JQuery} node JQuery selector representing a single DOM Node.
* @return {String}
*/
ShowAdvanced.prototype.generateId = function(node) {
var id = node.prop('id');
if (typeof id === 'undefined') {
id = IDPREFIX + (uniqIdSeed++);
node.prop('id', id);
}
return id;
};
/**
* @method enhanceFieldset
* @param {JQuery} fieldset JQuery selector representing a fieldset
* @return {ShowAdvanced}
*/
ShowAdvanced.prototype.enhanceFieldset = function(fieldset) {
var statuselement = $('input[name=mform_showmore_' + fieldset.prop('id') + ']');
if (!statuselement.length) {
Log.debug("M.form.showadvanced::processFieldset was called on an fieldset without a status field: '" +
fieldset.prop('id') + "'");
return this;
}
// Fetch some strings.
Strings.get_strings([{
key: 'showmore',
component: 'core_form'
}, {
key: 'showless',
component: 'core_form'
}]).then(function(results) {
var showmore = results[0],
showless = results[1];
// Generate more/less links.
var morelesslink = $('<a href="#"></a>');
morelesslink.addClass(CSS.MORELESSTOGGLER);
if (statuselement.val() === '0') {
morelesslink.html(showmore);
morelesslink.attr('aria-expanded', 'false');
} else {
morelesslink.html(showless);
morelesslink.attr('aria-expanded', 'true');
morelesslink.addClass(CSS.SHOWLESS);
fieldset.find(SELECTORS.DIVFITEMADVANCED).addClass(CSS.SHOW);
}
// Build a list of advanced fieldsets.
var idlist = [];
fieldset.find(SELECTORS.DIVFITEMADVANCED).each(function(index, node) {
idlist[idlist.length] = this.generateId($(node));
}.bind(this));
// Set aria attributes.
morelesslink.attr('role', 'button');
morelesslink.attr('aria-controls', 'form-advanced-div');
var formadvancedsection = $(WRAPPERS.ADVANCEDDIV);
fieldset.find(SELECTORS.DIVFITEMADVANCED).wrapAll(formadvancedsection);
// Add elements to the DOM.
var fitem = $(WRAPPERS.FITEM);
fitem.addClass(CSS.MORELESSACTIONS);
var felement = $(WRAPPERS.FELEMENT);
felement.append(morelesslink);
fitem.append(felement);
fieldset.find(SELECTORS.DIVADVANCEDSECTION).before(fitem);
return true;
}.bind(this)).fail(Notification.exception);
return this;
};
/**
* @method switchState
* @param {Event} e Event that triggered this action.
* @return {Boolean}
*/
ShowAdvanced.prototype.switchState = function(e) {
e.preventDefault();
// Fetch some strings.
Strings.get_strings([{
key: 'showmore',
component: 'core_form'
}, {
key: 'showless',
component: 'core_form'
}]).then(function(results) {
var showmore = results[0],
showless = results[1],
fieldset = $(e.target).closest(SELECTORS.FIELDSETCONTAINSADVANCED);
// Toggle collapsed class.
fieldset.find(SELECTORS.DIVFITEMADVANCED).toggleClass(CSS.SHOW);
// Get corresponding hidden variable.
var statuselement = $('input[name=mform_showmore_' + fieldset.prop('id') + ']');
// Invert it and change the link text.
if (statuselement.val() === '0') {
statuselement.val(1);
$(e.target).addClass(CSS.SHOWLESS);
$(e.target).html(showless);
$(e.target).attr('aria-expanded', 'true');
} else {
statuselement.val(0);
$(e.target).removeClass(CSS.SHOWLESS);
$(e.target).html(showmore);
$(e.target).attr('aria-expanded', 'false');
}
return true;
}).fail(Notification.exception);
return this;
};
return {
/**
* Initialise this module.
* @method init
* @param {String} formid
* @return {ShowAdvanced}
*/
init: function(formid) {
return new ShowAdvanced(formid);
}
};
});
+165
View File
@@ -0,0 +1,165 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Submit button JavaScript. All submit buttons will be automatically disabled once the form is
* submitted, unless that submission results in an error/cancelling the submit.
*
* @module core_form/submit
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.8
*/
import {eventTypes} from 'core_form/events';
/** @property {number} ID for setInterval used when polling for download cookie */
let cookieListener = 0;
/** @property {Array} Array of buttons that need re-enabling if we get a download cookie */
const cookieListeningButtons = [];
/** @property {number} Number of files uploading. */
let currentUploadCount = 0;
/** @property {Array} Array of buttons that need re-enabling if we get a upload process. */
const uploadListeningButtons = [];
/** @property {Boolean} Is upload listeners registered? */
let uploadListenersRegistered = false;
/**
* Listens in case a download cookie is provided.
*
* This function is used to detect file downloads. If there is a file download then we get a
* beforeunload event, but the page is never unloaded and when the file download completes we
* should re-enable the buttons. We detect this by watching for a specific cookie.
*
* PHP function \core_form\util::form_download_complete() can be used to send this cookie.
*
* @param {HTMLElement} button Button to re-enable
*/
const listenForDownloadCookie = (button) => {
cookieListeningButtons.push(button);
if (!cookieListener) {
cookieListener = setInterval(() => {
// Look for cookie.
const parts = document.cookie.split(getCookieName() + '=');
if (parts.length == 2) {
// We found the cookie, so the file is ready. Expire the cookie and cancel polling.
clearDownloadCookie();
clearInterval(cookieListener);
cookieListener = 0;
// Re-enable all the buttons.
cookieListeningButtons.forEach((button) => {
button.disabled = false;
});
}
}, 500);
}
};
/**
* Gets a unique name for the download cookie.
*
* @returns {string} Cookie name
*/
const getCookieName = () => {
return 'moodledownload_' + M.cfg.sesskey;
};
/**
* Clears the download cookie if there is one.
*/
const clearDownloadCookie = () => {
document.cookie = encodeURIComponent(getCookieName()) + '=deleted; expires=' + new Date(0).toUTCString();
};
/**
* Enable submit buttons when all files are uploaded.
*/
const checkUploadCount = () => {
if (currentUploadCount) {
uploadListeningButtons.forEach(button => {
button.disabled = true;
});
} else {
uploadListeningButtons.forEach(button => {
button.disabled = false;
});
}
};
/**
* Initialises submit buttons.
*
* @param {String} elementId Form button element
* @listens event:uploadStarted
* @listens event:uploadCompleted
*/
export const init = (elementId) => {
const button = document.getElementById(elementId);
if (button === null) {
// Exit early if invalid element id passed.
return;
}
// If buttons are disabled by default, we do not enable them when file upload completed event is fired.
if (!button.disabled) {
uploadListeningButtons.push(button);
}
if (!uploadListenersRegistered) {
// Add event listener for file upload start.
document.addEventListener(eventTypes.uploadStarted, () => {
currentUploadCount++;
checkUploadCount();
});
// Add event listener for file upload complete.
document.addEventListener(eventTypes.uploadCompleted, () => {
currentUploadCount--;
checkUploadCount();
});
uploadListenersRegistered = true;
}
// If the form has double submit protection disabled, do nothing.
if (button.form.dataset.doubleSubmitProtection === 'off') {
return;
}
button.form.addEventListener('submit', function(event) {
// Only disable it if the browser is really going to another page as a result of the
// submit.
const disableAction = function() {
// If the submit was cancelled, or the button is already disabled, don't do anything.
if (event.defaultPrevented || button.disabled) {
return;
}
button.disabled = true;
clearDownloadCookie();
listenForDownloadCookie(button);
};
window.addEventListener('beforeunload', disableAction);
// If there is no beforeunload event as a result of this form submit, then the form
// submit must have been cancelled, so don't disable the button if the page is
// unloaded later.
setTimeout(function() {
window.removeEventListener('beforeunload', disableAction);
}, 1);
}, false);
};
+30
View File
@@ -0,0 +1,30 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Serialize form values into a string.
*
* This must be used instead of URLSearchParams, which does not correctly encode nested values such as arrays.
*
* @param {Object} data The form values to serialize
* @param {string} prefix The prefix to use for key names
* @returns {string}
*/
export const serialize = (data, prefix = '') => [
...Object.entries(data).map(([index, value]) => {
const key = prefix ? `${prefix}[${index}]` : index;
return (value !== null && typeof value === "object") ? serialize(value, key) : `${key}=${encodeURIComponent(value)}`;
})
].join("&");
+257
View File
@@ -0,0 +1,257 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* autocomplete type form element
*
* Contains HTML class for a autocomplete type element
*
* @package core_form
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once($CFG->libdir . '/form/select.php');
/**
* Autocomplete as you type form element
*
* HTML class for a autocomplete type element
*
* @package core_form
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_autocomplete extends MoodleQuickForm_select {
/** @var boolean $tags Should we allow typing new entries to the field? */
protected $tags = false;
/** @var string $ajax Name of an AMD module to send/process ajax requests. */
protected $ajax = '';
/** @var string $placeholder Placeholder text for an empty list. */
protected $placeholder = '';
/** @var bool $casesensitive Whether the search has to be case-sensitive. */
protected $casesensitive = false;
/** @var bool $showsuggestions Show suggestions by default - but this can be turned off. */
protected $showsuggestions = true;
/** @var string $noselectionstring String that is shown when there are no selections. */
protected $noselectionstring = '';
/** @var callable|null Function to call (with existing value) to render it to HTML */
protected $valuehtmlcallback = null;
/**
* constructor
*
* @param string $elementName Select name attribute
* @param mixed $elementLabel Label(s) for the select
* @param mixed $options Data to be used to populate options
* @param mixed $attributes Either a typical HTML attribute string or an associative array. Special options
* "tags", "placeholder", "ajax", "multiple", "casesensitive" are supported.
*/
public function __construct($elementName=null, $elementLabel=null, $options=null, $attributes=null) {
// Even if the constructor gets called twice we do not really want 2x options (crazy forms!).
$this->_options = array();
if ($attributes === null) {
$attributes = array();
}
if (isset($attributes['tags'])) {
$this->tags = $attributes['tags'];
unset($attributes['tags']);
}
if (isset($attributes['showsuggestions'])) {
$this->showsuggestions = $attributes['showsuggestions'];
unset($attributes['showsuggestions']);
}
$this->placeholder = get_string('search');
if (isset($attributes['placeholder'])) {
$this->placeholder = $attributes['placeholder'];
unset($attributes['placeholder']);
}
$this->noselectionstring = get_string('noselection', 'form');
if (isset($attributes['noselectionstring'])) {
$this->noselectionstring = $attributes['noselectionstring'];
unset($attributes['noselectionstring']);
}
if (isset($attributes['ajax'])) {
$this->ajax = $attributes['ajax'];
unset($attributes['ajax']);
}
if (isset($attributes['casesensitive'])) {
$this->casesensitive = $attributes['casesensitive'] ? true : false;
unset($attributes['casesensitive']);
}
if (isset($attributes['valuehtmlcallback'])) {
$this->valuehtmlcallback = $attributes['valuehtmlcallback'];
unset($attributes['valuehtmlcallback']);
}
parent::__construct($elementName, $elementLabel, $options, $attributes);
$this->_type = 'autocomplete';
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_autocomplete($elementName=null, $elementLabel=null, $options=null, $attributes=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $options, $attributes);
}
/**
* Returns HTML for select form element.
*
* @return string
*/
function toHtml(){
global $PAGE;
// Enhance the select with javascript.
$this->_generateId();
$id = $this->getAttribute('id');
if (!$this->isFrozen()) {
$PAGE->requires->js_call_amd('core/form-autocomplete', 'enhance', $params = array('#' . $id, $this->tags, $this->ajax,
$this->placeholder, $this->casesensitive, $this->showsuggestions, $this->noselectionstring));
}
$html = parent::toHTML();
// Hacky bodge to add in the HTML code to the option tag. There is a nicer
// version of this code in the new template version (see export_for_template).
if ($this->valuehtmlcallback) {
$html = preg_replace_callback('~value="([^"]+)"~', function($matches) {
$value = html_entity_decode($matches[1], ENT_COMPAT);
$htmlvalue = call_user_func($this->valuehtmlcallback, $value);
if ($htmlvalue !== false) {
return $matches[0] . ' data-html="' . s($htmlvalue) . '"';
} else {
return $matches[0];
}
}, $html);
}
return $html;
}
/**
* Search the current list of options to see if there are any options with this value.
* @param string $value to search
* @return boolean
*/
function optionExists($value) {
foreach ($this->_options as $option) {
if (isset($option['attr']['value']) && ($option['attr']['value'] == $value)) {
return true;
}
}
return false;
}
/**
* Set the value of this element. If values can be added or are unknown, we will
* make sure they exist in the options array.
* @param mixed string|array $value The value to set.
* @return boolean
*/
function setValue($value) {
$values = (array) $value;
foreach ($values as $onevalue) {
if (($this->tags || $this->ajax) &&
(!$this->optionExists($onevalue)) &&
($onevalue !== '_qf__force_multiselect_submission')) {
$this->addOption($onevalue, $onevalue);
}
}
return parent::setValue($value);
}
/**
* Returns a 'safe' element's value
*
* @param array array of submitted values to search
* @param bool whether to return the value as associative array
* @access public
* @return mixed
*/
function exportValue(&$submitValues, $assoc = false) {
if ($this->ajax || $this->tags) {
// When this was an ajax request, we do not know the allowed list of values.
$value = $this->_findValue($submitValues);
if (null === $value) {
$value = $this->getValue();
}
// Quickforms inserts a duplicate element in the form with
// this value so that a value is always submitted for this form element.
// Normally this is cleaned as a side effect of it not being a valid option,
// but in this case we need to detect and skip it manually.
if ($value === '_qf__force_multiselect_submission' || $value === null) {
$value = $this->getMultiple() ? [] : '';
}
return $this->_prepareValue($value, $assoc);
} else {
return parent::exportValue($submitValues, $assoc);
}
}
/**
* 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_TAGLIST);
break;
}
return parent::onQuickFormEvent($event, $arg, $caller);
}
public function export_for_template(renderer_base $output) {
$this->_generateId();
$context = parent::export_for_template($output);
$context['tags'] = !empty($this->tags);
$context['ajax'] = $this->ajax;
$context['placeholder'] = $this->placeholder;
$context['casesensitive'] = !empty($this->casesensitive);
$context['showsuggestions'] = !empty($this->showsuggestions);
$context['noselectionstring'] = $this->noselectionstring;
if ($this->valuehtmlcallback) {
foreach ($context['options'] as &$option) {
$value = $option['value'];
$html = call_user_func($this->valuehtmlcallback, $value);
if ($html !== false) {
$option['html'] = $html;
if ($this->isFrozen()) {
$option['text'] = $html;
}
}
}
}
return $context;
}
}
+131
View File
@@ -0,0 +1,131 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Button form element
*
* Contains HTML class for a button type element
*
* @package core_form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("HTML/QuickForm/button.php");
require_once(__DIR__ . '/../outputcomponents.php');
require_once('templatable_form_element.php');
/**
* HTML class for a button type element
*
* Overloaded {@link HTML_QuickForm_button} to add help button
*
* @package core_form
* @category form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_button extends HTML_QuickForm_button implements templatable
{
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/** @var bool if true label will be hidden. */
protected $_hiddenLabel = false;
/**
* Any class apart from 'btn' would be overridden with this content.
*
* By default, buttons will utilize the btn-secondary class. However, there are cases where we
* require a button with different stylings (e.g. btn-primary). In these cases, $customclassoverride will override
* the defaults mentioned previously and utilize the provided class(es).
*
* @var null|string $customclassoverride Custom class override for the input element
*/
protected $customclassoverride;
/**
* constructor
*
* @param string $elementName (optional) name for the button
* @param string $value (optional) value for the button
* @param mixed $attributes (optional) Either a typical HTML attribute string
* or an associative array
* @param array $options Options to further customise the button. Currently accepted options are:
* customclassoverride String The CSS class to use for the button instead of the standard
* btn-primary and btn-secondary classes.
*/
public function __construct($elementName=null, $value=null, $attributes=null, $options = []) {
parent::__construct($elementName, $value, $attributes);
$this->customclassoverride = $options['customclassoverride'] ?? null;
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_button($elementName=null, $value=null, $attributes=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $value, $attributes);
}
/**
* get html for help button
*
* @return string html for help button
*/
function getHelpButton(){
return $this->_helpbutton;
}
/**
* Slightly different container template when frozen.
*
* @return string
*/
function getElementTemplateType(){
if ($this->_flagFrozen){
return 'nodisplay';
} else {
return 'default';
}
}
/**
* Sets label to be hidden
*
* @param bool $hiddenLabel sets if label should be hidden
*/
public function setHiddenLabel($hiddenLabel) {
$this->_hiddenLabel = $hiddenLabel;
}
public function export_for_template(renderer_base $output) {
$context = $this->export_for_template_base($output);
if ($this->customclassoverride) {
$context['customclassoverride'] = $this->customclassoverride;
}
return $context;
}
}
+114
View File
@@ -0,0 +1,114 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Button form element
*
* Contains HTML class for a button type element
*
* @package core_form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); // It must be included from a Moodle page
}
global $CFG;
require_once($CFG->libdir.'/form/submit.php');
/**
* HTML class for a submit cancel type element
*
* Overloaded {@link MoodleQuickForm_submit} with default behavior modified to cancel a form.
*
* @package core_form
* @category form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_cancel extends MoodleQuickForm_submit
{
/**
* constructor
*
* @param string $elementName (optional) name of the checkbox
* @param string $value (optional) value for the button
* @param mixed $attributes (optional) Either a typical HTML attribute string
* or an associative array
*/
public function __construct($elementName=null, $value=null, $attributes=null)
{
if ($elementName==null){
$elementName='cancel';
}
if ($value==null){
$value=get_string('cancel');
}
parent::__construct($elementName, $value, $attributes);
$this->updateAttributes(array('data-skip-validation' => 1, 'data-cancel' => 1,
'onclick' => 'skipClientValidation = true; return true;'));
// Add the class btn-cancel.
$class = $this->getAttribute('class');
if (empty($class)) {
$class = '';
}
$this->updateAttributes(array('class' => $class . ' btn-cancel'));
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_cancel($elementName=null, $value=null, $attributes=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $value, $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':
parent::onQuickFormEvent($event, $arg, $caller);
$caller->_registerCancelButton($this->getName());
return true;
break;
}
return parent::onQuickFormEvent($event, $arg, $caller);
}
/**
* Returns the value of field without HTML tags
*
* @return string
*/
function getFrozenHtml(){
return HTML_QuickForm_submit::getFrozenHtml();
}
}
+153
View File
@@ -0,0 +1,153 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* checkbox form element
*
* Contains HTML class for a checkbox type element
*
* @package core_form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('HTML/QuickForm/checkbox.php');
require_once('templatable_form_element.php');
/**
* HTML class for a checkbox type element
*
* Overloaded {@link HTML_QuickForm_checkbox} to add help button. Also, fixes bug in quickforms
* checkbox, which lets previous set value override submitted value if checkbox is not checked
* and no value is submitted
*
* @package core_form
* @category form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_checkbox extends HTML_QuickForm_checkbox implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/**
* Constructor
*
* @param string $elementName (optional) name of the checkbox
* @param string $elementLabel (optional) checkbox label
* @param string $text (optional) Text to put after the checkbox
* @param mixed $attributes (optional) Either a typical HTML attribute string
* or an associative array
*/
public function __construct($elementName=null, $elementLabel=null, $text='', $attributes=null) {
parent::__construct($elementName, $elementLabel, $text, $attributes);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_checkbox($elementName=null, $elementLabel=null, $text='', $attributes=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $text, $attributes);
}
/**
* get html for help button
*
* @return string html for help button
*/
function getHelpButton(){
return $this->_helpbutton;
}
/**
* 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)
{
//fixes bug in quickforms which lets previous set value override submitted value if checkbox is not checked
// and no value is submitted
switch ($event) {
case 'updateValue':
// constant values override both default and submitted ones
// default values are overriden by submitted
$value = $this->_findValue($caller->_constantValues);
if (null === $value) {
// if no boxes were checked, then there is no value in the array
// yet we don't want to display default value in this case
if ($caller->isSubmitted() && !$caller->is_new_repeat($this->getName())) {
$value = $this->_findValue($caller->_submitValues);
} else {
$value = $this->_findValue($caller->_defaultValues);
}
}
//fix here. setChecked should not be conditional
$this->setChecked($value);
break;
default:
parent::onQuickFormEvent($event, $arg, $caller);
}
return true;
}
/**
* Returns HTML for checbox form element.
*
* @return string
*/
function toHtml()
{
return '<span>' . parent::toHtml() . '</span>';
}
/**
* Returns the disabled field. Accessibility: the return "[ ]" from parent
* class is not acceptable for screenreader users, and we DO want a label.
*
* @return string
*/
function getFrozenHtml()
{
//$this->_generateId();
$output = '<input type="checkbox" disabled="disabled" id="'.$this->getAttribute('id').'" ';
if ($this->getChecked()) {
$output .= 'checked="checked" />'.$this->_getPersistantData();
} else {
$output .= '/>';
}
return $output;
}
public function export_for_template(renderer_base $output) {
$context = $this->export_for_template_base($output);
$context['frozenvalue'] = $this->getValue();
return $context;
}
}
+210
View File
@@ -0,0 +1,210 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
defined('MOODLE_INTERNAL') || die();
use core\output\choicelist;
use core\output\local\dropdown\status;
require_once('HTML/QuickForm/select.php');
require_once('templatable_form_element.php');
/**
* User choice using a dropdown type form element.
*
* @package core_form
* @category form
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_choicedropdown extends HTML_QuickForm_select implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/**
* @var string html for help button, if empty then no help.
*/
protected string $_helpbutton = '';
/**
* @var bool if true label will be hidden.
*/
protected bool $_hiddenLabel = false;
/**
* @var choicelist the user choices.
*/
protected ?choicelist $choice = null;
/**
* @var string[] Dropdown dialog width.
*/
public const WIDTH = status::WIDTH;
/**
* @var string the dropdown width (from core\output\local\dropdown\status::WIDTH).
*/
protected string $dropdownwidth = status::WIDTH['small'];
/**
* Constructor.
*
* @param string $elementname Select name attribute
* @param mixed $elementlabel Label(s) for the select
* @param choicelist $options Data to be used to populate options
* @param mixed $attributes Either a typical HTML attribute string or an associative array
*/
public function __construct(
$elementname = null,
$elementlabel = null,
choicelist $options = null,
$attributes = null
) {
parent::__construct($elementname, $elementlabel, $options, $attributes);
$this->_type = 'choicedropdown';
}
/**
* Set the dropdown width.
*
* @param string $width
*/
public function set_dialog_width(string $width) {
$this->dropdownwidth = $width;
}
/**
* Loads options from a choicelist.
*
* @param choicelist $choice Options source currently supports assoc array or DB_result
* @param string|null $value optional value (in case it is not defined in the choicelist)
* @param string|null $unused2 unused
* @param string|null $unused3 unused
* @param string|null $unused4 unused
* @return bool
*/
public function load(&$choice, $value = null, $unused2 = null, $unused3 = null, $unused4 = null): bool {
if (!$choice instanceof choicelist) {
throw new coding_exception('Choice must be instance of choicelist');
}
$this->choice = $choice;
$this->choice->set_allow_empty(false);
if ($value !== null && is_string($value)) {
$choice->set_selected_value($value);
}
$this->setSelected($choice->get_selected_value());
return true;
}
/**
* Sets label to be hidden
*
* @param bool $hiddenLabel sets if label should be hidden
*/
public function setHiddenLabel($hiddenLabel) {
$this->_hiddenLabel = $hiddenLabel;
}
/**
* Returns HTML for select form element.
*
* This method is only needed when forms renderer is forces via
* $GLOBALS['_HTML_QuickForm_default_renderer']. Otherwise the
* renderer will use mustache templates.
*
* @return string
*/
public function toHtml(): string {
$html = '';
if ($this->_hiddenLabel) {
$this->_generateId();
$html .= '<label class="accesshide" for="'.$this->getAttribute('id').'" >'.$this->getLabel().'</label>';
}
$html .= parent::toHtml();
return $html;
}
/**
* get html for help button
*
* @return string html for help button
*/
public function getHelpButton(): string {
return $this->_helpbutton;
}
/**
* Slightly different container template when frozen. Don't want to use a label tag
* with a for attribute in that case for the element label but instead use a div.
* Templates are defined in renderer constructor.
*
* @return string
*/
public function getElementTemplateType(): string {
if ($this->_flagFrozen) {
return 'static';
} else {
return 'default';
}
}
/**
* We check the options and return only the values that _could_ have been
* selected. We also return a scalar value if select is not "multiple"
*
* @param string $submitvalues submitted values
* @param bool $assoc if true the returned value is associated array
* @return string|null
*/
public function exportValue(&$submitvalues, $assoc = false) {
$value = $this->_findValue($submitvalues) ?? $this->getValue();
if (is_array($value)) {
$value = reset($value);
}
if ($value === null) {
return $this->_prepareValue($value, $assoc);
}
if (!$this->choice->has_value($value)) {
$value = $this->choice->get_selected_value();
}
return $this->_prepareValue($value, $assoc);
}
public function export_for_template(renderer_base $output): array {
$context = $this->export_for_template_base($output);
if (!empty($this->_values)) {
$this->choice->set_selected_value(reset($this->_values));
}
$dialog = new status(
$this->choice->get_selected_content($output),
$this->choice,
[
'extras' => ['data-form-controls' => $context['id']],
'buttonsync' => true,
'updatestatus' => true,
'dialogwidth' => $this->dropdownwidth,
]
);
$context['dropdown'] = $dialog->export_for_template($output);
$context['select'] = $this->choice->export_for_template($output);
$context['nameraw'] = $this->getName();
return $context;
}
}
+149
View File
@@ -0,0 +1,149 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_form;
use context;
use core_external\external_api;
use moodle_url;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/formslib.php');
/**
* Class modal
*
* Extend this class to create a form that can be used in a modal dialogue.
*
* @package core_form
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class dynamic_form extends \moodleform {
/**
* Constructor for modal forms can not be overridden, however the same form can be used both in AJAX and normally
*
* @param string $action
* @param array $customdata
* @param string $method
* @param string $target
* @param array $attributes
* @param bool $editable
* @param array $ajaxformdata Forms submitted via ajax, must pass their data here, instead of relying on _GET and _POST.
* @param bool $isajaxsubmission whether the form is called from WS and it needs to validate user access and set up context
*/
final public function __construct(
?string $action = null,
?array $customdata = null,
string $method = 'post',
string $target = '',
?array $attributes = [],
bool $editable = true,
?array $ajaxformdata = null,
bool $isajaxsubmission = false
) {
global $PAGE, $CFG;
$this->_ajaxformdata = $ajaxformdata;
if ($isajaxsubmission) {
// This form was created from the WS that needs to validate user access to it and set page context.
// It has to be done before calling parent constructor because elements definitions may need to use
// format_string functions and other methods that expect the page to be set up.
external_api::validate_context($this->get_context_for_dynamic_submission());
$PAGE->set_url($this->get_page_url_for_dynamic_submission());
$this->check_access_for_dynamic_submission();
}
$attributes = ['data-random-ids' => 1] + ($attributes ?: []);
parent::__construct($action, $customdata, $method, $target, $attributes, $editable, $ajaxformdata);
}
/**
* Returns context where this form is used
*
* This context is validated in {@see external_api::validate_context()}
*
* If context depends on the form data, it is available in $this->_ajaxformdata or
* by calling $this->optional_param()
*
* Example:
* $cmid = $this->optional_param('cmid', 0, PARAM_INT);
* return context_module::instance($cmid);
*
* @return context
*/
abstract protected function get_context_for_dynamic_submission(): context;
/**
* Checks if current user has access to this form, otherwise throws exception
*
* Sometimes permission check may depend on the action and/or id of the entity.
* If necessary, form data is available in $this->_ajaxformdata or
* by calling $this->optional_param()
*
* Example:
* require_capability('dosomething', $this->get_context_for_dynamic_submission());
*/
abstract protected function check_access_for_dynamic_submission(): void;
/**
* Process the form submission, used if form was submitted via AJAX
*
* This method can return scalar values or arrays that can be json-encoded, they will be passed to the caller JS.
*
* Submission data can be accessed as: $this->get_data()
*
* Example:
* $data = $this->get_data();
* file_postupdate_standard_filemanager($data, ....);
* api::save_entity($data); // Save into the DB, trigger event, etc.
*
* @return mixed
*/
abstract public function process_dynamic_submission();
/**
* Load in existing data as form defaults
*
* Can be overridden to retrieve existing values from db by entity id and also
* to preprocess editor and filemanager elements
*
* Example:
* $id = $this->optional_param('id', 0, PARAM_INT);
* $data = api::get_entity($id); // For example, retrieve a row from the DB.
* file_prepare_standard_filemanager($data, ...);
* $this->set_data($data);
*/
abstract public function set_data_for_dynamic_submission(): void;
/**
* Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX
*
* This is used in the form elements sensitive to the page url, such as Atto autosave in 'editor'
*
* If the form has arguments (such as 'id' of the element being edited), the URL should
* also have respective argument.
*
* Example:
* $id = $this->optional_param('id', 0, PARAM_INT);
* return new moodle_url('/my/page/where/form/is/used.php', ['id' => $id]);
*
* @return moodle_url
*/
abstract protected function get_page_url_for_dynamic_submission(): moodle_url;
}
+104
View File
@@ -0,0 +1,104 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the {@link core_form\external} class.
*
* @package core_form
* @category external
* @copyright 2017 David Mudrák <david@moodle.com>
* @copyright 2016 Jonathon Fowler <fowlerj@usq.edu.au>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_form;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
/**
* Implements the external functions provided by the core_form subsystem.
*
* @copyright 2017 David Mudrak <david@moodle.com>
* @copyright 2016 Jonathon Fowler <fowlerj@usq.edu.au>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external extends external_api {
/**
* Describes the input paramaters of the get_filetypes_browser_data external function.
*
* @return \core_external\external_description
*/
public static function get_filetypes_browser_data_parameters() {
return new external_function_parameters([
'onlytypes' => new external_value(PARAM_RAW, 'Limit the browser to the given groups and extensions', VALUE_DEFAULT, ''),
'allowall' => new external_value(PARAM_BOOL, 'Allows to select All file types, does not apply with onlytypes are set.',
VALUE_DEFAULT, true),
'current' => new external_value(PARAM_RAW, 'Current types that should be selected.', VALUE_DEFAULT, ''),
]);
}
/**
* Implements the get_filetypes_browser_data external function.
*
* @param string $onlytypes Allow selection from these file types only; for example 'web_image'.
* @param bool $allowall Allow to select 'All file types'. Does not apply if onlytypes is set.
* @param string $current Current values that should be selected.
* @return object
*/
public static function get_filetypes_browser_data($onlytypes, $allowall, $current) {
$params = self::validate_parameters(self::get_filetypes_browser_data_parameters(),
compact('onlytypes', 'allowall', 'current'));
$util = new filetypes_util();
return ['groups' => $util->data_for_browser($params['onlytypes'], $params['allowall'], $params['current'])];
}
/**
* Describes the output of the get_filetypes_browser_data external function.
*
* @return \core_external\external_description
*/
public static function get_filetypes_browser_data_returns() {
$type = new external_single_structure([
'key' => new external_value(PARAM_RAW, 'The file type identifier'),
'name' => new external_value(PARAM_RAW, 'The file type name'),
'selected' => new external_value(PARAM_BOOL, 'Should it be marked as selected'),
'ext' => new external_value(PARAM_RAW, 'The file extension associated with the file type'),
]);
$group = new external_single_structure([
'key' => new external_value(PARAM_RAW, 'The file type group identifier'),
'name' => new external_value(PARAM_RAW, 'The file type group name'),
'selectable' => new external_value(PARAM_BOOL, 'Can it be marked as selected'),
'selected' => new external_value(PARAM_BOOL, 'Should it be marked as selected'),
'ext' => new external_value(PARAM_RAW, 'The list of file extensions associated with the group'),
'expanded' => new external_value(PARAM_BOOL, 'Should the group start as expanded or collapsed'),
'types' => new external_multiple_structure($type, 'List of file types in the group'),
]);
return new external_single_structure([
'groups' => new external_multiple_structure($group, 'List of file type groups'),
]);
}
}
+127
View File
@@ -0,0 +1,127 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_form\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
/**
* Implements the external functions provided by the core_form subsystem.
*
* @copyright 2020 Marina Glancy
* @package core_form
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dynamic_form extends external_api {
/**
* Parameters for modal form
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'form' => new external_value(PARAM_RAW_TRIMMED, 'Form class', VALUE_REQUIRED),
'formdata' => new external_value(PARAM_RAW, 'url-encoded form data', VALUE_REQUIRED),
]);
}
/**
* Submit a form from a modal dialogue.
*
* @param string $formclass
* @param string $formdatastr
* @return array
* @throws \moodle_exception
*/
public static function execute(string $formclass, string $formdatastr): array {
global $PAGE, $OUTPUT;
$params = self::validate_parameters(self::execute_parameters(), [
'form' => $formclass,
'formdata' => $formdatastr,
]);
$formclass = $params['form'];
parse_str($params['formdata'], $formdata);
self::autoload_block_edit_form($formclass);
if (!class_exists($formclass) || !is_subclass_of($formclass, \core_form\dynamic_form::class)) {
// For security reason we don't throw exception "class does not exist" but rather an access exception.
throw new \moodle_exception('nopermissionform', 'core_form');
}
/** @var \core_form\dynamic_form $form */
$form = new $formclass(null, null, 'post', '', [], true, $formdata, true);
$form->set_data_for_dynamic_submission();
if (!$form->is_cancelled() && $form->is_submitted() && $form->is_validated()) {
// Form was properly submitted, process and return results of processing. No need to render it again.
return ['submitted' => true, 'data' => json_encode($form->process_dynamic_submission())];
}
// Render actual form.
if ($form->no_submit_button_pressed()) {
// If form has not been submitted, we have to recreate the form for being able to properly handle non-submit action
// like "repeat elements" to include additional JS.
/** @var \core_form\dynamic_form $form */
$form = new $formclass(null, null, 'post', '', [], true, $formdata, true);
$form->set_data_for_dynamic_submission();
}
// Hack alert: Forcing bootstrap_renderer to initiate moodle page.
$OUTPUT->header();
$PAGE->start_collecting_javascript_requirements();
$data = $form->render();
$jsfooter = $PAGE->requires->get_end_code();
$output = ['submitted' => false, 'html' => $data, 'javascript' => $jsfooter];
return $output;
}
/**
* Special autoloading for block forms.
*
* @param string $formclass
* @return void
*/
protected static function autoload_block_edit_form(string $formclass): void {
global $CFG;
if (preg_match('/^block_([\w_]+)_edit_form$/', $formclass, $matches)) {
\block_manager::get_block_edit_form_class($matches[1]);
}
if ($formclass === 'block_edit_form') {
require_once($CFG->dirroot . '/blocks/edit_form.php');
}
}
/**
* Return for modal
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure(
array(
'submitted' => new external_value(PARAM_BOOL, 'If form was submitted and validated'),
'data' => new external_value(PARAM_RAW, 'JSON-encoded return data from form processing method', VALUE_OPTIONAL),
'html' => new external_value(PARAM_RAW, 'HTML fragment of the form', VALUE_OPTIONAL),
'javascript' => new external_value(PARAM_RAW, 'JavaScript fragment of the form', VALUE_OPTIONAL)
)
);
}
}
+537
View File
@@ -0,0 +1,537 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the {@link core_form\filetypes_util} class.
*
* @package core_form
* @copyright 2017 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_form;
use core_collator;
use core_filetypes;
use core_text;
defined('MOODLE_INTERNAL') || die();
/**
* Utility class for handling with file types in the forms.
*
* This class is supposed to serve as a helper class for {@link MoodleQuickForm_filetypes}
* and {@link admin_setting_filetypes} classes.
*
* The file types can be specified in a syntax compatible with what filepicker
* and filemanager support via the "accepted_types" option: a list of extensions
* (e.g. ".doc"), mimetypes ("image/png") or groups ("audio").
*
* @copyright 2017 David Mudrak <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class filetypes_util {
/** @var array Cache of all file type groups for the {@link self::get_groups_info()}. */
protected $cachegroups = null;
/**
* Converts the argument into an array (list) of file types.
*
* The list can be separated by whitespace, end of lines, commas, colons and semicolons.
* Empty values are not returned. Values are converted to lowercase.
* Duplicates are removed. Glob evaluation is not supported.
*
* The return value can be used as the accepted_types option for the filepicker.
*
* @param string|array $types List of file extensions, groups or mimetypes
* @return array of strings
*/
public function normalize_file_types($types) {
if ($types === '' || $types === null) {
return [];
}
// Turn string into a list.
if (!is_array($types)) {
$types = preg_split('/[\s,;:"\']+/', $types, -1, PREG_SPLIT_NO_EMPTY);
}
// Fix whitespace and normalize the syntax a bit.
foreach ($types as $i => $type) {
$type = str_replace('*.', '.', $type);
$type = core_text::strtolower($type);
$type = trim($type);
if ($type === '*') {
return ['*'];
}
$types[$i] = $type;
}
// Do not make the user think that globs (like ".doc?") would work.
foreach ($types as $i => $type) {
if (strpos($type, '*') !== false or strpos($type, '?') !== false) {
unset($types[$i]);
}
}
foreach ($types as $i => $type) {
if (substr($type, 0, 1) === '.') {
// It looks like an extension.
$type = '.'.ltrim($type, '.');
$types[$i] = clean_param($type, PARAM_FILE);
} else if ($this->looks_like_mimetype($type)) {
// All good, it looks like a mimetype.
continue;
} else if ($this->is_filetype_group($type)) {
// All good, it is a known type group.
continue;
} else {
// We assume the user typed something like "png" so we consider
// it an extension.
$types[$i] = '.'.$type;
}
}
$types = array_filter($types, 'strlen');
$types = array_keys(array_flip($types));
return $types;
}
/**
* Does the given file type looks like a valid MIME type?
*
* This does not check of the MIME type is actually registered here/known.
*
* @param string $type
* @return bool
*/
public function looks_like_mimetype($type) {
return (bool)preg_match('~^[-\.a-z0-9]+/[a-z0-9]+([-\.\+][a-z0-9]+)*$~', $type);
}
/**
* Is the given string a known filetype group?
*
* @param string $type
* @return bool|object false or the group info
*/
public function is_filetype_group($type) {
$info = $this->get_groups_info();
if (isset($info[$type])) {
return $info[$type];
} else {
return false;
}
}
/**
* Provides a list of all known file type groups and their properties.
*
* @return array
*/
public function get_groups_info() {
if ($this->cachegroups !== null) {
return $this->cachegroups;
}
$groups = [];
foreach (core_filetypes::get_types() as $ext => $info) {
if (isset($info['groups']) && is_array($info['groups'])) {
foreach ($info['groups'] as $group) {
if (!isset($groups[$group])) {
$groups[$group] = (object) [
'extensions' => [],
'mimetypes' => [],
];
}
$groups[$group]->extensions['.'.$ext] = true;
if (isset($info['type'])) {
$groups[$group]->mimetypes[$info['type']] = true;
}
}
}
}
foreach ($groups as $group => $info) {
$info->extensions = array_keys($info->extensions);
$info->mimetypes = array_keys($info->mimetypes);
}
$this->cachegroups = $groups;
return $this->cachegroups;
}
/**
* Return a human readable name of the filetype group.
*
* @param string $group
* @return string
*/
public function get_group_description($group) {
if (get_string_manager()->string_exists('group:'.$group, 'core_mimetypes')) {
return get_string('group:'.$group, 'core_mimetypes');
} else {
return s($group);
}
}
/**
* Describe the list of file types for human user.
*
* Given the list of file types, return a list of human readable
* descriptive names of relevant groups, types or file formats.
*
* @param string|array $types
* @return object
*/
public function describe_file_types($types) {
$descriptions = [];
$types = $this->normalize_file_types($types);
foreach ($types as $type) {
if ($type === '*') {
$desc = get_string('filetypesany', 'core_form');
$descriptions[$desc] = [];
} else if ($group = $this->is_filetype_group($type)) {
$desc = $this->get_group_description($type);
$descriptions[$desc] = $group->extensions;
} else if ($this->looks_like_mimetype($type)) {
$desc = get_mimetype_description($type);
$descriptions[$desc] = file_get_typegroup('extension', [$type]);
} else {
$desc = get_mimetype_description(['filename' => 'fakefile'.$type]);
if (isset($descriptions[$desc])) {
$descriptions[$desc][] = $type;
} else {
$descriptions[$desc] = [$type];
}
}
}
$data = [];
foreach ($descriptions as $desc => $exts) {
sort($exts);
$data[] = (object)[
'description' => $desc,
'extensions' => join(' ', $exts),
];
}
core_collator::asort_objects_by_property($data, 'description', core_collator::SORT_NATURAL);
return (object)[
'hasdescriptions' => !empty($data),
'descriptions' => array_values($data),
];
}
/**
* Prepares data for the filetypes-browser.mustache
*
* @param string|array $onlytypes Allow selection from these file types only; for example 'web_image'.
* @param bool $allowall Allow to select 'All file types'. Does not apply with onlytypes are set.
* @param string|array $current Current values that should be selected.
* @return array
*/
public function data_for_browser($onlytypes=null, $allowall=true, $current=null) {
$groups = [];
$current = $this->normalize_file_types($current);
// Firstly populate the tree of extensions categorized into groups.
foreach ($this->get_groups_info() as $groupkey => $groupinfo) {
if (empty($groupinfo->extensions)) {
continue;
}
$group = (object) [
'key' => $groupkey,
'name' => $this->get_group_description($groupkey),
'selectable' => true,
'selected' => in_array($groupkey, $current),
'ext' => implode(' ', $groupinfo->extensions),
'expanded' => false,
];
$types = [];
foreach ($groupinfo->extensions as $extension) {
if ($onlytypes && !$this->is_listed($extension, $onlytypes)) {
$group->selectable = false;
$group->expanded = true;
$group->ext = '';
continue;
}
$desc = get_mimetype_description(['filename' => 'fakefile'.$extension]);
if ($selected = in_array($extension, $current)) {
$group->expanded = true;
}
$types[] = (object) [
'key' => $extension,
'name' => get_mimetype_description(['filename' => 'fakefile'.$extension]),
'selected' => $selected,
'ext' => $extension,
];
}
if (empty($types)) {
continue;
}
core_collator::asort_objects_by_property($types, 'name', core_collator::SORT_NATURAL);
$group->types = array_values($types);
$groups[] = $group;
}
core_collator::asort_objects_by_property($groups, 'name', core_collator::SORT_NATURAL);
// Append all other uncategorized extensions.
$others = [];
foreach (core_filetypes::get_types() as $extension => $info) {
// Reserved for unknown file types. Not available here.
if ($extension === 'xxx') {
continue;
}
$extension = '.'.$extension;
if ($onlytypes && !$this->is_listed($extension, $onlytypes)) {
continue;
}
if (!isset($info['groups']) || empty($info['groups'])) {
$others[] = (object) [
'key' => $extension,
'name' => get_mimetype_description(['filename' => 'fakefile'.$extension]),
'selected' => in_array($extension, $current),
'ext' => $extension,
];
}
}
core_collator::asort_objects_by_property($others, 'name', core_collator::SORT_NATURAL);
if (!empty($others)) {
$groups[] = (object) [
'key' => '',
'name' => get_string('filetypesothers', 'core_form'),
'selectable' => false,
'selected' => false,
'ext' => '',
'types' => array_values($others),
'expanded' => true,
];
}
if (empty($onlytypes) and $allowall) {
array_unshift($groups, (object) [
'key' => '*',
'name' => get_string('filetypesany', 'core_form'),
'selectable' => true,
'selected' => in_array('*', $current),
'ext' => null,
'types' => [],
'expanded' => false,
]);
}
$groups = array_values($groups);
return $groups;
}
/**
* Expands the file types into the list of file extensions.
*
* The groups and mimetypes are expanded into the list of their associated file
* extensions. Depending on the $keepgroups and $keepmimetypes, the groups
* and mimetypes themselves are either kept in the list or removed.
*
* @param string|array $types
* @param bool $keepgroups Keep the group item in the list after expansion
* @param bool $keepmimetypes Keep the mimetype item in the list after expansion
* @return array list of extensions and eventually groups and types
*/
public function expand($types, $keepgroups=false, $keepmimetypes=false) {
$expanded = [];
foreach ($this->normalize_file_types($types) as $type) {
if ($group = $this->is_filetype_group($type)) {
foreach ($group->extensions as $ext) {
$expanded[$ext] = true;
}
if ($keepgroups) {
$expanded[$type] = true;
}
} else if ($this->looks_like_mimetype($type)) {
// A mime type expands to the associated extensions.
foreach (file_get_typegroup('extension', [$type]) as $ext) {
$expanded[$ext] = true;
}
if ($keepmimetypes) {
$expanded[$type] = true;
}
} else {
// Single extension expands to itself.
$expanded[$type] = true;
}
}
return array_keys($expanded);
}
/**
* Should the file type be considered as a part of the given list.
*
* If multiple types are provided, all of them must be part of the list. Empty type is part of any list.
* Any type is part of an empty list.
*
* @param string|array $types File type or list of types to be checked.
* @param string|array $list An array or string listing the types to check against.
* @return boolean
*/
public function is_listed($types, $list) {
return empty($this->get_not_listed($types, $list));
}
/**
* @deprecated since Moodle 3.10 MDL-69050 - please use {@see is_listed} instead.
*/
public function is_whitelisted() {
throw new \coding_exception('\core_form\filetypes_util::is_whitelisted() has been removed.');
}
/**
* Returns all types that are not part of the given list.
*
* This is similar check to the {@see self::is_listed()} but this one actually returns the extra types.
*
* @param string|array $types File type or list of types to be checked.
* @param string|array $list An array or string listing the types to check against.
* @return array Types not present in the list.
*/
public function get_not_listed($types, $list) {
$listedtypes = $this->expand($list, true, true);
if (empty($listedtypes) || $listedtypes == ['*']) {
return [];
}
$giventypes = $this->normalize_file_types($types);
if (empty($giventypes)) {
return [];
}
return array_diff($giventypes, $listedtypes);
}
/**
* @deprecated since Moodle 3.10 MDL-69050 - please use {@see get_not_listed} instead.
*/
public function get_not_whitelisted() {
throw new \coding_exception('\core_form\filetypes_util::get_not_whitelisted() has been removed.');
}
/**
* Is the given filename of an allowed file type?
*
* Empty allowlist is interpreted as "any file type is allowed" rather
* than "no file can be uploaded".
*
* @param string $filename the file name
* @param string|array $allowlist list of allowed file extensions
* @return boolean True if the file type is allowed, false if not
*/
public function is_allowed_file_type($filename, $allowlist) {
$allowedextensions = $this->expand($allowlist);
if (empty($allowedextensions) || $allowedextensions == ['*']) {
return true;
}
$haystack = strrev(trim(core_text::strtolower($filename)));
foreach ($allowedextensions as $extension) {
if (strpos($haystack, strrev($extension)) === 0) {
// The file name ends with the extension.
return true;
}
}
return false;
}
/**
* Returns file types from the list that are not recognized
*
* @param string|array $types list of user-defined file types
* @return array A list of unknown file types.
*/
public function get_unknown_file_types($types) {
$unknown = [];
foreach ($this->normalize_file_types($types) as $type) {
if ($type === '*') {
// Any file is considered as a known type.
continue;
} else if ($type === '.xxx') {
$unknown[$type] = true;
} else if ($this->is_filetype_group($type)) {
// The type is a group that exists.
continue;
} else if ($this->looks_like_mimetype($type)) {
// If there's no extension associated with that mimetype, we consider it unknown.
if (empty(file_get_typegroup('extension', [$type]))) {
$unknown[$type] = true;
}
} else {
$coretypes = core_filetypes::get_types();
$typecleaned = str_replace(".", "", $type);
if (empty($coretypes[$typecleaned])) {
// If there's no extension, it doesn't exist.
$unknown[$type] = true;
}
}
}
return array_keys($unknown);
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides {@link \core_form\privacy\provider} class.
*
* @package core_form
* @category privacy
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_form\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\writer;
defined('MOODLE_INTERNAL') || die();
/**
* Implements the privacy API for the core_form subsystem.
*
* @package core_files
* @copyright 2018 David Mudrák <david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
// The forms subsystem does not store any data itself, it has no database tables.
\core_privacy\local\metadata\provider,
// The forms subsystem has user preferences.
\core_privacy\local\request\user_preference_provider {
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_user_preference('filemanager_recentviewmode', 'privacy:metadata:preference:filemanager_recentviewmode');
return $collection;
}
/**
* Export all user preferences for the subsystem.
*
* @param int $userid The ID of the user whose data is to be exported.
*/
public static function export_user_preferences(int $userid) {
$preference = get_user_preferences('filemanager_recentviewmode', null, $userid);
if ($preference !== null) {
switch ($preference) {
case 1:
$value = get_string('displayasicons', 'core_repository');
break;
case 2:
$value = get_string('displayastree', 'core_repository');
break;
case 3:
$value = get_string('displaydetails', 'core_repository');
break;
default:
$value = $preference;
}
$desc = get_string('privacy:preference:filemanager_recentviewmode', 'core_form', $value);
writer::export_user_preference('core_form', 'filemanager_recentviewmode', $preference, $desc);
}
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the {@link core_form\util} class.
*
* @package core_form
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_form;
defined('MOODLE_INTERNAL') || die();
/**
* General utility class for form-related methods.
*
* @copyright 2019 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class util {
/**
* This function should be called if a form submit results in a file download (i.e. with the
* Content-Disposition: attachment header) instead of navigating to a new page, before the
* file download is sent. It will set a cookie which is used to inform page javascript in
* submit.js.
*
* You may call this function in scripts which might not necessarily be called from forms; it
* will only set the cookie if there is a POST request from a form.
*
* This is automatically called from various points in Moodle such as send_file_xx functions
* in filelib.php.
*/
public static function form_download_complete() {
// If this doesn't look like a Moodle QuickForms request, ignore.
$quickform = false;
foreach ($_POST as $name => $value) {
if (preg_match('~^_qf__~', $name)) {
$quickform = true;
break;
}
}
if (!$quickform) {
return;
}
// Set a session cookie.
setcookie('moodledownload_' . sesskey(), time());
}
}
+151
View File
@@ -0,0 +1,151 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course selector field.
*
* Allows auto-complete ajax searching for cohort.
*
* @package core_form
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once($CFG->libdir . '/form/autocomplete.php');
require_once($CFG->dirroot . '/cohort/lib.php');
/**
* Form field type for choosing a cohort.
*
* Allows auto-complete ajax searching for cohort.
*
* @package core_form
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_cohort extends MoodleQuickForm_autocomplete {
/**
* @var array $exclude Exclude a list of cohorts from the list (e.g. the current cohort).
*/
protected $exclude = array();
/**
* @var int $contextid The context id to fetch cohorts in.
*/
protected $contextid = 0;
/**
* @var boolean $allowmultiple Allow selecting more than one cohort.
*/
protected $multiple = false;
/**
* @var array $requiredcapabilities Array of extra capabilities to check at the cohort context.
*/
protected $requiredcapabilities = array();
/**
* Constructor
*
* @param string $elementname Element name
* @param mixed $elementlabel Label(s) for an element
* @param array $options Options to control the element's display
* Valid options are:
* 'multiple' - boolean multi select
* 'exclude' - array or int, list of course ids to never show
* 'requiredcapabilities' - array of capabilities. Uses ANY to combine them.
*/
public function __construct($elementname = null, $elementlabel = null, $options = array()) {
if (isset($options['multiple'])) {
$this->multiple = $options['multiple'];
}
if (isset($options['contextid'])) {
$this->contextid = $options['contextid'];
} else {
$this->contextid = context_system::instance()->id;
}
if (isset($options['exclude'])) {
$this->exclude = $options['exclude'];
if (!is_array($this->exclude)) {
$this->exclude = array($this->exclude);
}
}
if (isset($options['requiredcapabilities'])) {
$this->requiredcapabilities = $options['requiredcapabilities'];
}
$validattributes = array(
'ajax' => 'core/form-cohort-selector',
'data-exclude' => implode(',', $this->exclude),
'data-contextid' => (int)$this->contextid
);
if ($this->multiple) {
$validattributes['multiple'] = 'multiple';
}
if (isset($options['noselectionstring'])) {
$validattributes['noselectionstring'] = $options['noselectionstring'];
}
if (isset($options['placeholder'])) {
$validattributes['placeholder'] = $options['placeholder'];
}
parent::__construct($elementname, $elementlabel, array(), $validattributes);
}
/**
* Set the value of this element. If values can be added or are unknown, we will
* make sure they exist in the options array.
* @param string|array $value The value to set.
* @return boolean
*/
public function setValue($value) {
global $DB;
$values = (array) $value;
$cohortstofetch = array();
foreach ($values as $onevalue) {
if ($onevalue && !$this->optionExists($onevalue) &&
($onevalue !== '_qf__force_multiselect_submission')) {
array_push($cohortstofetch, $onevalue);
}
}
if (empty($cohortstofetch)) {
$this->setSelected($values);
return true;
}
list($whereclause, $params) = $DB->get_in_or_equal($cohortstofetch, SQL_PARAMS_NAMED, 'id');
$list = $DB->get_records_select('cohort', 'id ' . $whereclause, $params, 'name');
$currentcontext = context_helper::instance_by_id($this->contextid);
foreach ($list as $cohort) {
// Make sure we can see the cohort.
if (!cohort_can_view_cohort($cohort, $currentcontext)) {
continue;
}
$label = format_string($cohort->name, true, ['context' => $currentcontext]);
$this->addOption($label, $cohort->id);
}
$this->setSelected($values);
return true;
}
}
+167
View File
@@ -0,0 +1,167 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course selector field.
*
* Allows auto-complete ajax searching for courses and can restrict by enrolment, permissions, viewhidden...
*
* @package core_form
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once($CFG->libdir . '/form/autocomplete.php');
/**
* Form field type for choosing a course.
*
* Allows auto-complete ajax searching for courses and can restrict by enrolment, permissions, viewhidden...
*
* @package core_form
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_course extends MoodleQuickForm_autocomplete {
/**
* @var array $exclude Exclude a list of courses from the list (e.g. the current course).
*/
protected $exclude = array();
/**
* @var boolean $allowmultiple Allow selecting more than one course.
*/
protected $multiple = false;
/**
* @var array $requiredcapabilities Array of extra capabilities to check at the course context.
*/
protected $requiredcapabilities = array();
/**
* @var bool $limittoenrolled Only allow enrolled courses.
*/
protected $limittoenrolled = false;
/**
* Constructor
*
* @param string $elementname Element name
* @param mixed $elementlabel Label(s) for an element
* @param mixed $attributes Array of typical HTML attributes plus additional options, such as:
* 'multiple' - boolean multi select
* 'exclude' - array or int, list of course ids to never show
* 'requiredcapabilities' - array of capabilities. Uses ANY to combine them.
* 'limittoenrolled' - boolean Limits to enrolled courses.
* 'includefrontpage' - boolean Enables the frontpage to be selected.
* 'onlywithcompletion' - boolean Limits to courses where completion is enabled.
*/
public function __construct($elementname = null, $elementlabel = null, $attributes = array()) {
if (!is_array($attributes)) {
$attributes = [];
}
if (isset($attributes['multiple'])) {
$this->multiple = $attributes['multiple'];
}
if (isset($attributes['exclude'])) {
$this->exclude = $attributes['exclude'];
if (!is_array($this->exclude)) {
$this->exclude = array($this->exclude);
}
unset($attributes['exclude']);
}
if (isset($attributes['requiredcapabilities'])) {
$this->requiredcapabilities = $attributes['requiredcapabilities'];
unset($attributes['requiredcapabilities']);
}
if (isset($attributes['limittoenrolled'])) {
$this->limittoenrolled = $attributes['limittoenrolled'];
unset($attributes['limittoenrolled']);
}
$attributes += array(
'ajax' => 'core/form-course-selector',
'data-requiredcapabilities' => implode(',', $this->requiredcapabilities),
'data-exclude' => implode(',', $this->exclude),
'data-limittoenrolled' => (int)$this->limittoenrolled
);
if (!empty($attributes['includefrontpage'])) {
$attributes['data-includefrontpage'] = SITEID;
unset($attributes['includefrontpage']);
}
if (!empty($attributes['onlywithcompletion'])) {
$attributes['data-onlywithcompletion'] = 1;
unset($attributes['onlywithcompletion']);
}
parent::__construct($elementname, $elementlabel, array(), $attributes);
}
/**
* Set the value of this element. If values can be added or are unknown, we will
* make sure they exist in the options array.
* @param string|array $value The value to set.
* @return boolean
*/
public function setValue($value) {
global $DB;
$values = (array) $value;
$coursestofetch = array();
foreach ($values as $onevalue) {
if ($onevalue && !$this->optionExists($onevalue) &&
($onevalue !== '_qf__force_multiselect_submission')) {
array_push($coursestofetch, $onevalue);
}
}
if (empty($coursestofetch)) {
return $this->setSelected($values);
}
// There is no API function to load a list of course from a list of ids.
$ctxselect = context_helper::get_preload_record_columns_sql('ctx');
$fields = array('c.id', 'c.category', 'c.sortorder',
'c.shortname', 'c.fullname', 'c.idnumber',
'c.startdate', 'c.visible', 'c.cacherev');
list($whereclause, $params) = $DB->get_in_or_equal($coursestofetch, SQL_PARAMS_NAMED, 'id');
$sql = "SELECT ". join(',', $fields). ", $ctxselect
FROM {course} c
JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = :contextcourse
WHERE c.id ". $whereclause." ORDER BY c.sortorder";
$list = $DB->get_records_sql($sql, array('contextcourse' => CONTEXT_COURSE) + $params);
$mycourses = enrol_get_my_courses(null, null, 0, array_keys($list));
$coursestoselect = array();
foreach ($list as $course) {
context_helper::preload_from_record($course);
$context = context_course::instance($course->id);
// Make sure we can see the course.
if (!array_key_exists($course->id, $mycourses) && !core_course_category::can_view_course_info($course)) {
continue;
}
$label = format_string(get_course_display_name_for_list($course), true, ['context' => $context]);
$this->addOption($label, $course->id);
array_push($coursestoselect, $course->id);
}
return $this->setSelected($values);
}
}
+307
View File
@@ -0,0 +1,307 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Group of date input element
*
* Contains class for a group of elements used to input a date.
*
* @package core_form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once($CFG->libdir . '/form/group.php');
require_once($CFG->libdir . '/formslib.php');
/**
* Class for a group of elements used to input a date.
*
* Emulates moodle print_date_selector function
*
* @package core_form
* @category form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_date_selector extends MoodleQuickForm_group {
/**
* Control the fieldnames for form elements.
*
* startyear => int start of range of years that can be selected
* stopyear => int last year that can be selected
* timezone => int|float|string (optional) timezone modifier used for edge case only.
* If not specified, then date is caclulated based on current user timezone.
* Note: dst will be calculated for string timezones only
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
* optional => if true, show a checkbox beside the date to turn it on (or off)
* @var array
*/
protected $_options = array();
/**
* @var array These complement separators, they are appended to the resultant HTML.
*/
protected $_wrap = array('', '');
/**
* @var null|bool Keeps track of whether the date selector was initialised using createElement
* or addElement. If true, createElement was used signifying the element has been
* added to a group - see MDL-39187.
*/
protected $_usedcreateelement = true;
/**
* constructor
*
* @param string $elementName Element's name
* @param mixed $elementLabel Label(s) for an element
* @param array $options Options to control the element's display
* @param mixed $attributes Either a typical HTML attribute string or an associative array
*/
public function __construct($elementName = null, $elementLabel = null, $options = array(), $attributes = null) {
// Get the calendar type used - see MDL-18375.
$calendartype = \core_calendar\type_factory::get_calendar_instance();
$this->_options = array('startyear' => $calendartype->get_min_year(), 'stopyear' => $calendartype->get_max_year(),
'defaulttime' => 0, 'timezone' => 99, 'step' => 1, 'optional' => false);
// TODO MDL-52313 Replace with the call to parent::__construct().
HTML_QuickForm_element::__construct($elementName, $elementLabel, $attributes);
$this->_persistantFreeze = true;
$this->_appendName = true;
$this->_type = 'date_selector';
// set the options, do not bother setting bogus ones
if (is_array($options)) {
foreach ($options as $name => $value) {
if (isset($this->_options[$name])) {
if (is_array($value) && is_array($this->_options[$name])) {
$this->_options[$name] = @array_merge($this->_options[$name], $value);
} else {
$this->_options[$name] = $value;
}
}
}
}
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_date_selector($elementName = null, $elementLabel = null, $options = array(), $attributes = null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $options, $attributes);
}
/**
* This will create date group element constisting of day, month and year.
*
* @access private
*/
function _createElements() {
global $OUTPUT;
// Get the calendar type used - see MDL-18375.
$calendartype = \core_calendar\type_factory::get_calendar_instance();
$this->_elements = array();
$dateformat = $calendartype->get_date_order($this->_options['startyear'], $this->_options['stopyear']);
// Reverse date element (Day, Month, Year), in RTL mode.
if (right_to_left()) {
$dateformat = array_reverse($dateformat);
}
// If optional we add a checkbox which the user can use to turn if on.
if ($this->_options['optional']) {
$this->_elements[] = $this->createFormElement('checkbox', 'enabled', null,
get_string('enable'), $this->getAttributesForFormElement(), true);
}
foreach ($dateformat as $key => $value) {
// E_STRICT creating elements without forms is nasty because it internally uses $this
$this->_elements[] = $this->createFormElement('select', $key, get_string($key, 'form'), $value,
$this->getAttributesForFormElement(), true);
}
// The YUI2 calendar only supports the gregorian calendar type so only display the calendar image if this is being used.
if ($calendartype->get_name() === 'gregorian') {
$image = $OUTPUT->pix_icon('i/calendar', get_string('calendar', 'calendar'), 'moodle');
$this->_elements[] = $this->createFormElement('link', 'calendar',
null, '#', $image);
}
foreach ($this->_elements as $element){
if (method_exists($element, 'setHiddenLabel')){
$element->setHiddenLabel(true);
}
}
}
/**
* 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) {
$this->setMoodleForm($caller);
switch ($event) {
case 'updateValue':
// Constant values override both default and submitted ones
// default values are overriden by submitted.
$value = $this->_findValue($caller->_constantValues);
if (null === $value) {
// If no boxes were checked, then there is no value in the array
// yet we don't want to display default value in this case.
if ($caller->isSubmitted() && !$caller->is_new_repeat($this->getName())) {
$value = $this->_findValue($caller->_submitValues);
} else {
$value = $this->_findValue($caller->_defaultValues);
}
}
$requestvalue=$value;
if ($value == 0) {
$value = time();
}
if (!is_array($value)) {
$calendartype = \core_calendar\type_factory::get_calendar_instance();
$currentdate = $calendartype->timestamp_to_date_array($value, $this->_options['timezone']);
$value = array(
'day' => $currentdate['mday'],
'month' => $currentdate['mon'],
'year' => $currentdate['year']);
// If optional, default to off, unless a date was provided.
if ($this->_options['optional']) {
$value['enabled'] = $requestvalue != 0;
}
} else {
$value['enabled'] = isset($value['enabled']);
}
if (null !== $value) {
$this->setValue($value);
}
break;
case 'createElement':
// Optional is an optional param, if its set we need to add a disabledIf rule.
// If its empty or not specified then its not an optional dateselector.
if (!empty($arg[2]['optional']) && !empty($arg[0])) {
// When using the function addElement, rather than createElement, we still
// enter this case, making this check necessary.
if ($this->_usedcreateelement) {
$caller->disabledIf($arg[0] . '[day]', $arg[0] . '[enabled]');
$caller->disabledIf($arg[0] . '[month]', $arg[0] . '[enabled]');
$caller->disabledIf($arg[0] . '[year]', $arg[0] . '[enabled]');
} else {
$caller->disabledIf($arg[0], $arg[0] . '[enabled]');
}
}
return parent::onQuickFormEvent($event, $arg, $caller);
break;
case 'addElement':
$this->_usedcreateelement = false;
return parent::onQuickFormEvent($event, $arg, $caller);
break;
default:
return parent::onQuickFormEvent($event, $arg, $caller);
}
}
/**
* Returns HTML for advchecbox form element.
*
* @return string
*/
function toHtml() {
include_once('HTML/QuickForm/Renderer/Default.php');
$renderer = new HTML_QuickForm_Renderer_Default();
$renderer->setElementTemplate('{element}');
parent::accept($renderer);
$html = $this->_wrap[0];
if ($this->_usedcreateelement) {
$html .= html_writer::tag('span', $renderer->toHtml(), array('class' => 'fdate_selector'));
} else {
$html .= $renderer->toHtml();
}
$html .= $this->_wrap[1];
return $html;
}
/**
* Accepts a renderer
*
* @param HTML_QuickForm_Renderer $renderer An HTML_QuickForm_Renderer object
* @param bool $required Whether a group is required
* @param string $error An error message associated with a group
*/
function accept(&$renderer, $required = false, $error = null) {
form_init_date_js();
$renderer->renderElement($this, $required, $error);
}
/**
* Export for template
*
* @param renderer_base $output
* @return array|stdClass
*/
public function export_for_template(renderer_base $output) {
form_init_date_js();
return parent::export_for_template($output);
}
/**
* Output a timestamp. Give it the name of the group.
*
* @param array $submitValues values submitted.
* @param bool $assoc specifies if returned array is associative
* @return array
*/
function exportValue(&$submitValues, $assoc = false) {
$valuearray = array();
foreach ($this->_elements as $element){
$thisexport = $element->exportValue($submitValues[$this->getName()], true);
if ($thisexport!=null){
$valuearray += $thisexport;
}
}
if (count($valuearray) && isset($valuearray['year'])) {
if($this->_options['optional']) {
// If checkbox is on, the value is zero, so go no further
if(empty($valuearray['enabled'])) {
return $this->_prepareValue(0, $assoc);
}
}
// Get the calendar type used - see MDL-18375.
$calendartype = \core_calendar\type_factory::get_calendar_instance();
$gregoriandate = $calendartype->convert_to_gregorian($valuearray['year'], $valuearray['month'], $valuearray['day']);
$value = make_timestamp($gregoriandate['year'],
$gregoriandate['month'],
$gregoriandate['day'],
0, 0, 0,
$this->_options['timezone'],
true);
return $this->_prepareValue($value, $assoc);
} else {
return null;
}
}
}
+339
View File
@@ -0,0 +1,339 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Group of date and time input element
*
* Contains class for a group of elements used to input a date and time.
*
* @package core_form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once($CFG->libdir . '/form/group.php');
require_once($CFG->libdir . '/formslib.php');
/**
* Element used to input a date and time.
*
* Class for a group of elements used to input a date and time.
*
* @package core_form
* @category form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_date_time_selector extends MoodleQuickForm_group {
/**
* Options for the element.
*
* startyear => int start of range of years that can be selected
* stopyear => int last year that can be selected
* defaulttime => default time value if the field is currently not set
* timezone => int|float|string (optional) timezone modifier used for edge case only.
* If not specified, then date is caclulated based on current user timezone.
* Note: dst will be calculated for string timezones only
* {@link https://moodledev.io/docs/apis/subsystems/time#timezone}
* step => step to increment minutes by
* optional => if true, show a checkbox beside the date to turn it on (or off)
* @var array
*/
protected $_options = array();
/**
* @var array These complement separators, they are appended to the resultant HTML.
*/
protected $_wrap = array('', '');
/**
* @var null|bool Keeps track of whether the date selector was initialised using createElement
* or addElement. If true, createElement was used signifying the element has been
* added to a group - see MDL-39187.
*/
protected $_usedcreateelement = true;
/**
* Class constructor
*
* @param string $elementName Element's name
* @param mixed $elementLabel Label(s) for an element
* @param array $options Options to control the element's display
* @param mixed $attributes Either a typical HTML attribute string or an associative array
*/
public function __construct($elementName = null, $elementLabel = null, $options = array(), $attributes = null) {
// Get the calendar type used - see MDL-18375.
$calendartype = \core_calendar\type_factory::get_calendar_instance();
$this->_options = array('startyear' => $calendartype->get_min_year(), 'stopyear' => $calendartype->get_max_year(),
'defaulttime' => 0, 'timezone' => 99, 'step' => 1, 'optional' => false);
// TODO MDL-52313 Replace with the call to parent::__construct().
HTML_QuickForm_element::__construct($elementName, $elementLabel, $attributes);
$this->_persistantFreeze = true;
$this->_appendName = true;
$this->_type = 'date_time_selector';
// set the options, do not bother setting bogus ones
if (is_array($options)) {
foreach ($options as $name => $value) {
if (isset($this->_options[$name])) {
if (is_array($value) && is_array($this->_options[$name])) {
$this->_options[$name] = @array_merge($this->_options[$name], $value);
} else {
$this->_options[$name] = $value;
}
}
}
}
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_date_time_selector($elementName = null, $elementLabel = null, $options = array(), $attributes = null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $options, $attributes);
}
/**
* This will create date group element constisting of day, month and year.
*
* @access private
*/
function _createElements() {
global $OUTPUT;
// Get the calendar type used - see MDL-18375.
$calendartype = \core_calendar\type_factory::get_calendar_instance();
for ($i = 0; $i <= 23; $i++) {
$hours[$i] = sprintf("%02d", $i);
}
for ($i = 0; $i < 60; $i += $this->_options['step']) {
$minutes[$i] = sprintf("%02d", $i);
}
$this->_elements = array();
// If optional we add a checkbox which the user can use to turn if on.
if ($this->_options['optional']) {
$this->_elements[] = $this->createFormElement('checkbox', 'enabled', null,
get_string('enable'), $this->getAttributesForFormElement(), true);
}
$dateformat = $calendartype->get_date_order($this->_options['startyear'], $this->_options['stopyear']);
if (right_to_left()) { // Display time to the right of date, in RTL mode.
$this->_elements[] = $this->createFormElement('select', 'minute', get_string('minute', 'form'),
$minutes, $this->getAttributesForFormElement(), true);
$this->_elements[] = $this->createFormElement('select', 'hour', get_string('hour', 'form'),
$hours, $this->getAttributesForFormElement(), true);
// Reverse date element (Should be: Day, Month, Year), in RTL mode.
$dateformat = array_reverse($dateformat);
}
foreach ($dateformat as $key => $date) {
// E_STRICT creating elements without forms is nasty because it internally uses $this
$this->_elements[] = $this->createFormElement('select', $key, get_string($key, 'form'), $date,
$this->getAttributesForFormElement(), true);
}
if (!right_to_left()) { // Display time to the left of date, in LTR mode.
$this->_elements[] = $this->createFormElement('select', 'hour', get_string('hour', 'form'), $hours,
$this->getAttributesForFormElement(), true);
$this->_elements[] = $this->createFormElement('select', 'minute', get_string('minute', 'form'), $minutes,
$this->getAttributesForFormElement(), true);
}
// The YUI2 calendar only supports the gregorian calendar type so only display the calendar image if this is being used.
if ($calendartype->get_name() === 'gregorian') {
$image = $OUTPUT->pix_icon('i/calendar', get_string('calendar', 'calendar'), 'moodle');
$this->_elements[] = $this->createFormElement('link', 'calendar',
null, '#', $image);
}
foreach ($this->_elements as $element){
if (method_exists($element, 'setHiddenLabel')){
$element->setHiddenLabel(true);
}
}
}
/**
* 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) {
$this->setMoodleForm($caller);
switch ($event) {
case 'updateValue':
// Constant values override both default and submitted ones
// default values are overriden by submitted.
$value = $this->_findValue($caller->_constantValues);
if (null === $value) {
// If no boxes were checked, then there is no value in the array
// yet we don't want to display default value in this case.
if ($caller->isSubmitted() && !$caller->is_new_repeat($this->getName())) {
$value = $this->_findValue($caller->_submitValues);
} else {
$value = $this->_findValue($caller->_defaultValues);
}
}
$requestvalue=$value;
if ($value == 0 || $value === '') {
$value = $this->_options['defaulttime'];
if (!$value) {
$value = time();
}
}
if (!is_array($value)) {
$calendartype = \core_calendar\type_factory::get_calendar_instance();
$currentdate = $calendartype->timestamp_to_date_array($value, $this->_options['timezone']);
// Round minutes to the previous multiple of step.
$currentdate['minutes'] -= $currentdate['minutes'] % $this->_options['step'];
$value = array(
'minute' => $currentdate['minutes'],
'hour' => $currentdate['hours'],
'day' => $currentdate['mday'],
'month' => $currentdate['mon'],
'year' => $currentdate['year']);
// If optional, default to off, unless a date was provided.
if ($this->_options['optional']) {
$value['enabled'] = $requestvalue != 0;
}
} else {
$value['enabled'] = isset($value['enabled']);
}
if (null !== $value) {
$this->setValue($value);
}
break;
case 'createElement':
if (isset($arg[2]['optional']) && $arg[2]['optional']) {
// When using the function addElement, rather than createElement, we still
// enter this case, making this check necessary.
if ($this->_usedcreateelement) {
$caller->disabledIf($arg[0] . '[day]', $arg[0] . '[enabled]');
$caller->disabledIf($arg[0] . '[month]', $arg[0] . '[enabled]');
$caller->disabledIf($arg[0] . '[year]', $arg[0] . '[enabled]');
$caller->disabledIf($arg[0] . '[hour]', $arg[0] . '[enabled]');
$caller->disabledIf($arg[0] . '[minute]', $arg[0] . '[enabled]');
} else {
$caller->disabledIf($arg[0], $arg[0] . '[enabled]');
}
}
return parent::onQuickFormEvent($event, $arg, $caller);
break;
case 'addElement':
$this->_usedcreateelement = false;
return parent::onQuickFormEvent($event, $arg, $caller);
break;
default:
return parent::onQuickFormEvent($event, $arg, $caller);
}
}
/**
* Returns HTML for advchecbox form element.
*
* @return string
*/
function toHtml() {
include_once('HTML/QuickForm/Renderer/Default.php');
$renderer = new HTML_QuickForm_Renderer_Default();
$renderer->setElementTemplate('{element}');
parent::accept($renderer);
$html = $this->_wrap[0];
if ($this->_usedcreateelement) {
$html .= html_writer::tag('span', $renderer->toHtml(), array('class' => 'fdate_time_selector'));
} else {
$html .= $renderer->toHtml();
}
$html .= $this->_wrap[1];
return $html;
}
/**
* Accepts a renderer
*
* @param HTML_QuickForm_Renderer $renderer An HTML_QuickForm_Renderer object
* @param bool $required Whether a group is required
* @param string $error An error message associated with a group
*/
function accept(&$renderer, $required = false, $error = null) {
form_init_date_js();
$renderer->renderElement($this, $required, $error);
}
/**
* Export for template
*
* @param renderer_base $output
* @return array|stdClass
*/
public function export_for_template(renderer_base $output) {
form_init_date_js();
return parent::export_for_template($output);
}
/**
* Output a timestamp. Give it the name of the group.
*
* @param array $submitValues values submitted.
* @param bool $assoc specifies if returned array is associative
* @return array
*/
function exportValue(&$submitValues, $assoc = false) {
$valuearray = array();
foreach ($this->_elements as $element){
$thisexport = $element->exportValue($submitValues[$this->getName()], true);
if ($thisexport!=null){
$valuearray += $thisexport;
}
}
if (count($valuearray)){
if($this->_options['optional']) {
// If checkbox is on, the value is zero, so go no further
if(empty($valuearray['enabled'])) {
return $this->_prepareValue(0, $assoc);
}
}
// Get the calendar type used - see MDL-18375.
$calendartype = \core_calendar\type_factory::get_calendar_instance();
$gregoriandate = $calendartype->convert_to_gregorian($valuearray['year'],
$valuearray['month'],
$valuearray['day'],
$valuearray['hour'],
$valuearray['minute']);
$value = make_timestamp($gregoriandate['year'],
$gregoriandate['month'],
$gregoriandate['day'],
$gregoriandate['hour'],
$gregoriandate['minute'],
0,
$this->_options['timezone'],
true);
return $this->_prepareValue($value, $assoc);
} else {
return null;
}
}
}
+279
View File
@@ -0,0 +1,279 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Creates an element with a dropdown Default/Custom and an input for the value (text or date_selector)
*
* @package core_form
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once($CFG->libdir . '/form/group.php');
require_once($CFG->libdir . '/formslib.php');
/**
* Creates an element with a dropdown Default/Custom and an input for the value (text or date_selector)
*
* @package core_form
* @copyright 2017 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_defaultcustom extends MoodleQuickForm_group {
/**
* @var array These complement separators, they are appended to the resultant HTML.
*/
protected $_wrap = array('', '');
/**
* @var null|bool Keeps track of whether the date selector was initialised using createElement
* or addElement. If true, createElement was used signifying the element has been
* added to a group - see MDL-39187.
*/
protected $_usedcreateelement = true;
/** @var array */
protected $_options;
/**
* Constructor
*
* @param string $elementname Element's name
* @param mixed $elementlabel Label(s) for an element
* @param array $options Options to control the element's display
* @param mixed $attributes Either a typical HTML attribute string or an associative array
*/
public function __construct($elementname = null, $elementlabel = null, $options = array(), $attributes = null) {
parent::__construct($elementname, $elementlabel);
$this->setAttributes($attributes);
$this->_appendName = true;
$this->_type = 'defaultcustom';
$calendartype = \core_calendar\type_factory::get_calendar_instance();
$this->_options = [
'type' => 'text', // Type of the element. Supported are 'text' and 'date_selector'.
'defaultvalue' => null, // Value to be used when not overridden.
'customvalue' => null, // Value to be used when overwriting.
'customlabel' => get_string('custom', 'form'), // Label for 'customize' checkbox
// Other options are the same as the ones that can be passed to 'date_selector' element.
'timezone' => 99,
'startyear' => $calendartype->get_min_year(),
'stopyear' => $calendartype->get_max_year(),
'defaulttime' => 0,
'step' => 1,
'optional' => false,
];
if (is_array($options)) {
foreach ($options as $name => $value) {
if (array_key_exists($name, $this->_options)) {
if ($name === 'type' && !in_array($value, ['text', 'date_selector', 'date_time_selector'])) {
throw new coding_exception('Only text, date_selector, and date_time_selector elements are supported in ' . $this->_type);
}
if ($name === 'optional' && $value) {
throw new coding_exception('Date selector can not be optional in ' . $this->_type);
}
$this->_options[$name] = $value;
}
}
}
}
/**
* Converts timestamp to the day/month/year array in the current calendar format
* @param int $value
* @return array
*/
protected function timestamp_to_date_array($value) {
$calendartype = \core_calendar\type_factory::get_calendar_instance();
$currentdate = $calendartype->timestamp_to_date_array((int)$value, $this->_options['timezone']);
return array(
'minute' => $currentdate['minutes'],
'hour' => $currentdate['hours'],
'day' => $currentdate['mday'],
'month' => $currentdate['mon'],
'year' => $currentdate['year']);
}
/**
* Should this element have default/custom switch?
*
* @return bool
*/
protected function has_customize_switch() {
return $this->_options['defaultvalue'] !== null;
}
/**
* This will create all elements in the group
*/
public function _createElements() {
if (!$this->has_customize_switch()) {
$element = $this->createFormElement('hidden', 'customize', 1);
} else {
$element = $this->createFormElement('advcheckbox', 'customize', '', $this->_options['customlabel']);
}
$this->_elements[] = $element;
if ($this->_options['type'] === 'text') {
$element = $this->createFormElement($this->_options['type'], 'value',
get_string('newvaluefor', 'form', $this->getLabel()), $this->getAttributes());
$element->setHiddenLabel(true);
} else if ($this->_options['type'] === 'date_selector') {
$element = $this->createFormElement($this->_options['type'], 'value', '', $this->_options,
$this->getAttributes());
} else if ($this->_options['type'] === 'date_time_selector') {
$element = $this->createFormElement($this->_options['type'], 'value', '', $this->_options,
$this->getAttributes());
}
$this->_elements[] = $element;
}
/**
* 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
*/
public function onQuickFormEvent($event, $arg, &$caller) {
$this->setMoodleForm($caller);
switch ($event) {
case 'updateValue':
// Constant values override both default and submitted ones
// default values are overriden by submitted.
$value = $this->_findValue($caller->_constantValues);
if (null === $value) {
// If no boxes were checked, then there is no value in the array
// yet we don't want to display default value in this case.
if ($caller->isSubmitted()) {
$value = $this->_findValue($caller->_submitValues);
} else {
$value = $this->_findValue($caller->_defaultValues);
}
}
if (!is_array($value)) {
$customize = ($value !== false || !$this->has_customize_switch());
if ($this->_options['type'] === 'text') {
$elementvalue = $customize ? $value : $this->_options['defaultvalue'];
} else {
$elementvalue = $this->timestamp_to_date_array($customize ? $value : $this->_options['defaultvalue']);
}
$value = [
'customize' => $customize,
'value' => $elementvalue
];
}
$this->setValue($value);
break;
case 'createElement':
$rv = parent::onQuickFormEvent($event, $arg, $caller);
if ($this->has_customize_switch()) {
if ($this->_options['type'] === 'text') {
$caller->disabledIf($arg[0] . '[value]', $arg[0] . '[customize]', 'notchecked');
} else if ($this->_options['type'] === 'date_selector') {
$caller->disabledIf($arg[0] . '[value][day]', $arg[0] . '[customize]', 'notchecked');
$caller->disabledIf($arg[0] . '[value][month]', $arg[0] . '[customize]', 'notchecked');
$caller->disabledIf($arg[0] . '[value][year]', $arg[0] . '[customize]', 'notchecked');
} else {
// Date / Time selector.
$caller->disabledIf($arg[0] . '[value][day]', $arg[0] . '[customize]', 'notchecked');
$caller->disabledIf($arg[0] . '[value][month]', $arg[0] . '[customize]', 'notchecked');
$caller->disabledIf($arg[0] . '[value][year]', $arg[0] . '[customize]', 'notchecked');
$caller->disabledIf($arg[0] . '[value][hour]', $arg[0] . '[customize]', 'notchecked');
$caller->disabledIf($arg[0] . '[value][minute]', $arg[0] . '[customize]', 'notchecked');
}
}
return $rv;
case 'addElement':
$this->_usedcreateelement = false;
return parent::onQuickFormEvent($event, $arg, $caller);
break;
default:
return parent::onQuickFormEvent($event, $arg, $caller);
}
}
public function freeze() {
parent::freeze();
$this->setPersistantFreeze(true);
}
public function toHtml() {
include_once('HTML/QuickForm/Renderer/Default.php');
$renderer = new HTML_QuickForm_Renderer_Default();
$renderer->setElementTemplate('{element}');
parent::accept($renderer);
$html = $this->_wrap[0];
if ($this->_usedcreateelement) {
$html .= html_writer::tag('span', $renderer->toHtml(), array('class' => 'fdefaultcustom'));
} else {
$html .= $renderer->toHtml();
}
$html .= $this->_wrap[1];
return $html;
}
public function accept(&$renderer, $required = false, $error = null) {
global $PAGE;
if (!$this->_flagFrozen && $this->has_customize_switch()) {
// Add JS to the default/custom switch.
$firstelement = reset($this->_elements);
$defaultvalue = $this->_options['defaultvalue'];
$customvalue = $this->_options['customvalue'];
if ($this->_options['type'] === 'date_selector' || $this->_options['type'] === 'date_time_selector') {
$defaultvalue = $this->timestamp_to_date_array($defaultvalue);
$customvalue = $this->timestamp_to_date_array($customvalue);
}
$firstelement->updateAttributes(['data-defaultcustom' => 'true',
'data-type' => $this->_options['type'],
'data-defaultvalue' => json_encode($defaultvalue),
'data-customvalue' => json_encode($customvalue)]);
$PAGE->requires->js_amd_inline("require(['core_form/defaultcustom'], function() {});");
}
$renderer->renderElement($this, $required, $error);
}
/**
* Output a value. Give it the name of the group. In case of "default" return false.
*
* @param array $submitvalues values submitted.
* @param bool $assoc specifies if returned array is associative
* @return array
*/
public function exportValue(&$submitvalues, $assoc = false) {
$valuearray = array();
foreach ($this->_elements as $element) {
$thisexport = $element->exportValue($submitvalues[$this->getName()], true);
if ($thisexport != null) {
$valuearray += $thisexport;
}
}
if (empty($valuearray['customize'])) {
return $this->_prepareValue(false, $assoc);
}
return array_key_exists('value', $valuearray) ? $this->_prepareValue($valuearray['value'], $assoc) : [];
}
}
File diff suppressed because it is too large Load Diff
+303
View File
@@ -0,0 +1,303 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Duration form element
*
* Contains class to create length of time for element.
*
* @package core_form
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once($CFG->libdir . '/form/group.php');
require_once($CFG->libdir . '/formslib.php');
require_once($CFG->libdir . '/form/text.php');
/**
* Duration element
*
* HTML class for a length of time. For example, 30 minutes of 4 days. The
* values returned to PHP is the duration in seconds (an int rounded to the nearest second).
*
* @package core_form
* @category form
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_duration extends MoodleQuickForm_group {
/**
* Control the field names for form elements
* optional => if true, show a checkbox beside the element to turn it on (or off)
* defaultunit => which unit is default when the form is blank (default Minutes).
* @var array
*/
protected $_options = ['optional' => false, 'defaultunit' => MINSECS];
/** @var array associative array of time units (days, hours, minutes, seconds) */
private $_units = null;
/**
* constructor
*
* @param ?string $elementName Element's name
* @param mixed $elementLabel Label(s) for an element
* @param array $options Options to control the element's display. Recognised values are
* 'optional' => true/false - whether to display an 'enabled' checkbox next to the element.
* 'defaultunit' => 1|MINSECS|HOURSECS|DAYSECS|WEEKSECS - the default unit to display when
* the time is blank. If not specified, minutes is used.
* 'units' => array containing some or all of 1, MINSECS, HOURSECS, DAYSECS and WEEKSECS
* which unit choices to offer.
* @param mixed $attributes Either a typical HTML attribute string or an associative array
*/
public function __construct($elementName = null, $elementLabel = null,
$options = [], $attributes = null) {
parent::__construct($elementName, $elementLabel, $attributes);
$this->_persistantFreeze = true;
$this->_appendName = true;
$this->_type = 'duration';
// Set the options, do not bother setting bogus ones
if (!is_array($options)) {
$options = [];
}
$this->_options['optional'] = !empty($options['optional']);
if (isset($options['defaultunit'])) {
if (!array_key_exists($options['defaultunit'], $this->get_units())) {
throw new coding_exception($options['defaultunit'] .
' is not a recognised unit in MoodleQuickForm_duration.');
}
$this->_options['defaultunit'] = $options['defaultunit'];
}
if (isset($options['units'])) {
if (!is_array($options['units'])) {
throw new coding_exception(
'When creating a duration form field, units option must be an array.');
}
// Validate and register requested units.
$availableunits = $this->get_units();
$displayunits = [];
foreach ($options['units'] as $requestedunit) {
if (!isset($availableunits[$requestedunit])) {
throw new coding_exception($requestedunit .
' is not a recognised unit in MoodleQuickForm_duration.');
}
$displayunits[$requestedunit] = $availableunits[$requestedunit];
}
krsort($displayunits, SORT_NUMERIC);
$this->_options['units'] = $displayunits;
}
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_duration($elementName = null, $elementLabel = null,
$options = [], $attributes = null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $options, $attributes);
}
/**
* Returns time associative array of unit length.
*
* @return array unit length in seconds => string unit name.
*/
public function get_units() {
if (is_null($this->_units)) {
$this->_units = [
WEEKSECS => get_string('weeks'),
DAYSECS => get_string('days'),
HOURSECS => get_string('hours'),
MINSECS => get_string('minutes'),
1 => get_string('seconds'),
];
}
return $this->_units;
}
/**
* Get the units to be used for this field.
*
* The ones specified in the options passed to the constructor, or all by default.
*
* @return array number of seconds => lang string.
*/
protected function get_units_used() {
if (!empty($this->_options['units'])) {
return $this->_options['units'];
} else {
return $this->get_units();
}
}
/**
* Converts seconds to the best possible time unit. for example
* 1800 -> [30, MINSECS] = 30 minutes.
*
* @param int $seconds an amout of time in seconds.
* @return array associative array ($number => $unit)
*/
public function seconds_to_unit($seconds) {
if (empty($seconds)) {
return [0, $this->_options['defaultunit']];
}
foreach ($this->get_units_used() as $unit => $notused) {
if (fmod($seconds, $unit) == 0) {
return [$seconds / $unit, $unit];
}
}
return [$seconds, 1];
}
/**
* Override of standard quickforms method to create this element.
*/
function _createElements() {
$attributes = $this->getAttributesForFormElement();
if (!isset($attributes['size'])) {
$attributes['size'] = 3;
}
$this->_elements = [];
// E_STRICT creating elements without forms is nasty because it internally uses $this
$number = $this->createFormElement('text', 'number',
get_string('time', 'form'), $attributes, true);
$number->set_force_ltr(true);
$this->_elements[] = $number;
unset($attributes['size']);
$this->_elements[] = $this->createFormElement('select', 'timeunit',
get_string('timeunit', 'form'), $this->get_units_used(), $attributes, true);
// If optional we add a checkbox which the user can use to turn if on
if($this->_options['optional']) {
$this->_elements[] = $this->createFormElement('checkbox', 'enabled', null,
get_string('enable'), $attributes, true);
}
foreach ($this->_elements as $element){
if (method_exists($element, 'setHiddenLabel')){
$element->setHiddenLabel(true);
}
}
}
/**
* Called by HTML_QuickForm whenever form event is made on this element
*
* @param string $event Name of event
* @param mixed $arg event arguments
* @param MoodleQuickForm $caller calling object
* @return bool
*/
function onQuickFormEvent($event, $arg, &$caller) {
$this->setMoodleForm($caller);
switch ($event) {
case 'updateValue':
// constant values override both default and submitted ones
// default values are overriden by submitted
$value = $this->_findValue($caller->_constantValues);
if (null === $value) {
// if no boxes were checked, then there is no value in the array
// yet we don't want to display default value in this case
if ($caller->isSubmitted() && !$caller->is_new_repeat($this->getName())) {
$value = $this->_findValue($caller->_submitValues);
} else {
$value = $this->_findValue($caller->_defaultValues);
}
}
if (!is_array($value)) {
list($number, $unit) = $this->seconds_to_unit($value);
$value = ['number' => $number, 'timeunit' => $unit];
// If optional, default to off, unless a date was provided
if ($this->_options['optional']) {
$value['enabled'] = $number != 0;
}
} else {
$value['enabled'] = isset($value['enabled']);
}
if (null !== $value){
$this->setValue($value);
}
break;
case 'createElement':
if (!empty($arg[2]['optional'])) {
$caller->disabledIf($arg[0], $arg[0] . '[enabled]');
}
$caller->setType($arg[0] . '[number]', PARAM_FLOAT);
return parent::onQuickFormEvent($event, $arg, $caller);
default:
return parent::onQuickFormEvent($event, $arg, $caller);
}
}
/**
* Returns HTML for advchecbox form element.
*
* @return string
*/
function toHtml() {
include_once('HTML/QuickForm/Renderer/Default.php');
$renderer = new HTML_QuickForm_Renderer_Default();
$renderer->setElementTemplate('{element}');
parent::accept($renderer);
return $renderer->toHtml();
}
/**
* Accepts a renderer
*
* @param HTML_QuickForm_Renderer $renderer An HTML_QuickForm_Renderer object
* @param bool $required Whether a group is required
* @param ?string $error An error message associated with a group
*/
function accept(&$renderer, $required = false, $error = null) {
$renderer->renderElement($this, $required, $error);
}
/**
* Output a timestamp. Give it the name of the group.
* Override of standard quickforms method.
*
* @param array $submitValues
* @param bool $assoc whether to return the value as associative array
* @return array field name => value. The value is the time interval in seconds.
*/
function exportValue(&$submitValues, $assoc = false) {
// Get the values from all the child elements.
$valuearray = [];
foreach ($this->_elements as $element) {
$thisexport = $element->exportValue($submitValues[$this->getName()], true);
if (!is_null($thisexport)) {
$valuearray += $thisexport;
}
}
// Convert the value to an integer number of seconds.
if (empty($valuearray)) {
return null;
}
if ($this->_options['optional'] && empty($valuearray['enabled'])) {
return $this->_prepareValue(0, $assoc);
}
return $this->_prepareValue(
(int) round($valuearray['number'] * $valuearray['timeunit']), $assoc);
}
}
+522
View File
@@ -0,0 +1,522 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Editor input element
*
* Contains class to create preffered editor form element
*
* @package core_form
* @copyright 2009 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once('HTML/QuickForm/element.php');
require_once($CFG->dirroot.'/lib/filelib.php');
require_once($CFG->dirroot.'/repository/lib.php');
require_once('templatable_form_element.php');
/**
* Editor element
*
* It creates preffered editor (textbox/Tiny) form element for the format (Text/HTML) selected.
*
* @package core_form
* @category form
* @copyright 2009 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo MDL-29421 element Freezing
* @todo MDL-29426 ajax format conversion
*/
class MoodleQuickForm_editor extends HTML_QuickForm_element implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var string html for help button, if empty then no help will icon will be dispalyed. */
public $_helpbutton = '';
/** @var string defines the type of editor */
public $_type = 'editor';
/** @var array options provided to initalize filepicker */
protected $_options = array('subdirs' => 0, 'maxbytes' => 0, 'maxfiles' => 0, 'changeformat' => 0,
'areamaxbytes' => FILE_AREA_MAX_BYTES_UNLIMITED, 'context' => null, 'noclean' => 0, 'trusttext' => 0,
'return_types' => 15, 'enable_filemanagement' => true, 'removeorphaneddrafts' => false, 'autosave' => true);
// 15 is $_options['return_types'] = FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK.
/** @var array values for editor */
protected $_values = array('text'=>null, 'format'=>null, 'itemid'=>null);
/** @var bool if true label will be hidden */
protected $_hiddenLabel = false;
/**
* Constructor
*
* @param string $elementName (optional) name of the editor
* @param string $elementLabel (optional) editor label
* @param array $attributes (optional) Either a typical HTML attribute string
* or an associative array
* @param array $options set of options to initalize filepicker
*/
public function __construct($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
global $CFG, $PAGE;
$options = (array)$options;
foreach ($options as $name=>$value) {
if (array_key_exists($name, $this->_options)) {
$this->_options[$name] = $value;
}
}
if (!empty($options['maxbytes'])) {
$this->_options['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $options['maxbytes']);
}
if (!$this->_options['context']) {
// trying to set context to the current page context to make legacy files show in filepicker (e.g. forum post)
if (!empty($PAGE->context->id)) {
$this->_options['context'] = $PAGE->context;
} else {
$this->_options['context'] = context_system::instance();
}
}
$this->_options['trusted'] = trusttext_trusted($this->_options['context']);
parent::__construct($elementName, $elementLabel, $attributes);
// Note: for some reason the code using this setting does not like bools.
$this->_options['subdirs'] = (int)($this->_options['subdirs'] == 1);
editors_head_setup();
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_editor($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $attributes, $options);
}
/**
* 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
*
* @param string $name name of the editor
*/
function setName($name) {
$this->updateAttributes(array('name'=>$name));
}
/**
* Returns name of element
*
* @return string
*/
function getName() {
return $this->getAttribute('name');
}
/**
* Updates editor values, if part of $_values
*
* @param array $values associative array of values to set
*/
function setValue($values) {
$values = (array)$values;
foreach ($values as $name=>$value) {
if (array_key_exists($name, $this->_values)) {
$this->_values[$name] = $value;
}
}
}
/**
* Returns editor values
*
* @return array
*/
function getValue() {
return $this->_values;
}
/**
* Returns maximum file size which can be uploaded
*
* @return int
*/
function getMaxbytes() {
return $this->_options['maxbytes'];
}
/**
* Sets maximum file size which can be uploaded
*
* @param int $maxbytes file size
*/
function setMaxbytes($maxbytes) {
global $CFG;
$this->_options['maxbytes'] = get_max_upload_file_size($CFG->maxbytes, $maxbytes);
}
/**
* Returns the maximum size of the area.
*
* @return int
*/
function getAreamaxbytes() {
return $this->_options['areamaxbytes'];
}
/**
* Sets the maximum size of the area.
*
* @param int $areamaxbytes size limit
*/
function setAreamaxbytes($areamaxbytes) {
$this->_options['areamaxbytes'] = $areamaxbytes;
}
/**
* Returns maximum number of files which can be uploaded
*
* @return int
*/
function getMaxfiles() {
return $this->_options['maxfiles'];
}
/**
* Sets maximum number of files which can be uploaded.
*
* @param int $num number of files
*/
function setMaxfiles($num) {
$this->_options['maxfiles'] = $num;
}
/**
* Returns true if subdirectoy can be created, else false
*
* @return bool
*/
function getSubdirs() {
return $this->_options['subdirs'];
}
/**
* Set option to create sub directory, while uploading file
*
* @param bool $allow true if sub directory can be created.
*/
function setSubdirs($allow) {
$this->_options['subdirs'] = (int)($allow == 1);
}
/**
* Returns editor text content
*
* @return string Text content
*/
public function get_text(): string {
return $this->_values['text'];
}
/**
* Returns editor format
*
* @return int.
*/
function getFormat() {
return $this->_values['format'];
}
/**
* Checks if editor used is a required field
*
* @return bool true if required field.
*/
function isRequired() {
return (isset($this->_options['required']) && $this->_options['required']);
}
/**
* @deprecated since Moodle 2.0
*/
function setHelpButton($_helpbuttonargs, $function='_helpbutton') {
throw new coding_exception('setHelpButton() can not be used any more, please see MoodleQuickForm::addHelpButton().');
}
/**
* Returns html for help button.
*
* @return string html for help button
*/
function getHelpButton() {
return $this->_helpbutton;
}
/**
* Returns type of editor element
*
* @return string
*/
function getElementTemplateType() {
if ($this->_flagFrozen){
return 'nodisplay';
} else {
return 'default';
}
}
/**
* Returns HTML for editor form element.
*
* @return string
*/
function toHtml() {
global $CFG, $PAGE, $OUTPUT;
require_once($CFG->dirroot.'/repository/lib.php');
if ($this->_flagFrozen) {
return $this->getFrozenHtml();
}
$ctx = $this->_options['context'];
$id = $this->_attributes['id'];
$elname = $this->_attributes['name'];
$subdirs = $this->_options['subdirs'];
$maxbytes = $this->_options['maxbytes'];
$areamaxbytes = $this->_options['areamaxbytes'];
$maxfiles = $this->_options['maxfiles'];
$changeformat = $this->_options['changeformat']; // TO DO: implement as ajax calls
$text = $this->_values['text'];
$format = $this->_values['format'];
$draftitemid = $this->_values['itemid'];
// security - never ever allow guest/not logged in user to upload anything
if (isguestuser() or !isloggedin()) {
$maxfiles = 0;
}
$str = $this->_getTabs();
$str .= '<div>';
$editor = editors_get_preferred_editor($format);
$strformats = format_text_menu();
$formats = $editor->get_supported_formats();
foreach ($formats as $fid) {
$formats[$fid] = $strformats[$fid];
}
// get filepicker info
//
$fpoptions = array();
if ($maxfiles != 0 ) {
if (empty($draftitemid)) {
// no existing area info provided - let's use fresh new draft area
require_once("$CFG->libdir/filelib.php");
$this->setValue(array('itemid'=>file_get_unused_draft_itemid()));
$draftitemid = $this->_values['itemid'];
}
$args = new stdClass();
// need these three to filter repositories list
$args->accepted_types = array('web_image');
$args->return_types = $this->_options['return_types'];
$args->context = $ctx;
$args->env = 'filepicker';
// advimage plugin
$image_options = initialise_filepicker($args);
$image_options->context = $ctx;
$image_options->client_id = uniqid();
$image_options->maxbytes = $this->_options['maxbytes'];
$image_options->areamaxbytes = $this->_options['areamaxbytes'];
$image_options->env = 'editor';
$image_options->itemid = $draftitemid;
// moodlemedia plugin
$args->accepted_types = array('video', 'audio');
$media_options = initialise_filepicker($args);
$media_options->context = $ctx;
$media_options->client_id = uniqid();
$media_options->maxbytes = $this->_options['maxbytes'];
$media_options->areamaxbytes = $this->_options['areamaxbytes'];
$media_options->env = 'editor';
$media_options->itemid = $draftitemid;
// advlink plugin
$args->accepted_types = '*';
$link_options = initialise_filepicker($args);
$link_options->context = $ctx;
$link_options->client_id = uniqid();
$link_options->maxbytes = $this->_options['maxbytes'];
$link_options->areamaxbytes = $this->_options['areamaxbytes'];
$link_options->env = 'editor';
$link_options->itemid = $draftitemid;
$args->accepted_types = array('.vtt');
$subtitle_options = initialise_filepicker($args);
$subtitle_options->context = $ctx;
$subtitle_options->client_id = uniqid();
$subtitle_options->maxbytes = $this->_options['maxbytes'];
$subtitle_options->areamaxbytes = $this->_options['areamaxbytes'];
$subtitle_options->env = 'editor';
$subtitle_options->itemid = $draftitemid;
if (has_capability('moodle/h5p:deploy', $ctx)) {
// Only set H5P Plugin settings if the user can deploy new H5P content.
// H5P plugin.
$args->accepted_types = array('.h5p');
$h5poptions = initialise_filepicker($args);
$h5poptions->context = $ctx;
$h5poptions->client_id = uniqid();
$h5poptions->maxbytes = $this->_options['maxbytes'];
$h5poptions->areamaxbytes = $this->_options['areamaxbytes'];
$h5poptions->env = 'editor';
$h5poptions->itemid = $draftitemid;
$fpoptions['h5p'] = $h5poptions;
}
$fpoptions['image'] = $image_options;
$fpoptions['media'] = $media_options;
$fpoptions['link'] = $link_options;
$fpoptions['subtitle'] = $subtitle_options;
}
// TODO Remove this in MDL-77334 for Moodle 4.6.
// If editor is required and tinymce, then set required_tinymce option to initalize tinymce validation.
if (($editor instanceof tinymce_texteditor) && !is_null($this->getAttribute('onchange'))) {
$this->_options['required'] = true;
}
// print text area - TODO: add on-the-fly switching, size configuration, etc.
$editor->set_text($text);
$editor->use_editor($id, $this->_options, $fpoptions);
$rows = empty($this->_attributes['rows']) ? 15 : $this->_attributes['rows'];
$cols = empty($this->_attributes['cols']) ? 80 : $this->_attributes['cols'];
//Apply editor validation if required field
$context = [];
$context['rows'] = $rows;
$context['cols'] = $cols;
$context['frozen'] = $this->_flagFrozen;
foreach ($this->getAttributes() as $name => $value) {
$context[$name] = $value;
}
$context['hasformats'] = count($formats) > 1;
$context['formats'] = [];
if (($format === '' || $format === null) && count($formats)) {
$format = key($formats);
}
foreach ($formats as $formatvalue => $formattext) {
$context['formats'][] = ['value' => $formatvalue, 'text' => $formattext, 'selected' => ($formatvalue == $format)];
}
$context['id'] = $id;
$context['value'] = $text;
$context['format'] = $format;
$context['formatlabel'] = get_string('editorxformat', 'editor', $this->_label);
if (!is_null($this->getAttribute('onblur')) && !is_null($this->getAttribute('onchange'))) {
$context['changelistener'] = true;
}
$str .= $OUTPUT->render_from_template('core_form/editor_textarea', $context);
// during moodle installation, user area doesn't exist
// so we need to disable filepicker here.
if (!during_initial_install() && empty($CFG->adminsetuppending)) {
// 0 means no files, -1 unlimited
if ($maxfiles != 0 ) {
$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(
'action'=>'browse',
'env'=>'editor',
'itemid'=>$draftitemid,
'subdirs'=>$subdirs,
'maxbytes'=>$maxbytes,
'areamaxbytes' => $areamaxbytes,
'maxfiles'=>$maxfiles,
'ctx_id'=>$ctx->id,
'course'=>$PAGE->course->id,
'sesskey'=>sesskey(),
));
$str .= '<noscript>';
$str .= "<div><object type='text/html' data='$editorurl' height='160' width='600' style='border:1px solid #000'></object></div>";
$str .= '</noscript>';
}
}
$str .= '</div>';
return $str;
}
public function export_for_template(renderer_base $output) {
$context = $this->export_for_template_base($output);
$context['html'] = $this->toHtml();
return $context;
}
/**
* Returns the formatted value. The return from parent class is not acceptable.
*
* @return string
*/
public function getFrozenHtml(): string {
return format_text($this->get_text(), $this->getFormat()) . $this->_getPersistantData();
}
/**
* Sets label to be hidden.
*
* @param bool $hiddenLabel Whether the label should be hidden or not.
* @return void
*/
function setHiddenLabel($hiddenLabel) {
$this->_hiddenLabel = $hiddenLabel;
}
}
File diff suppressed because it is too large Load Diff
+483
View File
@@ -0,0 +1,483 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* FileManager form element
*
* Contains HTML class for a filemanager form element
*
* @package core_form
* @copyright 2009 Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once('HTML/QuickForm/element.php');
require_once($CFG->dirroot.'/lib/filelib.php');
require_once($CFG->dirroot.'/repository/lib.php');
require_once('templatable_form_element.php');
/**
* Filemanager form element
*
* FilemaneManager lets user to upload/manage multiple files
* @package core_form
* @category form
* @copyright 2009 Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_filemanager extends HTML_QuickForm_element implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var string html for help button, if empty then no help will icon will be dispalyed. */
public $_helpbutton = '';
/** @var array options provided to initalize filemanager */
// PHP doesn't support 'key' => $value1 | $value2 in class definition
// We cannot do $_options = array('return_types'=> FILE_INTERNAL | FILE_REFERENCE);
// So I have to set null here, and do it in constructor
protected $_options = array('mainfile' => '', 'subdirs' => 1, 'maxbytes' => -1, 'maxfiles' => -1,
'accepted_types' => '*', 'return_types' => null, 'areamaxbytes' => FILE_AREA_MAX_BYTES_UNLIMITED);
/**
* Constructor
*
* @param string $elementName (optional) name of the filemanager
* @param string $elementLabel (optional) filemanager label
* @param array $attributes (optional) Either a typical HTML attribute string
* or an associative array
* @param array|stdClass $options set of options to initalize filemanager
*/
public function __construct($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
global $CFG, $PAGE;
$options = (array)$options;
foreach ($options as $name=>$value) {
if (array_key_exists($name, $this->_options)) {
$this->_options[$name] = $value;
}
}
if (!empty($options['maxbytes'])) {
$this->_options['maxbytes'] = get_user_max_upload_file_size($PAGE->context, $CFG->maxbytes, $options['maxbytes']);
}
if (empty($options['return_types'])) {
$this->_options['return_types'] = (FILE_INTERNAL | FILE_REFERENCE | FILE_CONTROLLED_LINK);
}
$this->_type = 'filemanager';
parent::__construct($elementName, $elementLabel, $attributes);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_filemanager($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $attributes, $options);
}
/**
* 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
*
* @param string $name name of the filemanager
*/
function setName($name) {
$this->updateAttributes(array('name'=>$name));
}
/**
* Returns name of filemanager
*
* @return string
*/
function getName() {
return $this->getAttribute('name');
}
/**
* Updates filemanager attribute value
*
* @param string $value value to set
*/
function setValue($value) {
$this->updateAttributes(array('value'=>$value));
}
/**
* Returns filemanager attribute value
*
* @return string
*/
function getValue() {
return $this->getAttribute('value');
}
/**
* Returns maximum file size which can be uploaded
*
* @return int
*/
function getMaxbytes() {
return $this->_options['maxbytes'];
}
/**
* Sets maximum file size which can be uploaded
*
* @param int $maxbytes file size
*/
function setMaxbytes($maxbytes) {
global $CFG, $PAGE;
$this->_options['maxbytes'] = get_user_max_upload_file_size($PAGE->context, $CFG->maxbytes, $maxbytes);
}
/**
* Returns the maximum size of the area.
*
* @return int
*/
function getAreamaxbytes() {
return $this->_options['areamaxbytes'];
}
/**
* Sets the maximum size of the area.
*
* @param int $areamaxbytes size limit
*/
function setAreamaxbytes($areamaxbytes) {
$this->_options['areamaxbytes'] = $areamaxbytes;
}
/**
* Returns true if subdirectoy can be created, else false
*
* @return bool
*/
function getSubdirs() {
return $this->_options['subdirs'];
}
/**
* Set option to create sub directory, while uploading file
*
* @param bool $allow true if sub directory can be created.
*/
function setSubdirs($allow) {
$this->_options['subdirs'] = $allow;
}
/**
* Returns maximum number of files which can be uploaded
*
* @return int
*/
function getMaxfiles() {
return $this->_options['maxfiles'];
}
/**
* Sets maximum number of files which can be uploaded.
*
* @param int $num number of files
*/
function setMaxfiles($num) {
$this->_options['maxfiles'] = $num;
}
/**
* Returns html for help button.
*
* @return string html for help button
*/
function getHelpButton() {
return $this->_helpbutton;
}
/**
* Returns type of filemanager element
*
* @return string
*/
function getElementTemplateType() {
if ($this->_flagFrozen){
return 'nodisplay';
} else {
return 'default';
}
}
/**
* Returns HTML for filemanager form element.
*
* @return string
*/
function toHtml() {
global $CFG, $USER, $COURSE, $PAGE, $OUTPUT;
require_once("$CFG->dirroot/repository/lib.php");
// security - never ever allow guest/not logged in user to upload anything or use this element!
if (isguestuser() or !isloggedin()) {
throw new \moodle_exception('noguest');
}
if ($this->_flagFrozen) {
return $this->getFrozenHtml();
}
$id = $this->_attributes['id'];
$elname = $this->_attributes['name'];
$subdirs = $this->_options['subdirs'];
$maxbytes = $this->_options['maxbytes'];
$draftitemid = $this->getValue();
$accepted_types = $this->_options['accepted_types'];
if (empty($draftitemid)) {
// no existing area info provided - let's use fresh new draft area
require_once("$CFG->libdir/filelib.php");
$this->setValue(file_get_unused_draft_itemid());
$draftitemid = $this->getValue();
}
$client_id = uniqid();
// filemanager options
$options = new stdClass();
$options->mainfile = $this->_options['mainfile'];
$options->maxbytes = $this->_options['maxbytes'];
$options->maxfiles = $this->getMaxfiles();
$options->client_id = $client_id;
$options->itemid = $draftitemid;
$options->subdirs = $this->_options['subdirs'];
$options->target = $id;
$options->accepted_types = $accepted_types;
$options->return_types = $this->_options['return_types'];
$options->context = $PAGE->context;
$options->areamaxbytes = $this->_options['areamaxbytes'];
$html = $this->_getTabs();
$fm = new form_filemanager($options);
$output = $PAGE->get_renderer('core', 'files');
$html .= $output->render($fm);
$html .= html_writer::empty_tag('input', array('value' => $draftitemid, 'name' => $elname, 'type' => 'hidden', 'id' => $id));
if (!empty($options->accepted_types) && $options->accepted_types != '*') {
$html .= html_writer::tag('p', get_string('filesofthesetypes', 'form'));
$util = new \core_form\filetypes_util();
$filetypes = $options->accepted_types;
$filetypedescriptions = $util->describe_file_types($filetypes);
$html .= $OUTPUT->render_from_template('core_form/filetypes-descriptions', $filetypedescriptions);
}
return $html;
}
public function export_for_template(renderer_base $output) {
$context = $this->export_for_template_base($output);
$context['html'] = $this->toHtml();
return $context;
}
/**
* Check that all files have the allowed type.
*
* @param int $value Draft item id with the uploaded files.
* @return string|null Validation error message or null.
*/
public function validateSubmitValue($value) {
if (empty($value)) {
return;
}
$filetypesutil = new \core_form\filetypes_util();
$allowlist = $filetypesutil->normalize_file_types($this->_options['accepted_types']);
if (empty($allowlist) || $allowlist === ['*']) {
// Any file type is allowed, nothing to check here.
return;
}
$draftfiles = file_get_all_files_in_draftarea($value);
$wrongfiles = array();
if (empty($draftfiles)) {
// No file uploaded, nothing to check here.
return;
}
foreach ($draftfiles as $file) {
if (!$filetypesutil->is_allowed_file_type($file->filename, $allowlist)) {
$wrongfiles[] = $file->filename;
}
}
if ($wrongfiles) {
$a = array(
'allowlist' => implode(', ', $allowlist),
'wrongfiles' => implode(', ', $wrongfiles),
);
return get_string('err_wrongfileextension', 'core_form', $a);
}
return;
}
}
/**
* Data structure representing a file manager.
*
* This class defines the data structure for file mnager
*
* @package core_form
* @copyright 2010 Dongsheng Cai
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @todo do not use this abstraction (skodak)
*/
class form_filemanager implements renderable {
/** @var stdClass $options options for filemanager */
public $options;
/**
* Constructor
*
* @param stdClass $options options for filemanager
* default options are:
* maxbytes=>-1,
* areamaxbytes => FILE_AREA_MAX_BYTES_UNLIMITED,
* maxfiles=>-1,
* itemid=>0,
* subdirs=>false,
* client_id=>uniqid(),
* acepted_types=>'*',
* return_types=>FILE_INTERNAL,
* context=>$PAGE->context,
* author=>fullname($USER),
* licenses=>array build from $CFG->licenses,
* defaultlicense=>$CFG->sitedefaultlicense
*/
public function __construct(stdClass $options) {
global $CFG, $USER, $PAGE;
require_once($CFG->dirroot. '/repository/lib.php');
require_once($CFG->libdir . '/licenselib.php');
$defaults = array(
'maxbytes'=>-1,
'areamaxbytes' => FILE_AREA_MAX_BYTES_UNLIMITED,
'maxfiles'=>-1,
'itemid'=>0,
'subdirs'=>0,
'client_id'=>uniqid(),
'accepted_types'=>'*',
'return_types'=>FILE_INTERNAL,
'context'=>$PAGE->context,
'author'=>fullname($USER),
'licenses'=>array()
);
$defaults['licenses'] = license_manager::get_licenses();
if (!empty($CFG->sitedefaultlicense)) {
$defaults['defaultlicense'] = $CFG->sitedefaultlicense;
}
foreach ($defaults as $key=>$value) {
// Using !isset() prevents us from overwriting falsey values with defaults (as empty() did).
if (!isset($options->$key)) {
$options->$key = $value;
}
}
$fs = get_file_storage();
// initilise options, getting files in root path
$this->options = file_get_drafarea_files($options->itemid, '/');
// calculate file count
$usercontext = context_user::instance($USER->id);
$files = $fs->get_area_files($usercontext->id, 'user', 'draft', $options->itemid, 'id', false);
$filecount = count($files);
$this->options->filecount = $filecount;
// copying other options
foreach ($options as $name=>$value) {
$this->options->$name = $value;
}
// calculate the maximum file size as minimum from what is specified in filepicker options,
// course options, global configuration and php settings
$coursebytes = $maxbytes = 0;
list($context, $course, $cm) = get_context_info_array($this->options->context->id);
if (is_object($course)) {
$coursebytes = $course->maxbytes;
}
if (!empty($this->options->maxbytes) && $this->options->maxbytes > 0) {
$maxbytes = $this->options->maxbytes;
}
$this->options->maxbytes = get_user_max_upload_file_size($context, $CFG->maxbytes, $coursebytes, $maxbytes);
$this->options->userprefs = array();
$this->options->userprefs['recentviewmode'] = get_user_preferences('filemanager_recentviewmode', '');
// building file picker options
$params = new stdClass();
$params->accepted_types = $options->accepted_types;
$params->return_types = $options->return_types;
$params->context = $options->context;
$params->env = 'filemanager';
$params->disable_types = !empty($options->disable_types)?$options->disable_types:array();
$filepicker_options = initialise_filepicker($params);
$this->options->filepicker = $filepicker_options;
}
public function get_nonjsurl() {
global $PAGE;
return new moodle_url('/repository/draftfiles_manager.php', array(
'env'=>'filemanager',
'action'=>'browse',
'itemid'=>$this->options->itemid,
'subdirs'=>$this->options->subdirs,
'maxbytes'=>$this->options->maxbytes,
'areamaxbytes' => $this->options->areamaxbytes,
'maxfiles'=>$this->options->maxfiles,
'ctx_id'=>$PAGE->context->id, // TODO ?
'course'=>$PAGE->course->id, // TODO ?
'sesskey'=>sesskey(),
));
}
}
+64
View File
@@ -0,0 +1,64 @@
M.form_filepicker = {};
M.form_filepicker.Y = null;
M.form_filepicker.instances = [];
M.form_filepicker.callback = function(params) {
var html = '<a href="'+params['url']+'">'+params['file']+'</a>';
html += '<div class="dndupload-progressbars"></div>';
M.form_filepicker.Y.one('#file_info_'+params['client_id'] + ' .filepicker-filename').setContent(html);
//When file is added then set status of global variable to true
var elementid = M.core_filepicker.instances[params['client_id']].options.elementid;
M.form_filepicker.instances[elementid].fileadded = true;
//generate event to indicate changes which will be used by disable if or validation code
M.form_filepicker.Y.one('#'+elementid).simulate('change');
};
/**
* This fucntion is called for each file picker on page.
*/
M.form_filepicker.init = function(Y, options) {
//Keep reference of YUI, so that it can be used in callback.
M.form_filepicker.Y = Y;
//For client side validation, initialize file status for this filepicker
M.form_filepicker.instances[options.elementid] = {};
M.form_filepicker.instances[options.elementid].fileadded = false;
//Set filepicker callback
options.formcallback = M.form_filepicker.callback;
if (!M.core_filepicker.instances[options.client_id]) {
M.core_filepicker.init(Y, options);
}
Y.on('click', function(e, client_id) {
e.preventDefault();
if (this.ancestor('.fitem.disabled') == null) {
M.core_filepicker.instances[client_id].show();
}
}, '#filepicker-button-'+options.client_id, null, options.client_id);
var item = document.getElementById('nonjs-filepicker-'+options.client_id);
if (item) {
item.parentNode.removeChild(item);
}
item = document.getElementById('filepicker-wrapper-'+options.client_id);
if (item) {
item.style.display = '';
}
var dndoptions = {
clientid: options.client_id,
acceptedtypes: options.accepted_types,
author: options.author,
maxfiles: -1,
maxbytes: options.maxbytes,
itemid: options.itemid,
repositories: options.repositories,
formcallback: options.formcallback,
containerprefix: '#file_info_',
containerid: 'file_info_'+options.client_id,
contextid: options.context.id
};
M.form_dndupload.init(Y, dndoptions);
};
+281
View File
@@ -0,0 +1,281 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Filepicker form element
*
* Contains HTML class for a single filepicker form element
*
* @package core_form
* @copyright 2009 Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once("HTML/QuickForm/button.php");
require_once($CFG->dirroot.'/repository/lib.php');
require_once('templatable_form_element.php');
/**
* Filepicker form element
*
* HTML class for a single filepicker element (based on button)
*
* @package core_form
* @category form
* @copyright 2009 Dongsheng Cai <dongsheng@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_filepicker extends HTML_QuickForm_input implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var string html for help button, if empty then no help will icon will be dispalyed. */
public $_helpbutton = '';
/** @var array options provided to initalize filemanager */
// PHP doesn't support 'key' => $value1 | $value2 in class definition
// We cannot do $_options = array('return_types'=> FILE_INTERNAL | FILE_REFERENCE);
// So I have to set null here, and do it in constructor
protected $_options = array('maxbytes'=>0, 'accepted_types'=>'*', 'return_types'=>null);
/**
* Constructor
*
* @param string $elementName (optional) name of the filepicker
* @param string $elementLabel (optional) filepicker label
* @param array $attributes (optional) Either a typical HTML attribute string
* or an associative array
* @param array $options set of options to initalize filepicker
*/
public function __construct($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
global $CFG, $PAGE;
$options = (array)$options;
foreach ($options as $name=>$value) {
if (array_key_exists($name, $this->_options)) {
$this->_options[$name] = $value;
}
}
if (empty($options['return_types'])) {
$this->_options['return_types'] = FILE_INTERNAL;
}
$fpmaxbytes = 0;
if (!empty($options['maxbytes'])) {
$fpmaxbytes = $options['maxbytes'];
}
$coursemaxbytes = 0;
if (!empty($PAGE->course->maxbytes)) {
$coursemaxbytes = $PAGE->course->maxbytes;
}
$this->_options['maxbytes'] = get_user_max_upload_file_size($PAGE->context, $CFG->maxbytes, $coursemaxbytes, $fpmaxbytes);
$this->_type = 'filepicker';
parent::__construct($elementName, $elementLabel, $attributes);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_filepicker($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $attributes, $options);
}
/**
* Returns html for help button.
*
* @return string html for help button
*/
function getHelpButton() {
return $this->_helpbutton;
}
/**
* Returns type of filepicker element
*
* @return string
*/
function getElementTemplateType() {
if ($this->_flagFrozen){
return 'nodisplay';
} else {
return 'default';
}
}
/**
* Returns HTML for filepicker form element.
*
* @return string
*/
function toHtml() {
global $CFG, $COURSE, $USER, $PAGE, $OUTPUT;
$id = $this->_attributes['id'];
$elname = $this->_attributes['name'];
if ($this->_flagFrozen) {
return $this->getFrozenHtml();
}
if (!$draftitemid = (int)$this->getValue()) {
// no existing area info provided - let's use fresh new draft area
$draftitemid = file_get_unused_draft_itemid();
$this->setValue($draftitemid);
}
if ($COURSE->id == SITEID) {
$context = context_system::instance();
} else {
$context = context_course::instance($COURSE->id);
}
$client_id = uniqid();
$args = new stdClass();
// need these three to filter repositories list
$args->accepted_types = $this->_options['accepted_types']?$this->_options['accepted_types']:'*';
$args->return_types = $this->_options['return_types'];
$args->itemid = $draftitemid;
$args->maxbytes = $this->_options['maxbytes'];
$args->context = $PAGE->context;
$args->buttonname = $elname.'choose';
$args->elementid = $id;
$html = $this->_getTabs();
$fp = new file_picker($args);
$options = $fp->options;
$options->context = $PAGE->context;
$html .= $OUTPUT->render($fp);
$html .= '<input type="hidden" name="'.$elname.'" id="'.$id.'" value="'.$draftitemid.'" class="filepickerhidden"/>';
$module = array('name'=>'form_filepicker', 'fullpath'=>'/lib/form/filepicker.js', 'requires'=>array('core_filepicker', 'node', 'node-event-simulate', 'core_dndupload'));
$PAGE->requires->js_init_call('M.form_filepicker.init', array($fp->options), true, $module);
$nonjsfilepicker = new moodle_url('/repository/draftfiles_manager.php', array(
'env'=>'filepicker',
'action'=>'browse',
'itemid'=>$draftitemid,
'subdirs'=>0,
'maxbytes'=>$options->maxbytes,
'maxfiles'=>1,
'ctx_id'=>$PAGE->context->id,
'course'=>$PAGE->course->id,
'sesskey'=>sesskey(),
));
// non js file picker
$html .= '<noscript>';
$html .= "<div><object type='text/html' data='$nonjsfilepicker' height='160' width='600' style='border:1px solid #000'></object></div>";
$html .= '</noscript>';
if (!empty($args->accepted_types) && $args->accepted_types != '*') {
$html .= html_writer::tag('p', get_string('filesofthesetypes', 'form'));
$util = new \core_form\filetypes_util();
$filetypedescriptions = $util->describe_file_types($args->accepted_types);
$html .= $OUTPUT->render_from_template('core_form/filetypes-descriptions', $filetypedescriptions);
}
return $html;
}
/**
* export uploaded file
*
* @param array $submitValues values submitted.
* @param bool $assoc specifies if returned array is associative
* @return array
*/
function exportValue(&$submitValues, $assoc = false) {
global $USER;
$draftitemid = $this->_findValue($submitValues);
if (null === $draftitemid) {
$draftitemid = $this->getValue();
}
// make sure max one file is present and it is not too big
if (!is_null($draftitemid)) {
$fs = get_file_storage();
$usercontext = context_user::instance($USER->id);
if ($files = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id DESC', false)) {
$file = array_shift($files);
if ($this->_options['maxbytes']
and $this->_options['maxbytes'] !== USER_CAN_IGNORE_FILE_SIZE_LIMITS
and $file->get_filesize() > $this->_options['maxbytes']) {
// bad luck, somebody tries to sneak in oversized file
$file->delete();
}
foreach ($files as $file) {
// only one file expected
$file->delete();
}
}
}
return $this->_prepareValue($draftitemid, true);
}
public function export_for_template(renderer_base $output) {
$context = $this->export_for_template_base($output);
$context['html'] = $this->toHtml();
return $context;
}
/**
* Check that the file has the allowed type.
*
* @param array $value Draft item id with the uploaded files.
* @return string|null Validation error message or null.
*/
public function validateSubmitValue($value) {
$filetypesutil = new \core_form\filetypes_util();
$allowlist = $filetypesutil->normalize_file_types($this->_options['accepted_types']);
if (empty($allowlist) || $allowlist === ['*']) {
// Any file type is allowed, nothing to check here.
return;
}
$draftfiles = file_get_drafarea_files($value);
$wrongfiles = array();
if (empty($draftfiles)) {
// No file uploaded, nothing to check here.
return;
}
foreach ($draftfiles->list as $file) {
if (!$filetypesutil->is_allowed_file_type($file->filename, $allowlist)) {
$wrongfiles[] = $file->filename;
}
}
if ($wrongfiles) {
$a = array(
'allowlist' => implode(', ', $allowlist),
'wrongfiles' => implode(', ', $wrongfiles),
);
return get_string('err_wrongfileextension', 'core_form', $a);
}
return;
}
}
+254
View File
@@ -0,0 +1,254 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides the {@link MoodleQuickForm_filetypes} class.
*
* @package core_form
* @copyright 2016 Jonathon Fowler <fowlerj@usq.edu.au>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_form\filetypes_util;
defined('MOODLE_INTERNAL') || die;
global $CFG;
require_once($CFG->dirroot.'/lib/form/group.php');
/**
* File types and type groups selection form element.
*
* @package core_form
* @category form
* @copyright 2016 Jonathon Fowler <fowlerj@usq.edu.au>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_filetypes extends MoodleQuickForm_group {
/** @var array Allow selection from these file types only. */
protected $onlytypes = [];
/** @var bool Allow selection of 'All file types' (will be stored as '*'). */
protected $allowall = true;
/** @var bool Skip implicit validation against known file types. */
protected $allowunknown = false;
/** @var core_form\filetypes_util instance to use as a helper. */
protected $util = null;
/**
* Constructor
*
* @param string $elementname Element's name
* @param string $elementlabel Label(s) for an element
* @param array $options element options:
* 'onlytypes': Allow selection from these file types only; for example ['onlytypes' => ['web_image']].
* 'allowall': Allow to select 'All file types', defaults to true. Does not apply with onlytypes are set.
* 'allowunknown': Skip implicit validation against the list of known file types.
* @param array|string $attributes Either a typical HTML attribute string or an associative array
*/
public function __construct($elementname = null, $elementlabel = null, $options = null, $attributes = null) {
parent::__construct($elementname, $elementlabel);
$this->_type = 'filetypes';
// Hard-frozen elements do not get the name populated automatically,
// which leads to PHP notice. Add it explicitly here.
$this->setAttributes(array('name' => $elementname));
$this->updateAttributes($attributes);
if (is_array($options) && $options) {
if (array_key_exists('onlytypes', $options) && is_array($options['onlytypes'])) {
$this->onlytypes = $options['onlytypes'];
}
if (!$this->onlytypes && array_key_exists('allowall', $options)) {
$this->allowall = (bool)$options['allowall'];
}
if (array_key_exists('allowunknown', $options)) {
$this->allowunknown = (bool)$options['allowunknown'];
}
}
$this->util = new filetypes_util();
}
/**
* Assemble the elements of the form control.
*/
public function _createElements() {
$this->_generateId();
$this->setElements([
$this->createFormElement('text', 'filetypes', $this->getLabel(), [
'id' => $this->getAttribute('id'),
]),
$this->createFormElement('static', 'browser', null,
'<span data-filetypesbrowser="'.$this->getAttribute('id').'"></span>'),
$this->createFormElement('static', 'descriptions', null,
'<div data-filetypesdescriptions="'.$this->getAttribute('id').'"></div>')
]);
}
/**
* Return the selected file types.
*
* @param array $submitted submitted values
* @param bool $assoc if true the retured value is associated array
* @return array
*/
public function exportValue(&$submitted, $assoc = false) {
$value = '';
$filetypeselement = null;
foreach ($this->_elements as $key => $element) {
if ($element->_attributes['name'] === 'filetypes') {
$filetypeselement = $this->_elements[$key];
}
}
if ($filetypeselement) {
$formval = $filetypeselement->exportValue($submitted[$this->getName()], false);
if ($formval) {
$value = $this->util->normalize_file_types($formval);
if ($value === ['*'] && !$this->allowall) {
$value = [];
}
$value = implode(',', $value);
}
}
return $this->_prepareValue($value, $assoc);
}
/**
* Accepts a renderer (called shortly before the renderer's toHtml() method).
*
* @param HTML_QuickForm_Renderer $renderer An HTML_QuickForm_Renderer object
* @param bool $required Whether a group is required
* @param string $error An error message associated with a group
*/
public function accept(&$renderer, $required = false, $error = null) {
global $PAGE;
$PAGE->requires->js_call_amd('core_form/filetypes', 'init', [
$this->getAttribute('id'),
$this->getLabel(),
$this->onlytypes,
$this->allowall,
]);
if ($this->isFrozen()) {
// Don't render the choose button if the control is frozen.
foreach ($this->_elements as $key => $element) {
if ($element->_attributes['name'] === 'browser') {
unset($this->_elements[$key]);
}
}
}
parent::accept($renderer, $required, $error);
}
/**
* 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
*/
public function onQuickFormEvent($event, $arg, &$caller) {
global $OUTPUT;
switch ($event) {
case 'updateValue':
$value = $this->_findValue($caller->_constantValues);
if (null === $value) {
if ($caller->isSubmitted()) {
$value = $this->_findValue($caller->_submitValues);
} else {
$value = (string)$this->_findValue($caller->_defaultValues);
}
}
if (!is_array($value)) {
$value = array('filetypes' => $value);
}
if ($value['filetypes'] !== null) {
$filetypes = $this->util->normalize_file_types($value['filetypes']);
if ($filetypes === ['*'] && !$this->allowall) {
$filetypes = [];
}
$value['descriptions'] = '<div data-filetypesdescriptions="'.$this->getAttribute('id').'">' .
$OUTPUT->render_from_template('core_form/filetypes-descriptions',
$this->util->describe_file_types($filetypes)).'</div>';
}
$this->setValue($value);
return true;
break;
}
return parent::onQuickFormEvent($event, $arg, $caller);
}
/**
* Check that the submitted list contains only known and allowed file types.
*
* The validation obeys the element options 'allowall', 'allowunknown' and
* 'onlytypes' passed when creating the element.
*
* @param array $value Submitted value.
* @return string|null Validation error message or null.
*/
public function validateSubmitValue($value) {
$value = $value ?? ['filetypes' => null]; // A null $value can arrive here. Coalesce, creating the default array.
if (!$this->allowall) {
// Assert that there is an actual list provided.
$normalized = $this->util->normalize_file_types($value['filetypes']);
if (empty($normalized) || $normalized == ['*']) {
return get_string('filetypesnotall', 'core_form');
}
}
if (!$this->allowunknown) {
// Assert that all file types are known.
$unknown = $this->util->get_unknown_file_types($value['filetypes']);
if ($unknown) {
return get_string('filetypesunknown', 'core_form', implode(', ', $unknown));
}
}
if ($this->onlytypes) {
// Assert that all file types are allowed here.
$notlisted = $this->util->get_not_listed($value['filetypes'], $this->onlytypes);
if ($notlisted) {
return get_string('filetypesnotallowed', 'core_form', implode(', ', $notlisted));
}
}
return;
}
}
+193
View File
@@ -0,0 +1,193 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Float type form element
*
* Contains HTML class for a float type element
*
* @package core_form
* @category form
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/form/text.php');
/**
* Float type form element.
*
* This is preferred over the text element when working with float numbers, and takes care of the fact that different languages
* may use different symbols as the decimal separator.
* Using this element, submitted float numbers will be automatically translated from the localised format into the computer format,
* and vice versa when they are being displayed.
*
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_float extends MoodleQuickForm_text {
/**
* MoodleQuickForm_float constructor.
*
* @param string $elementName (optional) name of the float field
* @param string $elementLabel (optional) float field label
* @param string $attributes (optional) Either a typical HTML attribute string or an associative array
*/
public function __construct($elementName = null, $elementLabel = null, $attributes = null) {
parent::__construct($elementName, $elementLabel, $attributes);
$this->_type = 'float';
}
/**
* 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
*/
public function onQuickFormEvent($event, $arg, &$caller) {
switch ($event) {
case 'updateValue':
if ($value = $this->_findValue($caller->_constantValues)) {
$value = $this->format_float($value);
}
if (null === $value) {
$value = $this->_findValue($caller->_submitValues);
if (null === $value) {
if ($value = $this->_findValue($caller->_defaultValues)) {
$value = $this->format_float($value);
}
}
}
if (null !== $value) {
parent::setValue($value);
}
return true;
case 'createElement':
$caller->setType($arg[0], PARAM_RAW_TRIMMED);
default:
return parent::onQuickFormEvent($event, $arg, $caller);
}
}
/**
* Checks that the submitted value is a valid float number.
*
* @param string $value The localised float number that is submitted.
* @return string|null Validation error message or null.
*/
public function validateSubmitValue($value) {
if (false === unformat_float($value, true)) {
return get_string('err_numeric', 'core_form');
}
}
/**
* Sets the value of the form element.
*
* @param string $value Default value of the form element
*/
public function setValue($value) {
$value = $this->format_float($value);
parent::setValue($value);
}
/**
* Returns the value of the form element.
*
* @return false|float
*/
public function getValue() {
$value = parent::getValue();
if ($value) {
$value = unformat_float($value, true);
}
return $value;
}
/**
* Returns a 'safe' element's value.
*
* @param array $submitValues array of submitted values to search
* @param bool $assoc whether to return the value as associative array
* @return mixed
*/
public function exportValue(&$submitValues, $assoc = false) {
$value = $this->_findValue($submitValues);
if (null === $value) {
$value = $this->getValue();
} else if ($value) {
$value = unformat_float($value, true);
}
return $this->_prepareValue($value, $assoc);
}
/**
* Used by getFrozenHtml() to pass the element's value if _persistantFreeze is on.
*
* @return string
*/
public function _getPersistantData() {
if (!$this->_persistantFreeze) {
return '';
} else {
$id = $this->getAttribute('id');
if (isset($id)) {
// Id of persistant input is different then the actual input.
$id = array('id' => $id . '_persistant');
} else {
$id = array();
}
return '<input' . $this->_getAttrString(array(
'type' => 'hidden',
'name' => $this->getAttribute('name'),
'value' => $this->getAttribute('value')
) + $id) . ' />';
}
}
/**
* Given a float, prints it nicely.
* This function reserves the number of decimal places.
*
* @param float|null $value The float number to format
* @return string Localised float
*/
private function format_float($value) {
if (is_numeric($value)) {
// We want to keep trailing zeros after the decimal point if there is any.
// Therefore we cannot just call format_float() and pass -1 as the number of decimal points.
$pieces = preg_split('/E/i', $value); // In case it is in the scientific format.
$decimalpos = strpos($pieces[0], '.');
if ($decimalpos !== false) {
$decimalpart = substr($pieces[0], $decimalpos + 1);
$decimals = strlen($decimalpart);
} else {
$decimals = 0;
}
$pieces[0] = format_float($pieces[0], $decimals);
$value = implode('E', $pieces);
}
return $value;
}
}
+743
View File
@@ -0,0 +1,743 @@
/**
* This file contains JS functionality required by mforms and is included automatically
* when required.
*/
// Namespace for the form bits and bobs
M.form = M.form || {};
if (typeof M.form.dependencyManager === 'undefined') {
var dependencyManager = function() {
dependencyManager.superclass.constructor.apply(this, arguments);
};
Y.extend(dependencyManager, Y.Base, {
_locks: null,
_hides: null,
_dirty: null,
_nameCollections: null,
_fileinputs: null,
_staticElements: null,
_editors: null,
_editorNameSuffix: '[text]',
initializer: function() {
// Setup initial values for complex properties.
this._locks = {};
this._hides = {};
this._dirty = {};
// Setup event handlers.
Y.Object.each(this.get('dependencies'), function(value, i) {
var elements = this.elementsByName(i);
elements.each(function(node) {
var nodeName = node.get('nodeName').toUpperCase();
if (nodeName == 'INPUT') {
if (node.getAttribute('type').match(/^(button|submit|radio|checkbox)$/)) {
node.on('click', this.updateEventDependencies, this);
} else {
node.on('blur', this.updateEventDependencies, this);
}
node.on('change', this.updateEventDependencies, this);
} else if (nodeName == 'SELECT') {
node.on('change', this.updateEventDependencies, this);
} else {
node.on('click', this.updateEventDependencies, this);
node.on('blur', this.updateEventDependencies, this);
node.on('change', this.updateEventDependencies, this);
}
}, this);
}, this);
// Handle the reset button.
this.get('form').get('elements').each(function(input) {
if (input.getAttribute('type') == 'reset') {
input.on('click', function() {
this.get('form').reset();
this.updateAllDependencies();
}, this);
}
}, this);
this.updateAllDependencies();
},
/**
* Initializes the mapping from element name to YUI NodeList
*/
initElementsByName: function() {
var names = {}; // Form elements with a given name.
var allnames = {}; // Form elements AND outer elements for groups with a given name.
// Collect element names.
Y.Object.each(this.get('dependencies'), function(conditions, i) {
names[i] = new Y.NodeList();
allnames[i] = new Y.NodeList();
for (var condition in conditions) {
for (var value in conditions[condition]) {
for (var hide in conditions[condition][value]) {
for (var ei in conditions[condition][value][hide]) {
names[conditions[condition][value][hide][ei]] = new Y.NodeList();
allnames[conditions[condition][value][hide][ei]] = new Y.NodeList();
}
}
}
}
});
// Locate elements for each name.
this.get('form').get('elements').each(function(node) {
var name = node.getAttribute('name');
if (({}).hasOwnProperty.call(names, name)) {
names[name].push(node);
allnames[name].push(node);
} else if (this.isEditor(name)) {
// If this is an editor, we need to remove the suffix.
name = name.replace(this._editorNameSuffix, '');
if (({}).hasOwnProperty.call(names, name)) {
names[name].push(node);
allnames[name].push(node);
}
}
}, this);
// Locate any groups with the given name.
this.get('form').all('.fitem').each(function(node) {
var name = node.getData('groupname');
if (name && ({}).hasOwnProperty.call(allnames, name)) {
allnames[name].push(node);
}
});
// Locate any static elements for each name.
this.get('form').all('.form-control-static').each(function(node) {
var name = node.getData('name');
if (({}).hasOwnProperty.call(allnames, name)) {
names[name].push(node);
allnames[name].push(node);
}
});
this._nameCollections = {names: names, allnames: allnames};
},
/**
* Gets all elements in the form by their name and returns
* a YUI NodeList
*
* @param {String} name The form element name.
* @param {Boolean} includeGroups (optional - default false) Should the outer element for groups be included?
* @return {Y.NodeList}
*/
elementsByName: function(name, includeGroups) {
if (includeGroups === undefined) {
includeGroups = false;
}
var collection = (includeGroups ? 'allnames' : 'names');
if (!this._nameCollections) {
this.initElementsByName();
}
if (!({}).hasOwnProperty.call(this._nameCollections[collection], name)) {
return new Y.NodeList();
}
return this._nameCollections[collection][name];
},
/**
* Checks the dependencies the form has an makes any changes to the
* form that are required.
*
* Changes are made by functions title _dependency{Dependencytype}
* and more can easily be introduced by defining further functions.
*
* @param {EventFacade | null} e The event, if any.
* @param {String} dependon The form element name to check dependencies against.
* @return {Boolean}
*/
checkDependencies: function(e, dependon) {
var dependencies = this.get('dependencies'),
tohide = {},
tolock = {},
condition, value, isHide, lock, hide,
checkfunction, result, elements;
if (!({}).hasOwnProperty.call(dependencies, dependon)) {
return true;
}
elements = this.elementsByName(dependon);
for (condition in dependencies[dependon]) {
for (value in dependencies[dependon][condition]) {
for (isHide in dependencies[dependon][condition][value]) {
checkfunction = '_dependency' + condition[0].toUpperCase() + condition.slice(1);
if (Y.Lang.isFunction(this[checkfunction])) {
result = this[checkfunction].apply(this, [elements, value, (isHide === "1"), e]);
} else {
result = this._dependencyDefault(elements, value, (isHide === "1"), e);
}
lock = result.lock || false;
hide = result.hide || false;
for (var ei in dependencies[dependon][condition][value][isHide]) {
var eltolock = dependencies[dependon][condition][value][isHide][ei];
if (({}).hasOwnProperty.call(tohide, eltolock)) {
tohide[eltolock] = tohide[eltolock] || hide;
} else {
tohide[eltolock] = hide;
}
if (({}).hasOwnProperty.call(tolock, eltolock)) {
tolock[eltolock] = tolock[eltolock] || lock;
} else {
tolock[eltolock] = lock;
}
}
}
}
}
for (var el in tolock) {
var needsupdate = false;
if (!({}).hasOwnProperty.call(this._locks, el)) {
this._locks[el] = {};
}
if (({}).hasOwnProperty.call(tolock, el) && tolock[el]) {
if (!({}).hasOwnProperty.call(this._locks[el], dependon) || this._locks[el][dependon]) {
this._locks[el][dependon] = true;
needsupdate = true;
}
} else if (({}).hasOwnProperty.call(this._locks[el], dependon) && this._locks[el][dependon]) {
delete this._locks[el][dependon];
needsupdate = true;
}
if (!({}).hasOwnProperty.call(this._hides, el)) {
this._hides[el] = {};
}
if (({}).hasOwnProperty.call(tohide, el) && tohide[el]) {
if (!({}).hasOwnProperty.call(this._hides[el], dependon) || this._hides[el][dependon]) {
this._hides[el][dependon] = true;
needsupdate = true;
}
} else if (({}).hasOwnProperty.call(this._hides[el], dependon) && this._hides[el][dependon]) {
delete this._hides[el][dependon];
needsupdate = true;
}
if (needsupdate) {
this._dirty[el] = true;
}
}
return true;
},
/**
* Update all dependencies in form
*/
updateAllDependencies: function() {
Y.Object.each(this.get('dependencies'), function(value, name) {
this.checkDependencies(null, name);
}, this);
this.updateForm();
},
/**
* Update dependencies associated with event
*
* @param {Event} e The event.
*/
updateEventDependencies: function(e) {
var el = e.target.getAttribute('name');
this.checkDependencies(e, el);
this.updateForm();
},
/**
* Flush pending changes to the form
*/
updateForm: function() {
var el;
for (el in this._dirty) {
if (({}).hasOwnProperty.call(this._locks, el)) {
this._disableElement(el, !Y.Object.isEmpty(this._locks[el]));
}
if (({}).hasOwnProperty.call(this._hides, el)) {
this._hideElement(el, !Y.Object.isEmpty(this._hides[el]));
}
}
this._dirty = {};
},
/**
* Disables or enables all form elements with the given name
*
* @param {String} name The form element name.
* @param {Boolean} disabled True to disable, false to enable.
*/
_disableElement: function(name, disabled) {
const els = this.elementsByName(name),
filepicker = this.isFilePicker(name),
editors = this.get('form').all('.fitem [data-fieldtype="editor"] textarea[name="' + name + '[text]"]'),
staticElement = this.isStaticElement(name);
els.each(function(node) {
const fitem = node.ancestor('.fitem');
if (disabled) {
node.setAttribute('disabled', 'disabled');
} else {
node.removeAttribute('disabled');
}
// Enable/Disable static elements if exist.
if (staticElement) {
const disabledNonTextElements = 'INPUT,SELECT,TEXTAREA,BUTTON,A';
if (disabled) {
// Mute the text inside the current static element.
fitem.addClass('text-muted');
// Disabled non-text elements in the static if exist.
fitem.all(disabledNonTextElements).each(function(disabledElement) {
if (disabledElement.get('tagName').toUpperCase() === "A") {
disabledElement.addClass('disabled');
} else {
disabledElement.setAttribute('disabled', 'disabled');
}
});
} else {
// Unmute the text inside the current static element.
fitem.removeClass('text-muted');
// Enabled non-text elements in the static if exist.
fitem.all(disabledNonTextElements).each(function(disabledElement) {
if (disabledElement.get('tagName').toUpperCase() === "A") {
disabledElement.removeClass('disabled');
} else {
disabledElement.removeAttribute('disabled', 'disabled');
}
});
}
}
// Extra code to disable filepicker or filemanager form elements
if (filepicker) {
if (fitem) {
if (disabled) {
fitem.addClass('disabled');
} else {
fitem.removeClass('disabled');
}
}
}
});
editors.each(function(editor) {
if (disabled) {
editor.setAttribute('readonly', 'readonly');
} else {
editor.removeAttribute('readonly', 'readonly');
}
editor.getDOMNode().dispatchEvent(new Event('form:editorUpdated'));
});
},
/**
* Hides or shows all form elements with the given name.
*
* @param {String} name The form element name.
* @param {Boolean} hidden True to hide, false to show.
*/
_hideElement: function(name, hidden) {
var els = this.elementsByName(name, true);
els.each(function(node) {
var e = node.ancestor('.fitem', true);
var label = null,
id = null;
if (e) {
// Cope with differences between clean and boost themes.
if (e.hasClass('fitem_fgroup')) {
// Items within groups are not wrapped in div.fitem in theme_clean, so
// we need to hide the input, not the div.fitem.
e = node;
}
if (hidden) {
e.setAttribute('hidden', 'hidden');
} else {
e.removeAttribute('hidden');
}
e.setStyles({
display: (hidden) ? 'none' : ''
});
// Hide/unhide the label as well.
id = node.get('id');
if (id) {
label = Y.all('label[for="' + id + '"]');
if (label) {
if (hidden) {
label.setAttribute('hidden', 'hidden');
} else {
label.removeAttribute('hidden');
}
label.setStyles({
display: (hidden) ? 'none' : ''
});
}
}
}
});
},
/**
* Is the form element inside a filepicker or filemanager?
*
* @param {String} el The form element name.
* @return {Boolean}
*/
isFilePicker: function(el) {
if (!this._fileinputs) {
var fileinputs = {};
var selector = '.fitem [data-fieldtype="filepicker"] input,.fitem [data-fieldtype="filemanager"] input';
// Include a selector where the filemanager input is nested in a group.
selector += ',.fitem [data-fieldtype="group"] input[id*="filemanager"]';
var els = this.get('form').all(selector);
els.each(function(node) {
fileinputs[node.getAttribute('name')] = true;
});
this._fileinputs = fileinputs;
}
if (({}).hasOwnProperty.call(this._fileinputs, el)) {
return this._fileinputs[el] || false;
}
return false;
},
/**
* Checks if a form element with the given name is static.
*
* @param {string} el - The name of the form element to check.
* @returns {boolean} - Returns true if the form element is static, otherwise false.
*/
isStaticElement: function(el) {
if (!this._staticElements) {
const staticElements = {};
const els = this.get('form').all('.fitem [data-fieldtype="static"] .form-control-static');
els.each(function(node) {
if (node.getData('name') === el) {
staticElements[node.getData('name')] = true;
}
});
this._staticElements = staticElements;
}
if (({}).hasOwnProperty.call(this._staticElements, el)) {
return this._staticElements[el] || false;
}
return false;
},
_dependencyNotchecked: function(elements, value, isHide) {
var lock = false;
elements.each(function() {
if (this.getAttribute('type').toLowerCase() == 'hidden' &&
!this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
// This is the hidden input that is part of an advcheckbox.
return;
}
if (this.getAttribute('type').toLowerCase() == 'radio' && this.get('value') != value) {
return;
}
lock = lock || !Y.Node.getDOMNode(this).checked;
});
return {
lock: lock,
hide: isHide ? lock : false
};
},
_dependencyChecked: function(elements, value, isHide) {
var lock = false;
elements.each(function() {
if (this.getAttribute('type').toLowerCase() == 'hidden' &&
!this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
// This is the hidden input that is part of an advcheckbox.
return;
}
if (this.getAttribute('type').toLowerCase() == 'radio' && this.get('value') != value) {
return;
}
lock = lock || Y.Node.getDOMNode(this).checked;
});
return {
lock: lock,
hide: isHide ? lock : false
};
},
_dependencyNoitemselected: function(elements, value, isHide) {
var lock = false;
elements.each(function() {
lock = lock || this.get('selectedIndex') == -1;
});
return {
lock: lock,
hide: isHide ? lock : false
};
},
_dependencyEq: function(elements, value, isHide) {
var lock = false;
var hiddenVal = false;
var options, v, selected, values;
elements.each(function() {
if (this.getAttribute('type').toLowerCase() == 'radio' && !Y.Node.getDOMNode(this).checked) {
return;
} else if (this.getAttribute('type').toLowerCase() == 'hidden' &&
!this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
// This is the hidden input that is part of an advcheckbox.
hiddenVal = (this.get('value') == value);
return;
} else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
lock = lock || hiddenVal;
return;
}
if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
// Check for filepicker status.
var elementname = this.getAttribute('name');
if (elementname && M.form_filepicker.instances[elementname].fileadded) {
lock = false;
} else {
lock = true;
}
} else if (this.get('nodeName').toUpperCase() === 'SELECT' && this.get('multiple') === true) {
// Multiple selects can have one or more value assigned. A pipe (|) is used as a value separator
// when multiple values have to be selected at the same time.
values = value.split('|');
selected = [];
options = this.get('options');
options.each(function() {
if (this.get('selected')) {
selected[selected.length] = this.get('value');
}
});
if (selected.length > 0 && selected.length === values.length) {
for (var i in selected) {
v = selected[i];
if (values.indexOf(v) > -1) {
lock = true;
} else {
lock = false;
return;
}
}
} else {
lock = false;
}
} else {
lock = lock || this.get('value') == value;
}
});
return {
lock: lock,
hide: isHide ? lock : false
};
},
/**
* Lock the given field if the field value is in the given set of values.
*
* @param {Array} elements
* @param {String} values Single value or pipe (|) separated values when multiple
* @returns {{lock: boolean, hide: boolean}}
* @private
*/
_dependencyIn: function(elements, values, isHide) {
// A pipe (|) is used as a value separator
// when multiple values have to be passed on at the same time.
values = values.split('|');
var lock = false;
var hiddenVal = false;
var options, v, selected, value;
elements.each(function() {
if (this.getAttribute('type').toLowerCase() == 'radio' && !Y.Node.getDOMNode(this).checked) {
return;
} else if (this.getAttribute('type').toLowerCase() == 'hidden' &&
!this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
// This is the hidden input that is part of an advcheckbox.
hiddenVal = (values.indexOf(this.get('value')) > -1);
return;
} else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
lock = lock || hiddenVal;
return;
}
if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
// Check for filepicker status.
var elementname = this.getAttribute('name');
if (elementname && M.form_filepicker.instances[elementname].fileadded) {
lock = false;
} else {
lock = true;
}
} else if (this.get('nodeName').toUpperCase() === 'SELECT' && this.get('multiple') === true) {
// Multiple selects can have one or more value assigned.
selected = [];
options = this.get('options');
options.each(function() {
if (this.get('selected')) {
selected[selected.length] = this.get('value');
}
});
if (selected.length > 0 && selected.length === values.length) {
for (var i in selected) {
v = selected[i];
if (values.indexOf(v) > -1) {
lock = true;
} else {
lock = false;
return;
}
}
} else {
lock = false;
}
} else {
value = this.get('value');
lock = lock || (values.indexOf(value) > -1);
}
});
return {
lock: lock,
hide: isHide ? lock : false
};
},
_dependencyHide: function(elements, value) {
return {
lock: false,
hide: true
};
},
_dependencyDefault: function(elements, value, isHide) {
var lock = false,
hiddenVal = false,
values
;
elements.each(function() {
var selected;
if (this.getAttribute('type').toLowerCase() == 'radio' && !Y.Node.getDOMNode(this).checked) {
return;
} else if (this.getAttribute('type').toLowerCase() == 'hidden' &&
!this.siblings('input[type=checkbox][name="' + this.get('name') + '"]').isEmpty()) {
// This is the hidden input that is part of an advcheckbox.
hiddenVal = (this.get('value') != value);
return;
} else if (this.getAttribute('type').toLowerCase() == 'checkbox' && !Y.Node.getDOMNode(this).checked) {
lock = lock || hiddenVal;
return;
}
// Check for filepicker status.
if (this.getAttribute('class').toLowerCase() == 'filepickerhidden') {
var elementname = this.getAttribute('name');
if (elementname && M.form_filepicker.instances[elementname].fileadded) {
lock = true;
} else {
lock = false;
}
} else if (this.get('nodeName').toUpperCase() === 'SELECT' && this.get('multiple') === true) {
// Multiple selects can have one or more value assigned. A pipe (|) is used as a value separator
// when multiple values have to be selected at the same time.
values = value.split('|');
selected = [];
this.get('options').each(function() {
if (this.get('selected')) {
selected[selected.length] = this.get('value');
}
});
if (selected.length > 0 && selected.length === values.length) {
for (var i in selected) {
if (values.indexOf(selected[i]) > -1) {
lock = false;
} else {
lock = true;
return;
}
}
} else {
lock = true;
}
} else {
lock = lock || this.get('value') != value;
}
});
return {
lock: lock,
hide: isHide ? lock : false
};
},
/**
* Is the form element an editor?
*
* @param {String} el The form element name.
* @return {Boolean}
*/
isEditor: function(el) {
if (!this._editors) {
let editors = {};
const selector = '.fitem [data-fieldtype="editor"] textarea';
const els = this.get('form').all(selector);
els.each(function(node) {
editors[node.getAttribute('name')] = true;
});
this._editors = editors;
}
return this._editors[el] || false;
},
}, {
NAME: 'mform-dependency-manager',
ATTRS: {
form: {
setter: function(value) {
return Y.one('#' + value);
},
value: null
},
dependencies: {
value: {}
}
}
});
M.form.dependencyManager = dependencyManager;
}
/**
* Stores a list of the dependencyManager for each form on the page.
*/
M.form.dependencyManagers = {};
/**
* Initialises a manager for a forms dependencies.
* This should happen once per form.
*
* @param {YUI} Y YUI3 instance
* @param {String} formid ID of the form
* @param {Array} dependencies array
* @return {M.form.dependencyManager}
*/
M.form.initFormDependencies = function(Y, formid, dependencies) {
// If the dependencies isn't an array or object we don't want to
// know about it
if (!Y.Lang.isArray(dependencies) && !Y.Lang.isObject(dependencies)) {
return false;
}
/**
* Fixes an issue with YUI's processing method of form.elements property
* in Internet Explorer.
* http://yuilibrary.com/projects/yui3/ticket/2528030
*/
Y.Node.ATTRS.elements = {
getter: function() {
return Y.all(new Y.Array(this._node.elements, 0, true));
}
};
M.form.dependencyManagers[formid] = new M.form.dependencyManager({form: formid, dependencies: dependencies});
return M.form.dependencyManagers[formid];
};
/**
* Update the state of a form. You need to call this after, for example, changing
* the state of some of the form input elements in your own code, in order that
* things like the disableIf state of elements can be updated.
*
* @param {String} formid ID of the form
*/
M.form.updateFormState = function(formid) {
if (formid in M.form.dependencyManagers) {
M.form.dependencyManagers[formid].updateAllDependencies();
}
};
+173
View File
@@ -0,0 +1,173 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Advance grading form element
*
* Element-container for advanced grading custom input
*
* @package core_form
* @copyright 2011 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once("HTML/QuickForm/element.php");
require_once($CFG->dirroot.'/grade/grading/form/lib.php');
require_once('templatable_form_element.php');
if (class_exists('HTML_QuickForm')) {
HTML_QuickForm::registerRule('gradingvalidated', 'callback', '_validate', 'MoodleQuickForm_grading');
}
/**
* Advance grading form element
*
* HTML class for a grading element. This is a wrapper for advanced grading plugins.
* When adding the 'grading' element to the form, developer must pass an object of
* class gradingform_instance as $attributes['gradinginstance']. Otherwise an exception will be
* thrown.
* This object is responsible for implementing functions to render element html and validate it
*
* @package core_form
* @category form
* @copyright 2011 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_grading extends HTML_QuickForm_input implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/** @var array Stores attributes passed to the element */
private $gradingattributes;
/**
* Class constructor
*
* @param string $elementName Input field name attribute
* @param mixed $elementLabel Label(s) for the input field
* @param mixed $attributes Either a typical HTML attribute string or an associative array
*/
public function __construct($elementName=null, $elementLabel=null, $attributes=null) {
parent::__construct($elementName, $elementLabel, $attributes);
$this->_type = 'grading';
$this->gradingattributes = $attributes;
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_grading($elementName=null, $elementLabel=null, $attributes=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $attributes);
}
/**
* Helper function to retrieve gradingform_instance passed in element attributes
*
* @return gradingform_instance
*/
public function get_gradinginstance() {
if (is_array($this->gradingattributes) && array_key_exists('gradinginstance', $this->gradingattributes)) {
return $this->gradingattributes['gradinginstance'];
} else {
return null;
}
}
/**
* Returns the input field in HTML
*
* @return string
*/
public function toHtml(){
global $PAGE;
return $this->get_gradinginstance()->render_grading_element($PAGE, $this);
}
/**
* get html for help button
*
* @return string html for help button
*/
public function getHelpButton(){
return $this->_helpbutton;
}
/**
* The renderer of gradingform_instance will take care itself about different display
* in normal and frozen states
*
* @return string
*/
public function getElementTemplateType(){
return 'default';
}
/**
* Called by HTML_QuickForm whenever form event is made on this element.
* Adds necessary rules to the element and checks that coorenct instance of gradingform_instance
* is passed in attributes
*
* @param string $event Name of event
* @param mixed $arg event arguments
* @param object $caller calling object
* @return bool
* @throws moodle_exception
*/
public function onQuickFormEvent($event, $arg, &$caller) {
if ($event == 'createElement') {
$attributes = $arg[2];
if (!is_array($attributes) || !array_key_exists('gradinginstance', $attributes) || !($attributes['gradinginstance'] instanceof gradingform_instance)) {
throw new moodle_exception('exc_gradingformelement', 'grading');
}
}
$name = $this->getName();
if ($name && $caller->elementExists($name)) {
$caller->addRule($name, $this->get_gradinginstance()->default_validation_error_message(), 'gradingvalidated', $this->gradingattributes);
}
return parent::onQuickFormEvent($event, $arg, $caller);
}
/**
* Function registered as rule for this element and is called when this element is being validated.
* This is a wrapper to pass the validation to the method gradingform_instance::validate_grading_element
*
* @param mixed $elementvalue value of element to be validated
* @param array $attributes element attributes
* @return MoodleQuickForm_grading
*/
public static function _validate($elementvalue, $attributes = null) {
if (!$attributes['gradinginstance']->is_empty_form($elementvalue)) {
return $attributes['gradinginstance']->validate_grading_element($elementvalue);
}
return true;
}
public function export_for_template(renderer_base $output) {
$context = $this->export_for_template_base($output);
$context['html'] = $this->toHtml();
return $context;
}
}
+310
View File
@@ -0,0 +1,310 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Form element group
*
* Contains HTML class for group form element
*
* @package core_form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("HTML/QuickForm/group.php");
require_once('templatable_form_element.php');
/**
* HTML class for a form element group
*
* Overloaded {@link HTML_QuickForm_group} with default behavior modified for Moodle.
*
* @package core_form
* @category form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_group extends HTML_QuickForm_group implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/** @var bool if true label will be hidden. */
protected $_hiddenLabel = false;
/** @var MoodleQuickForm */
protected $_mform = null;
protected $_renderedfromtemplate = false;
/**
* constructor
*
* @param string $elementName (optional) name of the group
* @param string $elementLabel (optional) group label
* @param array $elements (optional) array of HTML_QuickForm_element elements to group
* @param string|array $separator (optional) Use a string for one separator, or use an array to alternate the separators
* @param string $appendName (optional) string to appened to grouped elements.
* @param mixed $attributes (optional) Either a typical HTML attribute string
* or an associative array
*/
public function __construct(
$elementName = null,
$elementLabel = null,
$elements = null,
$separator = null,
$appendName = true,
$attributes = null
) {
parent::__construct($elementName, $elementLabel, $elements, $separator, $appendName, $attributes);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_group($elementName=null, $elementLabel=null, $elements=null, $separator=null, $appendName = true) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $elements, $separator, $appendName);
}
/** @var string template type, would cause problems with client side validation so will leave for now */
//var $_elementTemplateType='fieldset';
/**
* set html for help button
*/
function getHelpButton(){
return $this->_helpbutton;
}
/**
* Returns element template, nodisplay/static/fieldset
*
* @return string
*/
function getElementTemplateType(){
if ($this->_flagFrozen){
if ($this->getGroupType() == 'submit'){
return 'nodisplay';
} else {
return 'static';
}
} else {
if ($this->getGroupType() == 'submit') {
return 'actionbuttons';
}
return 'fieldset';
}
}
/**
* Sets label to be hidden
*
* @param bool $hiddenLabel sets if label should be hidden
*/
public function setHiddenLabel($hiddenLabel) {
$this->_hiddenLabel = $hiddenLabel;
}
/**
* Sets the grouped elements and hides label
*
* @param array $elements
*/
function setElements($elements){
parent::setElements($elements);
foreach ($this->_elements as $element){
if (method_exists($element, 'setHiddenLabel')){
$element->setHiddenLabel(true);
}
}
}
/**
* Stores the form this element was added to
* This object is later used by {@link MoodleQuickForm_group::createElement()}
* @param null|MoodleQuickForm $mform
*/
public function setMoodleForm($mform) {
if ($mform && $mform instanceof MoodleQuickForm) {
$this->_mform = $mform;
}
}
/**
* Called by HTML_QuickForm whenever form event is made on this element
*
* If this function is overridden and parent is not called the element must be responsible for
* storing the MoodleQuickForm object, see {@link MoodleQuickForm_group::setMoodleForm()}
*
* @param string $event Name of event
* @param mixed $arg event arguments
* @param mixed $caller calling object
* @return ?bool
*/
public function onQuickFormEvent($event, $arg, &$caller) {
$this->setMoodleForm($caller);
return parent::onQuickFormEvent($event, $arg, $caller);
}
/**
* Creates an element to add to the group
* Expects the same arguments as MoodleQuickForm::createElement()
*/
public function createFormElement() {
if (!$this->_mform) {
throw new coding_exception('You can not call createFormElement() on the group element that was not yet added to a form.');
}
return call_user_func_array([$this->_mform, 'createElement'], func_get_args());
}
/**
* Return attributes suitable for passing to {@see createFormElement}, comprised of all group attributes without ID in
* order to ensure uniqueness of that value within the group
*
* @return array
*/
public function getAttributesForFormElement(): array {
return array_diff_key((array) $this->getAttributes(), array_flip(['id']));
}
public function export_for_template(renderer_base $output) {
global $OUTPUT;
$context = $this->export_for_template_base($output);
$this->_renderedfromtemplate = true;
include_once('HTML/QuickForm/Renderer/Default.php');
$elements = [];
$name = $this->getName();
$i = 0;
foreach ($this->_elements as $key => $element) {
$elementname = '';
if ($this->_appendName) {
$elementname = $element->getName();
if (isset($elementname)) {
$element->setName($name . '['. (strlen($elementname) ? $elementname : $key) .']');
} else {
$element->setName($name);
}
}
$element->_generateId();
$out = $OUTPUT->mform_element($element, false, false, '', true);
if (empty($out)) {
$renderer = new HTML_QuickForm_Renderer_Default();
$renderer->setElementTemplate('{element}');
$element->accept($renderer);
$out = $renderer->toHtml();
}
// Replicates the separator logic from 'pear/HTML/QuickForm/Renderer/Default.php'.
$separator = '';
if ($i > 0) {
if (is_array($this->_separator)) {
$separator = $this->_separator[($i - 1) % count($this->_separator)];
} else if ($this->_separator === null) {
$separator = '&nbsp;';
} else {
$separator = (string) $this->_separator;
}
}
$elements[] = [
'separator' => $separator,
'html' => $out
];
// Restore the element's name.
if ($this->_appendName) {
$element->setName($elementname);
}
$i++;
}
$context['groupname'] = $name;
$context['elements'] = $elements;
return $context;
}
/**
* Accepts a renderer
*
* @param object An HTML_QuickForm_Renderer object
* @param bool Whether a group is required
* @param string An error message associated with a group
* @access public
* @return void
*/
public function accept(&$renderer, $required = false, $error = null) {
$this->_createElementsIfNotExist();
$renderer->startGroup($this, $required, $error);
if (!$this->_renderedfromtemplate) {
// Backwards compatible path - only do this if we didn't render the sub-elements already.
$name = $this->getName();
foreach (array_keys($this->_elements) as $key) {
$element =& $this->_elements[$key];
$elementname = '';
if ($this->_appendName) {
$elementname = $element->getName();
if (isset($elementname)) {
$element->setName($name . '['. (strlen($elementname) ? $elementname : $key) .']');
} else {
$element->setName($name);
}
}
$required = !$element->isFrozen() && in_array($element->getName(), $this->_required);
$element->accept($renderer, $required);
// Restore the element's name.
if ($this->_appendName) {
$element->setName($elementname);
}
}
}
$renderer->finishGroup($this);
}
/**
* Calls the validateSubmitValue function for the containing elements and returns an error string as soon as it finds one.
*
* @param array $values Values of the containing elements.
* @return string|null Validation error message or null.
*/
public function validateSubmitValue($values) {
foreach ($this->_elements as $element) {
if (method_exists($element, 'validateSubmitValue')) {
$value = $values[$element->getName()] ?? null;
$result = $element->validateSubmitValue($value);
if (!empty($result) && is_string($result)) {
return $result;
}
}
}
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Header form element
*
* Contains a pseudo-element used for adding headers to form
*
* @package core_form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once 'HTML/QuickForm/header.php';
/**
* Header form element
*
* A pseudo-element used for adding headers to form
*
* @package core_form
* @category form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_header extends HTML_QuickForm_header
{
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/**
* constructor
*
* @param string $elementName name of the header element
* @param string $text text displayed in header element
*/
public function __construct($elementName = null, $text = null) {
parent::__construct($elementName, $text);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_header($elementName = null, $text = null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $text);
}
/**
* Accepts a renderer
*
* @param HTML_QuickForm_Renderer $renderer a HTML_QuickForm_Renderer object
*/
function accept(&$renderer, $required=false, $error=null)
{
$renderer->renderHeader($this);
}
/**
* get html for help button
*
* @return string html for help button
*/
function getHelpButton(){
return $this->_helpbutton;
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Hidden type form element
*
* Contains HTML class for a hidden type element
*
* @package core_form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('HTML/QuickForm/hidden.php');
/**
* Hidden type form element
*
* HTML class for a hidden type element
*
* @package core_form
* @category form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_hidden extends HTML_QuickForm_hidden{
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/**
* Constructor
*
* @param string $elementName (optional) name of the hidden element
* @param string $value (optional) value of the element
* @param mixed $attributes (optional) Either a typical HTML attribute string
* or an associative array
*/
public function __construct($elementName=null, $value='', $attributes=null) {
parent::__construct($elementName, $value, $attributes);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_hidden($elementName=null, $value='', $attributes=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
return self::__construct($elementName, $value, $attributes);
}
/**
* @deprecated since Moodle 2.0
*/
function setHelpButton($helpbuttonargs, $function='helpbutton'){
throw new coding_exception('setHelpButton() can not be used any more, please see MoodleQuickForm::addHelpButton().');
}
/**
* get html for help button
*
* @return string html for help button
*/
function getHelpButton(){
return '';
}
}
+161
View File
@@ -0,0 +1,161 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Listing form element.
*
* Contains HTML class for a listing form element.
*
* @package core_form
* @copyright 2012 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.');
}
require_once("HTML/QuickForm/input.php");
/**
* The listing element is a simple customizable "select" without the input type=select.
* One main div contains the "large" html of an item.
* A show/hide div shows a hidden div containing the list of all items.
* This list is composed by the "small" html of each item.
*
* How to use it:
* The options parameter is an array containing:
* - items => array of object: the key is the value of the form input
* $item->rowhtml => small html
* $item->mainhtml => large html
* - showall/hideall => string for the Show/Hide button
*
* WARNINGS: The form lets you display HTML. So it is subject to CROSS-SCRIPTING if you send it uncleaned HTML.
* Don't forget to escape your HTML as soon as one string comes from an input/external source.
*
* How to customize it:
* You can change the css in core.css. For example if you remove float:left; from .formlistingrow,
* then the item list is not display as tabs but as rows.
*
* @package core_form
* @copyright 2012 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_listing extends HTML_QuickForm_input {
/** @var array items to display. */
protected $items = array();
/** @var string language string for Show All. */
protected $showall;
/** @var string language string for Hide. */
protected $hideall;
/**
* Constructor.
*
* @param string $elementName (optional) name of the listing.
* @param string $elementLabel (optional) listing label.
* @param array $attributes (optional) Either a typical HTML attribute string or an associative array.
* @param array $options set of options to initalize listing.
*/
public function __construct($elementName=null, $elementLabel=null, $attributes=null, $options=array()) {
$this->_type = 'listing';
if (!empty($options['items'])) {
$this->items = $options['items'];
}
if (!empty($options['showall'])) {
$this->showall = $options['showall'];
} else {
$this->showall = get_string('showall');
}
if (!empty($options['hideall'])) {
$this->hideall = $options['hideall'];
} else {
$this->hideall = get_string('hide');
}
parent::__construct($elementName, $elementLabel, $attributes);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_listing($elementName=null, $elementLabel=null, $attributes=null, $options=array()) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $attributes, $options);
}
/**
* Returns HTML for listing form element.
*
* @return string the HTML.
*/
function toHtml() {
global $CFG, $PAGE;
$this->_generateId();
$elementid = $this->getAttribute('id');
$mainhtml = html_writer::tag('div', $this->items[$this->getValue()]->mainhtml,
array('id' => $elementid . '_items_main', 'class' => 'formlistingmain'));
// Add the main div containing the selected item (+ the caption: "More items").
$html = html_writer::tag('div', $mainhtml .
html_writer::tag('div', $this->showall,
array('id' => $elementid . '_items_caption', 'class' => 'formlistingmore')),
array('id' => $elementid . '_items', 'class' => 'formlisting hide'));
// Add collapsible region: all the items.
$itemrows = '';
$html .= html_writer::tag('div', $itemrows,
array('id' => $elementid . '_items_all', 'class' => 'formlistingall'));
// Add radio buttons for non javascript support.
$radiobuttons = '';
foreach ($this->items as $itemid => $item) {
$radioparams = array('name' => $this->getName(), 'value' => $itemid,
'id' => 'id_'.$itemid, 'class' => 'formlistinginputradio', 'type' => 'radio');
if ($itemid == $this->getValue()) {
$radioparams['checked'] = 'checked';
}
$radiobuttons .= html_writer::tag('div', html_writer::tag('input',
html_writer::tag('div', $item->rowhtml, array('class' => 'formlistingradiocontent')), $radioparams),
array('class' => 'formlistingradio'));
}
// Container for the hidden hidden input which will contain the selected item.
$html .= html_writer::tag('div', $radiobuttons,
array('id' => 'formlistinginputcontainer_' . $elementid, 'class' => 'formlistinginputcontainer'));
$module = array('name'=>'form_listing', 'fullpath'=>'/lib/form/yui/listing/listing.js',
'requires'=>array('node', 'event', 'transition', 'escape'));
$PAGE->requires->js_init_call('M.form_listing.init',
array(array(
'elementid' => $elementid.'_items',
'hideall' => $this->hideall,
'showall' => $this->showall,
'hiddeninputid' => $this->getAttribute('id'),
'items' => $this->items,
'inputname' => $elementid,
'currentvalue' => $this->getValue())), true, $module);
return $html;
}
}
+551
View File
@@ -0,0 +1,551 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Drop down form element to select the grade
*
* Contains HTML class for a drop down element to select the grade for an activity,
* used in mod update form
*
* @package core_form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once "$CFG->libdir/form/select.php";
require_once("HTML/QuickForm/element.php");
require_once($CFG->dirroot.'/lib/form/group.php');
require_once($CFG->dirroot.'/lib/grade/grade_scale.php');
/**
* Drop down form element to select the grade
*
* HTML class for a drop down element to select the grade for an activity,
* used in mod update form
*
* @package core_form
* @category form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_modgrade extends MoodleQuickForm_group {
/** @var boolean $isupdate Is this an add or an update ? */
public $isupdate = false;
/** @var float $currentgrade The current grademax for the grade_item */
public $currentgrade = false;
/** @var boolean $hasgrades Has this grade_item got any real grades (with values) */
public $hasgrades = false;
/** @var boolean $canrescale Does this activity support rescaling grades? */
public $canrescale = false;
/** @var int $currentscaleid The current scale id */
public $currentscaleid = null;
/** @var string $currentgradetype The current gradetype - can either be 'none', 'scale', or 'point' */
public $currentgradetype = 'none';
/** @var boolean $useratings Set to true if the activity is using ratings, false otherwise */
public $useratings = false;
/** @var MoodleQuickForm_select $gradetypeformelement */
private $gradetypeformelement;
/** @var MoodleQuickForm_select $scaleformelement */
private $scaleformelement;
/** @var MoodleQuickForm_text $maxgradeformelement */
private $maxgradeformelement;
/**
* Constructor
*
* @param string $elementname Element's name
* @param mixed $elementlabel Label(s) for an element
* @param array $options Options to control the element's display. Required - must contain the following options:
* 'isupdate' - is this a new module or are we editing an existing one?
* 'currentgrade' - the current grademax in the database for this gradeitem
* 'hasgrades' - whether or not the grade_item has existing grade_grades
* 'canrescale' - whether or not the activity supports rescaling grades
* @param mixed $attributes Either a typical HTML attribute string or an associative array
*/
public function __construct($elementname = null, $elementlabel = null, $options = array(), $attributes = null) {
// TODO MDL-52313 Replace with the call to parent::__construct().
HTML_QuickForm_element::__construct($elementname, $elementlabel, $attributes);
$this->_persistantFreeze = true;
$this->_appendName = true;
$this->_type = 'modgrade';
$this->isupdate = !empty($options['isupdate']);
if (isset($options['currentgrade'])) {
$this->currentgrade = $options['currentgrade'];
}
if (isset($options['currentgradetype'])) {
$gradetype = $options['currentgradetype'];
switch ($gradetype) {
case GRADE_TYPE_NONE :
$this->currentgradetype = 'none';
break;
case GRADE_TYPE_SCALE :
$this->currentgradetype = 'scale';
break;
case GRADE_TYPE_VALUE :
$this->currentgradetype = 'point';
break;
}
}
if (isset($options['currentscaleid'])) {
$this->currentscaleid = $options['currentscaleid'];
}
$this->hasgrades = !empty($options['hasgrades']);
$this->canrescale = !empty($options['canrescale']);
$this->useratings = !empty($options['useratings']);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_modgrade($elementname = null, $elementlabel = null, $options = array(), $attributes = null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementname, $elementlabel, $options, $attributes);
}
/**
* Create elements for this group.
*/
public function _createElements() {
global $COURSE, $CFG, $OUTPUT;
$attributes = $this->getAttributes();
if (is_null($attributes)) {
$attributes = array();
}
$this->_elements = array();
// Create main elements
// We have to create the scale and point elements first, as we need their IDs.
// Grade scale select box.
$scales = get_scales_menu($COURSE->id);
$langscale = get_string('modgradetypescale', 'grades');
$this->scaleformelement = $this->createFormElement('select', 'modgrade_scale', $langscale,
$scales, $attributes);
$this->scaleformelement->setHiddenLabel(true);
$scaleformelementid = $this->generate_modgrade_subelement_id('modgrade_scale');
$this->scaleformelement->updateAttributes(array('id' => $scaleformelementid));
// Maximum grade textbox.
$langmaxgrade = get_string('modgrademaxgrade', 'grades');
$this->maxgradeformelement = $this->createFormElement('text', 'modgrade_point', $langmaxgrade, array());
$this->maxgradeformelement->setHiddenLabel(true);
$maxgradeformelementid = $this->generate_modgrade_subelement_id('modgrade_point');
$this->maxgradeformelement->updateAttributes(array('id' => $maxgradeformelementid));
// Grade type select box.
$gradetype = array(
'none' => get_string('modgradetypenone', 'grades'),
'scale' => get_string('modgradetypescale', 'grades'),
'point' => get_string('modgradetypepoint', 'grades'),
);
$langtype = get_string('modgradetype', 'grades');
$this->gradetypeformelement = $this->createFormElement('select', 'modgrade_type', $langtype, $gradetype,
$attributes, true);
$this->gradetypeformelement->setHiddenLabel(true);
$gradetypeformelementid = $this->generate_modgrade_subelement_id('modgrade_type');
$this->gradetypeformelement->updateAttributes(array('id' => $gradetypeformelementid));
if ($this->isupdate && $this->hasgrades) {
$this->gradetypeformelement->updateAttributes(array('disabled' => 'disabled'));
$this->scaleformelement->updateAttributes(array('disabled' => 'disabled'));
// Check box for options for processing existing grades.
if ($this->canrescale) {
$langrescalegrades = get_string('modgraderescalegrades', 'grades');
$choices = array();
$choices[''] = get_string('choose');
$choices['no'] = get_string('no');
$choices['yes'] = get_string('yes');
$rescalegradesselect = $this->createFormElement('select',
'modgrade_rescalegrades',
$langrescalegrades,
$choices);
$rescalegradesselect->setHiddenLabel(true);
$rescalegradesselectid = $this->generate_modgrade_subelement_id('modgrade_rescalegrades');
$rescalegradesselect->updateAttributes(array('id' => $rescalegradesselectid));
}
}
// Add elements.
if ($this->isupdate && $this->hasgrades) {
// Set a message so the user knows why they can not alter the grade type or scale.
if ($this->currentgradetype == 'scale') {
$gradesexistmsg = get_string('modgradecantchangegradetyporscalemsg', 'grades');
} else if ($this->canrescale) {
$gradesexistmsg = get_string('modgradecantchangegradetypemsg', 'grades');
} else {
$gradesexistmsg = get_string('modgradecantchangegradetype', 'grades');
}
$gradesexisthtml = '<div class=\'alert alert-warning\'>' . $gradesexistmsg . '</div>';
$this->_elements[] = $this->createFormElement('static', 'gradesexistmsg', '', $gradesexisthtml);
}
// Grade type select box.
$label = html_writer::tag('label', $this->gradetypeformelement->getLabel(),
array('for' => $this->gradetypeformelement->getAttribute('id')));
$this->_elements[] = $this->createFormElement('static', 'gradetypelabel', '', '&nbsp;'.$label);
$this->_elements[] = $this->gradetypeformelement;
$this->_elements[] = $this->createFormElement('static', 'gradetypespacer', '', '<br />');
// Only show the grade scale select box when applicable.
if (!$this->isupdate || !$this->hasgrades || $this->currentgradetype == 'scale') {
$label = html_writer::tag('label', $this->scaleformelement->getLabel(),
array('for' => $this->scaleformelement->getAttribute('id')));
$this->_elements[] = $this->createFormElement('static', 'scalelabel', '', $label);
$this->_elements[] = $this->scaleformelement;
$this->_elements[] = $this->createFormElement('static', 'scalespacer', '', '<br />');
}
if ($this->isupdate && $this->hasgrades && $this->canrescale && $this->currentgradetype == 'point') {
// We need to know how to apply any changes to maxgrade - ie to either update, or don't touch exising grades.
$label = html_writer::tag('label', $rescalegradesselect->getLabel(),
array('for' => $rescalegradesselect->getAttribute('id')));
$labelhelp = new help_icon('modgraderescalegrades', 'grades');
$this->_elements[] = $this->createFormElement('static', 'scalelabel', '', $label . $OUTPUT->render($labelhelp));
$this->_elements[] = $rescalegradesselect;
$this->_elements[] = $this->createFormElement('static', 'scalespacer', '', '<br />');
}
// Only show the max points form element when applicable.
if (!$this->isupdate || !$this->hasgrades || $this->currentgradetype == 'point') {
$label = html_writer::tag('label', $this->maxgradeformelement->getLabel(),
array('for' => $this->maxgradeformelement->getAttribute('id')));
$this->_elements[] = $this->createFormElement('static', 'pointlabel', '', $label);
// Check if there are grades and if so then disable maxgradeformelement.
if ($this->hasgrades) {
// If it has grades then disable it.
$this->maxgradeformelement->updateAttributes(['disabled' => 'disabled']);
}
$this->_elements[] = $this->maxgradeformelement;
$this->_elements[] = $this->createFormElement('static', 'pointspacer', '', '<br />');
}
}
/**
* Calculate the output value for the element as a whole.
*
* @param array $submitvalues The incoming values from the form.
* @param bool $notused Not used.
* @return array Return value for the element, formatted like field name => value.
*/
public function exportValue(&$submitvalues, $notused = false) {
global $COURSE;
// Get the values from all the child elements.
$vals = array();
foreach ($this->_elements as $element) {
$thisexport = $element->exportValue($submitvalues[$this->getName()], true);
if (!is_null($thisexport)) {
$vals += $thisexport;
}
}
$type = (isset($vals['modgrade_type'])) ? $vals['modgrade_type'] : 'none';
$point = (isset($vals['modgrade_point'])) ? $vals['modgrade_point'] : null;
$scale = (isset($vals['modgrade_scale'])) ? $vals['modgrade_scale'] : null;
$rescalegrades = (isset($vals['modgrade_rescalegrades'])) ? $vals['modgrade_rescalegrades'] : null;
$return = $this->process_value($type, $scale, $point, $rescalegrades);
return array($this->getName() => $return, $this->getName() . '_rescalegrades' => $rescalegrades);
}
/**
* Process the value for the group based on the selected grade type, and the input for the scale and point elements.
*
* @param string $type The value of the grade type select box. Can be 'none', 'scale', or 'point'
* @param string|int $scale The value of the scale select box.
* @param string|int $point The value of the point grade textbox.
* @param string $rescalegrades The value of the rescalegrades select.
* @return int The resulting value
*/
protected function process_value($type='none', $scale=null, $point=null, $rescalegrades=null) {
global $COURSE;
$val = 0;
// If the maxgrade field is disabled with javascript, no value is sent with the form and mform assumes the default.
if ($this->isupdate && $this->hasgrades && $this->currentgradetype == 'point') {
// If the user cannot rescale, then return the original.
$returnoriginal = !$this->canrescale;
// If the user was forced to choose a rescale option - and they haven't - prevent any changes to the max grade.
$returnoriginal = $returnoriginal || ($this->canrescale && empty($rescalegrades));
if ($returnoriginal) {
return (string)unformat_float($this->currentgrade);
}
}
switch ($type) {
case 'point':
if ($this->validate_point($point) === true) {
$val = (int)$point;
}
break;
case 'scale':
if ($this->validate_scale($scale)) {
$val = (int)(-$scale);
}
break;
}
return $val;
}
/**
* Determines whether a given value is a valid scale selection.
*
* @param string|int $val The value to test.
* @return bool Valid or invalid
*/
protected function validate_scale($val) {
global $COURSE;
$scales = get_scales_menu($COURSE->id);
return (!empty($val) && isset($scales[(int)$val])) ? true : false;
}
/**
* Determines whether a given value is a valid point selection.
*
* @param string|int $val The value to test.
* @return bool Valid or invalid
*/
protected function validate_point($val) {
if (empty($val)) {
return false;
}
$maxgrade = (int)get_config('core', 'gradepointmax');
$isintlike = ((string)(int)$val === $val) ? true : false;
return ($isintlike === true && $val > 0 && $val <= $maxgrade) ? true : false;
}
/**
* Called by HTML_QuickForm whenever form event is made on this element.
*
* @param string $event Name of event
* @param mixed $arg event arguments
* @param MoodleQuickForm $caller calling object
* @return mixed
*/
public function onQuickFormEvent($event, $arg, &$caller) {
$this->setMoodleForm($caller);
switch ($event) {
case 'createElement':
// The first argument is the name.
$name = $arg[0];
// Set disable actions.
$caller->hideIf($name.'[modgrade_scale]', $name.'[modgrade_type]', 'neq', 'scale');
$caller->hideIf($name.'[modgrade_point]', $name.'[modgrade_type]', 'neq', 'point');
$caller->hideIf($name.'[modgrade_rescalegrades]', $name.'[modgrade_type]', 'neq', 'point');
// Set validation rules for the sub-elements belonging to this element.
// A handy note: the parent scope of a closure is the function in which the closure was declared.
// Because of this using $this is safe despite the closures being called statically.
// A nasty magic hack!
$checkgradetypechange = function($val) {
// Nothing is affected by changes to the grade type if there are no grades yet.
if (!$this->hasgrades) {
return true;
}
// Check if we are changing the grade type when grades are present.
if (isset($val['modgrade_type']) && $val['modgrade_type'] !== $this->currentgradetype) {
return false;
}
return true;
};
$checkscalechange = function($val) {
// Nothing is affected by changes to the scale if there are no grades yet.
if (!$this->hasgrades) {
return true;
}
// Check if we are changing the scale type when grades are present.
// If modgrade_type is empty then use currentgradetype.
$gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype;
if ($gradetype === 'scale') {
if (isset($val['modgrade_scale']) && ($val['modgrade_scale'] !== $this->currentscaleid)) {
return false;
}
}
return true;
};
$checkmaxgradechange = function($val) {
// Nothing is affected by changes to the max grade if there are no grades yet.
if (!$this->hasgrades) {
return true;
}
// If we are not using ratings we can change the max grade.
if (!$this->useratings) {
return true;
}
// Check if we are changing the max grade if we are using ratings and there is a grade.
// If modgrade_type is empty then use currentgradetype.
$gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype;
if ($gradetype === 'point') {
if (isset($val['modgrade_point']) &&
grade_floats_different($this->currentgrade, $val['modgrade_point'])) {
return false;
}
}
return true;
};
$checkmaxgrade = function($val) {
// Closure to validate a max points value. See the note above about scope if this confuses you.
// If modgrade_type is empty then use currentgradetype.
$gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype;
if ($gradetype === 'point') {
if (isset($val['modgrade_point'])) {
return $this->validate_point($val['modgrade_point']);
}
}
return true;
};
$checkvalidscale = function($val) {
// Closure to validate a scale value. See the note above about scope if this confuses you.
// If modgrade_type is empty then use currentgradetype.
$gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype;
if ($gradetype === 'scale') {
if (isset($val['modgrade_scale'])) {
return $this->validate_scale($val['modgrade_scale']);
}
}
return true;
};
$checkrescale = function($val) {
// Nothing is affected by changes to grademax if there are no grades yet.
if (!$this->isupdate || !$this->hasgrades || !$this->canrescale) {
return true;
}
// Closure to validate a scale value. See the note above about scope if this confuses you.
// If modgrade_type is empty then use currentgradetype.
$gradetype = isset($val['modgrade_type']) ? $val['modgrade_type'] : $this->currentgradetype;
if ($gradetype === 'point' && isset($val['modgrade_point'])) {
// Work out if the value was actually changed in the form.
if (grade_floats_different($this->currentgrade, $val['modgrade_point'])) {
if (empty($val['modgrade_rescalegrades'])) {
// This was an "edit", the grademax was changed and the process existing setting was not set.
return false;
}
}
}
return true;
};
$cantchangegradetype = get_string('modgradecantchangegradetype', 'grades');
$cantchangemaxgrade = get_string('modgradecantchangeratingmaxgrade', 'grades');
$maxgradeexceeded = get_string('modgradeerrorbadpoint', 'grades', get_config('core', 'gradepointmax'));
$invalidscale = get_string('modgradeerrorbadscale', 'grades');
$cantchangescale = get_string('modgradecantchangescale', 'grades');
$mustchooserescale = get_string('mustchooserescaleyesorno', 'grades');
// When creating the rules the sixth arg is $force, we set it to true because otherwise the form
// will attempt to validate the existence of the element, we don't want this because the element
// is being created right now and doesn't actually exist as a registered element yet.
$caller->addRule($name, $cantchangegradetype, 'callback', $checkgradetypechange, 'server', false, true);
$caller->addRule($name, $cantchangemaxgrade, 'callback', $checkmaxgradechange, 'server', false, true);
$caller->addRule($name, $maxgradeexceeded, 'callback', $checkmaxgrade, 'server', false, true);
$caller->addRule($name, $invalidscale, 'callback', $checkvalidscale, 'server', false, true);
$caller->addRule($name, $cantchangescale, 'callback', $checkscalechange, 'server', false, true);
$caller->addRule($name, $mustchooserescale, 'callback', $checkrescale, 'server', false, true);
break;
case 'updateValue':
// As this is a group element with no value of its own we are only interested in situations where the
// default value or a constant value are being provided to the actual element.
// In this case we expect an int that is going to translate to a scale if negative, or to max points
// if positive.
// Set the maximum points field to disabled if the rescale option has not been chosen and there are grades.
$caller->disabledIf($this->getName() . '[modgrade_point]', $this->getName() .
'[modgrade_rescalegrades]', 'eq', '');
// A constant value should be given as an int.
// The default value should be an int and be either $CFG->gradepointdefault or whatever was set in set_data().
$value = $this->_findValue($caller->_constantValues);
if (null === $value) {
if ($caller->isSubmitted() && $this->_findValue($caller->_submitValues) !== null) {
// Submitted values are array, one value for each individual element in this group.
// When there is submitted data let parent::onQuickFormEvent() process it.
break;
}
$value = $this->_findValue($caller->_defaultValues);
}
if (!is_null($value) && !is_scalar($value)) {
// Something unexpected (likely an array of subelement values) has been given - this will be dealt
// with somewhere else - where exactly... likely the subelements.
debugging('An invalid value (type '.gettype($value).') has arrived at '.__METHOD__, DEBUG_DEVELOPER);
break;
}
// Set element state for existing data.
// This is really a pretty hacky thing to do, when data is being set the group element is called
// with the data first and the subelements called afterwards.
// This means that the subelements data (inc const and default values) can be overridden by form code.
// So - when we call this code really we can't be sure that will be the end value for the element.
if (!empty($this->_elements)) {
if (!empty($value)) {
if ($value < 0) {
$this->gradetypeformelement->setValue('scale');
$this->scaleformelement->setValue(($value * -1));
} else if ($value > 0) {
$this->gradetypeformelement->setValue('point');
$maxvalue = !empty($this->currentgrade) ? (string)unformat_float($this->currentgrade) : $value;
$this->maxgradeformelement->setValue($maxvalue);
}
} else {
$this->gradetypeformelement->setValue('none');
$this->maxgradeformelement->setValue(100);
}
}
break;
}
// Always let the parent do its thing!
return parent::onQuickFormEvent($event, $arg, $caller);
}
/**
* Generates the id attribute for the subelement of the modgrade group.
*
* Uses algorithm similar to what {@link HTML_QuickForm_element::_generateId()}
* does but takes the name of the wrapping modgrade group into account.
*
* @param string $subname the name of the HTML_QuickForm_element in this modgrade group
* @return string
*/
protected function generate_modgrade_subelement_id($subname) {
$gid = str_replace(array('[', ']'), array('_', ''), $this->getName());
return clean_param('id_'.$gid.'_'.$subname, PARAM_ALPHANUMEXT);
}
}
+156
View File
@@ -0,0 +1,156 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Drop down form element to select visibility in an activity mod update form
*
* Contains HTML class for a drop down element to select visibility in an activity mod update form
*
* @package core_form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once "$CFG->libdir/form/select.php";
/**
* Drop down form element to select visibility in an activity mod update form
*
* HTML class for a drop down element to select visibility in an activity mod update form
*
* @package core_form
* @category form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_modvisible extends MoodleQuickForm_select{
/** @var int activity state: visible=0, visibleoncoursepage=any */
const HIDE = 0;
/** @var int activity state: visible=1, visibleoncoursepage=1 */
const SHOW = 1;
/** @var int activity state: visible=1, visibleoncoursepage=0 */
const STEALTH = -1;
/**
* Class constructor
*
* @param string $elementName Select name attribute
* @param mixed $elementLabel Label(s) for the select
* @param mixed $attributes Either a typical HTML attribute string or an associative array
* @param array $options ignored
*/
public function __construct($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
parent::__construct($elementName, $elementLabel, null, $attributes);
$this->_type = 'modvisible';
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_modvisible($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $attributes, $options);
}
/**
* 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
*/
public function onQuickFormEvent($event, $arg, &$caller) {
switch ($event) {
case 'createElement':
$options = is_array($arg[3]) ? $arg[3] : [];
$sectionvisible = array_key_exists('sectionvisible', $options) ? $options['sectionvisible'] : 1;
$cm = !empty($options['cm']) ? cm_info::create($options['cm']) : null;
$choices = array();
if (!$sectionvisible) {
// If section is not visible the activity is hidden by default but it can also be made available.
$choices[self::HIDE] = get_string('hidefromstudents');
if (!$cm || $cm->has_view()) {
$choices[self::SHOW] = get_string('hideoncoursepage');
}
} else {
$choices[self::SHOW] = get_string('showoncoursepage');
$choices[self::HIDE] = get_string('hidefromstudents');
if (!empty($options['allowstealth']) && (!$cm || $cm->has_view())) {
// If allowed in this course/section, add a third visibility option
// "Available but not displayed on course page".
$choices[self::STEALTH] = get_string('hideoncoursepage');
}
}
$this->load($choices);
break;
case 'updateValue':
// Given two bool values of 'visible' and 'visibleoncoursepage' convert to a single
// three-state value (show, hide, hide-on-course-page).
$name = $this->getName();
$value = $this->_findValue($caller->_constantValues);
if (!empty($value) && isset($caller->_constantValues[$name.'oncoursepage']) &&
!$caller->_constantValues[$name.'oncoursepage']) {
$value = self::STEALTH;
}
if (null === $value) {
if ($caller->isSubmitted()) {
break;
}
$value = $this->_findValue($caller->_defaultValues);
if (!empty($value) && isset($caller->_defaultValues[$name.'oncoursepage']) &&
!$caller->_defaultValues[$name.'oncoursepage']) {
$value = self::STEALTH;
}
}
if ($value !== null) {
$this->setSelected($value);
}
return true;
}
return parent::onQuickFormEvent($event, $arg, $caller);
}
/**
* As usual, to get the group's value we access its elements and call
* their exportValue() methods
*
* @param array $submitvalues submitted values
* @param bool $assoc if true the retured value is associated array
* @return mixed
*/
public function exportValue(&$submitvalues, $assoc = false) {
if ($assoc) {
$value = parent::exportValue($submitvalues, $assoc);
$key = key($value);
$v = $value[$key];
// Convert three-state dropdown value (show, hide, hide-on-course-page) into the array of two bool values:
// array('visible' => x, 'visibleoncoursepage' => y).
return array($key => ($v == self::HIDE ? 0 : 1),
$key . 'oncoursepage' => ($v == self::STEALTH ? 0 : 1));
} else {
return parent::exportValue($submitvalues, $assoc);
}
}
}
+106
View File
@@ -0,0 +1,106 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Password type form element
*
* Contains HTML class for a password type element
*
* @package core_form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('HTML/QuickForm/password.php');
require_once('templatable_form_element.php');
/**
* Password type form element
*
* HTML class for a password type element
*
* @package core_form
* @category form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_password extends HTML_QuickForm_password implements templatable {
use templatable_form_element;
/** @var string, html for help button, if empty then no help */
var $_helpbutton='';
/** @var bool if true label will be hidden. */
protected $_hiddenLabel = false;
/**
* constructor
*
* @param string $elementName (optional) name of the password element
* @param string $elementLabel (optional) label for password element
* @param mixed $attributes (optional) Either a typical HTML attribute string
* or an associative array
*/
public function __construct($elementName=null, $elementLabel=null, $attributes=null) {
global $CFG;
// No standard mform in moodle should allow autocomplete of passwords.
if (empty($attributes)) {
$attributes = ['autocomplete' => 'new-password'];
} else if (is_array($attributes) && empty($attributes['autocomplete'])) {
$attributes['autocomplete'] = 'new-password';
} else if (is_array($attributes) && $attributes['autocomplete'] === 'off') {
// A value of 'off' is ignored in all modern browsers and password
// managers and should be new-password instead.
$attributes['autocomplete'] = 'new-password';
} else if (is_string($attributes)) {
if (strpos($attributes, 'autocomplete') === false) {
$attributes .= ' autocomplete="new-password" ';
}
}
parent::__construct($elementName, $elementLabel, $attributes);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_password($elementName=null, $elementLabel=null, $attributes=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $attributes);
}
/**
* get html for help button
*
* @return string html for help button
*/
function getHelpButton(){
return $this->_helpbutton;
}
/**
* Sets label to be hidden
*
* @param bool $hiddenLabel sets if label should be hidden
*/
public function setHiddenLabel($hiddenLabel) {
$this->_hiddenLabel = $hiddenLabel;
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Password type form element with unmask option
*
* Contains HTML class for a password type element with unmask option
*
* @package core_form
* @copyright 2009 Petr Skoda
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
if (!defined('MOODLE_INTERNAL')) {
die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
}
global $CFG;
require_once($CFG->libdir.'/form/password.php');
/**
* Password type form element with unmask option
*
* HTML class for a password type element with unmask option
*
* @package core_form
* @category form
* @copyright 2009 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_passwordunmask extends MoodleQuickForm_password {
/**
* constructor
*
* @param string $elementName (optional) name of the password element
* @param string $elementLabel (optional) label for password element
* @param mixed $attributes (optional) Either a typical HTML attribute string
* or an associative array
*/
public function __construct($elementName=null, $elementLabel=null, $attributes=null) {
$this->_persistantFreeze = true;
parent::__construct($elementName, $elementLabel, $attributes);
$this->setType('passwordunmask');
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_passwordunmask($elementName=null, $elementLabel=null, $attributes=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $attributes);
}
/**
* Function to export the renderer data in a format that is suitable for a mustache template.
*
* @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
* @return stdClass|array
*/
public function export_for_template(renderer_base $output) {
$context = parent::export_for_template($output);
$context['valuechars'] = array_fill(0, strlen($context['value'] ?? ''), 'x');
return $context;
}
/**
* Check that there is no whitespace at the beginning and end of the password.
*
* It turned out that wrapping whitespace can easily be pasted by accident when copying the text from elsewhere.
* Such a mistake is very hard to debug as the whitespace is not displayed.
*
* @param string $value Submitted value.
* @return string|null Validation error message or null.
*/
public function validateSubmitValue($value) {
if ($value !== null && $value !== trim($value)) {
return get_string('err_wrappingwhitespace', 'core_form');
}
return;
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Drop down for question categories.
*
* Contains HTML class for a drop down element to select a question category.
*
* @package core_form
* @copyright 2007 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
use qbank_managecategories\helper;
require_once("$CFG->libdir/form/selectgroups.php");
require_once("$CFG->libdir/questionlib.php");
/**
* Drop down for question categories.
*
* HTML class for a drop down element to select a question category.
*
* @package core_form
* @category form
* @copyright 2007 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_questioncategory extends MoodleQuickForm_selectgroups {
/** @var array default options for question categories */
var $_options = array('top'=>false, 'currentcat'=>0, 'nochildrenof' => -1);
/**
* Constructor
*
* @param string $elementName Select name attribute
* @param mixed $elementLabel Label(s) for the select
* @param array $options additional options. Recognised options are courseid, published and
* only_editable, corresponding to the arguments of question_category_options
* from moodlelib.php.
* @param mixed $attributes Either a typical HTML attribute string or an associative array
*/
public function __construct($elementName = null, $elementLabel = null, $options = null, $attributes = null) {
parent::__construct($elementName, $elementLabel, array(), $attributes);
$this->_type = 'questioncategory';
if (is_array($options)) {
$this->_options = $options + $this->_options;
$this->loadArrayOptGroups(
helper::question_category_options($this->_options['contexts'], $this->_options['top'],
$this->_options['currentcat'], false, $this->_options['nochildrenof'], false));
}
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_questioncategory($elementName = null, $elementLabel = null, $options = null, $attributes = null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $options, $attributes);
}
}
+130
View File
@@ -0,0 +1,130 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* radio type form element
*
* Contains HTML class for a radio type element
*
* @package core_form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('HTML/QuickForm/radio.php');
require_once('templatable_form_element.php');
/**
* radio type form element
*
* HTML class for a radio type element
*
* @package core_form
* @category form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_radio extends HTML_QuickForm_radio implements templatable {
use templatable_form_element;
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/** @var bool if true label will be hidden. */
protected $_hiddenLabel = false;
/**
* constructor
*
* @param string $elementName (optional) name of the radio element
* @param string $elementLabel (optional) label for radio element
* @param string $text (optional) Text to put after the radio element
* @param string $value (optional) default value
* @param mixed $attributes (optional) Either a typical HTML attribute string
* or an associative array
*/
public function __construct($elementName=null, $elementLabel=null, $text=null, $value=null, $attributes=null) {
parent::__construct($elementName, $elementLabel, $text, $value, $attributes);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_radio($elementName=null, $elementLabel=null, $text=null, $value=null, $attributes=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $text, $value, $attributes);
}
/**
* get html for help button
*
* @return string html for help button
*/
function getHelpButton(){
return $this->_helpbutton;
}
/**
* Slightly different container template when frozen.
*
* @return string
*/
function getElementTemplateType(){
if ($this->_flagFrozen){
return 'static';
} else {
return 'default';
}
}
/**
* Returns the disabled field. Accessibility: the return "( )" from parent
* class is not acceptable for screenreader users, and we DO want a label.
*
* @return string
*/
function getFrozenHtml()
{
$output = '<input type="radio" disabled="disabled" id="'.$this->getAttribute('id').'" ';
if ($this->getChecked()) {
$output .= 'checked="checked" />'.$this->_getPersistantData();
} else {
$output .= '/>';
}
return $output;
}
/**
* Returns HTML for advchecbox form element.
*
* @return string
*/
function toHtml()
{
return '<span>' . parent::toHtml() . '</span>';
}
/**
* Sets label to be hidden
*
* @param bool $hiddenLabel sets if label should be hidden
*/
public function setHiddenLabel($hiddenLabel) {
$this->_hiddenLabel = $hiddenLabel;
}
}
+129
View File
@@ -0,0 +1,129 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* recaptcha type form element
*
* Contains HTML class for a recaptcha type element
*
* @package core_form
* @copyright 2008 Nicolas Connault <nicolasconnault@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('HTML/QuickForm/input.php');
require_once('templatable_form_element.php');
/**
* recaptcha type form element
*
* HTML class for a recaptcha type element
*
* @package core_form
* @category form
* @copyright 2008 Nicolas Connault <nicolasconnault@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_recaptcha extends HTML_QuickForm_input implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/**
* constructor
*
* @param string $elementName (optional) name of the recaptcha element
* @param string $elementLabel (optional) label for recaptcha element
* @param mixed $attributes (optional) Either a typical HTML attribute string
* or an associative array
*/
public function __construct($elementName = null, $elementLabel = null, $attributes = null) {
parent::__construct($elementName, $elementLabel, $attributes);
$this->_type = 'recaptcha';
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_recaptcha($elementName = null, $elementLabel = null, $attributes = null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $attributes);
}
/**
* Returns the reCAPTCHA element in HTML
*
* @return string The HTML to render
*/
public function toHtml() {
global $CFG;
require_once($CFG->libdir . '/recaptchalib_v2.php');
return recaptcha_get_challenge_html(RECAPTCHA_API_URL, $CFG->recaptchapublickey);
}
/**
* get html for help button
*
* @return string html for help button
*/
function getHelpButton(){
return $this->_helpbutton;
}
/**
* Checks recaptcha response with Google.
*
* @param string $responsestr
* @return bool
*/
public function verify($responsestr) {
global $CFG;
require_once($CFG->libdir . '/recaptchalib_v2.php');
$response = recaptcha_check_response(RECAPTCHA_VERIFY_URL, $CFG->recaptchaprivatekey,
getremoteaddr(), $responsestr);
if (!$response['isvalid']) {
$attributes = $this->getAttributes();
$attributes['error_message'] = $response['error'];
$this->setAttributes($attributes);
return $response['error'];
}
return true;
}
public function export_for_template(renderer_base $output) {
$context = $this->export_for_template_base($output);
$context['html'] = $this->toHtml();
return $context;
}
/**
* Get force LTR option.
*
* @return bool
*/
public function get_force_ltr() {
return true;
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Searchable selector field (alias for autocomplete).
*
* Allows auto-complete selector.
*
* @package core_form
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once($CFG->libdir . '/form/autocomplete.php');
/**
* Form field type for selecting from a list of options.
*
* Allows auto-complete ajax searching.
*
* @package core_form
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_searchableselector extends MoodleQuickForm_autocomplete {
/**
* Constructor
*
* @param string $elementname Element name
* @param mixed $elementlabel Label(s) for an element
* @param array $options List of valid options for the select
* @param array $attributes List of HTML attributes for the select
*/
public function __construct($elementname = null, $elementlabel = null, $options = [], $attributes = []) {
unset($options['']);
$options = ['' => get_string('noselection', 'form')] + $options;
parent::__construct($elementname, $elementlabel, $options, $attributes);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_searchableselector($elementName=null, $elementLabel=null, $options=null, $attributes=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $options, $attributes);
}
}
+238
View File
@@ -0,0 +1,238 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* select type form element
*
* Contains HTML class for a select type element
*
* @package core_form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('HTML/QuickForm/select.php');
require_once('templatable_form_element.php');
/**
* select type form element
*
* HTML class for a select type element
*
* @package core_form
* @category form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_select extends HTML_QuickForm_select implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/** @var bool if true label will be hidden */
var $_hiddenLabel=false;
/**
* constructor
*
* @param string $elementName Select name attribute
* @param mixed $elementLabel Label(s) for the select
* @param mixed $options Data to be used to populate options
* @param mixed $attributes Either a typical HTML attribute string or an associative array
*/
public function __construct($elementName=null, $elementLabel=null, $options=null, $attributes=null) {
parent::__construct($elementName, $elementLabel, $options, $attributes);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_select($elementName=null, $elementLabel=null, $options=null, $attributes=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $options, $attributes);
}
/**
* Sets label to be hidden
*
* @param bool $hiddenLabel sets if label should be hidden
*/
function setHiddenLabel($hiddenLabel){
$this->_hiddenLabel = $hiddenLabel;
}
/**
* Returns HTML for select form element.
*
* @return string
*/
function toHtml(){
$html = '';
if ($this->getMultiple()) {
// Adding an hidden field forces the browser to send an empty data even though the user did not
// select any element. This value will be cleaned up in self::exportValue() as it will not be part
// of the select options.
$html .= '<input type="hidden" name="'.$this->getName().'" value="_qf__force_multiselect_submission">';
}
if ($this->_hiddenLabel){
$this->_generateId();
$html .= '<label class="accesshide" for="'.$this->getAttribute('id').'" >'.$this->getLabel().'</label>';
}
$html .= parent::toHtml();
return $html;
}
/**
* get html for help button
*
* @return string html for help button
*/
function getHelpButton(){
return $this->_helpbutton;
}
/**
* Removes an OPTION from the SELECT
*
* @param string $value Value for the OPTION to remove
* @return void
*/
function removeOption($value)
{
$key=array_search($value, $this->_values);
if ($key!==FALSE and $key!==null) {
unset($this->_values[$key]);
}
foreach ($this->_options as $key=>$option){
if ($option['attr']['value']==$value){
unset($this->_options[$key]);
// we must reindex the options because the ugly code in quickforms' select.php expects that keys are 0,1,2,3... !?!?
$this->_options = array_merge($this->_options);
return;
}
}
}
/**
* Removes all OPTIONs from the SELECT
*/
function removeOptions()
{
$this->_options = array();
}
/**
* Slightly different container template when frozen. Don't want to use a label tag
* with a for attribute in that case for the element label but instead use a div.
* Templates are defined in renderer constructor.
*
* @return string
*/
function getElementTemplateType(){
if ($this->_flagFrozen){
return 'static';
} else {
return 'default';
}
}
/**
* We check the options and return only the values that _could_ have been
* selected. We also return a scalar value if select is not "multiple"
*
* @param array $submitValues submitted values
* @param bool $assoc if true the retured value is associated array
* @return mixed
*/
function exportValue(&$submitValues, $assoc = false)
{
$emptyvalue = $this->getMultiple() ? [] : null;
if (empty($this->_options)) {
return $this->_prepareValue($emptyvalue, $assoc);
}
$value = $this->_findValue($submitValues);
if (is_null($value)) {
$value = $this->getValue();
}
$value = (array)$value;
$cleaned = array();
foreach ($value as $v) {
foreach ($this->_options as $option) {
if ((string)$option['attr']['value'] === (string)$v) {
$cleaned[] = (string)$option['attr']['value'];
break;
}
}
}
if (empty($cleaned)) {
return $this->_prepareValue($emptyvalue, $assoc);
}
if ($this->getMultiple()) {
return $this->_prepareValue($cleaned, $assoc);
} else {
return $this->_prepareValue($cleaned[0], $assoc);
}
}
public function export_for_template(renderer_base $output) {
// For multi-selects, if there is not already a size set, then set a sensible default.
if ($this->getMultiple()) {
if (!isset($this->_attributes['size'])) {
$this->_attributes['size'] = min(count($this->_options), 10);
}
}
$context = $this->export_for_template_base($output);
$options = [];
// Standard option attributes.
$standardoptionattributes = ['text', 'value', 'selected', 'disabled'];
foreach ($this->_options as $option) {
if (is_array($this->_values) && in_array( (string) $option['attr']['value'], $this->_values)) {
$this->_updateAttrArray($option['attr'], ['selected' => 'selected']);
}
$o = [
'text' => $option['text'],
'value' => $option['attr']['value'],
'selected' => !empty($option['attr']['selected']),
'disabled' => !empty($option['attr']['disabled']),
];
// Set other attributes.
$otheroptionattributes = [];
foreach ($option['attr'] as $attr => $value) {
if (!in_array($attr, $standardoptionattributes) && $attr != 'class' && !is_object($value)) {
$otheroptionattributes[] = $attr . '="' . s($value) . '"';
}
}
$o['optionattributes'] = implode(' ', $otheroptionattributes);
$options[] = $o;
}
$context['options'] = $options;
$context['nameraw'] = $this->getName();
return $context;
}
}
+570
View File
@@ -0,0 +1,570 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* select type form element
*
* Class to dynamically create an HTML SELECT with all options grouped in optgroups
*
* @package core_form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('HTML/QuickForm/element.php');
require_once('templatable_form_element.php');
/**
* select type form element
*
* Class to dynamically create an HTML SELECT with all options grouped in optgroups
*
* @package core_form
* @category form
* @copyright 2007 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_selectgroups extends HTML_QuickForm_element implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var bool add choose option */
var $showchoose = false;
/** @var array Contains the select optgroups */
var $_optGroups = array();
/** @var array Default values of the SELECT */
var $_values = null;
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/** @var bool if true label will be hidden */
var $_hiddenLabel=false;
/**
* Class constructor
*
* @param string $elementName Select name attribute
* @param mixed $elementLabel Label(s) for the select
* @param array $optgrps Data to be used to populate options
* @param mixed $attributes Either a typical HTML attribute string or an associative array
* @param bool $showchoose add standard moodle "Choose..." option as first item
*/
public function __construct($elementName=null, $elementLabel=null, $optgrps=null, $attributes=null, $showchoose=false)
{
$this->showchoose = $showchoose;
parent::__construct($elementName, $elementLabel, $attributes);
$this->_persistantFreeze = true;
$this->_type = 'selectgroups';
if (isset($optgrps)) {
$this->loadArrayOptGroups($optgrps);
}
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_selectgroups($elementName=null, $elementLabel=null, $optgrps=null, $attributes=null, $showchoose=false) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $optgrps, $attributes, $showchoose);
}
/**
* Sets the default values of the select box
*
* @param mixed $values Array or comma delimited string of selected values
*/
function setSelected($values)
{
if (is_string($values) && $this->getMultiple()) {
$values = preg_split("/[ ]?,[ ]?/", $values);
}
if (is_array($values)) {
$this->_values = array_values($values);
} else {
$this->_values = array($values);
}
}
/**
* Returns an array of the selected values
*
* @return array of selected values
*/
function getSelected()
{
return $this->_values;
}
/**
* Sets the input field name
*
* @param string $name Input field name attribute
*/
function setName($name)
{
$this->updateAttributes(array('name' => $name));
}
/**
* Returns the element name
*
* @return string
*/
function getName()
{
return $this->getAttribute('name');
}
/**
* Returns the element name (possibly with brackets appended)
*
* @return string
*/
function getPrivateName()
{
if ($this->getAttribute('multiple')) {
return $this->getName() . '[]';
} else {
return $this->getName();
}
}
/**
* Sets the value of the form element
*
* @param mixed $value Array or comma delimited string of selected values
*/
function setValue($value)
{
$this->setSelected($value);
}
/**
* Returns an array of the selected values
*
* @return array of selected values
*/
function getValue()
{
return $this->_values;
}
/**
* Sets the select field size, only applies to 'multiple' selects
*
* @param int $size Size of select field
*/
function setSize($size)
{
$this->updateAttributes(array('size' => $size));
}
/**
* Returns the select field size
*
* @return int
*/
function getSize()
{
return $this->getAttribute('size');
}
/**
* Sets the select mutiple attribute
*
* @param bool $multiple Whether the select supports multi-selections
*/
function setMultiple($multiple)
{
if ($multiple) {
$this->updateAttributes(array('multiple' => 'multiple'));
} else {
$this->removeAttribute('multiple');
}
}
/**
* Returns the select mutiple attribute
*
* @return bool true if multiple select, false otherwise
*/
function getMultiple()
{
return (bool)$this->getAttribute('multiple');
}
/**
* Loads the options from an associative array
*
* @param array $arr Associative array of options
* @param mixed $values (optional) Array or comma delimited string of selected values
* @return PEAR_Error|bool on error or true
* @throws PEAR_Error
*/
function loadArrayOptGroups($arr, $values=null)
{
if (!is_array($arr)) {
return self::raiseError('Argument 1 of HTML_Select::loadArrayOptGroups is not a valid array');
}
if (isset($values)) {
$this->setSelected($values);
}
foreach ($arr as $key => $val) {
// Warning: new API since release 2.3
$this->addOptGroup($key, $val);
}
return true;
}
/**
* Adds a new OPTION to the SELECT
*
* @param string $text Display text for the OPTION
* @param string $value Value for the OPTION
* @param mixed $attributes Either a typical HTML attribute string
* or an associative array
*/
function addOptGroup($text, $value, $attributes=null)
{
if (null === $attributes) {
$attributes = array('label' => $text);
} else {
$attributes = $this->_parseAttributes($attributes);
$this->_updateAttrArray($attributes, array('label' => $text));
}
$index = count($this->_optGroups);
$this->_optGroups[$index] = array('attr' => $attributes);
$this->loadArrayOptions($index, $value);
}
/**
* Loads the options from an associative array
*
* @param string $optgroup name of the options group
* @param array $arr Associative array of options
* @param mixed $values (optional) Array or comma delimited string of selected values
* @return PEAR_Error|bool on error or true
* @throws PEAR_Error
*/
function loadArrayOptions($optgroup, $arr, $values=null)
{
if (!is_array($arr)) {
return self::raiseError('Argument 1 of HTML_Select::loadArray is not a valid array');
}
if (isset($values)) {
$this->setSelected($values);
}
foreach ($arr as $key => $val) {
// Warning: new API since release 2.3
$this->addOption($optgroup, $val, $key);
}
return true;
}
/**
* Adds a new OPTION to an optgroup
*
* @param string $optgroup name of the option group
* @param string $text Display text for the OPTION
* @param string $value Value for the OPTION
* @param mixed $attributes Either a typical HTML attribute string
* or an associative array
*/
function addOption($optgroup, $text, $value, $attributes=null)
{
if (null === $attributes) {
$attributes = array('value' => $value);
} else {
$attributes = $this->_parseAttributes($attributes);
if (isset($attributes['selected'])) {
// the 'selected' attribute will be set in toHtml()
$this->_removeAttr('selected', $attributes);
if (is_null($this->_values)) {
$this->_values = array($value);
} elseif (!in_array($value, $this->_values)) {
$this->_values[] = $value;
}
}
$this->_updateAttrArray($attributes, array('value' => $value));
}
$this->_optGroups[$optgroup]['options'][] = array('text' => $text, 'attr' => $attributes);
}
/**
* Returns the SELECT in HTML
*
* @return string
*/
function toHtml()
{
if ($this->_flagFrozen) {
return $this->getFrozenHtml();
} else {
$tabs = $this->_getTabs();
$strHtml = '';
if ($this->getComment() != '') {
$strHtml .= $tabs . '<!-- ' . $this->getComment() . " //-->\n";
}
if (!$this->getMultiple()) {
$attrString = $this->_getAttrString($this->_attributes);
} else {
$myName = $this->getName();
$this->setName($myName . '[]');
$attrString = $this->_getAttrString($this->_attributes);
$this->setName($myName);
}
$strHtml .= $tabs;
if ($this->_hiddenLabel){
$this->_generateId();
$strHtml .= '<label class="accesshide" for="'.$this->getAttribute('id').'" >'.
$this->getLabel().'</label>';
}
$strHtml .= '<select' . $attrString . ">\n";
if ($this->showchoose) {
$strHtml .= $tabs . "\t\t<option value=\"\">" . get_string('choose') . "...</option>\n";
}
foreach ($this->_optGroups as $optGroup) {
if (empty($optGroup['options'])) {
//xhtml strict
continue;
}
$strHtml .= $tabs . "\t<optgroup" . ($this->_getAttrString($optGroup['attr'])) . '>';
foreach ($optGroup['options'] as $option){
if (is_array($this->_values) && in_array((string)$option['attr']['value'], $this->_values)) {
$this->_updateAttrArray($option['attr'], array('selected' => 'selected'));
}
$strHtml .= $tabs . "\t\t<option" . $this->_getAttrString($option['attr']) . '>' .
$option['text'] . "</option>\n";
}
$strHtml .= $tabs . "\t</optgroup>\n";
}
return $strHtml . $tabs . '</select>';
}
}
/**
* Returns the value of field without HTML tags
*
* @return string
*/
function getFrozenHtml()
{
$value = array();
if (is_array($this->_values)) {
foreach ($this->_values as $key => $val) {
foreach ($this->_optGroups as $optGroup) {
if (empty($optGroup['options'])) {
continue;
}
for ($i = 0, $optCount = count($optGroup['options']); $i < $optCount; $i++) {
if ((string)$val == (string)$optGroup['options'][$i]['attr']['value']) {
$value[$key] = $optGroup['options'][$i]['text'];
break;
}
}
}
}
}
$html = empty($value)? '&nbsp;': join('<br />', $value);
if ($this->_persistantFreeze) {
$name = $this->getPrivateName();
// Only use id attribute if doing single hidden input
if (1 == count($value)) {
$id = $this->getAttribute('id');
$idAttr = isset($id)? array('id' => $id): array();
} else {
$idAttr = array();
}
foreach ($value as $key => $item) {
$html .= '<input' . $this->_getAttrString(array(
'type' => 'hidden',
'name' => $name,
'value' => $this->_values[$key]
) + $idAttr) . ' />';
}
}
return $html;
}
/**
* We check the options and return only the values that _could_ have been
* selected. We also return a scalar value if select is not "multiple"
*
* @param array $submitValues submitted values
* @param bool $assoc if true the retured value is associated array
* @return mixed
*/
function exportValue(&$submitValues, $assoc = false)
{
if (empty($this->_optGroups)) {
return $this->_prepareValue(null, $assoc);
}
$value = $this->_findValue($submitValues);
if (is_null($value)) {
$value = $this->getValue();
}
$value = (array)$value;
$cleaned = array();
foreach ($value as $v) {
foreach ($this->_optGroups as $optGroup){
if (empty($optGroup['options'])) {
continue;
}
foreach ($optGroup['options'] as $option) {
if ((string)$option['attr']['value'] === (string)$v) {
$cleaned[] = (string)$option['attr']['value'];
break;
}
}
}
}
if (empty($cleaned)) {
return $this->_prepareValue(null, $assoc);
}
if ($this->getMultiple()) {
return $this->_prepareValue($cleaned, $assoc);
} else {
return $this->_prepareValue($cleaned[0], $assoc);
}
}
/**
* 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)
{
if ('updateValue' == $event) {
$value = $this->_findValue($caller->_constantValues);
if (null === $value) {
$value = $this->_findValue($caller->_submitValues);
// Fix for bug #4465 & #5269
// XXX: should we push this to element::onQuickFormEvent()?
if (null === $value && (!$caller->isSubmitted() || !$this->getMultiple())) {
$value = $this->_findValue($caller->_defaultValues);
}
}
if (null !== $value) {
$this->setValue($value);
}
return true;
} else {
return parent::onQuickFormEvent($event, $arg, $caller);
}
}
/**
* Sets label to be hidden
*
* @param bool $hiddenLabel sets if label should be hidden
*/
function setHiddenLabel($hiddenLabel){
$this->_hiddenLabel = $hiddenLabel;
}
/**
* get html for help button
*
* @return string html for help button
*/
function getHelpButton(){
return $this->_helpbutton;
}
/**
* Slightly different container template when frozen. Don't want to use a label tag
* with a for attribute in that case for the element label but instead use a div.
* Templates are defined in renderer constructor.
*
* @return string
*/
function getElementTemplateType(){
if ($this->_flagFrozen){
return 'static';
} else {
return 'default';
}
}
public function export_for_template(renderer_base $output) {
$context = $this->export_for_template_base($output);
$optiongroups = [];
if ($this->showchoose) {
$optionsgroups[] = [
'text' => get_string('choosedots')
];
}
// Standard option attributes.
$standardoptionattributes = ['text', 'value', 'selected', 'disabled'];
foreach ($this->_optGroups as $group) {
$options = [];
if (empty($group['options'])) {
continue;
}
foreach ($group['options'] as $option) {
$o = ['value' => (string)$option['attr']['value']];
if (is_array($this->_values) && in_array($o['value'], $this->_values)) {
$o['selected'] = true;
} else {
$o['selected'] = false;
}
$o['text'] = $option['text'];
$o['disabled'] = !empty($option['attr']['disabled']);
// Set other attributes.
$otheroptionattributes = [];
foreach ($option['attr'] as $attr => $value) {
if (!in_array($attr, $standardoptionattributes) && $attr != 'class' && !is_object($value)) {
$otheroptionattributes[] = $attr . '="' . s($value) . '"';
}
}
$o['optionattributes'] = implode(' ', $otheroptionattributes);
$options[] = $o;
}
$og = [
'text' => $group['attr']['label'],
'options' => $options
];
$optiongroups[] = $og;
}
$context['optiongroups'] = $optiongroups;
// If the select is a static element and does not allow the user to change the value (Ex: Auth method),
// we need to change the label to static.
$context['staticlabel'] = $this->_flagFrozen;
return $context;
}
}
+275
View File
@@ -0,0 +1,275 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* select type form element
*
* Contains HTML class for a select type element with options containing link
*
* @package core_form
* @copyright 2008 Nicolas Connault <nicolasconnault@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('HTML/QuickForm/select.php');
require_once('templatable_form_element.php');
/**
* select type form element
*
* HTML class for a select type element with options containing link
*
* @deprecated since 3.2
* @package core_form
* @category form
* @copyright 2008 Nicolas Connault <nicolasconnault@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_selectwithlink extends HTML_QuickForm_select implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/** @var bool if true label will be hidden */
var $_hiddenLabel=false;
/** @var string url to which select option will be posted */
var $_link=null;
/** @var string data which will be posted to link */
var $_linklabel=null;
/** @var string url return link */
var $_linkreturn=null;
/**
* constructor
*
* @param string $elementName Select name attribute
* @param mixed $elementLabel Label(s) for the select
* @param array $options Data to be used to populate options
* @param mixed $attributes Either a typical HTML attribute string or an associative array
* @param bool $linkdata data to be posted
*/
public function __construct($elementName=null, $elementLabel=null, $options=null, $attributes=null, $linkdata=null) {
if (!empty($linkdata['link']) && !empty($linkdata['label'])) {
$this->_link = $linkdata['link'];
$this->_linklabel = $linkdata['label'];
}
if (!empty($linkdata['return'])) {
$this->_linkreturn = $linkdata['return'];
}
parent::__construct($elementName, $elementLabel, $options, $attributes);
$this->_type = 'selectwithlink';
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_selectwithlink($elementName=null, $elementLabel=null, $options=null, $attributes=null, $linkdata=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $options, $attributes, $linkdata);
}
/**
* Sets label to be hidden
*
* @param bool $hiddenLabel sets if label should be hidden
*/
function setHiddenLabel($hiddenLabel){
$this->_hiddenLabel = $hiddenLabel;
}
/**
* Returns the SELECT in HTML
*
* @return string
*/
function toHtml(){
$retval = '';
if ($this->_hiddenLabel){
$this->_generateId();
$retval = '<label class="accesshide" for="'.$this->getAttribute('id').'" >'.
$this->getLabel().'</label>'.parent::toHtml();
} else {
$retval = parent::toHtml();
}
if (!empty($this->_link)) {
if (!empty($this->_linkreturn) && is_array($this->_linkreturn)) {
$appendchar = '?';
if (strstr($this->_link, '?')) {
$appendchar = '&amp;';
}
foreach ($this->_linkreturn as $key => $val) {
$this->_link .= $appendchar."$key=$val";
$appendchar = '&amp;';
}
}
$retval .= '<a style="margin-left: 5px" href="'.$this->_link.'">'.$this->_linklabel.'</a>';
}
return $retval;
}
/**
* get html for help button
*
* @return string html for help button
*/
function getHelpButton(){
return $this->_helpbutton;
}
/**
* Removes an OPTION from the SELECT
*
* @param string $value Value for the OPTION to remove
*/
function removeOption($value)
{
$key=array_search($value, $this->_values);
if ($key!==FALSE and $key!==null) {
unset($this->_values[$key]);
}
foreach ($this->_options as $key=>$option){
if ($option['attr']['value']==$value){
unset($this->_options[$key]);
return;
}
}
}
/**
* Removes all OPTIONs from the SELECT
*/
function removeOptions()
{
$this->_options = array();
}
/**
* Slightly different container template when frozen. Don't want to use a label tag
* with a for attribute in that case for the element label but instead use a div.
* Templates are defined in renderer constructor.
*
* @return string
*/
function getElementTemplateType(){
if ($this->_flagFrozen){
return 'static';
} else {
return 'default';
}
}
/**
* We check the options and return only the values that _could_ have been
* selected. We also return a scalar value if select is not "multiple"
*
* @param array $submitValues submitted values
* @param bool $assoc if true the retured value is associated array
* @return mixed
*/
function exportValue(&$submitValues, $assoc = false)
{
if (empty($this->_options)) {
return $this->_prepareValue(null, $assoc);
}
$value = $this->_findValue($submitValues);
if (is_null($value)) {
$value = $this->getValue();
}
$value = (array)$value;
$cleaned = array();
foreach ($value as $v) {
foreach ($this->_options as $option) {
if ((string)$option['attr']['value'] === (string)$v) {
$cleaned[] = (string)$option['attr']['value'];
break;
}
}
}
if (empty($cleaned)) {
return $this->_prepareValue(null, $assoc);
}
if ($this->getMultiple()) {
return $this->_prepareValue($cleaned, $assoc);
} else {
return $this->_prepareValue($cleaned[0], $assoc);
}
}
public function export_for_template(renderer_base $output) {
$context = $this->export_for_template_base($output);
$options = [];
// Standard option attributes.
$standardoptionattributes = ['text', 'value', 'selected', 'disabled'];
foreach ($this->_options as $option) {
if (is_array($this->_values) && in_array( (string) $option['attr']['value'], $this->_values)) {
$this->_updateAttrArray($option['attr'], ['selected' => 'selected']);
}
$o = [
'text' => $option['text'],
'value' => $option['attr']['value'],
'selected' => !empty($option['attr']['selected']),
'disabled' => !empty($option['attr']['disabled']),
];
// Set other attributes.
$otheroptionattributes = [];
foreach ($option['attr'] as $attr => $value) {
if (!in_array($attr, $standardoptionattributes) && $attr != 'class' && !is_object($value)) {
$otheroptionattributes[] = $attr . '="' . s($value) . '"';
}
}
$o['optionattributes'] = implode(' ', $otheroptionattributes);
$options[] = $o;
}
$context['options'] = $options;
if (!empty($this->_link)) {
if (!empty($this->_linkreturn) && is_array($this->_linkreturn)) {
$appendchar = '?';
if (strstr($this->_link, '?')) {
$appendchar = '&amp;';
}
foreach ($this->_linkreturn as $key => $val) {
$this->_link .= $appendchar."$key=$val";
$appendchar = '&amp;';
}
}
}
$context['link'] = $this->_link;
$context['linklabel'] = $this->_linklabel;
return $context;
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Yes/No drop down type form element
*
* Contains HTML class for a simple yes/ no drop down element
*
* @package core_form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once "$CFG->libdir/form/select.php";
/**
* Yes/No drop down type form element
*
* HTML class for a simple yes/ no drop down element
*
* @package core_form
* @category form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_selectyesno extends MoodleQuickForm_select{
/**
* Class constructor
*
* @param string $elementName Select name attribute
* @param mixed $elementLabel Label(s) for the select
* @param mixed $attributes Either a typical HTML attribute string or an associative array
* @param mixed $options ignored, not used.
*/
public function __construct($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
// TODO MDL-52313 Replace with the call to parent::__construct().
HTML_QuickForm_element::__construct($elementName, $elementLabel, $attributes, null);
$this->_type = 'selectyesno';
$this->_persistantFreeze = true;
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_selectyesno($elementName=null, $elementLabel=null, $attributes=null, $options=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $attributes, $options);
}
/**
* 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 mixed
*/
function onQuickFormEvent($event, $arg, &$caller)
{
switch ($event) {
case 'createElement':
$choices=array();
$choices[0] = get_string('no');
$choices[1] = get_string('yes');
$this->load($choices);
break;
}
return parent::onQuickFormEvent($event, $arg, $caller);
}
}
+97
View File
@@ -0,0 +1,97 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Text type form element
*
* Contains HTML class for a text type element
*
* @package core_form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("HTML/QuickForm/static.php");
require_once('templatable_form_element.php');
/**
* Text type element
*
* HTML class for a text type element
*
* @package core_form
* @category form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_static extends HTML_QuickForm_static implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/** @var string Form element type */
var $_elementTemplateType='static';
/** @var string html for help button, if empty then no help */
var $_helpbutton='';
/**
* constructor
*
* @param string $elementName (optional) name of the text field
* @param string $elementLabel (optional) text field label
* @param string $text (optional) Text to put in text field
*/
public function __construct($elementName=null, $elementLabel=null, $text=null) {
parent::__construct($elementName, $elementLabel, $text);
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_static($elementName=null, $elementLabel=null, $text=null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $text);
}
/**
* get html for help button
*
* @return string html for help button
*/
function getHelpButton(){
return $this->_helpbutton;
}
/**
* Gets the type of form element
*
* @return string
*/
function getElementTemplateType(){
return $this->_elementTemplateType;
}
public function export_for_template(renderer_base $output) {
$context = $this->export_for_template_base($output);
$context['html'] = $this->toHtml();
$context['staticlabel'] = true;
return $context;
}
}
+156
View File
@@ -0,0 +1,156 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* submit type form element
*
* Contains HTML class for a submit type element
*
* @package core_form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once("HTML/QuickForm/submit.php");
require_once('templatable_form_element.php');
/**
* submit type form element
*
* HTML class for a submit type element
*
* @package core_form
* @category form
* @copyright 2006 Jamie Pratt <me@jamiep.org>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_submit extends HTML_QuickForm_submit implements templatable {
use templatable_form_element {
export_for_template as export_for_template_base;
}
/**
* @var bool $primary Is this button a primary button?
*/
protected $primary;
/**
* Any class apart from 'btn' would be overridden with this content.
*
* By default, submit buttons will utilize the btn-primary OR btn-secondary classes. However there are cases where we
* require a submit button with different stylings (e.g. btn-link). In these cases, $customclassoverride will override
* the defaults mentioned previously and utilize the provided class(es).
*
* @var string $customclassoverride Custom class override for the input element
*/
protected $customclassoverride;
/**
* constructor
*
* @param string $elementName (optional) name of the field
* @param string $value (optional) field label
* @param string $attributes (optional) Either a typical HTML attribute string or an associative array
* @param bool|null $primary Is this button a primary button?
* @param array $options Options to further customise the submit button. Currently accepted options are:
* customclassoverride String The CSS class to use for the button instead of the standard
* btn-primary and btn-secondary classes.
*/
public function __construct($elementName=null, $value=null, $attributes=null, $primary = null, $options = []) {
parent::__construct($elementName, $value, $attributes);
// Fallback to legacy behaviour if no value specified.
if (is_null($primary)) {
$this->primary = $this->getName() != 'cancel';
} else {
$this->primary = $primary;
}
$this->customclassoverride = $options['customclassoverride'] ?? false;
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_submit($elementName=null, $value=null, $attributes=null, $primary = null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $value, $attributes, $primary);
}
/**
* 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
*/
function onQuickFormEvent($event, $arg, &$caller)
{
switch ($event) {
case 'createElement':
parent::onQuickFormEvent($event, $arg, $caller);
if ($caller->isNoSubmitButton($arg[0])){
//need this to bypass client validation
//for buttons that submit but do not process the
//whole form.
$onClick = $this->getAttribute('onclick');
$skip = 'skipClientValidation = true;';
$onClick = ($onClick !== null)?$skip.' '.$onClick:$skip;
$this->updateAttributes(array('data-skip-validation' => 1, 'data-no-submit' => 1, 'onclick' => $onClick));
}
return true;
break;
}
return parent::onQuickFormEvent($event, $arg, $caller);
}
/**
* Slightly different container template when frozen. Don't want to display a submit
* button if the form is frozen.
*
* @return string
*/
function getElementTemplateType(){
if ($this->_flagFrozen){
return 'nodisplay';
} else {
return 'actionbuttons';
}
}
/**
* Freeze the element so that only its value is returned
*/
function freeze(){
$this->_flagFrozen = true;
}
public function export_for_template(renderer_base $output) {
$context = $this->export_for_template_base($output);
if (!$this->primary) {
$context['secondary'] = true;
}
if ($this->customclassoverride) {
$context['customclassoverride'] = $this->customclassoverride;
}
return $context;
}
}
+274
View File
@@ -0,0 +1,274 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tag autocomplete field.
*
* Contains HTML class for editing tags, both standard and not.
*
* @package core_form
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once($CFG->libdir . '/form/autocomplete.php');
/**
* Form field type for editing tags.
*
* HTML class for editing tags, both standard and not.
*
* @package core_form
* @copyright 2009 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_tags extends MoodleQuickForm_autocomplete {
/**
* Inidcates that the user should be the usual interface, with the official
* tags listed seprately, and a text box where they can type anything.
* @deprecated since 3.1
* @var int
*/
const DEFAULTUI = 'defaultui';
/**
* Indicates that the user should only be allowed to select official tags.
* @deprecated since 3.1
* @var int
*/
const ONLYOFFICIAL = 'onlyofficial';
/**
* Indicates that the user should just be given a text box to type in (they
* can still type official tags though.
* @deprecated since 3.1
* @var int
*/
const NOOFFICIAL = 'noofficial';
/**
* @var boolean $showstandard Standard tags suggested? (if not, then don't show link to manage standard tags).
*/
protected $showstandard = false;
/**
* Options passed when creating an element.
* @var array
*/
protected $tagsoptions = array();
/**
* Constructor
*
* @param string $elementName Element name
* @param mixed $elementLabel Label(s) for an element
* @param array $options Options to control the element's display
* @param mixed $attributes Either a typical HTML attribute string or an associative array.
*/
public function __construct($elementName = null, $elementLabel = null, $options = array(), $attributes = null) {
$validoptions = array();
if (!empty($options)) {
// Only execute it when the element was created and $options has values set by user.
// In onQuickFormEvent() we make sure that $options is not empty even if developer left it empty.
$showstandard = core_tag_tag::BOTH_STANDARD_AND_NOT;
if (isset($options['showstandard'])) {
$showstandard = $options['showstandard'];
} else if (isset($options['display'])) {
debugging('Option "display" is deprecated, each tag area can be configured to show standard tags or not ' .
'by admin or manager. If it is necessary for the developer to override it, please use "showstandard" option',
DEBUG_DEVELOPER);
if ($options['display'] === self::NOOFFICIAL) {
$showstandard = core_tag_tag::HIDE_STANDARD;
} else if ($options['display'] === self::ONLYOFFICIAL) {
$showstandard = core_tag_tag::STANDARD_ONLY;
}
} else if (!empty($options['component']) && !empty($options['itemtype'])) {
$showstandard = core_tag_area::get_showstandard($options['component'], $options['itemtype']);
}
$this->tagsoptions = $options;
$this->showstandard = ($showstandard != core_tag_tag::HIDE_STANDARD);
if ($this->showstandard) {
$validoptions = $this->load_standard_tags();
}
// Option 'tags' allows us to type new tags.
$attributes['tags'] = ($showstandard != core_tag_tag::STANDARD_ONLY);
$attributes['multiple'] = 'multiple';
$attributes['placeholder'] = get_string('entertags', 'tag');
$attributes['showsuggestions'] = $this->showstandard;
}
parent::__construct($elementName, $elementLabel, $validoptions, $attributes);
$this->_type = 'tags';
}
/**
* 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
*/
public function onQuickFormEvent($event, $arg, &$caller) {
if ($event === 'createElement') {
if (!is_array($arg[2])) {
$arg[2] = [];
}
$arg[2] += array('itemtype' => '', 'component' => '');
}
return parent::onQuickFormEvent($event, $arg, $caller);
}
/**
* Checks if tagging is enabled for this itemtype
*
* @return boolean
*/
protected function is_tagging_enabled() {
if (!empty($this->tagsoptions['itemtype']) && !empty($this->tagsoptions['component'])) {
$enabled = core_tag_tag::is_enabled($this->tagsoptions['component'], $this->tagsoptions['itemtype']);
if ($enabled === false) {
return false;
}
}
// Backward compatibility with code developed before Moodle 3.0 where itemtype/component were not specified.
return true;
}
/**
* Old syntax of class constructor. Deprecated in PHP7.
*
* @deprecated since Moodle 3.1
*/
public function MoodleQuickForm_tags($elementName = null, $elementLabel = null, $options = array(), $attributes = null) {
debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
self::__construct($elementName, $elementLabel, $options, $attributes);
}
/**
* Finds the tag collection to use for standard tag selector
*
* @return int
*/
protected function get_tag_collection() {
if (empty($this->tagsoptions['tagcollid']) && (empty($this->tagsoptions['itemtype']) ||
empty($this->tagsoptions['component']))) {
debugging('You need to specify \'itemtype\' and \'component\' of the tagged '
. 'area in the tags form element options',
DEBUG_DEVELOPER);
}
if (!empty($this->tagsoptions['tagcollid'])) {
return $this->tagsoptions['tagcollid'];
}
if ($this->tagsoptions['itemtype']) {
$this->tagsoptions['tagcollid'] = core_tag_area::get_collection($this->tagsoptions['component'],
$this->tagsoptions['itemtype']);
} else {
$this->tagsoptions['tagcollid'] = core_tag_collection::get_default();
}
return $this->tagsoptions['tagcollid'];
}
/**
* Returns HTML for select form element.
*
* @return string
*/
function toHtml(){
global $OUTPUT;
$managelink = '';
if (has_capability('moodle/tag:manage', context_system::instance()) && $this->showstandard) {
$url = new moodle_url('/tag/manage.php', array('tc' => $this->get_tag_collection()));
$managelink = ' ' . $OUTPUT->action_link($url, get_string('managestandardtags', 'tag'));
}
return parent::toHTML() . $managelink;
}
/**
* Accepts a renderer
*
* @param HTML_QuickForm_Renderer $renderer An HTML_QuickForm_Renderer object
* @param bool $required Whether a group is required
* @param string $error An error message associated with a group
*/
public function accept(&$renderer, $required = false, $error = null) {
if ($this->is_tagging_enabled()) {
$renderer->renderElement($this, $required, $error);
} else {
$renderer->renderHidden($this);
}
}
/**
* Internal function to load standard tags
*/
protected function load_standard_tags() {
global $CFG, $DB;
if (!$this->is_tagging_enabled()) {
return array();
}
$namefield = empty($CFG->keeptagnamecase) ? 'name' : 'rawname';
$tags = $DB->get_records_menu('tag',
array('isstandard' => 1, 'tagcollid' => $this->get_tag_collection()),
$namefield, 'id,' . $namefield);
return array_combine($tags, $tags);
}
/**
* Returns a 'safe' element's value
*
* @param array $submitValues array of submitted values to search
* @param bool $assoc whether to return the value as associative array
* @return mixed
*/
public function exportValue(&$submitValues, $assoc = false) {
if (!$this->is_tagging_enabled()) {
return $this->_prepareValue([], $assoc);
}
if ($this->_findValue($submitValues) === '_qf__force_multiselect_submission') {
// Nothing was selected.
return $this->_prepareValue([], $assoc);
}
// Submitted tag data will be encoded, we want original text.
if (array_key_exists($this->getName(), $submitValues)) {
array_walk($submitValues[$this->getName()], static function(string &$tag): void {
$tag = html_entity_decode($tag, ENT_COMPAT);
});
}
return parent::exportValue($submitValues, $assoc);
}
public function export_for_template(renderer_base $output) {
$context = parent::export_for_template($output);
if (has_capability('moodle/tag:manage', context_system::instance()) && $this->showstandard) {
$url = new moodle_url('/tag/manage.php', array('tc' => $this->get_tag_collection()));
$context['managestandardtagsurl'] = $url->out(false);
}
return $context;
}
}
+97
View File
@@ -0,0 +1,97 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Adds export_for_template behaviour to an mform element in a consistent and predictable way.
*
* @package core_form
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
// Some form elements are used before $CFG is created - do not rely on it here.
require_once(__DIR__ . '/../outputcomponents.php');
/**
* templatable_form_element trait.
*
* @package core_form
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
trait templatable_form_element {
/**
* Function to export the renderer data in a format that is suitable for a
* mustache template. This means:
* 1. No complex types - only stdClass, array, int, string, float, bool
* 2. Any additional info that is required for the template is pre-calculated (e.g. capability checks).
*
* This trait can be used as-is for simple form elements - or imported with a different name
* so it can be extended with additional context variables before being returned.
*
* @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
* @return stdClass|array
*/
public function export_for_template(renderer_base $output) {
$context = [];
// Not all elements have all of these attributes - but they are common enough to be valid for a few.
$standardattributes = ['id', 'name', 'label', 'multiple', 'checked', 'error', 'size', 'value', 'type'];
$standardproperties = ['helpbutton', 'hiddenLabel'];
// Standard attributes.
foreach ($standardattributes as $attrname) {
$value = $this->getAttribute($attrname);
$context[$attrname] = $value;
}
// Standard class properties.
foreach ($standardproperties as $propname) {
$classpropname = '_' . $propname;
$context[strtolower($propname)] = isset($this->$classpropname) ? $this->$classpropname : false;
}
$extraclasses = $this->getAttribute('class');
$parentonlyclasses = $this->getAttribute('parentclass');
// Special wierd named property.
$context['frozen'] = !empty($this->_flagFrozen);
$context['hardfrozen'] = !empty($this->_flagFrozen) && empty($this->_persistantFreeze);
// Other attributes.
$otherattributes = [];
foreach ($this->getAttributes() as $attr => $value) {
if (!in_array($attr, $standardattributes) && $attr != 'class' && $attr != 'parentclass' && !is_object($value)) {
$otherattributes[] = $attr . '="' . s($value) . '"';
}
}
$context['extraclasses'] = $extraclasses;
$context['parentclasses'] = $parentonlyclasses;
$context['type'] = $this->getType();
$context['attributes'] = implode(' ', $otherattributes);
$context['emptylabel'] = ($this->getLabel() === '');
$context['iderror'] = preg_replace('/_id_/', '_id_error_', $context['id']);
$context['iderror'] = preg_replace('/^id_/', 'id_error_', $context['iderror']);
// Elements with multiple values need array syntax.
if ($this->getAttribute('multiple')) {
$context['name'] = $context['name'] . '[]';
}
return $context;
}
}
@@ -0,0 +1,37 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_form/collapsesections
Renders a form section header
Example context (json):
{
}
}}
<div class="collapsible-actions">
<a id="collapsesections{{uniqid}}" href="#" aria-expanded="false" class="btn btn-link p-1 collapseexpand collapsemenu collapsed"{{!
}} role="button">
<span class="collapseall">{{#str}}collapseall{{/str}}</span>
<span class="expandall">{{#str}}expandall{{/str}}</span>
</a>
</div>
{{#js}}
require(['core_form/collapsesections'], function(Collapsesections) {
Collapsesections.init('#collapsesections{{uniqid}}');
});
{{/js}}
@@ -0,0 +1,48 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_form/editor_textarea
Displays an editor field in a form.
Example context (json):
{
"name": "test",
"id": "test0",
"rows": 4,
"cols": 20,
"value": "Sample text",
"format": 3
}
}}
<div>
<textarea id="{{id}}" name="{{name}}[text]" class="form-control" rows="{{rows}}" cols="{{cols}}" spellcheck="true" {{#changelistener}} onblur="{{onblur}}"
onchange="{{onchange}}" {{/changelistener}}>{{value}}</textarea>
</div>
<div>
{{#hasformats}}
<label for="menu{{name}}format" class="sr-only">{{formatlabel}}</label>
<select name="{{name}}[format]" id="menu{{name}}format" class="custom-select">
{{#formats}}
<option value="{{value}}" {{#selected}}selected{{/selected}}>{{text}}</option>
{{/formats}}
</select>
{{/hasformats}}
{{^hasformats}}
<input name="{{name}}[format]" id="menu{{name}}format" type="hidden" value="{{format}}"/>
{{/hasformats}}
</div>
@@ -0,0 +1,41 @@
<label class="form-check-inline {{#error}}has-danger{{/error}} fitem {{#advanced}}advanced{{/advanced}} {{{element.extraclasses}}}">
{{^element.hardfrozen}}
{{^element.frozen}}
<input type="hidden" name="{{element.name}}" value="{{element.deselectedvalue}}">
{{/element.frozen}}
{{#element.frozen}}
<input type="hidden" name="{{element.name}}" value="{{element.frozenvalue}}">
{{/element.frozen}}
{{/element.hardfrozen}}
<input type="checkbox" name="{{element.name}}" class="form-check-input {{element.extraclasses}}"
id="{{element.id}}"
{{#element.selectedvalue}}
value="{{element.selectedvalue}}"
{{/element.selectedvalue}}
{{#element.checked}}checked{{/element.checked}}
{{#error}}
autofocus aria-describedby="{{element.iderror}}"
{{/error}}
{{#required}}
aria-required="true"
{{/required}}
{{#element.frozen}}
disabled
{{/element.frozen}}
{{{element.attributes}}} >
{{{label}}}
</label>
{{#text}}
<em>{{{.}}}</em>
{{/text}}
{{{helpbutton}}}
<span class="form-control-feedback invalid-feedback" id="{{element.iderror}}" {{#error}} style="display: block;"{{/error}}>
{{{error}}}
</span>
{{^element.frozen}}
{{#js}}
require(['theme_boost/form-display-errors'], function(module) {
module.enhance({{#quote}}{{element.id}}{{/quote}});
});
{{/js}}
{{/element.frozen}}
@@ -0,0 +1,71 @@
<div class="mb-3 row {{#error}}has-danger{{/error}} fitem {{#advanced}}advanced{{/advanced}} {{{element.extraclasses}}} {{{element.parentclasses}}}">
<div class="col-md-3 col-form-label pb-0 pt-0">
{{#text}}
<label class="d-inline word-break" for="{{element.id}}">
{{{label}}}
</label>
{{/text}}
</div>
<div class="col-md-9 checkbox">
<div class="form-check d-flex">
{{^element.hardfrozen}}
{{^element.frozen}}
<input type="hidden" name="{{element.name}}" value="{{element.deselectedvalue}}">
{{/element.frozen}}
{{#element.frozen}}
<input type="hidden" name="{{element.name}}" value="{{element.frozenvalue}}">
{{/element.frozen}}
{{/element.hardfrozen}}
<input type="checkbox"
name="{{element.name}}"
class="form-check-input {{element.extraclasses}}"
{{#element.selectedvalue}}
value="{{element.selectedvalue}}"
{{/element.selectedvalue}}
id="{{element.id}}" {{#element.checked}}checked{{/element.checked}}
{{#error}}
autofocus aria-describedby="{{#text}}{{element.id}}_description {{/text}}{{element.iderror}}"
{{/error}}
{{^error}}
{{#text}}
aria-describedby="{{element.id}}_description"
{{/text}}
{{/error}}
{{#required}}
aria-required="true"
{{/required}}
{{#element.frozen}}
disabled
{{/element.frozen}}
{{{element.attributes}}} >
{{#text}}
<span id="{{element.id}}_description">
{{{.}}}
</span>
{{/text}}
{{^text}}
<label for="{{element.id}}">
{{{label}}}
</label>
{{/text}}
<div class="ml-2 d-flex align-items-center align-self-start">
{{#required}}
<div class="text-danger" title="{{#str}}required{{/str}}">
{{#pix}}req, core, {{#str}}required{{/str}}{{/pix}}
</div>
{{/required}}
{{{helpbutton}}}
</div>
</div>
<div class="form-control-feedback invalid-feedback" id="{{element.iderror}}" {{#error}} style="display: block;"{{/error}}>
{{{error}}}
</div>
</div>
</div>
{{^element.frozen}}
{{#js}}
require(['theme_boost/form-display-errors'], function(module) {
module.enhance({{#quote}}{{element.id}}{{/quote}});
});
{{/js}}
{{/element.frozen}}
@@ -0,0 +1,46 @@
{{< core_form/element-template-inline }}
{{$element}}
{{^element.frozen}}
{{#element.multiple}}
<input type="hidden" name="{{element.nameraw}}" value="_qf__force_multiselect_submission">
{{/element.multiple}}
<select class="custom-select {{#error}}is-invalid{{/error}}" name="{{element.name}}"
id="{{element.id}}"
{{#element.multiple}}multiple{{/element.multiple}}
{{#error}}
autofocus aria-describedby="{{element.iderror}}"
{{/error}}
{{#required}}
aria-required="true"
{{/required}}
{{{element.attributes}}} >
{{#element.options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}{{#html}} data-html="{{html}}"{{/html}}>{{{text}}}</option>
{{/element.options}}
</select>
{{/element.frozen}}
{{#element.frozen}}
{{#element.options}}
{{#selected}}
{{{text}}}
{{^element.hardfrozen}}
<input type="hidden" name="{{element.name}}" value="{{value}}">
{{/element.hardfrozen}}
{{/selected}}
{{/element.options}}
{{/element.frozen}}
{{/element}}
{{/ core_form/element-template-inline }}
{{^element.frozen}}
{{#js}}
require(['core/form-autocomplete'], function(module) {
module.enhance({{#quote}}#{{element.id}}{{/quote}},
{{element.tags}},
{{#quote}}{{element.ajax}}{{/quote}},
{{#quote}}{{element.placeholder}}{{/quote}},
{{element.casesensitive}},
{{element.showsuggestions}},
{{#quote}}{{element.noselectionstring}}{{/quote}});
});
{{/js}}
{{/element.frozen}}
@@ -0,0 +1,46 @@
{{< core_form/element-template }}
{{$element}}
{{^element.frozen}}
{{#element.multiple}}
<input type="hidden" name="{{element.nameraw}}" value="_qf__force_multiselect_submission">
{{/element.multiple}}
<select class="custom-select {{#error}}is-invalid{{/error}}" name="{{element.name}}"
id="{{element.id}}"
{{#element.multiple}}multiple{{/element.multiple}}
{{#error}}
autofocus aria-describedby="{{element.iderror}}"
{{/error}}
{{#required}}
aria-required="true"
{{/required}}
{{{element.attributes}}} >
{{#element.options}}
<option value="{{value}}" {{#selected}}selected{{/selected}}{{#html}} data-html="{{html}}"{{/html}}>{{{text}}}</option>
{{/element.options}}
</select>
{{/element.frozen}}
{{#element.frozen}}
{{#element.options}}
{{#selected}}
{{{text}}}
{{^element.hardfrozen}}
<input type="hidden" name="{{element.name}}" value="{{value}}">
{{/element.hardfrozen}}
{{/selected}}
{{/element.options}}
{{/element.frozen}}
{{/element}}
{{/ core_form/element-template }}
{{^element.frozen}}
{{#js}}
require(['core/form-autocomplete'], function(module) {
module.enhance({{#quote}}#{{element.id}}{{/quote}},
{{element.tags}},
{{#quote}}{{element.ajax}}{{/quote}},
{{#quote}}{{element.placeholder}}{{/quote}},
{{element.casesensitive}},
{{element.showsuggestions}},
{{#quote}}{{element.noselectionstring}}{{/quote}});
});
{{/js}}
{{/element.frozen}}
@@ -0,0 +1,20 @@
{{< core_form/element-template-inline }}
{{$element}}
{{^element.frozen}}
<button
class="btn
{{^element.customclassoverride}}btn-secondary{{/element.customclassoverride}}
{{#element.customclassoverride}}{{.}}{{/element.customclassoverride}}"
name="{{element.name}}"
id="{{element.id}}"
type="button"
{{#error}}
autofocus aria-describedby="{{element.iderror}}"
{{/error}}
{{{element.attributes}}}
>
{{{element.value}}}
</button>
{{/element.frozen}}
{{/element}}
{{/ core_form/element-template-inline }}
@@ -0,0 +1,19 @@
{{< core_form/element-template }}
{{$element}}
{{^element.frozen}}
<button
class="btn
{{^element.customclassoverride}}btn-secondary ml-0{{/element.customclassoverride}}
{{#element.customclassoverride}}{{.}}{{/element.customclassoverride}}"
name="{{element.name}}"
id="{{element.id}}"
type="button"
{{#error}}
autofocus aria-describedby="{{element.iderror}}"
{{/error}}
{{{element.attributes}}}>
{{{element.value}}}
</button>
{{/element.frozen}}
{{/element}}
{{/ core_form/element-template }}
@@ -0,0 +1,41 @@
<label data-fieldtype="checkbox" class="form-check {{#error}}has-danger{{/error}} fitem {{#advanced}}advanced{{/advanced}} {{{element.extraclasses}}}">
{{^element.hardfrozen}}
{{#element.frozen}}
<input type="hidden" name="{{element.name}}" value="{{element.frozenvalue}}">
{{/element.frozen}}
{{/element.hardfrozen}}
<input type="checkbox" name="{{element.name}}" class="form-check-input {{element.extraclasses}}"
id="{{element.id}}"
{{#element.value}}
value="{{element.value}}"
{{/element.value}}
{{^element.value}}
value="1"
{{/element.value}}
{{#element.checked}}checked{{/element.checked}}
{{#error}}
autofocus aria-describedby="{{element.iderror}}"
{{/error}}
{{#required}}
aria-required="true"
{{/required}}
{{#element.frozen}}
disabled
{{/element.frozen}}
{{{element.attributes}}} >
{{{label}}}
</label>
{{#text}}
<em>{{{.}}}</em>
{{/text}}
{{{helpbutton}}}
<span class="form-control-feedback invalid-feedback" id="{{element.iderror}}" {{#error}} style="display: block;"{{/error}}>
{{{error}}}
</span>
{{^element.frozen}}
{{#js}}
require(['theme_boost/form-display-errors'], function(module) {
module.enhance({{#quote}}{{element.id}}{{/quote}});
});
{{/js}}
{{/element.frozen}}
@@ -0,0 +1,72 @@
<div class="mb-3 row {{#error}}has-danger{{/error}} fitem {{#advanced}}advanced{{/advanced}} {{{element.extraclasses}}} {{{element.parentclasses}}}">
<div class="col-md-3 col-form-label pb-0 pt-0">
{{#text}}
<label class="d-inline word-break" for="{{element.id}}">
{{{label}}}
</label>
{{/text}}
</div>
<div class="col-md-9 checkbox">
<div class="form-check d-flex">
{{^element.hardfrozen}}
{{#element.frozen}}
<input type="hidden" name="{{element.name}}" value="{{element.frozenvalue}}">
{{/element.frozen}}
{{/element.hardfrozen}}
<input type="checkbox"
name="{{element.name}}"
class="form-check-input {{element.extraclasses}}"
{{#element.value}}
value="{{element.value}}"
{{/element.value}}
{{^element.value}}
value="1"
{{/element.value}}
id="{{element.id}}" {{#element.checked}}checked{{/element.checked}}
{{#error}}
autofocus aria-describedby="{{#text}}{{element.id}}_description {{/text}}{{element.iderror}}"
{{/error}}
{{^error}}
{{#text}}
aria-describedby="{{element.id}}_description"
{{/text}}
{{/error}}
{{#required}}
aria-required="true"
{{/required}}
{{#element.frozen}}
disabled
{{/element.frozen}}
{{{element.attributes}}} >
{{#text}}
<span id="{{element.id}}_description">
{{{.}}}
</span>
{{/text}}
{{^text}}
<label for="{{element.id}}">
{{{label}}}
</label>
{{/text}}
<div class="ml-2 d-flex align-items-center align-self-start">
{{#required}}
<div class="text-danger" title="{{#str}}required{{/str}}">
{{#pix}}req, core, {{#str}}required{{/str}}{{/pix}}
</div>
{{/required}}
{{{helpbutton}}}
</div>
</div>
<div class="form-control-feedback invalid-feedback" id="{{element.iderror}}" {{#error}} style="display: block;"{{/error}}>
{{{error}}}
</div>
</div>
</div>
{{^element.frozen}}
{{#js}}
require(['theme_boost/form-display-errors'], function(module) {
module.enhance({{#quote}}{{element.id}}{{/quote}});
});
{{/js}}
{{/element.frozen}}

Some files were not shown because too many files have changed in this diff Show More