Merge branch 'MDL-53048-master' of git://github.com/andrewnicols/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 24 Oct 2016 03:12:25 +0000 (11:12 +0800)
committerDavid Monllao <davidm@moodle.com>
Mon, 24 Oct 2016 03:12:25 +0000 (11:12 +0800)
42 files changed:
admin/auth_config.php
admin/templates/setting_configpasswordunmask.mustache
admin/templates/settings.mustache
admin/templates/settings_search_results.mustache
admin/upgradesettings.php
files/renderer.php
lang/en/deprecated.txt
lang/en/form.php
lib/adminlib.php
lib/behat/behat_field_manager.php
lib/behat/classes/partial_named_selector.php
lib/behat/form_field/behat_form_passwordunmask.php [new file with mode: 0644]
lib/deprecatedlib.php
lib/form/amd/build/passwordunmask.min.js [new file with mode: 0644]
lib/form/amd/src/passwordunmask.js [new file with mode: 0644]
lib/form/passwordunmask.php
lib/form/templatable_form_element.php
lib/form/templates/element-passwordunmask-fill.mustache [new file with mode: 0644]
lib/form/templates/element-passwordunmask.mustache [new file with mode: 0644]
lib/form/templates/element-template.mustache [new file with mode: 0644]
lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-debug.js
lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-min.js
lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask.js
lib/form/yui/src/passwordunmask/js/passwordunmask.js
lib/form/yui/src/passwordunmask/meta/passwordunmask.json
lib/formslib.php
lib/templates/prevent_form_autofill_password.mustache [deleted file]
lib/upgrade.txt
lib/weblib.php
pix/t/passwordunmask-edit.png [new file with mode: 0644]
pix/t/passwordunmask-edit.svg [new file with mode: 0644]
pix/t/passwordunmask-reveal.png [new file with mode: 0644]
pix/t/passwordunmask-reveal.svg [new file with mode: 0644]
repository/filepicker.php
theme/boost/classes/output/core/files_renderer.php
theme/boost/templates/core/filemanager_loginform.mustache
theme/boost/templates/core_admin/setting_configpasswordunmask.mustache
theme/boost/templates/core_admin/settings.mustache
theme/boost/templates/core_admin/settings_search_results.mustache
theme/boost/templates/core_form/element-passwordunmask.mustache
theme/bootstrapbase/less/moodle/forms.less
theme/bootstrapbase/style/moodle.css

index 1fe945b..b307cd7 100644 (file)
@@ -64,8 +64,6 @@ echo "<form id=\"authmenu\" method=\"post\" action=\"auth_config.php\">\n";
 echo "<div>\n";
 echo "<input type=\"hidden\" name=\"sesskey\" value=\"".sesskey()."\" />\n";
 echo "<input type=\"hidden\" name=\"auth\" value=\"".$auth."\" />\n";
-// HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
-echo prevent_form_autofill_password();
 
 // auth plugin description
 echo $OUTPUT->box_start();
index b9da812..bf00ea7 100644 (file)
     }
 }}
 <div class="form-password">
