Skip to content

Latest commit

 

History

History
776 lines (599 loc) · 19.2 KB

File metadata and controls

776 lines (599 loc) · 19.2 KB

Colorlib Login Customizer - Comprehensive Improvement Plan

Executive Summary

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.


Phase 1: Critical Security Fixes (Priority: IMMEDIATE)

1.1 XSS Vulnerabilities - Unescaped Output

Files affected:

  • includes/login-template.php:167
  • includes/login-template.php:242
  • includes/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'] ) . '"...';

1.2 Missing Capability Check for Template Override

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' ) ) {

1.3 CSS Sanitization Function

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;
}

Phase 2: PHP 8.2+ Modernization

2.1 Update Minimum Requirements

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

2.2 Add Strict Types and Type Declarations

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, [] );
}

2.3 Property Type Declarations

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';

2.4 Constructor Property Promotion

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 = '';

2.5 Replace Loose Comparisons with Strict

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' )

2.6 Use Named Arguments (PHP 8.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
);

2.7 Nullsafe Operator

// 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;

2.8 Match Expression (PHP 8.0+)

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,
    };
}

Phase 3: Code Quality Improvements

3.1 Implement Proper Uninstall Cleanup

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();

3.2 Fix JavaScript Typo

File: assets/js/clc-customizer.js:59, 419

// BEFORE
} else if ( 'show-image-ony' === value ) {

// AFTER
} else if ( 'show-image-only' === value ) {

3.3 Remove Empty Else Block

File: assets/js/clc-customizer.js:77-79

// BEFORE
if ( '02' === templates || '03' === templates ) {
    // code
} else {

}

// AFTER
if ( '02' === templates || '03' === templates ) {
    // code
}

3.4 Remove Unused Constant

File: includes/class-colorlib-login-customizer.php:38

// REMOVE this line (constant is never used)
define( 'COLORLIB_LOGIN_CUSTOMIZER_SITE', rtrim( ABSPATH, '\\/' ) );

3.5 Fix Incorrect register_section_type Call

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' );

Phase 4: Input Validation & Sanitization

4.1 Add Color Validation

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 '';
}

4.2 Add Numeric Validation for Dimensions

/**
 * 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 '';
}

4.3 Register Sanitization Callbacks in Customizer

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
    ]
);

Phase 5: Performance Optimizations

5.1 Consolidate gettext Filters

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;
}

5.2 Cache Generated CSS

/**
 * 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_%'"
    );
}

5.3 Lazy Load Assets

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...
}

Phase 6: CI/CD Modernization

6.1 Update Travis CI for Modern PHP

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

6.2 Add composer.json

{
    "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"
    }
}

6.3 Update phpcs.ruleset.xml

<?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>

Phase 7: New Features & Enhancements

7.1 Add Nonce Protection for Preview

// 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';
}

7.2 Add Export/Import Settings

/**
 * 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 );
}

7.3 Add Reset to Defaults

/**
 * Reset all settings to defaults.
 */
public function reset_to_defaults(): bool {
    $defaults = Colorlib_Login_Customizer::clc_defaults();
    return update_option( 'clc-options', $defaults );
}

Implementation Order

Week 1: Critical Security

  1. Fix all XSS vulnerabilities (Phase 1.1)
  2. Add capability checks (Phase 1.2)
  3. Implement CSS sanitization (Phase 1.3)
  4. Release as v1.3.5 (security patch)

Week 2: PHP Modernization

  1. Update minimum PHP to 8.0 (Phase 2.1)
  2. Add strict types throughout (Phase 2.2-2.3)
  3. Fix all loose comparisons (Phase 2.5)
  4. Update CI/CD (Phase 6)

Week 3: Code Quality

  1. Implement proper uninstall (Phase 3.1)
  2. Fix JavaScript issues (Phase 3.2-3.3)
  3. Remove dead code (Phase 3.4-3.5)
  4. Add input validation (Phase 4)

Week 4: Performance & Features

  1. Consolidate filters (Phase 5.1)
  2. Add CSS caching (Phase 5.2)
  3. Lazy load assets (Phase 5.3)
  4. Add export/import/reset (Phase 7)
  5. Release as v2.0.0

Breaking Changes in v2.0.0

  1. PHP 8.0+ required - Users on older PHP will see admin notice and plugin will not activate
  2. WordPress 6.0+ required - Aligns with modern WP development
  3. Options structure unchanged - Existing settings preserved during upgrade

Testing Checklist

  • 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

Files Changed Summary

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