Move inline JS to external script files#814
Move inline JS to external script files#814aslamdoctor wants to merge 3 commits intoWordPress:masterfrom
Conversation
Extract 6 inline script blocks from 4 PHP files into 5 new external JS files under providers/js/, improving CSP compatibility and enabling proper WordPress script dependency management. New files: - totp-admin-qrcode.js: QR code generation for TOTP setup - totp-admin.js: TOTP setup form, checkbox focus, reset key - backup-codes-admin.js: Backup code generation, copy, download - two-factor-login.js: Shared login page authcode clear/focus - two-factor-login-authcode.js: Numeric-only enforcement, auto-submit The customizer messenger one-liner uses wp_add_inline_script() instead of a raw <script> tag. Fixes WordPress#812
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
The "Reset authenticator app" button's AJAX response injects HTML with a QR code placeholder, but the QR code generator script only runs on page load and never re-triggers. Additionally, the qrcode library wasn't loaded when TOTP was already configured. Add qr-code-generator as a dependency of totp-admin script and generate the QR code in JavaScript after the reset response HTML is inserted.
Move all var declarations to the top of their respective function scopes.
masteradhoc
left a comment
There was a problem hiding this comment.
Thanks for the fast fix regarding my comment about the QR-Code not rendering after resetting the TOTP. Checked it now and looks good from my side.
|
For big migrations like this, I've found that copilot is really good at catching stuff my eyes gloss over. Just wanna make sure it doesn't surface anything significant. |
There was a problem hiding this comment.
Pull request overview
This PR extracts previously inline JavaScript from provider PHP templates into external script files under providers/js/, enabling better CSP compatibility and allowing WordPress dependency management (notably ensuring wp-api-request is properly enqueued when needed).
Changes:
- Added 5 new external JS files for login behaviors and provider admin UIs (TOTP + backup codes).
- Updated provider PHP to register/enqueue these scripts and pass dynamic values via
wp_localize_script(). - Replaced a raw customizer
<script>snippet withwp_add_inline_script().
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
providers/js/two-factor-login.js |
New login helper script (focus/clear authcode). |
providers/js/two-factor-login-authcode.js |
New login authcode input enforcement + autosubmit logic. |
providers/js/totp-admin.js |
New admin/profile TOTP setup/reset interactions via REST calls. |
providers/js/totp-admin-qrcode.js |
New TOTP QR-code rendering script using qrcode generator. |
providers/js/backup-codes-admin.js |
New backup codes UI (generate/copy/download) via REST calls. |
providers/class-two-factor-totp.php |
Registers/enqueues new TOTP scripts and localizes REST params. |
providers/class-two-factor-email.php |
Enqueues shared login script instead of inline <script>. |
providers/class-two-factor-backup-codes.php |
Registers/enqueues new backup codes script and localizes REST params. |
class-two-factor-core.php |
Registers login scripts and switches customizer snippet to wp_add_inline_script(). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| */ | ||
| public function enqueue_assets() { |
There was a problem hiding this comment.
admin_enqueue_scripts passes the $hook_suffix argument, but enqueue_assets() is declared with no parameters. On PHP 8+, WordPress will call this with 1 arg and trigger an ArgumentCountError, breaking admin pages. Make enqueue_assets( $hook_suffix = '' ) accept the parameter (or register the action with accepted_args = 0) so the callback signature matches the hook.
| */ | |
| public function enqueue_assets() { | |
| * | |
| * @param string $hook_suffix Optional. The current admin page hook suffix. | |
| */ | |
| public function enqueue_assets( $hook_suffix = '' ) { |
| if ( undefined !== form.requestSubmit ) { | ||
| form.requestSubmit(); | ||
| form.submit.disabled = 'disabled'; |
There was a problem hiding this comment.
form.submit.disabled = 'disabled' won't reliably disable the submit button (most login forms don't have a control named submit, and form.submit is typically the form method). This means auto-submit can still be triggered multiple times. Disable the actual submit control (e.g., #wp-submit or form.querySelector('[type="submit"]')) and guard form being null before calling requestSubmit().
| if ( undefined !== form.requestSubmit ) { | |
| form.requestSubmit(); | |
| form.submit.disabled = 'disabled'; | |
| if ( form && typeof form.requestSubmit === 'function' ) { | |
| form.requestSubmit(); | |
| var submitControl = form.querySelector( '[type="submit"]' ); | |
| if ( submitControl ) { | |
| submitControl.disabled = true; | |
| } |
| inputEl.addEventListener( | ||
| 'input', | ||
| function() { | ||
| var value = this.value.replace( /[^0-9 ]/g, '' ).trimStart(); |
There was a problem hiding this comment.
trimStart() is not available in ES5 and can break in environments without that newer String API. Since these scripts are intended to follow WordPress' ES5 rules, avoid trimStart() here (e.g., strip leading whitespace via a regex) to keep compatibility consistent.
| var value = this.value.replace( /[^0-9 ]/g, '' ).trimStart(); | |
| var value = this.value.replace( /[^0-9 ]/g, '' ).replace( /^\s+/, '' ); |
| enable_provider: true | ||
| } | ||
| } ).fail( function( response, status ) { | ||
| var errorMessage = response.responseJSON.message || status, |
There was a problem hiding this comment.
In the fail handler, response.responseJSON can be undefined (e.g., non-JSON error responses), which would throw when accessing .message and prevent the error UI from rendering. Guard the lookup (check response.responseJSON && response.responseJSON.message) and fall back to a safer string (like response.statusText or status).
| var errorMessage = response.responseJSON.message || status, | |
| var errorMessage = ( response && response.responseJSON && response.responseJSON.message ) || ( response && response.statusText ) || status || '', |
| svg.role = 'image'; | ||
| svg.ariaLabel = 'Authenticator App QR Code'; | ||
| title.innerText = svg.ariaLabel; |
There was a problem hiding this comment.
The SVG accessibility attributes are set using svg.role = 'image' and svg.ariaLabel = .... image is not a valid ARIA role value (use img), and setting ARIA via DOM properties is not consistently supported for SVG; prefer setAttribute('role', 'img') and setAttribute('aria-label', ...). Also, the label is hard-coded in English here; pass a localized label via twoFactorTotpAdmin (as done in twoFactorTotpQrcode) so it can be translated.
| svg.role = 'image'; | |
| svg.ariaLabel = 'Authenticator App QR Code'; | |
| title.innerText = svg.ariaLabel; | |
| var ariaLabel = ( typeof twoFactorTotpAdmin !== 'undefined' && twoFactorTotpAdmin && twoFactorTotpAdmin.qrCodeAriaLabel ) ? twoFactorTotpAdmin.qrCodeAriaLabel : 'Authenticator App QR Code'; | |
| svg.setAttribute( 'role', 'img' ); | |
| svg.setAttribute( 'aria-label', ariaLabel ); | |
| title.innerText = ariaLabel; |
| svg.role = 'image'; | ||
| svg.ariaLabel = twoFactorTotpQrcode.qrCodeLabel; | ||
| title.innerText = svg.ariaLabel; |
There was a problem hiding this comment.
The SVG is annotated via svg.role = 'image' and svg.ariaLabel = .... image is not a valid ARIA role value (use img), and ARIA should be applied via attributes for SVG (setAttribute('role', 'img'), setAttribute('aria-label', ...)) for consistent accessibility support.
| svg.role = 'image'; | |
| svg.ariaLabel = twoFactorTotpQrcode.qrCodeLabel; | |
| title.innerText = svg.ariaLabel; | |
| svg.setAttribute( 'role', 'img' ); | |
| svg.setAttribute( 'aria-label', twoFactorTotpQrcode.qrCodeLabel ); | |
| title.innerText = twoFactorTotpQrcode.qrCodeLabel; |
| <?php | ||
| wp_add_inline_script( | ||
| 'customize-base', | ||
| 'setTimeout( function(){ new wp.customize.Messenger({ url: ' . wp_json_encode( esc_url( wp_customize_url() ) ) . ', channel: \'login\' }).send(\'login\') }, 1000 );' | ||
| ); | ||
| ?> |
There was a problem hiding this comment.
wp_add_inline_script() is called after do_action( 'login_footer' ). On this flow, login_footer is where core prints footer scripts (including customize-base), so adding the inline script afterwards means it likely never gets output and the messenger won't fire. Move the wp_add_inline_script( 'customize-base', ... ) call to before do_action( 'login_footer' ) (after wp_enqueue_script( 'customize-base' )) so it prints with the customize-base script tag.
|
Also previously: |
Summary
<script>blocks from 4 PHP files into 5 new external JS files underproviders/js/, improving CSP compatibility and enabling proper WordPress script dependency management (fixes issues like Missing script dependency: Uncaught TypeError: wp.apiRequest is not a function #558 wherewp.apiRequestwas unavailable)totp-admin-qrcode.js,totp-admin.js,backup-codes-admin.js,two-factor-login.js,two-factor-login-authcode.jswp_add_inline_script()instead of raw<script>tagnpm run lint:js(WordPress ES5 rules)Test plan
jquery,wp-api-request,qrcode.js)Fixes #812