-    <input type="password" name="{{name}}" size="{{size}}" id="{{id}}" value="{{value}}">
-    <div class="unmask" id="{{id}}unmaskdiv"></div>
+    <span data-passwordunmask="wrapper" data-passwordunmaskid="{{ id }}">
+        <noscript>
+            <!-- Backwards compatability for Behat -->
+            <input  type="password"
+                    name="{{ name }}"
+                    id="{{ id }}"
+                    value="{{ value }}"
+                    size="{{ size }}"
+                    >
+        </noscript>
+        <span class="visibleifjs">
+            <span data-passwordunmask="editor">
+                <!-- The input in the noscript will be moved here as part of the page load -->
+            </span>
+            <a href="#" data-passwordunmask="edit" title="{{ edithint }}">
+                <span data-passwordunmask="displayvalue">{{> core_form/element-passwordunmask-fill }}</span>
+                {{# pix }} t/passwordunmask-edit, core, {{# str }} passwordunmaskedithint, form {{/ str }}{{/ pix }}
+            </a>
+            <a href="#" data-passwordunmask="unmask" title="{{ unmaskhint }}">
+                {{# pix }} t/passwordunmask-reveal, core, {{# str }} passwordunmaskrevealhint, form {{/ str }}{{/ pix }}
+            </a>
+            <span data-passwordunmask="instructions" class="form-text text-muted" style="display: none;">
+                {{# str }} passwordunmaskinstructions, form {{/ str }}
+            </span>
+        </span>
+    </span>
 </div>
 {{#js}}
-(function() {
-    var id = '{{id}}';
-    var unmaskid = id + 'unmask';
-    var unmaskdivid = id + 'unmaskdiv';
-    var unmaskstr = {{#quote}}{{#str}}unmaskpassword, form{{/str}}{{/quote}};
-    var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
-
-    document.getElementById(id).setAttribute("autocomplete", "off");
-
-    var unmaskdiv = document.getElementById(unmaskdivid);
-
-    var unmaskchb = document.createElement("input");
-    unmaskchb.setAttribute("type", "checkbox");
-    unmaskchb.setAttribute("id", unmaskid);
-    unmaskchb.onchange = function() {unmaskPassword(id);};
-    unmaskdiv.appendChild(unmaskchb);
-
-    var unmasklbl = document.createElement("label");
-    unmasklbl.innerHTML = unmaskstr;
-    if (is_ie) {
-      unmasklbl.setAttribute("htmlFor", unmaskid);
-    } else {
-      unmasklbl.setAttribute("for", unmaskid);
-    }
-    unmaskdiv.appendChild(unmasklbl);
-
-    if (is_ie) {
-      // Ugly hack to work around the famous onchange IE bug.
-      unmaskchb.onclick = function() {this.blur();};
-      unmaskdiv.onclick = function() {this.blur();};
-    }
-})()
+require(['core_form/passwordunmask'], function(PasswordUnmask) {
+    new PasswordUnmask("{{ id }}");
+});
 {{/js}}
index aba6601..4f351b8 100644 (file)
@@ -43,7 +43,6 @@
         {{/params}}
         <input type="hidden" name="sesskey" value="{{sesskey}}">
         <input type="hidden" name="return" value="{{return}}">
-        {{>core/prevent_form_autofill_password}}
         {{#title}}
             <h2>{{title}}</h2>
         {{/title}}
index dd392f7..c6f7bbb 100644 (file)
@@ -37,7 +37,6 @@
 <form action="{{actionurl}}" method="post" id="adminsettings">
     <div>
         <input type="hidden" name="sesskey" value="{{sesskey}}">
-        {{>core/prevent_form_autofill_password}}
     </div>
     <fieldset>
         <div class="clearer"></div>
index f4aa617..38a8f06 100644 (file)
@@ -63,8 +63,6 @@ echo '<form action="upgradesettings.php" method="post" id="adminsettings">';
 echo '<div>';
 echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
 echo '<input type="hidden" name="return" value="'.$return.'" />';
-// HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
-echo prevent_form_autofill_password();
 echo '<fieldset>';
 echo '<div class="clearer"><!-- --></div>';
 echo $newsettingshtml;
index 7fce284..46324fc 100644 (file)
@@ -1016,8 +1016,6 @@ class core_files_renderer extends plugin_renderer_base {
 
                     <div class="controls"><select></select></div>
                 </div>';
-        // HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
-        $rv .= prevent_form_autofill_password();
         $rv .= '
                 <div class="fp-login-input control-group clearfix">
                     <label class="control-label"></label>
index b5986aa..3aa78e6 100644 (file)
@@ -34,3 +34,4 @@ downloadoptions,core_table
 downloadtsv,core_table
 downloadxhtml,core_table
 invalidpersistent,core_competency
+revealpassword,core_form
index 1f70f67..39c5c80 100644 (file)
@@ -51,10 +51,14 @@ $string['nomethodforaddinghelpbutton'] = 'There is no method for adding a help b
 $string['nonexistentformelements'] = 'Trying to add help buttons to non-existent form elements : {$a}';
 $string['noselection'] = 'No selection';
 $string['nosuggestions'] = 'No suggestions';
+$string['novalue'] = 'Nothing entered';
+$string['novalueclicktoset'] = 'Click to enter text';
 $string['optional'] = 'Optional';
 $string['othersettings'] = 'Other settings';
+$string['passwordunmaskedithint'] = 'Edit password';
+$string['passwordunmaskrevealhint'] = 'Reveal';
+$string['passwordunmaskinstructions'] = 'Press enter to save changes';
 $string['requiredelement'] = 'Required field';
-$string['revealpassword'] = 'Reveal';
 $string['security'] = 'Security';
 $string['selectallornone'] = 'Select all/none';
 $string['selected'] = 'Selected';
@@ -68,3 +72,6 @@ $string['timeunit'] = 'Time unit';
 $string['timing'] = 'Timing';
 $string['unmaskpassword'] = 'Unmask';
 $string['year'] = 'Year';
+
+// Deprecated since 3.2.
+$string['revealpassword'] = 'Reveal';
index 68e1b48..515a6c2 100644 (file)
@@ -2406,13 +2406,11 @@ class admin_setting_configpasswordunmask extends admin_setting_configtext {
     }
 
     /**
-     * Returns XHTML for the field
-     * Writes Javascript into the HTML below right before the last div
+     * Returns HTML for the field.
      *
-     * @todo Make javascript available through newer methods if possible
-     * @param string $data Value for the field
-     * @param string $query Passed as final argument for format_admin_setting
-     * @return string XHTML field
+     * @param   string  $data       Value for the field
+     * @param   string  $query      Passed as final argument for format_admin_setting
+     * @return  string              Rendered HTML
      */
     public function output_html($data, $query='') {
         global $OUTPUT;
index 27fbafa..3c770bb 100644 (file)
@@ -228,6 +228,10 @@ class behat_field_manager {
             return $type;
         }
 
+        if (!empty($fieldnode->find('xpath', '/ancestor::*[@data-passwordunmaskid]'))) {
+            return 'passwordunmask';
+        }
+
         // We look for a parent node with 'felement' class.
         if ($class = $fieldnode->getParent()->getAttribute('class')) {
 
index 538e3fc..44338c1 100644 (file)
@@ -186,6 +186,11 @@ XPATH
             'filemanager' => <<<XPATH
 .//*[@data-fieldtype = 'filemanager' or @data-fieldtype = 'filepicker']
     /descendant::input[@id = //label[contains(normalize-space(string(.)), %locator%)]/@for]
+XPATH
+        ,
+             'passwordunmask' => <<<XPATH
+.//*[@data-passwordunmask='wrapper']
+    /descendant::input[@id = //label[contains(normalize-space(string(.)), %locator%)]/@for]
 XPATH
         ],
     ];
diff --git a/lib/behat/form_field/behat_form_passwordunmask.php b/lib/behat/form_field/behat_form_passwordunmask.php
new file mode 100644 (file)
index 0000000..439643b
--- /dev/null
@@ -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/>.
+
+/**
+ * Silly behat_form_select extension.
+ *
+ * @package    core_form
+ * @category   test
+ * @copyright  2013 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
+
+require_once(__DIR__  . '/behat_form_text.php');
+
+/**
+ * Allows interaction with passwordunmask form fields.
+ *
+ * Plain behat_form_select extension as it is the same
+ * kind of field.
+ *
+ * @package    core_form
+ * @category   test
+ * @copyright  2013 David Monllaó
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_form_passwordunmask extends behat_form_text {
+    /**
+     * Sets the value to a field.
+     *
+     * @param string $value
+     * @return void
+     */
+    public function set_value($value) {
+        if ($this->running_javascript()) {
+            $id = $this->field->getAttribute('id');
+            $js = <<<JS
+require(["jquery"], function($) {
+    var wrapper = $(document.getElementById("{$id}")).closest('[data-passwordunmask="wrapper"]');
+        wrapper.find('[data-passwordunmask="edit"]').trigger("click");
+});
+JS;
+            $this->session->executeScript($js);
+        }
+
+        $this->field->setValue($value);
+    }
+}
index 01bd4ca..93d360c 100644 (file)
@@ -5620,6 +5620,7 @@ function message_is_user_blocked($recipient, $sender = null) {
 }
 
 /**
+<<<<<<< 919b9dfabd69f71c088f565746c547e6e169a0b7
  * Display logs.
  *
  * @deprecated since 3.2
@@ -6369,5 +6370,15 @@ function get_logs($select, array $params=null, $order='l.time DESC', $limitfrom=
            $select
             $order";
 
-    return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum) ;
+    return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
+}
+
+/**
+ * Renders a hidden password field so that browsers won't incorrectly autofill password fields with the user's password.
+ *
+ * @deprecated since Moodle 3.2 MDL-53048
+ */
+function prevent_form_autofill_password() {
+    debugging('prevent_form_autofill_password has been deprecated and is no longer in use.', DEBUG_DEVELOPER);
+    return '';
 }
diff --git a/lib/form/amd/build/passwordunmask.min.js b/lib/form/amd/build/passwordunmask.min.js
new file mode 100644 (file)
index 0000000..5687ff9
Binary files /dev/null and b/lib/form/amd/build/passwordunmask.min.js differ
diff --git a/lib/form/amd/src/passwordunmask.js b/lib/form/amd/src/passwordunmask.js
new file mode 100644 (file)
index 0000000..8354f47
--- /dev/null
@@ -0,0 +1,286 @@
+// 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
+ * @package    core_form
+ * @class      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.
+     *
+     * @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"]');
+
+        // Move and convert the input field to the editor, then remove the noscript.
+        // We only want a single input field.
+        var noscriptContent = $(this.wrapper.find('noscript').text());
+        this.inputField = noscriptContent.filter('input');
+        this.inputField.attr('type', 'hidden');
+        this.editorSpace.append(this.inputField);
+        this.wrapper.find('noscript').remove();
+
+        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.inputField.attr('type') !== 'hidden') {
+                // 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));
+
+        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.attr('type') !== 'hidden';
+    };
+
+    /**
+     * Enable the editing functionality.
+     *
+     * @method  turnEditingOn
+     * @return  {PasswordUnmask}
+     * @chainable
+     */
+    PasswordUnmask.prototype.turnEditingOn = function() {
+        if (this.passwordVisible()) {
+            this.inputField.attr('type', 'text');
+        } else {
+            this.inputField.attr('type', 'password');
+        }
+
+        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);
+        this.inputField
+            // Hide the field again.
+            .attr('type', 'hidden')
+
+            // Ensure that the aria-describedby is removed.
+            .attr('aria-describedby', null);
+
+        this.editInstructions.hide();
+
+        // Remove the visible attr.
+        this.wrapper.removeAttr('data-passwordunmask-visible');
+
+        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() {
+        if (this.isEditing()) {
+            if (this.wrapper.data('unmasked')) {
+                this.inputField.attr('type', 'text');
+            } else {
+                this.inputField.attr('type', 'password');
+            }
+        }
+
+        // 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.
+        var value = this.getDisplayValue();
+        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;
+});
index 73bee9d..db18e88 100644 (file)
@@ -52,7 +52,6 @@ class MoodleQuickForm_passwordunmask extends MoodleQuickForm_password {
      *              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 = array('autocomplete'=>'off');
@@ -63,6 +62,7 @@ class MoodleQuickForm_passwordunmask extends MoodleQuickForm_password {
                 $attributes .= ' autocomplete="off" ';
             }
         }
+        $this->_persistantFreeze = true;
 
         parent::__construct($elementName, $elementLabel, $attributes);
         $this->setType('passwordunmask');
@@ -79,25 +79,15 @@ class MoodleQuickForm_passwordunmask extends MoodleQuickForm_password {
     }
 
     /**
-     * Returns HTML for password form element.
+     * Function to export the renderer data in a format that is suitable for a mustache template.
      *
-     * @return string
+     * @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
+     * @return stdClass|array
      */
-    function toHtml() {
-        global $PAGE;
+    public function export_for_template(renderer_base $output) {
+        $context = parent::export_for_template($output);
+        $context['valuechars'] = array_fill(0, strlen($context['value']), 'x');
 
-        if ($this->_flagFrozen) {
-            return $this->getFrozenHtml();
-        } else {
-            $unmask = get_string('unmaskpassword', 'form');
-            //Pass id of the element, so that unmask checkbox can be attached.
-            $attributes = array('formid' => $this->getAttribute('id'),
-                'checkboxlabel' => $unmask,
-                'checkboxname' => $this->getAttribute('name'));
-            $PAGE->requires->yui_module('moodle-form-passwordunmask', 'M.form.passwordunmask',
-                    array($attributes));
-            return $this->_getTabs() . '<input' . $this->_getAttrString($this->_attributes) . ' />';
-        }
+        return $context;
     }
-
 }
index 2f27d2b..844a90e 100644 (file)
@@ -69,6 +69,7 @@ trait templatable_form_element {
 
         // Special wierd named property.
         $context['frozen'] = !empty($this->_flagFrozen);
+        $context['hardfrozen'] = !empty($this->_flagFrozen) && empty($this->_persistantFreeze);
 
         // Other attributes.
         $otherattributes = [];
diff --git a/lib/form/templates/element-passwordunmask-fill.mustache b/lib/form/templates/element-passwordunmask-fill.mustache
new file mode 100644 (file)
index 0000000..56360d1
--- /dev/null
@@ -0,0 +1,56 @@
+{{!
+    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/element-passwordunmask-fill
+
+    The fill for a passwordunmask form element.
+
+    The purpose of this template is to render the fill for a passwordunmask element.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * element
+      * valuechars (optional)
+
+    Example context (json):
+    {
+        "element": {
+            "valuechars": [
+                "E",
+                "x",
+                "a",
+                "m",
+                "p",
+                "l",
+                "e"
+            ]
+        }
+    }
+
+}}
+<span>
+{{# element.valuechars }}&bull;{{/ element.valuechars }}
+{{^ element.valuechars }}<em>{{!
+    }}{{^ element.frozen }}{{# str }} novalueclicktoset, form {{/ str }}{{/ element.frozen }}{{!
+    }}{{# element.frozen }}{{# str }} novalue, form {{/ str }}{{/ element.frozen }}{{!
+}}</em>{{/ element.valuechars }}
+</span>
diff --git a/lib/form/templates/element-passwordunmask.mustache b/lib/form/templates/element-passwordunmask.mustache
new file mode 100644 (file)
index 0000000..8634e54
--- /dev/null
@@ -0,0 +1,93 @@
+{{!
+    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/element-passwordunmask
+
+    Moodle passwordunmask form element template.
+
+    The purpose of this template is to render a passwordunmask form element.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * element
+        * id
+        * name
+        * value
+        * size
+
+    Example context (json):
+    {
+        "element": {
+            "id": "example_password_unmask",
+            "name": "example",
+            "value": "Password1!",
+            "size": 40
+        }
+    }
+
+}}
+{{< core_form/element-template }}
+    {{$ element }}
+        <div data-passwordunmask="wrapper" data-passwordunmaskid="{{ element.id }}">
+            <noscript>
+                <!-- Backwards compatability for Behat -->
+                <input  type="password"
+                        {{# element.frozen }}readonly{{/ element.frozen }}
+                        {{^ element.hardfrozen}} name="{{ element.name }}"{{/ element.hardfrozen }}
+                        id="{{ element.id }}"
+                        value="{{element.value }}"
+                        size="{{ element.size }}"
+                        {{# error }}
+                            autofocus aria-describedby="id_error_{{ element.name }}"
+                        {{/ error }}
+                        {{{ attributes }}}
+                        >
+            </noscript>
+            <span class="visibleifjs">
+                <span data-passwordunmask="editor">
+                    <!-- The input in the noscript will be moved here as part of the page load -->
+                    <span data-passwordunmask="instructions" class="editinstructions">
+                        {{^ element.frozen }}
+                        {{# str }} passwordunmaskinstructions, form {{/ str }}
+                        {{/ element.frozen }}
+                    </span>
+                </span>
+                {{^ element.frozen }}
+                <a href="#" data-passwordunmask="edit" title="{{ edithint }}">
+                {{/ element.frozen }}
+                    <span data-passwordunmask="displayvalue">{{> core_form/element-passwordunmask-fill }}</span>
+                {{^ element.frozen }}
+                    {{# pix }} t/passwordunmask-edit, core, {{ edithint }}{{/ pix }}
+                </a>
+                {{/ element.frozen }}
+                <a href="#" data-passwordunmask="unmask" title="{{ unmaskhint }}">
+                    {{# pix }} t/passwordunmask-reveal, core, {{ edithint }}{{/ pix }}
+                </a>
+            </span>
+        </div>
+    {{/ element }}
+{{/ core_form/element-template }}
+{{# js }}
+require(['core_form/passwordunmask'], function(PasswordUnmask) {
+    new PasswordUnmask("{{ element.id }}");
+});
+{{/ js }}
diff --git a/lib/form/templates/element-template.mustache b/lib/form/templates/element-template.mustache
new file mode 100644 (file)
index 0000000..9463064
--- /dev/null
@@ -0,0 +1,64 @@
+{{!
+    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/element-template
+
+    Moodle form element wrapper template.
+
+    The purpose of this template is to wrap a form element.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * label
+    * helpbutton
+    * error
+    * element
+        * id
+        * name
+
+    Example context (json):
+    {
+        "label": "Password",
+        "error": "No password set",
+        "element": {
+            "id": "example_password_unmask",
+            "name": "example"
+        }
+    }
+
+}}
+<div id="fitem_{{ element.id }}" class="fitem fitem_{{ element.type }}">
+    <div class="fitemtitle">
+      <label for="{{element.id}}">{{{ label }}}</label>
+        {{{ helpbutton }}}
+    </div>
+    <div class="felement {{# error }} error{{/ error }} f{{ element.type }}">
+        {{# error }}
+        <span class="error" tabindex="0" id="id_error_{{ element.name }}">
+            {{{ error }}}
+        </span>
+        {{/ error }}
+        {{$ element }}
+            <!-- Element goes here -->
+        {{/ element }}
+    </div>
+</div>
index 4822968..2acf834 100644 (file)
Binary files a/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-debug.js and b/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-debug.js differ
index 1c79dd4..d1ffeb6 100644 (file)
Binary files a/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-min.js and b/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-min.js differ
index 4822968..d1c1242 100644 (file)
Binary files a/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask.js and b/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask.js differ
index 56097ea..e617441 100644 (file)
@@ -1,41 +1,5 @@
-var PASSWORDUNMASK = function() {
-    PASSWORDUNMASK.superclass.constructor.apply(this, arguments);
-};
-
-Y.extend(PASSWORDUNMASK, Y.Base, {
-    // Initialize checkbox if id is passed.
-    initializer: function(params) {
-        if (params && params.formid) {
-            this.add_checkbox(params.formid, params.checkboxlabel, params.checkboxname);
-        }
-    },
-
-    // Create checkbox for unmasking password.
-    add_checkbox: function(elementid, checkboxlabel, checkboxname) {
-        var node = Y.one('#' + elementid);
-
-        // Retaining unmask div from previous implementation.
-        var unmaskdiv = Y.Node.create('<div id="' + elementid + 'unmaskdiv" class="unmask"></div>');
-
-        // Add checkbox for unmasking to unmaskdiv.
-        var unmaskchb = Y.Node.create('<input id="' + elementid + 'unmask" type="checkbox" name="' +
-            checkboxname + 'unmask">');
-        unmaskdiv.appendChild(unmaskchb);
-        // Attach event using static javascript function for unmasking password.
-        unmaskchb.on('click', function() {
-            window.unmaskPassword(elementid);
-        });
-
-        // Add label for checkbox to unmaskdiv.
-        var unmasklabel = Y.Node.create('<label for="' + elementid + 'unmask">' + checkboxlabel + '</label>');
-        unmaskdiv.appendChild(unmasklabel);
-
-        // Insert unmask div in the same div as password input.
-        node.get('parentNode').insert(unmaskdiv, node.get('lastNode'));
-    }
-});
-
 M.form = M.form || {};
-M.form.passwordunmask = function(params) {
-    return new PASSWORDUNMASK(params);
+M.form.passwordunmask = function() {
+    Y.log("The moodle-form-passwordunmask module has been deprecated. " +
+            "Please use the core_forum/passwordunmask amd module instead.", 'moodle-form-passwordunmask', 'warn');
 };
index 1830917..185a757 100644 (file)
@@ -1,8 +1,5 @@
 {
     "moodle-form-passwordunmask": {
-        "requires": [
-            "node",
-            "base"
-        ]
+        "requires": []
     }
 }
index adb76dd..282ebbe 100644 (file)
@@ -2750,8 +2750,6 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
         $this->_collapseButtons = '';
         $formid = $form->getAttribute('id');
         parent::startForm($form);
-        // HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
-        $this->_hiddenHtml .= prevent_form_autofill_password();
         if ($form->isFrozen()){
             $this->_formTemplate = "\n<div class=\"mform frozen\">\n{content}\n</div>";
         } else {
diff --git a/lib/templates/prevent_form_autofill_password.mustache b/lib/templates/prevent_form_autofill_password.mustache
deleted file mode 100644 (file)
index 307a0e4..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-{{!
-    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/>.
-}}
-{{!
-    Snippet to prevent browsers from autofilling password fields.
-}}
-<div class="hide">
-    <input type="text" class="ignoredirty" />
-    <input type="password" class="ignoredirty" />
-</div>
index 6c71317..fed4655 100644 (file)
@@ -37,6 +37,7 @@ information provided here is intended especially for developers.
   - get_logs()            - See MDL-43681 for more information
   - get_logs_usercourse() - See MDL-43681 for more information
   - get_logs_userday()    - See MDL-43681 for more information
+  - prevent_form_autofill_password() Please do not use anymore.
 * The password_compat library was removed as it is no longer required.
 * Phpunit has been upgraded to 5.4.x and following has been deprecated and is not used any more:
   - setExpectedException(), use @expectedException or $this->expectException() and $this->expectExceptionMessage()
index 74ec6af..75a227c 100644 (file)
@@ -3556,14 +3556,3 @@ function get_formatted_help_string($identifier, $component, $ajax = false, $a =
     }
     return $data;
 }
-
-/**
- * Renders a hidden password field so that browsers won't incorrectly autofill password fields with the user's password.
- *
- * @since 3.0
- * @return string HTML to prevent password autofill
- */
-function prevent_form_autofill_password() {
-    global $OUTPUT;
-    return $OUTPUT->render_from_template('core/prevent_form_autofill_password', []);
-}
diff --git a/pix/t/passwordunmask-edit.png b/pix/t/passwordunmask-edit.png
new file mode 100644 (file)
index 0000000..52d985c
Binary files /dev/null and b/pix/t/passwordunmask-edit.png differ
diff --git a/pix/t/passwordunmask-edit.svg b/pix/t/passwordunmask-edit.svg
new file mode 100644 (file)
index 0000000..0922939
--- /dev/null
@@ -0,0 +1,3 @@
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M5.2 11H12v1H4.2l1-1zm1.4-8.4L0 9.1V12h2.8l6.6-6.6-2.8-2.8zm4.8.8l-1 1-2.8-2.8 1-1c.8-.8 2.1-.8 2.8 0 .8.8.8 2 0 2.8z" fill="#999"/></svg>
\ No newline at end of file
diff --git a/pix/t/passwordunmask-reveal.png b/pix/t/passwordunmask-reveal.png
new file mode 100644 (file)
index 0000000..166e791
Binary files /dev/null and b/pix/t/passwordunmask-reveal.png differ
diff --git a/pix/t/passwordunmask-reveal.svg b/pix/t/passwordunmask-reveal.svg
new file mode 100644 (file)
index 0000000..64c1277
--- /dev/null
@@ -0,0 +1,3 @@
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M11.4 9.4L9.9 7.9c-.1-.1-.2-.1-.3-.2.4-.8.7-1.6.7-2.5C10.3 2.3 8 0 5.2 0 2.3 0 0 2.3 0 5.2s2.3 5.2 5.2 5.2c.9 0 1.7-.2 2.4-.6.1 0 .1.1.2.2l1.5 1.5c.6.6 1.5.6 2.1 0s.6-1.5 0-2.1zM5.2 7.3C4 7.3 3 6.4 3 5.2 3 4 4 3 5.2 3c1.2 0 2.2 1 2.2 2.2-.1 1.2-1 2.1-2.2 2.1z" fill="#999"/></svg>
\ No newline at end of file
index e488e3d..4ce0236 100644 (file)
@@ -272,8 +272,6 @@ case 'sign':
         echo '<form method="post">';
         echo '<input type="hidden" name="action" value="sign" />';
         echo '<input type="hidden" name="repo_id" value="'.s($repo_id).'" />';
-        // HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
-        echo prevent_form_autofill_password();
         $repo->print_login();
         echo '</form>';
     }
index 50257d9..66d2e82 100644 (file)
@@ -107,7 +107,7 @@ class files_renderer extends \core_files_renderer {
      * @return string
      */
     protected function fp_js_template_loginform() {
-        return $this->render_from_template('core/filemanager_loginform', ['autofillhack' => prevent_form_autofill_password()]);
+        return $this->render_from_template('core/filemanager_loginform', []);
     }
 
     /**
index 5de0b3f..e3c67cb 100644 (file)
@@ -14,7 +14,6 @@
                     <label class="form-control-label"></label>
                     <select class="form-control"></select>
                 </div>
-                {{{autofillhack}}}
                 <div class="fp-login-input form-group">
                     <label class="form-control-label"></label>
                     <input class="form-control"/>
@@ -27,4 +26,4 @@
             <p class="mdl-align"><button class="fp-login-submit btn-primary btn">{{#str}}submit, repository{{/str}}</button></p>
         </form>
     </div>
-</div>
\ No newline at end of file
+</div>
index b7e755c..d532554 100644 (file)
     along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 }}
 {{!
-    Setting configpasswordunmask.
-}}
-<div class="form-password">
-    <input type="password" name="{{name}}" size="{{size}}" id="{{id}}" value="{{value}}" class="form-control d-inline-block">
-    <div class="unmask" id="{{id}}unmaskdiv"></div>
-</div>
-{{#js}}
-(function() {
-    var id = '{{id}}';
-    var unmaskid = id + 'unmask';
-    var unmaskdivid = id + 'unmaskdiv';
-    var unmaskstr = {{#quote}}{{#str}}unmaskpassword, form{{/str}}{{/quote}};
-    var is_ie = (navigator.userAgent.toLowerCase().indexOf("msie") != -1);
-
-    document.getElementById(id).setAttribute("autocomplete", "off");
+    @template core_admin/setting_configpasswordunmask
 
-    var unmaskdiv = document.getElementById(unmaskdivid);
+    Admin password unmask setting template.
 
-    var unmaskchb = document.createElement("input");
-    unmaskchb.setAttribute("type", "checkbox");
-    unmaskchb.setAttribute("id", unmaskid);
-    unmaskchb.onchange = function() {unmaskPassword(id);};
-    unmaskdiv.appendChild(unmaskchb);
+    Context variables required for this template:
+    * name - form element name
+    * size - form element size
+    * value - form element value
+    * id - element id
 
-    var unmasklbl = document.createElement("label");
-    unmasklbl.innerHTML = unmaskstr;
-    if (is_ie) {
-      unmasklbl.setAttribute("htmlFor", unmaskid);
-    } else {
-      unmasklbl.setAttribute("for", unmaskid);
+    Example context (json):
+    {
+        "name": "test",
+        "id": "test0",
+        "size": "8",
+        "value": "secret"
     }
-    unmaskdiv.appendChild(unmasklbl);
-
-    if (is_ie) {
-      // Ugly hack to work around the famous onchange IE bug.
-      unmaskchb.onclick = function() {this.blur();};
-      unmaskdiv.onclick = function() {this.blur();};
-    }
-})()
+}}
+<div class="form-password">
+    <span data-passwordunmask="wrapper" data-passwordunmaskid="{{ id }}">
+        <noscript>
+            <!-- Backwards compatability for Behat -->
+            <input  type="password"
+                    name="{{ name }}"
+                    id="{{ id }}"
+                    value="{{ value }}"
+                    size="{{ size }}"
+                    class="form-control d-inline-block"
+                    >
+        </noscript>
+        <span class="visibleifjs">
+            <span data-passwordunmask="editor">
+                <!-- The input in the noscript will be moved here as part of the page load -->
+            </span>
+            <a href="#" data-passwordunmask="edit" title="{{ edithint }}">
+                <span data-passwordunmask="displayvalue">{{> core_form/element-passwordunmask-fill }}</span>
+                {{# pix }} t/passwordunmask-edit, core, {{# str }} passwordunmaskedithint, form {{/ str }}{{/ pix }}
+            </a>
+            <a href="#" data-passwordunmask="unmask" title="{{ unmaskhint }}">
+                {{# pix }} t/passwordunmask-reveal, core, {{# str }} passwordunmaskrevealhint, form {{/ str }}{{/ pix }}
+            </a>
+            <span data-passwordunmask="instructions" class="form-text text-muted" style="display: none;">
+                {{# str }} passwordunmaskinstructions, form {{/ str }}
+            </span>
+        </span>
+    </span>
+</div>
+{{#js}}
+require(['core_form/passwordunmask'], function(PasswordUnmask) {
+    new PasswordUnmask("{{ id }}");
+});
 {{/js}}
index e971f03..1de9250 100644 (file)
@@ -24,7 +24,6 @@
         {{/params}}
         <input type="hidden" name="sesskey" value="{{sesskey}}">
         <input type="hidden" name="return" value="{{return}}">
-        {{>core/prevent_form_autofill_password}}
         {{#title}}
             <h2>{{title}}</h2>
         {{/title}}
index ce09da8..d5f0bc1 100644 (file)
@@ -20,7 +20,6 @@
 <form action="{{actionurl}}" method="post" id="adminsettings">
     <div>
         <input type="hidden" name="sesskey" value="{{sesskey}}">
-        {{>core/prevent_form_autofill_password}}
     </div>
     <fieldset>
         <div class="clearer"></div>
index a759af1..0c2e756 100644 (file)
@@ -1,12 +1,94 @@
-{{> core_form/element-password }}
-{{^element.frozen}}
+{{!
+    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/element-passwordunmask
+
+    Moodle passwordunmask form element template.
+
+    The purpose of this template is to render a passwordunmask form element.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * element
+        * id
+        * name
+        * value
+        * size
+
+    Example context (json):
+    {
+        "element": {
+            "id": "example_password_unmask",
+            "name": "example",
+            "value": "Password1!",
+            "size": 40
+        }
+    }
+
+}}
+{{< core_form/element-template }}
+    {{$ element }}
+        <span data-passwordunmask="wrapper" data-passwordunmaskid="{{ element.id }}">
+            <noscript>
+                <!-- Backwards compatability for Behat -->
+                <input  type="password"
+                        {{# element.frozen }}readonly{{/ element.frozen }}
+                        {{^ element.hardfrozen}} name="{{ element.name }}"{{/ element.hardfrozen }}
+                        id="{{ element.id }}"
+                        value="{{ element.value }}"
+                        class="form-control d-inline-block {{# error }}form-control-danger{{/ error }}"
+                        size="{{ element.size }}"
+                        {{# error }}
+                            autofocus aria-describedby="id_error_{{ element.name }}"
+                        {{/ error }}
+                        {{{ attributes }}}
+                        >
+            </noscript>
+            <span class="visibleifjs">
+                <span data-passwordunmask="editor">
+                    <!-- The input in the noscript will be moved here as part of the page load -->
+                </span>
+                {{^ element.frozen }}
+                <a href="#" data-passwordunmask="edit" title="{{ edithint }}">
+                {{/ element.frozen }}
+                    <span data-passwordunmask="displayvalue">{{> core_form/element-passwordunmask-fill }}</span>
+                {{^ element.frozen }}
+                    {{# pix }} t/passwordunmask-edit, core, {{# str }} passwordunmaskedithint, form {{/ str }}{{/ pix }}
+                </a>
+                {{/ element.frozen }}
+                <a href="#" data-passwordunmask="unmask" title="{{ unmaskhint }}">
+                    {{# pix }} t/passwordunmask-reveal, core, {{# str }} passwordunmaskrevealhint, form {{/ str }}{{/ pix }}
+                </a>
+                <span data-passwordunmask="instructions" class="form-text text-muted" style="display: none;">
+                    {{^ element.frozen }}
+                    {{# str }} passwordunmaskinstructions, form {{/ str }}
+                    {{/ element.frozen }}
+                </span>
+            </span>
+        </span>
+    {{/ element }}
+{{/ core_form/element-template }}
 {{#js}}
-require(['core/yui'], function(Y) {
-    Y.use('moodle-form-passwordunmask', function() {
-        M.form.passwordunmask({ formid: {{#quote}}{{element.id}}{{/quote}},
-                                checkboxlabel: {{#quote}}{{#str}}unmaskpassword, form{{/str}}{{/quote}},
-                                checkboxname: {{#quote}}{{element.name}}{{/quote}} });
-    });
+require(['core_form/passwordunmask'], function(PasswordUnmask) {
+    new PasswordUnmask("{{ element.id }}");
 });
-{{/js}}
-{{/element.frozen}}
+{{/ js }}
index a75cd9e..58d4801 100644 (file)
@@ -524,3 +524,9 @@ input[size] {
 textarea[data-auto-rows] {
     overflow-x: hidden;
 }
+
+div[data-passwordunmask="wrapper"] {
+    height: 30px;
+    line-height: 30px;
+    margin-bottom: 10px;
+}
index 8c1442e..e846745 100644 (file)
@@ -14352,6 +14352,11 @@ input[size] {
 textarea[data-auto-rows] {
   overflow-x: hidden;
 }
+div[data-passwordunmask="wrapper"] {
+  height: 30px;
+  line-height: 30px;
+  margin-bottom: 10px;
+}
 body.modal-open {
   overflow: hidden;
 }