chore: improve workflow names and fix CSS indentation consistency (#18)
* fix: resolve plugin class loading reliability issues * fix: address CodeRabbit XSS and accessibility findings from PR #18 - admin/js/admin-scripts.js: replace HTML string interpolation in showNotice with DOM API construction and .text() to prevent XSS; whitelist type values - admin/js/update-source-selector.js: replace .html(message) with .text(message) in showMessage to prevent XSS from AJAX response content - admin/templates/modal.php: add role=dialog, aria-modal=true, aria-labelledby for screen reader semantics; replace <span> close control with <button> for keyboard operability and proper ARIA role
This commit is contained in:
@@ -105,10 +105,16 @@
|
|||||||
* @param {string} message Notice message
|
* @param {string} message Notice message
|
||||||
*/
|
*/
|
||||||
showNotice: function (type, message) {
|
showNotice: function (type, message) {
|
||||||
const $notice = $( '<div class="wpst-notice ' + type + '"><p>' + message + '</p></div>' );
|
const allowedTypes = [ 'success', 'error', 'warning' ];
|
||||||
|
const safeType = allowedTypes.includes( type ) ? type : 'error';
|
||||||
|
const $p = $( '<p>' );
|
||||||
|
const $notice = $( '<div>' ).addClass( 'wpst-notice ' + safeType ).append( $p );
|
||||||
|
|
||||||
|
// Set message as plain text to prevent XSS.
|
||||||
|
$p.text( message );
|
||||||
|
|
||||||
// Add notice to the page.
|
// Add notice to the page.
|
||||||
$( '.wpst-notices' ).html( $notice );
|
$( '.wpst-notices' ).empty().append( $notice );
|
||||||
|
|
||||||
// Automatically remove notice after 5 seconds.
|
// Automatically remove notice after 5 seconds.
|
||||||
setTimeout(
|
setTimeout(
|
||||||
|
|||||||
@@ -153,8 +153,8 @@
|
|||||||
showMessage: function (type, message) {
|
showMessage: function (type, message) {
|
||||||
const $message = this.$modal.find( '.wpst-modal-message' );
|
const $message = this.$modal.find( '.wpst-modal-message' );
|
||||||
|
|
||||||
// Set message content and type.
|
// Set message as plain text to prevent XSS, then apply type class.
|
||||||
$message.html( message ).removeClass( 'success error' ).addClass( type ).show();
|
$message.text( message ).removeClass( 'success error' ).addClass( type ).show();
|
||||||
|
|
||||||
// Hide message after a delay for success messages.
|
// Hide message after a delay for success messages.
|
||||||
if (type === 'success') {
|
if (type === 'success') {
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<!-- Update Source Modal -->
|
<!-- Update Source Modal -->
|
||||||
<div id="wpst-update-source-modal" class="wpst-modal">
|
<div id="wpst-update-source-modal" class="wpst-modal" role="dialog" aria-modal="true" aria-labelledby="wpst-modal-title">
|
||||||
<div class="wpst-modal-content">
|
<div class="wpst-modal-content">
|
||||||
<div class="wpst-modal-header">
|
<div class="wpst-modal-header">
|
||||||
<h2 class="wpst-modal-title"><?php esc_html_e( 'Select Update Source', 'wp-plugin-starter-template' ); ?></h2>
|
<h2 id="wpst-modal-title" class="wpst-modal-title"><?php esc_html_e( 'Select Update Source', 'wp-plugin-starter-template' ); ?></h2>
|
||||||
<span class="wpst-modal-close">×</span>
|
<button type="button" class="wpst-modal-close" aria-label="<?php esc_attr_e( 'Close', 'wp-plugin-starter-template' ); ?>">×</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wpst-modal-body">
|
<div class="wpst-modal-body">
|
||||||
|
|||||||
@@ -69,10 +69,12 @@ class Admin {
|
|||||||
// Get the plugin version.
|
// Get the plugin version.
|
||||||
$plugin_version = $this->core->get_plugin_version();
|
$plugin_version = $this->core->get_plugin_version();
|
||||||
|
|
||||||
|
$plugin_url = $this->get_plugin_base_url();
|
||||||
|
|
||||||
// Enqueue styles.
|
// Enqueue styles.
|
||||||
\wp_enqueue_style(
|
\wp_enqueue_style(
|
||||||
'wpst-admin-styles',
|
'wpst-admin-styles',
|
||||||
plugin_dir_url( dirname( __DIR__ ) ) . 'admin/css/admin-styles.css',
|
$plugin_url . 'admin/css/admin-styles.css',
|
||||||
array(), // Dependencies.
|
array(), // Dependencies.
|
||||||
$plugin_version // Version.
|
$plugin_version // Version.
|
||||||
);
|
);
|
||||||
@@ -80,7 +82,7 @@ class Admin {
|
|||||||
// Enqueue admin scripts.
|
// Enqueue admin scripts.
|
||||||
\wp_enqueue_script(
|
\wp_enqueue_script(
|
||||||
'wpst-admin-script',
|
'wpst-admin-script',
|
||||||
plugin_dir_url( dirname( __DIR__ ) ) . 'admin/js/admin-scripts.js',
|
$plugin_url . 'admin/js/admin-scripts.js',
|
||||||
array( 'jquery' ),
|
array( 'jquery' ),
|
||||||
$plugin_version, // Version.
|
$plugin_version, // Version.
|
||||||
true
|
true
|
||||||
@@ -99,4 +101,21 @@ class Admin {
|
|||||||
$data
|
$data
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get plugin base URL.
|
||||||
|
*
|
||||||
|
* @return string Plugin base URL with trailing slash.
|
||||||
|
*/
|
||||||
|
private function get_plugin_base_url(): string {
|
||||||
|
if ( defined( 'WP_PLUGIN_STARTER_TEMPLATE_URL' ) ) {
|
||||||
|
return WP_PLUGIN_STARTER_TEMPLATE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( defined( 'WPST_PLUGIN_URL' ) ) {
|
||||||
|
return WPST_PLUGIN_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return \plugin_dir_url( dirname( __DIR__, 2 ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ if ( getenv( 'WP_PHPUNIT__DIR' ) ) {
|
|||||||
// Include plugin files needed for tests.
|
// Include plugin files needed for tests.
|
||||||
require_once WPST_PLUGIN_DIR . 'includes/class-core.php';
|
require_once WPST_PLUGIN_DIR . 'includes/class-core.php';
|
||||||
require_once WPST_PLUGIN_DIR . 'includes/class-plugin.php';
|
require_once WPST_PLUGIN_DIR . 'includes/class-plugin.php';
|
||||||
if ( file_exists( WPST_PLUGIN_DIR . 'admin/lib/admin.php' ) ) {
|
if ( file_exists( WPST_PLUGIN_DIR . 'includes/Admin/class-admin.php' ) ) {
|
||||||
require_once WPST_PLUGIN_DIR . 'admin/lib/admin.php';
|
require_once WPST_PLUGIN_DIR . 'includes/Admin/class-admin.php';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,17 +101,12 @@ class AdminTest extends \WP_Mock\Tools\TestCase {
|
|||||||
'return' => 'wp_plugin_starter_template_settings',
|
'return' => 'wp_plugin_starter_template_settings',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Mock WordPress functions used in the method
|
|
||||||
WP_Mock::userFunction('plugin_dir_url', [
|
|
||||||
'return' => 'http://example.com/wp-content/plugins/wp-plugin-starter-template/includes/Admin/',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Mock wp_enqueue_style
|
// Mock wp_enqueue_style
|
||||||
WP_Mock::userFunction('wp_enqueue_style', [
|
WP_Mock::userFunction('wp_enqueue_style', [
|
||||||
'times' => 1,
|
'times' => 1,
|
||||||
'args' => [
|
'args' => [
|
||||||
'wpst-admin-styles',
|
'wpst-admin-styles',
|
||||||
'http://example.com/wp-content/plugins/wp-plugin-starter-template/includes/Admin/../../admin/css/admin-styles.css',
|
'http://example.org/wp-content/plugins/wp-plugin-starter-template/admin/css/admin-styles.css',
|
||||||
[],
|
[],
|
||||||
'1.0.0',
|
'1.0.0',
|
||||||
],
|
],
|
||||||
@@ -122,7 +117,7 @@ class AdminTest extends \WP_Mock\Tools\TestCase {
|
|||||||
'times' => 1,
|
'times' => 1,
|
||||||
'args' => [
|
'args' => [
|
||||||
'wpst-admin-script',
|
'wpst-admin-script',
|
||||||
'http://example.com/wp-content/plugins/wp-plugin-starter-template/includes/Admin/../../admin/js/admin-scripts.js',
|
'http://example.org/wp-content/plugins/wp-plugin-starter-template/admin/js/admin-scripts.js',
|
||||||
['jquery'],
|
['jquery'],
|
||||||
'1.0.0',
|
'1.0.0',
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -55,13 +55,19 @@ spl_autoload_register(
|
|||||||
// Get the relative class name.
|
// Get the relative class name.
|
||||||
$relative_class = substr( $className, $len );
|
$relative_class = substr( $className, $len );
|
||||||
|
|
||||||
// Convert namespace to path.
|
// Build class file path using WordPress-style class file names.
|
||||||
$file = WP_PLUGIN_STARTER_TEMPLATE_PATH . 'includes/' . str_replace( '\\', '/', $relative_class ) . '.php';
|
$relative_path = str_replace( '\\', '/', $relative_class );
|
||||||
|
$path_parts = explode( '/', $relative_path );
|
||||||
|
$class_name = array_pop( $path_parts );
|
||||||
|
$directory = '';
|
||||||
|
|
||||||
// Convert class name format to file name format.
|
if ( ! empty( $path_parts ) ) {
|
||||||
$file = str_replace( 'class-', '', $file );
|
$directory = implode( '/', $path_parts ) . '/';
|
||||||
$file = preg_replace( '/([a-z])([A-Z])/', '$1-$2', $file );
|
}
|
||||||
$file = strtolower( $file );
|
|
||||||
|
$class_file = preg_replace( '/([a-z])([A-Z])/', '$1-$2', $class_name );
|
||||||
|
$class_file = 'class-' . strtolower( $class_file ) . '.php';
|
||||||
|
$file = WP_PLUGIN_STARTER_TEMPLATE_PATH . 'includes/' . $directory . $class_file;
|
||||||
|
|
||||||
// If the file exists, require it.
|
// If the file exists, require it.
|
||||||
if ( file_exists( $file ) ) {
|
if ( file_exists( $file ) ) {
|
||||||
|
|||||||
Reference in New Issue
Block a user