This plan addresses security vulnerabilities, modernizes the codebase for PHP 8.2+, improves performance, and enhances maintainability. The plugin currently supports PHP 5.6+ but should be upgraded to PHP 7.4+ minimum (or 8.0+ recommended) to align with WordPress 6.9's requirements and modern security standards.
Files affected:
includes/login-template.php:167includes/login-template.php:242includes/lib/class-colorlib-login-customizer-css-customization.php:784, 793
Issues:
// VULNERABLE: login-template.php:167
<span id="logo-text"><?php echo $login_header_text ?></span>
// VULNERABLE: login-template.php:242
echo '<a href="#">...' . $clc_options['login-link-label'] . '</a>';
// VULNERABLE: css-customization.php:784
echo '<style type="text/css" id="clc-custom-css">' . $custom_css . '</style>';
// VULNERABLE: css-customization.php:793
echo '... href="' . $options['custom-background-link'] . '"...';Fixes:
// FIXED: login-template.php:167
<span id="logo-text"><?php echo esc_html( $login_header_text ); ?></span>
// FIXED: login-template.php:242
echo '<a href="#">...' . esc_html( $clc_options['login-link-label'] ) . '</a>';
// FIXED: css-customization.php:784 - sanitize CSS properly
echo '<style type="text/css" id="clc-custom-css">' . wp_strip_all_tags( $custom_css ) . '</style>';
// FIXED: css-customization.php:793
echo '... href="' . esc_url( $options['custom-background-link'] ) . '"...';File: includes/class-colorlib-login-customizer.php:272
Current (vulnerable):
if ( is_customize_preview() && isset( $_REQUEST['colorlib-login-customizer-customization'] ) && is_user_logged_in() ) {Fixed:
if ( is_customize_preview()
&& isset( $_GET['colorlib-login-customizer-customization'] )
&& is_user_logged_in()
&& current_user_can( 'edit_theme_options' ) ) {New function to add in class-colorlib-login-customizer-css-customization.php:
/**
* Sanitize CSS input to prevent injection attacks.
*
* @param string $css Raw CSS input.
* @return string Sanitized CSS.
*/
private function sanitize_css( string $css ): string {
// Remove any potential script injections
$css = wp_strip_all_tags( $css );
// Remove potentially dangerous CSS
$css = preg_replace( '/expression\s*\(/i', '', $css );
$css = preg_replace( '/javascript\s*:/i', '', $css );
$css = preg_replace( '/behavior\s*:/i', '', $css );
$css = preg_replace( '/-moz-binding\s*:/i', '', $css );
$css = preg_replace( '/@import/i', '', $css );
$css = preg_replace( '/url\s*\(\s*["\']?\s*data:/i', 'url(', $css );
return $css;
}File: colorlib-login-customizer.php
// Update header
* Requires PHP: 8.0
// Add version check at top of file
if ( version_compare( PHP_VERSION, '8.0.0', '<' ) ) {
add_action( 'admin_notices', function() {
echo '<div class="error"><p>';
echo esc_html__( 'Colorlib Login Customizer requires PHP 8.0 or higher.', 'colorlib-login-customizer' );
echo '</p></div>';
});
return;
}File: readme.txt
Requires PHP: 8.0
All PHP files should start with:
<?php
declare(strict_types=1);Example modernization for class-colorlib-login-customizer.php:
// BEFORE
public function get_options(){
return get_option( $this->key_name, array() );
}
// AFTER (PHP 8.0+)
public function get_options(): array {
return get_option( $this->key_name, [] );
}File: includes/class-colorlib-login-customizer.php
// BEFORE
private static $_instance = null;
public $_version;
public $_token;
// AFTER (PHP 8.0+)
private static ?self $_instance = null;
public string $_version;
public string $_token;
private string $key_name = 'clc-options';File: includes/lib/controls/*.php
// BEFORE
class Colorlib_Login_Customizer_Range_Slider_Control extends WP_Customize_Control {
public $type = 'clc-range-slider';
public $default = '';
// AFTER (PHP 8.0+)
class Colorlib_Login_Customizer_Range_Slider_Control extends WP_Customize_Control {
public string $type = 'clc-range-slider';
public string $default = '';Multiple files - search and replace:
// BEFORE
if ( '01' == $options['templates'] )
if ( '2' == $control->manager->get_setting(...) )
if ( $user_can_register == '0' )
// AFTER
if ( '01' === $options['templates'] )
if ( '2' === $control->manager->get_setting(...) )
if ( $user_can_register === '0' )// BEFORE
add_action( 'customize_register', array( $this, 'register' ), 10, 1 );
// AFTER (more readable)
add_action(
hook_name: 'customize_register',
callback: [ $this, 'register' ],
priority: 10,
accepted_args: 1
);// BEFORE
if ( isset( $this->options['logo-url'] ) && '' != $this->options['logo-url'] ) {
return esc_url( $this->options['logo-url'] );
}
// AFTER (PHP 8.0+)
return $this->options['logo-url'] ?? null
? esc_url( $this->options['logo-url'] )
: $url;File: includes/lib/class-colorlib-login-customizer-css-customization.php:661-687
// BEFORE
private function add_artifacts( $property, $value ) {
switch ( $property ) {
case 'background-image':
$value = 'url(' . $value . ')';
break;
case 'background-size':
$value = 'cover';
break;
// ... more cases
}
return $value;
}
// AFTER (PHP 8.0+)
private function add_artifacts( string $property, string $value ): string {
return match ( $property ) {
'background-image' => 'url(' . esc_url( $value ) . ')',
'background-size' => 'cover',
'background-repeat' => 'no-repeat',
'background-position' => 'center center',
'display' => $value ? 'none' : 'block',
default => $value,
};
}File: uninstall.php
<?php
declare(strict_types=1);
// Exit if accessed directly or not uninstalling
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
exit;
}
// Delete plugin options
delete_option( 'clc-options' );
// Delete review transients
delete_transient( 'colorlib_login_customizer_review_notice' );
delete_option( 'colorlib_login_customizer_review_notice' );
// Clean up user meta for dismissed notices
$users = get_users( [ 'fields' => 'ID' ] );
foreach ( $users as $user_id ) {
delete_user_meta( $user_id, 'colorlib_login_customizer_dismissed_notice' );
}
// Clear any cached data
wp_cache_flush();File: assets/js/clc-customizer.js:59, 419
// BEFORE
} else if ( 'show-image-ony' === value ) {
// AFTER
} else if ( 'show-image-only' === value ) {File: assets/js/clc-customizer.js:77-79
// BEFORE
if ( '02' === templates || '03' === templates ) {
// code
} else {
}
// AFTER
if ( '02' === templates || '03' === templates ) {
// code
}File: includes/class-colorlib-login-customizer.php:38
// REMOVE this line (constant is never used)
define( 'COLORLIB_LOGIN_CUSTOMIZER_SITE', rtrim( ABSPATH, '\\/' ) );File: includes/lib/controls/class-colorlib-login-customizer-template-control.php:36
// BEFORE (incorrect - calling section_type in a Control)
$manager->register_section_type( 'Colorlib_Login_Customizer_Template_Control' );
// AFTER (correct)
$manager->register_control_type( 'Colorlib_Login_Customizer_Template_Control' );New helper function:
/**
* Validate and sanitize color values.
*
* @param string $color Color value (hex, rgb, rgba).
* @return string Sanitized color or empty string.
*/
function clc_sanitize_color( string $color ): string {
$color = trim( $color );
// Allow empty
if ( empty( $color ) ) {
return '';
}
// Hex color
if ( preg_match( '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/', $color ) ) {
return $color;
}
// RGB/RGBA
if ( preg_match( '/^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(,\s*(0|1|0?\.\d+))?\s*\)$/', $color ) ) {
return $color;
}
return '';
}/**
* Sanitize dimension values (px, em, rem, %).
*
* @param string $value Dimension value.
* @return string Sanitized value.
*/
function clc_sanitize_dimension( string $value ): string {
$value = trim( $value );
if ( empty( $value ) ) {
return '';
}
// Allow number + unit
if ( preg_match( '/^-?\d+(\.\d+)?(px|em|rem|%|vh|vw)?$/', $value ) ) {
return $value;
}
// Allow just numbers (will add px later)
if ( is_numeric( $value ) ) {
return $value;
}
return '';
}File: includes/lib/class-colorlib-login-customizer-customizer.php
// For all color settings
$manager->add_setting(
'clc-options[form-background-color]',
[
'default' => $defaults['form-background-color'],
'type' => 'option',
'capability' => 'edit_theme_options',
'sanitize_callback' => 'clc_sanitize_color', // ADD THIS
]
);
// For all dimension settings
$manager->add_setting(
'clc-options[form-width]',
[
'default' => $defaults['form-width'],
'type' => 'option',
'capability' => 'edit_theme_options',
'sanitize_callback' => 'clc_sanitize_dimension', // ADD THIS
]
);File: includes/lib/class-colorlib-login-customizer-css-customization.php
// BEFORE: Multiple separate filter registrations
public function check_general_texts() {
add_filter( 'gettext', array( $this, 'change_lost_password_text' ), 99, 3 );
add_filter( 'gettext_with_context', array( $this, 'change_back_to_text' ), 99, 4 );
}
public function check_login_texts() {
add_filter( 'gettext', array( $this, 'change_username_label' ), 99, 3 );
add_filter( 'gettext', array( $this, 'change_password_label' ), 99, 3 );
// ... more filters
}
// AFTER: Single consolidated filter with lookup table
private array $text_replacements = [];
public function init_text_filters(): void {
$this->build_text_replacements();
if ( ! empty( $this->text_replacements ) ) {
add_filter( 'gettext', [ $this, 'replace_texts' ], 99, 3 );
}
}
private function build_text_replacements(): void {
$options = $this->options;
$mappings = [
'Username or Email Address' => $options['username-label'] ?? '',
'Password' => $options['password-label'] ?? '',
'Remember Me' => $options['rememberme-label'] ?? '',
'Log In' => $options['login-label'] ?? '',
'Lost your password?' => $options['lost-password-text'] ?? '',
// ... other mappings
];
foreach ( $mappings as $original => $replacement ) {
if ( ! empty( $replacement ) && $replacement !== $original ) {
$this->text_replacements[ $original ] = $replacement;
}
}
}
public function replace_texts( string $translated, string $text, string $domain ): string {
if ( $domain !== 'default' ) {
return $translated;
}
return $this->text_replacements[ $text ] ?? $translated;
}/**
* Get or generate CSS with caching.
*/
public function get_cached_css(): string {
$cache_key = 'clc_generated_css_' . md5( serialize( $this->options ) );
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached;
}
$css = $this->generate_css();
set_transient( $cache_key, $css, DAY_IN_SECONDS );
return $css;
}
/**
* Clear CSS cache when options change.
*/
public function clear_css_cache(): void {
global $wpdb;
$wpdb->query(
"DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_clc_generated_css_%'"
);
}File: includes/class-colorlib-login-customizer.php
// Only load customizer assets when in customizer
public function enqueue_customizer_assets(): void {
if ( ! is_customize_preview() ) {
return;
}
// Enqueue assets...
}File: .travis.yml
language: php
dist: focal
matrix:
fast_finish: true
include:
- php: '8.0'
- php: '8.1'
- php: '8.2'
env: SNIFF=1
- php: '8.3'
before_script:
- composer install --no-interaction
- if [[ "$SNIFF" == "1" ]]; then composer require --dev wp-coding-standards/wpcs dealerdirect/phpcodesniffer-composer-installer phpcompatibility/phpcompatibility-wp; fi
script:
- find . -name '*.php' -not -path './vendor/*' -not -path './node_modules/*' | xargs -n 1 php -l
- if [[ "$SNIFF" == "1" ]]; then vendor/bin/phpcs; fi{
"name": "colorlib/login-customizer",
"description": "Customize the WordPress login page",
"type": "wordpress-plugin",
"license": "GPL-3.0-or-later",
"require": {
"php": ">=8.0"
},
"require-dev": {
"wp-coding-standards/wpcs": "^3.0",
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"phpcompatibility/phpcompatibility-wp": "^2.1"
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
},
"scripts": {
"phpcs": "phpcs",
"phpcbf": "phpcbf"
}
}<?xml version="1.0"?>
<ruleset name="Colorlib Login Customizer">
<description>Coding standards for Colorlib Login Customizer</description>
<file>.</file>
<exclude-pattern>/vendor/*</exclude-pattern>
<exclude-pattern>/node_modules/*</exclude-pattern>
<exclude-pattern>/.git/*</exclude-pattern>
<arg name="extensions" value="php"/>
<arg name="colors"/>
<arg value="sp"/>
<rule ref="WordPress">
<exclude name="WordPress.Files.FileName.InvalidClassFileName"/>
</rule>
<rule ref="PHPCompatibilityWP"/>
<config name="testVersion" value="8.0-"/>
<config name="minimum_supported_wp_version" value="6.0"/>
</ruleset>// Generate nonce for preview URL
public function get_preview_url(): string {
$nonce = wp_create_nonce( 'clc_preview' );
return add_query_arg( [
'colorlib-login-customizer-customization' => 'true',
'_wpnonce' => $nonce,
], wp_login_url() );
}
// Verify nonce in template override
public function change_template_if_necessary( string $template ): string {
if ( ! is_customize_preview() ) {
return $template;
}
if ( ! isset( $_GET['colorlib-login-customizer-customization'] ) ) {
return $template;
}
if ( ! is_user_logged_in() || ! current_user_can( 'edit_theme_options' ) ) {
return $template;
}
// Verify nonce
if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'clc_preview' ) ) {
return $template;
}
return plugin_dir_path( __FILE__ ) . 'login-template.php';
}/**
* Export settings as JSON.
*/
public function export_settings(): string {
$options = get_option( 'clc-options', [] );
return wp_json_encode( $options, JSON_PRETTY_PRINT );
}
/**
* Import settings from JSON.
*/
public function import_settings( string $json ): bool {
$options = json_decode( $json, true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
return false;
}
// Sanitize all imported values
$sanitized = $this->sanitize_options( $options );
return update_option( 'clc-options', $sanitized );
}/**
* Reset all settings to defaults.
*/
public function reset_to_defaults(): bool {
$defaults = Colorlib_Login_Customizer::clc_defaults();
return update_option( 'clc-options', $defaults );
}- Fix all XSS vulnerabilities (Phase 1.1)
- Add capability checks (Phase 1.2)
- Implement CSS sanitization (Phase 1.3)
- Release as v1.3.5 (security patch)
- Update minimum PHP to 8.0 (Phase 2.1)
- Add strict types throughout (Phase 2.2-2.3)
- Fix all loose comparisons (Phase 2.5)
- Update CI/CD (Phase 6)
- Implement proper uninstall (Phase 3.1)
- Fix JavaScript issues (Phase 3.2-3.3)
- Remove dead code (Phase 3.4-3.5)
- Add input validation (Phase 4)
- Consolidate filters (Phase 5.1)
- Add CSS caching (Phase 5.2)
- Lazy load assets (Phase 5.3)
- Add export/import/reset (Phase 7)
- Release as v2.0.0
- PHP 8.0+ required - Users on older PHP will see admin notice and plugin will not activate
- WordPress 6.0+ required - Aligns with modern WP development
- Options structure unchanged - Existing settings preserved during upgrade
- All XSS vectors tested and blocked
- Capability checks working correctly
- CSS sanitization not breaking valid CSS
- PHP 8.0, 8.1, 8.2, 8.3 compatibility verified
- All customizer controls functional
- Live preview working
- Login page rendering correctly
- Register/Lost Password pages rendering correctly
- Export/Import working
- Reset to defaults working
- Uninstall removes all data
- No PHP deprecation notices
- PHPCS passes all checks
| File | Changes |
|---|---|
colorlib-login-customizer.php |
PHP version check, header updates |
includes/class-colorlib-login-customizer.php |
Type declarations, capability checks, remove unused constant |
includes/lib/class-colorlib-login-customizer-customizer.php |
Sanitization callbacks, strict comparisons |
includes/lib/class-colorlib-login-customizer-css-customization.php |
XSS fixes, CSS sanitization, filter consolidation, caching |
includes/login-template.php |
Escape all output |
includes/lib/controls/*.php |
Type declarations, fix register_control_type |
assets/js/clc-customizer.js |
Fix typos, remove empty blocks |
uninstall.php |
Complete cleanup implementation |
.travis.yml |
Modern PHP versions |
phpcs.ruleset.xml |
Updated rules |
composer.json |
NEW - dependency management |
readme.txt |
Updated requirements |