Files
shopify-ai-backup/scripts/validate-wordpress-plugin.sh

1022 lines
47 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# ==============================================================================
# COMPREHENSIVE WORDPRESS PLUGIN VALIDATOR (STRICT/PARANOID MODE)
# ==============================================================================
# Validates WP plugins against strict security and coding standards.
# Flags ALL potential vulnerabilities for manual review.
# ==============================================================================
PLUGIN_DIR="${1:-}"
# Color codes
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
NC='\033[0m'
ERROR_COUNT=0
WARN_COUNT=0
if [[ -z "${PLUGIN_DIR}" ]]; then
echo "Usage: $(basename "$0") /path/to/plugin" >&2
exit 1
fi
echo -e "${BLUE}========================================================${NC}"
echo -e "${BLUE} STRICT WORDPRESS PLUGIN SECURITY & CODE AUDIT ${NC}"
echo -e "${BLUE}========================================================${NC}"
echo "Scanning: ${PLUGIN_DIR}"
echo ""
mapfile -t php_files < <(find "${PLUGIN_DIR}" -type f -name "*.php" ! -path "*/vendor/*" ! -path "*/node_modules/*" 2>/dev/null | sort || true)
# Fallback to glob if mapfile fails or array is empty
if [[ ${#php_files[@]} -eq 0 ]] || [[ -z "${php_files[*]:-}" ]]; then
IFS=$'\n' read -d '' -r -a php_files < <(find "${PLUGIN_DIR}" -type f -name "*.php" ! -path "*/vendor/*" ! -path "*/node_modules/*" 2>/dev/null | sort || true) || true
fi
if [[ "${#php_files[@]}" -eq 0 ]] || [[ -z "${php_files[*]:-}" ]]; then
echo -e "${RED}✗ FATAL: No PHP files found.${NC}"
exit 1
fi
# ============================================
# 1. FORBIDDEN FUNCTIONS (SECURITY)
# ============================================
echo -e "${MAGENTA}[1/11] Checking for Dangerous/Forbidden Functions...${NC}"
# List of dangerous functions that should rarely/never be used in WP plugins
FORBIDDEN_FUNCS=(
"eval" "exec" "passthru" "shell_exec" "system" "proc_open" "popen"
"curl_exec:Use wp_remote_get()" "file_get_contents:Use wp_remote_get()"
"base64_decode:Suspicious obfuscation risk"
"create_function:Deprecated and dangerous"
"extract:Variable pollution risk"
"assert:Deprecated in PHP 8"
"preg_replace with /e:Use callback instead"
)
for file in "${php_files[@]}"; do
for rule in "${FORBIDDEN_FUNCS[@]}"; do
func="${rule%%:*}"
reason="${rule##*:}"
# We use a regex that looks for the function name followed by (
if grep -nE "\b${func}\s*\(" "${file}" > /dev/null; then
# Ignore benign usages if needed (e.g. file_get_contents on local file)
# But for strict mode, we flag all.
echo -e "${RED} ✗ SECURITY RISK in ${file}:${NC}"
echo " Found forbidden function: '${func}'"
if [[ "$reason" != "$func" ]]; then echo " Reason: $reason"; fi
grep -nE "\b${func}\s*\(" "${file}" | head -1
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
done
done
echo ""
# ============================================
# 2. SQL INJECTION (STRICT)
# ============================================
echo -e "${MAGENTA}[2/11] Checking for SQL Injection Patterns...${NC}"
for file in "${php_files[@]}"; do
# 2.1 Direct variable interpolation in query
# Matches: $wpdb->query( "SELECT ... $var" ) or similar
# This is a high-confidence SQLi pattern
if grep -nE "\$wpdb->(query|get_results|get_row|get_var|get_col)\s*\(\s*[\"'].*\\$[a-zA-Z].*[\"']" "${file}"; then
echo -e "${RED} ✗ SQL INJECTION RISK in ${file}:${NC}"
echo " Variable directly interpolated into SQL string."
echo " MUST use \$wpdb->prepare()."
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
# 2.2 Missing prepare
# Matches queries that look dynamic but lack 'prepare'
if grep -q "\$wpdb->" "${file}"; then
# If file has wpdb usage but NO prepare() call, and isn't just checking errors
if ! grep -q "prepare" "${file}" && grep -qE "SELECT|INSERT|UPDATE|DELETE" "${file}"; then
echo -e "${YELLOW} ⚠ WARNING: SQL usage detected without \$wpdb->prepare() in ${file}.${NC}"
echo " Verify manual escaping is robust."
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
done
echo ""
# ============================================
# 3. XSS & SANITIZATION (STRICT)
# ============================================
echo -e "${MAGENTA}[3/11] Checking for XSS & Input Sanitization...${NC}"
for file in "${php_files[@]}"; do
# 3.1 Echoing superglobals directly
if grep -nE "echo\s+.*(\\\$_GET|\\\$_POST|\\\$_REQUEST|\\\$_SERVER)" "${file}"; then
echo -e "${RED} ✗ XSS VULNERABILITY in ${file}:${NC}"
echo " Direct echo of superglobal detected. Must use esc_html / esc_attr."
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
# 3.2 Setting variables from input without sanitization (Paranoid Check)
# Looks for $var = $_POST[...] without a sanitize_ function on the same line
if grep -nE "\\\$[a-zA-Z0-9_]+\s*=\s*(\\\$_POST|\\\$_GET|\\\$_REQUEST)" "${file}" | grep -vE "sanitize_|absint|intval|esc_"; then
echo -e "${YELLOW} ⚠ UNSANITIZED INPUT in ${file}:${NC}"
echo " Raw input assigned to variable without immediate sanitization."
grep -nE "\\\$[a-zA-Z0-9_]+\s*=\s*(\\\$_POST|\\\$_GET|\\\$_REQUEST)" "${file}" | head -1
WARN_COUNT=$((WARN_COUNT + 1))
fi
# 3.3 Print/Echo without escaping functions (General Heuristic)
# Flags lines like `echo $variable;` if $variable isn't obviously an object method
# This produces noise, but ensures strictness.
if grep -nE "(echo|print)\s+\\\$[a-zA-Z0-9_]+;" "${file}"; then
# Simple variable echo - likely unsafe unless variable source is known trusted
echo -e "${YELLOW} ⚠ UNESCAPED OUTPUT in ${file}:${NC}"
echo " Printing variable without escaping function (esc_html, etc)."
WARN_COUNT=$((WARN_COUNT + 1))
fi
done
echo ""
# ============================================
# 4. NONCES & CAPABILITIES (CSRF/Auth)
# ============================================
echo -e "${MAGENTA}[4/11] Checking Nonces and Permissions...${NC}"
for file in "${php_files[@]}"; do
# 4.1 Processing Form Data
if grep -q "\$_POST" "${file}"; then
# If file processes POST, it MUST have nonce verification
if ! grep -qE "wp_verify_nonce|check_admin_referer|check_ajax_referer" "${file}"; then
echo -e "${RED} ✗ CSRF VULNERABILITY in ${file}:${NC}"
echo " File processes \$_POST but no nonce check found."
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
# It SHOULD also have capability checks
if ! grep -qE "current_user_can" "${file}"; then
echo -e "${YELLOW} ⚠ MISSING CAPABILITY CHECK in ${file}:${NC}"
echo " File processes \$_POST but 'current_user_can' not found."
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
done
echo ""
# ============================================
# 5. PHP CODING STANDARDS (LINTING)
# ============================================
echo -e "${MAGENTA}[5/11] PHP Syntax & Standards...${NC}"
# 5.4 UNDEFINED ARRAY KEY DETECTION
echo -e "${MAGENTA}[5.4/11] Checking for Undefined Array Keys...${NC}"
for file in "${php_files[@]}"; do
# 5.4.1 Direct array access without isset check on superglobals
# Matches patterns like: $_POST['key'] or $_GET['key'] without preceding isset()
UNSAFE_KEYS=$(grep -nE '\$_POST\[|\$_GET\[|\$_REQUEST\[|\$_COOKIE\[' "${file}" | grep -vB1 "isset\|array_key_exists" | grep -E '\$_POST\[|\$_GET\[|\$_REQUEST\[|\$_COOKIE\[' || true)
if [ -n "$UNSAFE_KEYS" ]; then
echo -e "${RED} ✗ UNDEFINED ARRAY KEY RISK in ${file}:${NC}"
echo " Superglobal array access without isset()/array_key_exists() check."
echo " Lines with potential undefined key access:"
echo "$UNSAFE_KEYS" | head -3
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
# 5.4.2 Array key access in expressions without isset check
# Matches: $array['key'] or $array[$var] where key might not exist
if grep -nE '\$[a-zA-Z_][a-zA-Z0-9_]*\[' "${file}" | grep -vB1 "isset\|array_key_exists\|foreach.*as.*=>" | grep -E '\$[a-zA-Z_][a-zA-Z0-9_]*\[' > /dev/null 2>&1; then
echo -e "${YELLOW} ⚠ POTENTIAL UNDEFINED ARRAY KEY in ${file}:${NC}"
echo " Array key access without isset() or array_key_exists() check."
echo " Consider using isset() or the null coalescing operator (??)"
grep -nE '\$[a-zA-Z_][a-zA-Z0-9_]*\[' "${file}" | grep -vB1 "isset\|array_key_exists\|foreach.*as.*=>" | grep -E '\$[a-zA-Z_][a-zA-Z0-9_]*\[' | head -3
WARN_COUNT=$((WARN_COUNT + 1))
fi
# 5.4.3 Multi-dimensional array access without validation
# Matches: $array['key1']['key2'] which is risky without validation
if grep -nE '\$[a-zA-Z_][a-zA-Z0-9_]*\[.*\]\[' "${file}" | grep -vB1 "isset\|array_key_exists" | head -1 > /dev/null 2>&1; then
echo -e "${YELLOW} ⚠ MULTI-DIMENSIONAL ARRAY ACCESS in ${file}:${NC}"
echo " Multi-dimensional array access without validation."
grep -nE '\$[a-zA-Z_][a-zA-Z0-9_]*\[.*\]\[' "${file}" | grep -vB1 "isset\|array_key_exists" | head -1
WARN_COUNT=$((WARN_COUNT + 1))
fi
# 5.4.4 Array access in assignment without check
# Matches: $var = $_POST['key'] without isset
if grep -nE '\$[a-zA-Z_]+\s*=\s*\$_(POST\[|GET\[|REQUEST\[|COOKIE\[)' "${file}" | grep -vB1 "isset\|array_key_exists" | head -1 > /dev/null 2>&1; then
echo -e "${RED} ✗ UNSAFE ARRAY KEY ASSIGNMENT in ${file}:${NC}"
echo " Assignment from array key without isset() check."
grep -nE '\$[a-zA-Z_]+\s*=\s*\$_(POST\[|GET\[|REQUEST\[|COOKIE\[)' "${file}" | grep -vB1 "isset\|array_key_exists" | head -1
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
done
echo ""
# 5.1 Syntax
if command -v php >/dev/null 2>&1; then
for file in "${php_files[@]}"; do
if ! php -l "${file}" > /dev/null 2>&1; then
echo -e "${RED} ✗ PHP SYNTAX ERROR in ${file}${NC}"
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
done
# 5.5 Duplicate Class Declaration Check (Runtime Error Detection)
echo -e "${MAGENTA}[5.5/11] Checking for Duplicate Class/Function Declarations...${NC}"
php "${BASH_SOURCE[0]%/*}/check-duplicate-classes.php" "${PLUGIN_DIR}" 2>&1 | while IFS= read -r line; do
if [[ "$line" == *DUPLICATE* ]]; then
echo -e "${RED} ✗ RUNTIME ERROR: ${line}${NC}"
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$line" == *MISSING* ]]; then
echo -e "${RED} ✗ RUNTIME ERROR: ${line}${NC}"
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$line" == *WARNING* ]]; then
echo -e "${YELLOW}${line}${NC}"
WARN_COUNT=$((WARN_COUNT + 1))
elif [[ "$line" != *"No duplicate"* ]] && [[ "$line" != *"No runtime"* ]] && [[ "$line" != *"Scanned"* ]]; then
echo " ${line}"
fi
done
fi
# 5.6 Direct file access check
# Every PHP file should start with strict denial of direct access
for file in "${php_files[@]}"; do
# Skip templates usually, but plugin core files must have it
first_lines=$(head -n 20 "${file}")
if ! echo "$first_lines" | grep -qE "defined\s*\(\s*['\"]ABSPATH['\"]\s*\)|defined\s*\(\s*['\"]WPINC['\"]\s*\)"; then
echo -e "${YELLOW} ⚠ DIRECT ACCESS PROTECTION MISSING in ${file}${NC}"
echo " Add 'defined(ABSPATH) || exit;' at top of file."
WARN_COUNT=$((WARN_COUNT + 1))
fi
done
echo ""
# ============================================
# 6. ACTIVATION HOOK VALIDATION
# ============================================
echo -e "${MAGENTA}[6/11] Checking Activation Hook Setup...${NC}"
for file in "${php_files[@]}"; do
# Check for register_activation_hook usage
if grep -q "register_activation_hook" "${file}"; then
# Extract the callback being used
callbacks=$(grep -oE "register_activation_hook\s*\([^,]+,\s*[^)]+\)" "${file}" || true)
while IFS= read -r callback; do
# Check if callback references a class method like array($obj, 'method') or [new Class(), 'method']
if echo "$callback" | grep -qE "\[\s*(\$[a-zA-Z_]+|new\s+[A-Z][a-zA-Z0-9_]+\(\))\s*,\s*['\"]install['\"]"; then
# Check if the class is instantiated after the register_activation_hook call
if grep -A5 "register_activation_hook" "${file}" | grep -q "install"; then
# Look for require/include statements that load the class
if ! grep -B10 "register_activation_hook" "${file}" | grep -qE "(require|include|autoload)"; then
echo -e "${RED} ✗ ACTIVATION HOOK - INSTALL CLASS NOT LOADED in ${file}:${NC}"
echo " register_activation_hook references install class but class may not be loaded."
grep -n "register_activation_hook" "${file}" | head -1
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
fi
# Check for callback using class array syntax: ['ClassName', 'method']
if echo "$callback" | grep -qE "\[\s*['\"][A-Z][a-zA-Z0-9_]*['\"]\s*,\s*['\"]install['\"]"; then
class_name=$(echo "$callback" | grep -oE "['\"][A-Z][a-zA-Z0-9_]*['\"]\s*," | sed "s/['\"]//g" | sed "s/[, ]//g" | head -1)
# Check if this class file is loaded before the hook registration
if ! grep -B20 "register_activation_hook" "${file}" | grep -qi "class ${class_name}"; then
if ! grep -B20 "register_activation_hook" "${file}" | grep -qiE "require.*${class_name}|include.*${class_name}|autoload.*${class_name}"; then
echo -e "${RED} ✗ ACTIVATION HOOK - CLASS NOT LOADED in ${file}:${NC}"
echo " register_activation_hook references class '${class_name}' but class file may not be loaded."
grep -n "register_activation_hook" "${file}" | head -1
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
fi
# Check for callback like ClassName::install_method
if echo "$callback" | grep -qE "[A-Z][a-zA-Z0-9_]*::install"; then
class_name=$(echo "$callback" | grep -oE "[A-Z][a-zA-Z0-9_]*::install" | sed 's/::install//' | head -1)
if ! grep -B20 "register_activation_hook" "${file}" | grep -qiE "class ${class_name}|require.*${class_name}|include.*${class_name}"; then
echo -e "${RED} ✗ ACTIVATION HOOK - STATIC CLASS NOT LOADED in ${file}:${NC}"
echo " register_activation_hook references static class '${class_name}' but class may not be loaded."
grep -n "register_activation_hook" "${file}" | head -1
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
done <<< "$callbacks"
fi
# Check for activation hook in a separate file that references external classes
if grep -q "register_activation_hook" "${file}" && grep -q "use.*;" "${file}"; then
use_classes=$(grep "^use " "${file}" | grep -oE "use [A-Z][a-zA-Z0-9_\\]+;" | sed 's/use //;s/;//' || true)
if [ -n "$use_classes" ]; then
echo -e "${YELLOW} ⚠ ACTIVATION HOOK - EXTERNAL CLASS USAGE in ${file}:${NC}"
echo " Activation hook uses external classes via 'use' statements. Verify they are autoloaded."
grep -n "register_activation_hook" "${file}" | head -1
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
# Check if activation hook is inside a class (common anti-pattern)
if grep -q "register_activation_hook" "${file}" && grep -qE "^\s*(public|protected|private)\s+static\s+function\s+.*install" "${file}"; then
echo -e "${YELLOW} ⚠ ACTIVATION HOOK - INSIDE CLASS in ${file}:${NC}"
echo " Activation hook inside class. Consider using separate activation file."
grep -n "register_activation_hook" "${file}" | head -1
WARN_COUNT=$((WARN_COUNT + 1))
fi
done
echo ""
# ============================================
# 7. CLASS LOADING VALIDATION (INSTALL/ADMIN/FRONTEND)
# ============================================
echo -e "${MAGENTA}[7/11] Comprehensive Class Loading Validation...${NC}"
# Define common WordPress hook patterns for different component types
ADMIN_HOOKS_PATTERN="admin_menu|admin_init|admin_head|admin_footer|admin_enqueue_scripts|wp_ajax_|load-(tools|plugins|themes)-page"
FRONTEND_HOOKS_PATTERN="wp_enqueue_scripts|wp_head|wp_footer|template_redirect|wp_ajax_nopriv_"
INSTALL_HOOKS_PATTERN="register_activation_hook|register_deactivation_hook|register_uninstall_hook"
# Find all PHP files and analyze class loading patterns
for file in "${php_files[@]}"; do
# Check for admin hooks without admin class loading
if grep -qE "add_action\s*\(\s*['\"](${ADMIN_HOOKS_PATTERN})" "${file}"; then
# Look for admin class references
if grep -qE "(Admin|admin|Admin_[A-Z])" "${file}"; then
# Check if admin class is loaded before hooks
if ! grep -B10 "add_action.*(${ADMIN_HOOKS_PATTERN})" "${file}" | grep -qE "(require|include|autoload).*(Admin|admin|Admin_[A-Z])"; then
# Also check for class instantiation before hooks
if ! grep -B10 "add_action.*(${ADMIN_HOOKS_PATTERN})" "${file}" | grep -qE "(new\s+.*Admin|new\s+.*admin|\$admin\s*=)"; then
echo -e "${RED} ✗ ADMIN CLASS NOT LOADED in ${file}:${NC}"
echo " Admin hooks found but admin class may not be loaded before hook registration."
grep -nE "add_action\s*\(\s*['\"](${ADMIN_HOOKS_PATTERN})" "${file}" | head -1
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
fi
fi
# Check for frontend hooks without frontend class loading
if grep -qE "add_action\s*\(\s*['\"](${FRONTEND_HOOKS_PATTERN})" "${file}"; then
# Look for frontend class references
if grep -qE "(Frontend|frontend|Frontend_[A-Z])" "${file}"; then
# Check if frontend class is loaded before hooks
if ! grep -B10 "add_action.*(${FRONTEND_HOOKS_PATTERN})" "${file}" | grep -qE "(require|include|autoload).*(Frontend|frontend|Frontend_[A-Z])"; then
# Also check for class instantiation before hooks
if ! grep -B10 "add_action.*(${FRONTEND_HOOKS_PATTERN})" "${file}" | grep -qE "(new\s+.*Frontend|new\s+.*frontend|\$frontend\s*=)"; then
echo -e "${RED} ✗ FRONTEND CLASS NOT LOADED in ${file}:${NC}"
echo " Frontend hooks found but frontend class may not be loaded before hook registration."
grep -nE "add_action\s*\(\s*['\"](${FRONTEND_HOOKS_PATTERN})" "${file}" | head -1
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
fi
fi
# Check for install/uninstall hooks without proper class loading
if grep -qE "add_action\s*\(\s*['\"](activate_|deactivate_|uninstall_)" "${file}"; then
# Look for install/uninstall class references
if grep -qE "(Install|install|Install_[A-Z]|Uninstall|uninstall|Uninstall_[A-Z])" "${file}"; then
# Check if install/uninstall class is loaded before hooks
if ! grep -B10 "add_action.*(activate_|deactivate_|uninstall_)" "${file}" | grep -qE "(require|include|autoload).*(Install|install|Install_[A-Z]|Uninstall|uninstall|Uninstall_[A-Z])"; then
echo -e "${RED} ✗ INSTALL/UNINSTALL CLASS NOT LOADED in ${file}:${NC}"
echo " Activation/deactivation/uninstall hooks found but install/uninstall class may not be loaded."
grep -nE "add_action\s*\(\s*['\"](activate_|deactivate_|uninstall_)" "${file}" | head -1
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
fi
# Check for class instantiations that are used but may not be defined
if grep -qE "(new\s+(Admin|Frontend|Install|Uninstall)[A-Za-z0-9_]*\s*\()" "${file}"; then
class_name=$(grep -oE "(new\s+(Admin|Frontend|Install|Uninstall)[A-Za-z0-9_]*\s*\()" "${file}" | grep -oE "(Admin|Frontend|Install|Uninstall)[A-Za-z0-9_]*" | head -1)
# Check if class file exists or is included
class_file=$(find "${PLUGIN_DIR}" -name "class-${class_name,,}.php" -o -name "*${class_name}*.php" 2>/dev/null | head -1)
if [[ -z "$class_file" ]]; then
# Check if class is loaded via require/include
if ! grep -qE "(require|include).*${class_name}" "${file}"; then
echo -e "${RED} ✗ CLASS DEFINITION MISSING in ${file}:${NC}"
echo " Class '${class_name}' instantiated but class file may not be included."
grep -nE "(new\s+(Admin|Frontend|Install|Uninstall)[A-Za-z0-9_]*\s*\()" "${file}" | head -1
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
fi
# Check for static method calls on classes that may not be loaded
if grep -qE "(Admin|Frontend|Install|Uninstall)::[a-zA-Z_]+\s*\(" "${file}"; then
class_name=$(echo "${file}" | grep -oE "(Admin|Frontend|Install|Uninstall)::" | sed 's/:://' | head -1)
# Check if class is loaded or defined
if ! grep -qE "(class\s+${class_name}|require.*${class_name}|include.*${class_name})" "${file}"; then
echo -e "${YELLOW} ⚠ STATIC CLASS CALL WITHOUT LOAD in ${file}:${NC}"
echo " Static method call on '${class_name}' but class may not be loaded."
grep -nE "(Admin|Frontend|Install|Uninstall)::[a-zA-Z_]+\s*\(" "${file}" | head -1
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
# Check for proper dependency ordering in include statements
# Look for common patterns where install/admin/frontend classes might be missing
if grep -qE "(admin|frontend|install)" "${file}" | grep -qE "(add_action|new\s+|::)"; then
# Extract potential class names from includes
includes=$(grep -oE "(require|include).*(admin|frontend|install)" "${file}" | grep -oE "[a-zA-Z0-9_-]+\.(php|inc)" || true)
# Check if required files exist
for include_file in $includes; do
if [[ -n "$include_file" ]]; then
include_path=$(find "${PLUGIN_DIR}" -name "${include_file}" -type f 2>/dev/null | head -1)
if [[ -z "$include_path" ]]; then
echo -e "${RED} ✗ MISSING INCLUDE FILE in ${file}:${NC}"
echo " Required file '${include_file}' not found."
echo " Referenced in: ${file}"
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
done
fi
# Check for class method calls that might fail due to missing dependencies
if grep -qE "->(install|activate|deactivate|uninstall|init|run|load)" "${file}"; then
# Look for potential missing method definitions
method_call=$(grep -oE "->(install|activate|deactivate|uninstall|init|run|load)\s*\(" "${file}" | head -1)
if [[ -n "$method_call" ]]; then
echo -e "${YELLOW} ⚠ POTENTIAL MISSING METHOD in ${file}:${NC}"
echo " Method call ${method_call} found - ensure corresponding class method is defined."
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
done
# Additional comprehensive check: Scan for common class loading patterns
echo -e "${MAGENTA}[7.1/11] Scanning for Class Loading Patterns...${NC}"
# Define class name patterns to look for
CLASS_PATTERNS=("Admin" "Frontend" "Install" "Uninstall" "Loader" "Controller" "Handler")
for file in "${php_files[@]}"; do
for class_pattern in "${CLASS_PATTERNS[@]}"; do
# Check if class is referenced but not defined in file
if grep -q "${class_pattern}" "${file}"; then
# Check if class is actually defined in this file
if ! grep -qE "class\s+${class_pattern}" "${file}"; then
# Check if class is properly loaded via require/include
if ! grep -qE "(require|include|require_once|include_once).*${class_pattern}" "${file}"; then
# Count how many times it's referenced
ref_count=$(grep -c "${class_pattern}" "${file}" || true)
if [[ "$ref_count" -gt 2 ]]; then
echo -e "${YELLOW} ⚠ CLASS REFERENCE WITHOUT LOAD in ${file}:${NC}"
echo " Class '${class_pattern}' referenced ${ref_count} times but may not be loaded."
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
fi
fi
done
done
echo ""
# 5.7 RUNTIME ERROR DETECTION (Enhanced)
# ============================================
echo -e "${MAGENTA}[5.7/11] Runtime Error Detection...${NC}"
if command -v php >/dev/null 2>&1; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ANALYZER_OUTPUT=$(php "${SCRIPT_DIR}/check-duplicate-classes.php" "${PLUGIN_DIR}" 2>&1 || true)
# Parse and report issues
if echo "$ANALYZER_OUTPUT" | grep -q "UNDEFINED FUNCTION"; then
echo -e "${RED} ✗ UNDEFINED FUNCTIONS DETECTED${NC}"
echo "$ANALYZER_OUTPUT" | grep "UNDEFINED FUNCTION" | head -5
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
if echo "$ANALYZER_OUTPUT" | grep -q "EARLY FUNCTION CALL"; then
echo -e "${RED} ✗ EARLY FUNCTION CALLS DETECTED${NC}"
echo "$ANALYZER_OUTPUT" | grep "EARLY FUNCTION CALL" | head -5
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
if echo "$ANALYZER_OUTPUT" | grep -q "POTENTIAL UNDEFINED ARRAY"; then
echo -e "${YELLOW} ⚠ POTENTIAL UNDEFINED ARRAYS${NC}"
echo "$ANALYZER_OUTPUT" | grep "POTENTIAL UNDEFINED ARRAY" | head -3
WARN_COUNT=$((WARN_COUNT + 1))
fi
if echo "$ANALYZER_OUTPUT" | grep -q "POTENTIAL CSS OVERLAP"; then
echo -e "${YELLOW} ⚠ POTENTIAL CSS OVERLAPS${NC}"
echo "$ANALYZER_OUTPUT" | grep "POTENTIAL CSS OVERLAP" | head -3
WARN_COUNT=$((WARN_COUNT + 1))
fi
if ! echo "$ANALYZER_OUTPUT" | grep -qE "(UNDEFINED|EARLY|OVERLAP|FAIL)"; then
echo -e "${GREEN} ✓ No runtime issues detected${NC}"
fi
fi
echo ""
# ============================================
# 8. FILE PERMISSIONS
# ============================================
echo -e "${MAGENTA}[8/11] Checking File Permissions...${NC}"
# Find any file that is world-writable or executable (excluding scripts)
find "${PLUGIN_DIR}" -type f -perm -002 -print0 | while IFS= read -r -d '' file; do
echo -e "${RED} ✗ INSECURE PERMISSION (World Writable): ${file}${NC}"
ERROR_COUNT=$((ERROR_COUNT + 1))
done
find "${PLUGIN_DIR}" -type f -name "*.php" -perm -100 -print0 | while IFS= read -r -d '' file; do
echo -e "${YELLOW} ⚠ EXECUTABLE PHP FILE: ${file}${NC}"
echo " PHP files should generally not have +x permission."
WARN_COUNT=$((WARN_COUNT + 1))
done
echo ""
# ============================================
# 9. FILE PATH VALIDATION
# ============================================
echo -e "${MAGENTA}[9/11] Checking File Path Construction...${NC}"
for file in "${php_files[@]}"; do
# 6.1 Hardcoded absolute paths (excluding WordPress constants)
if grep -nE "(include|require|fopen|file_get_contents|file_put_contents|is_file|is_dir|file_exists)\s*\(\s*['\"](/|[A-Za-z]:)" "${file}" | grep -vE "(ABSPATH|WPINC|WP_CONTENT_DIR|WP_PLUGIN_DIR|WPMU_PLUGIN_DIR|WP_CONTENT_URL|WP_PLUGIN_URL|WPMU_PLUGIN_URL)"; then
echo -e "${RED} ✗ HARDCODED ABSOLUTE PATH in ${file}:${NC}"
echo " Hardcoded absolute path detected. Use WordPress path functions."
grep -nE "(include|require|fopen|file_get_contents|file_put_contents|is_file|is_dir|file_exists)\s*\(\s*['\"](/|[A-Za-z]:)" "${file}" | grep -vE "(ABSPATH|WPINC|WP_CONTENT_DIR|WP_PLUGIN_DIR|WPMU_PLUGIN_DIR|WP_CONTENT_URL|WP_PLUGIN_URL|WPMU_PLUGIN_URL)" | head -2
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
# 6.2 Missing directory separators in path concatenation
if grep -nE '\$[a-zA-Z_]+\s*\.\s*["'"'"']' "${file}" | grep -vE "DIRECTORY_SEPARATOR|DS|plugin_dir_path|template_directory|get_template_directory"; then
echo -e "${YELLOW} ⚠ POTENTIAL PATH CONCATENATION ERROR in ${file}:${NC}"
echo " Path concatenation without proper directory separator."
grep -nE '\$[a-zA-Z_]+\s*\.\s*["'"'"']' "${file}" | grep -vE "DIRECTORY_SEPARATOR|DS|plugin_dir_path|template_directory|get_template_directory" | head -1
WARN_COUNT=$((WARN_COUNT + 1))
fi
# 6.3 Direct usage of __FILE__ without dirname() for directory paths
if grep -nE "(plugin_dir_path|plugin_dir_url|template_directory|get_template_directory)" "${file}" | grep -q "__FILE__" && ! grep -q "__FILE__.*dirname" "${file}"; then
echo -e "${YELLOW} ⚠ INCORRECT __FILE__ USAGE in ${file}:${NC}"
echo " __FILE__ returns the file path. Use dirname(__FILE__) for directory paths."
grep -n "__FILE__" "${file}" | head -1
WARN_COUNT=$((WARN_COUNT + 1))
fi
# 6.4 User input in file operations (path traversal risk)
if grep -nE "(include|require|fopen|file_get_contents|file_put_contents|unlink|mkdir|rmdir)\s*\(\s*(\\\$_GET|\\\$_POST|\\\$_REQUEST|\\\$_COOKIE)" "${file}"; then
echo -e "${RED} ✗ PATH TRAVERSAL RISK in ${file}:${NC}"
echo " Direct user input used in file operation. Must validate and sanitize."
grep -nE "(include|require|fopen|file_get_contents|file_put_contents|unlink|mkdir|rmdir)\s*\(\s*(\\\$_GET|\\\$_POST|\\\$_REQUEST|\\\$_COOKIE)" "${file}" | head -1
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
# 6.5 Not using WordPress path functions for plugin paths
if grep -nE "(include|require)\s*\(\s*['\"][^'\"]*\.(php|inc)" "${file}" | grep -vE "plugin_dir_path|plugin_dir_url|locate_template|get_template_directory|get_stylesheet_directory"; then
if ! grep -qE "defined\s*\(\s*['\"]ABSPATH['\"]" "${file}" | head -1; then
echo -e "${YELLOW} ⚠ MISSING WORDPRESS PATH FUNCTION in ${file}:${NC}"
echo " Use plugin_dir_path(), template_directory(), or similar WP functions."
grep -nE "(include|require)\s*\(\s*['\"][^'\"]*\.(php|inc)" "${file}" | grep -vE "plugin_dir_path|plugin_dir_url|locate_template|get_template_directory|get_stylesheet_directory" | head -1
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
# 6.6 Windows backslashes in paths (should use forward slashes or DIRECTORY_SEPARATOR)
if grep -nE "[A-Za-z]:\\\\" "${file}" | grep -vE "(DIRECTORY_SEPARATOR|DS|dirname|realpath)"; then
echo -e "${YELLOW} ⚠ WINDOWS BACKSLASH DETECTED in ${file}:${NC}"
echo " Use forward slashes or DIRECTORY_SEPARATOR constant for cross-platform compatibility."
grep -nE "[A-Za-z]:\\\\" "${file}" | head -1
WARN_COUNT=$((WARN_COUNT + 1))
fi
done
echo ""
# ============================================
# 10. DEPENDENCIES & ENQUEUES
# ============================================
echo -e "${MAGENTA}[10/11] Checking Dependencies & Enqueues...${NC}"
for file in "${php_files[@]}"; do
# Check for direct wp-load.php inclusion (Major sin)
if grep -q "wp-load.php" "${file}"; then
echo -e "${RED} ✗ ARCHITECTURE FAIL: Direct load of wp-load.php in ${file}${NC}"
echo " Plugins must hook into WP, not load it manually."
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
# Check for enqueuing scripts outside hooks
# We look for wp_enqueue_script NOT inside a function definition
# (This is a rough heuristic)
if grep -E "^wp_enqueue_script" "${file}"; then
echo -e "${YELLOW} ⚠ DOING IT WRONG: wp_enqueue_script called at root level in ${file}${NC}"
echo " Must be inside 'wp_enqueue_scripts' or 'admin_enqueue_scripts' hook."
WARN_COUNT=$((WARN_COUNT + 1))
fi
# Check for direct admin notice output
if grep -q "add_action.*admin_notices" "${file}" && ! grep -q "is_admin()" "${file}"; then
echo -e "${YELLOW} ⚠ Missing is_admin() check in ${file}${NC}"
echo " admin_notices hook should verify is_admin()"
WARN_COUNT=$((WARN_COUNT + 1))
fi
done
echo ""
# ============================================
# 11. WORDPRESS COMPATIBILITY & DEPRECATED FUNCTIONS
# ============================================
echo -e "${MAGENTA}[11/11] Checking WordPress Compatibility & Deprecated Code...${NC}"
DEPRECATED_WP_PATTERNS=(
"wp_get_http:Use wp_remote_get()"
"make_url_footnote:Deprecated"
"wp_specialchars:Use esc_html()"
"translate_with_context:Deprecated"
"get_the_author_email:Use get_the_author_meta('email')"
"get_the_author_icq:Removed"
"get_the_author_yim:Removed"
"get_the_author_msn:Removed"
"get_the_author_aim:Removed"
"wp_dashboard_plugins:Removed"
"the_editor:Use wp_editor()"
)
for file in "${php_files[@]}"; do
for pattern_set in "${DEPRECATED_WP_PATTERNS[@]}"; do
pattern="${pattern_set%%:*}"
advice="${pattern_set##*:}"
if grep -qE "\b${pattern}\b" "${file}"; then
echo -e "${RED} ✗ DEPRECATED WORDPRESS FUNCTION in ${file}:${NC}"
echo " Found: '${pattern}'"
echo " Fix: $advice"
grep -nE "\b${pattern}\b" "${file}" | head -1
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
done
# Check for proper text domain usage
if grep -qE "__|_e|_x|_n|_ex|_nx|esc_html__|esc_html_e|esc_html_x|esc_attr__|esc_attr_e|esc_attr_x" "${file}"; then
# Check if text domain matches plugin slug pattern
text_domain=$(grep -oE "(__|_e|_x)\(['\"][^'\"]*['\"],\s*['\"][^'\"]*['\"]" "${file}" | head -1 | grep -oE "[^',\"]*$" | tr -d ')' | sed 's/^\s*//' | sed 's/\s*$//')
if [[ -n "$text_domain" ]] && [[ ! "$text_domain" =~ ^pc-[a-z0-9-]+$ ]]; then
echo -e "${YELLOW} ⚠ Text domain may not follow naming convention in ${file}${NC}"
echo " Expected: pc-{{slug}}-{{id}}"
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
# Check for hardcoded URLs
if grep -qE "http://example\.com|http://your-site\.com|http://localhost" "${file}"; then
echo -e "${YELLOW} ⚠ Hardcoded URLs found in ${file}${NC}"
echo " Replace with site_url() or home_url()"
WARN_COUNT=$((WARN_COUNT + 1))
fi
# Check for AJAX implementation
if grep -qE "add_action.*wp_ajax_" "${file}"; then
if ! grep -qE "check_ajax_referer|wp_verify_nonce" "${file}"; then
echo -e "${RED} ✗ AJAX SECURITY RISK in ${file}${NC}"
echo " AJAX handler missing nonce verification"
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
# Check for proper REST API permissions
if grep -qE "register_rest_route" "${file}"; then
if ! grep -qE "permission_callback" "${file}"; then
echo -e "${RED} ✗ REST API SECURITY RISK in ${file}${NC}"
echo " REST route missing permission_callback"
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
done
echo ""
# ============================================
# 10. CSS & UI OVERLAP DETECTION
# ============================================
echo -e "${MAGENTA}[11/11] Checking CSS & UI for Overlap Issues...${NC}"
mapfile -t css_files < <(find "${PLUGIN_DIR}" -type f -name "*.css" ! -path "*/vendor/*" ! -path "*/node_modules/*" 2>/dev/null || true)
if [[ "${#css_files[@]}" -gt 0 ]]; then
CSS_ISSUE_COUNT=0
for file in "${css_files[@]}"; do
# Check for negative margins (common overlap cause)
if grep -nE "margin-(top|bottom|left|right)\s*:\s*-[0-9]" "${file}" >/dev/null 2>&1; then
if [[ $CSS_ISSUE_COUNT -lt 5 ]]; then
echo -e "${YELLOW} ⚠ CSS ISSUE in ${file}:${NC}"
echo " Negative margins detected (may cause overlap)"
grep -nE "margin-(top|bottom|left|right)\s*:\s*-[0-9]" "${file}" | head -1
fi
CSS_ISSUE_COUNT=$((CSS_ISSUE_COUNT + 1))
WARN_COUNT=$((WARN_COUNT + 1))
fi
# Check for absolute positioning without z-index
if grep -E "position\s*:\s*absolute" "${file}" >/dev/null 2>&1; then
if ! grep -E "z-index\s*:\s*[1-9]" "${file}" >/dev/null 2>&1; then
if [[ $CSS_ISSUE_COUNT -lt 5 ]]; then
echo -e "${YELLOW} ⚠ CSS ISSUE in ${file}:${NC}"
echo " Absolute positioning without z-index (may cause overlap)"
grep -nE "position\s*:\s*absolute" "${file}" | head -1
fi
CSS_ISSUE_COUNT=$((CSS_ISSUE_COUNT + 1))
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
# Check for fixed positioning at z-index 0
if grep -E "position\s*:\s*fixed" "${file}" >/dev/null 2>&1; then
if grep -E "position\s*:\s*fixed" "${file}" | grep -q "z-index\s*:\s*0"; then
if [[ $CSS_ISSUE_COUNT -lt 5 ]]; then
echo -e "${YELLOW} ⚠ CSS ISSUE in ${file}:${NC}"
echo " Fixed positioning at z-index 0 (may overlap other elements)"
grep -nE "position\s*:\s*fixed.*z-index\s*:\s*0" "${file}" | head -1
fi
CSS_ISSUE_COUNT=$((CSS_ISSUE_COUNT + 1))
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
done
if [[ $CSS_ISSUE_COUNT -eq 0 ]]; then
echo -e "${GREEN} ✓ No CSS overlap issues detected${NC}"
else
echo -e "${YELLOW} (Additional CSS issues may exist - showing first 5)${NC}"
fi
else
echo -e "${GREEN} ✓ No CSS files found${NC}"
fi
echo ""
# ============================================
# 12. CLASS LOADING ERRORS & MISSING CLASSES
# ============================================
echo -e "${MAGENTA}[12/12] Detecting Class Loading Errors and Missing Classes...${NC}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLASS_ERRORS=0
CLASS_WARNINGS=0
if command -v php >/dev/null 2>&1; then
php "${SCRIPT_DIR}/check-duplicate-classes.php" "${PLUGIN_DIR}" 2>&1 | while IFS= read -r line; do
if [[ "$line" == *"MISSING CLASS"* ]]; then
echo -e "${RED} ✗ CLASS LOADING ERROR: ${line}${NC}"
CLASS_ERRORS=$((CLASS_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$line" == *"MISSING CLASS/TYPE"* ]]; then
echo -e "${RED} ✗ CLASS LOADING ERROR: ${line}${NC}"
CLASS_ERRORS=$((CLASS_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$line" == *"DUPLICATE CLASS"* ]]; then
echo -e "${RED} ✗ CLASS LOADING ERROR: ${line}${NC}"
CLASS_ERRORS=$((CLASS_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$line" == *"DUPLICATE INTERFACE"* ]]; then
echo -e "${RED} ✗ CLASS LOADING ERROR: ${line}${NC}"
CLASS_ERRORS=$((CLASS_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$line" == *"DUPLICATE TRAIT"* ]]; then
echo -e "${RED} ✗ CLASS LOADING ERROR: ${line}${NC}"
CLASS_ERRORS=$((CLASS_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$line" == *"install_class_not_loaded"* ]] || [[ "$line" == *"class_not_loaded"* ]]; then
echo -e "${RED} ✗ CLASS LOADING ERROR: ${line}${NC}"
CLASS_ERRORS=$((CLASS_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$line" == *"MISSING INCLUDE"* ]]; then
echo -e "${RED} ✗ MISSING INCLUDE: ${line}${NC}"
CLASS_ERRORS=$((CLASS_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$line" == *"ACTIVATION HOOK ISSUE"* ]]; then
echo -e "${RED} ✗ ACTIVATION HOOK CLASS ERROR: ${line}${NC}"
CLASS_ERRORS=$((CLASS_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$line" == *"UNDEFINED FUNCTION"* ]]; then
echo -e "${RED} ✗ UNDEFINED FUNCTION: ${line}${NC}"
CLASS_ERRORS=$((CLASS_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$line" == *"EARLY FUNCTION CALL"* ]]; then
echo -e "${YELLOW} ⚠ CLASS LOADING WARNING: ${line}${NC}"
CLASS_WARNINGS=$((CLASS_WARNINGS + 1))
WARN_COUNT=$((WARN_COUNT + 1))
elif [[ "$line" == *"static_class_not_loaded"* ]]; then
echo -e "${RED} ✗ STATIC CLASS NOT LOADED: ${line}${NC}"
CLASS_ERRORS=$((CLASS_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
elif [[ "$line" == *"Checking for"* ]] || [[ "$line" == *"No"* ]] || [[ "$line" == *"✓"* ]] || [[ "$line" == *"PASS"* ]] || [[ "$line" == *"FAIL"* ]] || [[ "$line" == *"Scanned"* ]] || [[ "$line" == *"found"* ]]; then
:
else
echo " ${line}"
fi
done
echo ""
echo -e "${MAGENTA}[12.1/12] Static Class Loading Pattern Analysis...${NC}"
for file in "${php_files[@]}"; do
if grep -qE "(class_exists|interface_exists|trait_exists)\s*\(" "${file}"; then
if ! grep -qE "(class_exists|interface_exists|trait_exists)\s*\([^)]+\)\s*;" "${file}"; then
echo -e "${YELLOW} ⚠ DYNAMIC CLASS CHECK in ${file}:${NC}"
echo " Dynamic class loading detected - ensure autoload is configured."
CLASS_WARNINGS=$((CLASS_WARNINGS + 1))
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
if grep -qE "spl_autoload_register|autoload\s*\(" "${file}"; then
echo -e "${GREEN} ✓ Custom autoloader registered in ${file}${NC}"
fi
if grep -qE "new\s+[A-Z][a-zA-Z0-9_]*\s*\(" "${file}"; then
class_refs=$(grep -oE "new\s+[A-Z][a-zA-Z0-9_]*\s*\(" "${file}" | grep -oE "[A-Z][a-zA-Z0-9_]*" | sort -u)
for class_ref in $class_refs; do
if ! grep -qE "(class|interface|trait)\s+${class_ref}" "${file}"; then
include_check=$(grep -nE "(require|include).*${class_ref}" "${file}" | head -1 || true)
if [[ -z "$include_check" ]]; then
echo -e "${RED} ✗ POTENTIAL MISSING CLASS in ${file}:${NC}"
echo " Class '${class_ref}' instantiated but may not be loaded."
CLASS_ERRORS=$((CLASS_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
done
fi
if grep -qE "[A-Z][a-zA-Z0-9_]*::[a-z]" "${file}"; then
static_refs=$(grep -oE "[A-Z][a-zA-Z0-9_]*::[a-z][a-zA-Z0-9_]*" "${file}" | cut -d':' -f1 | sort -u)
for static_ref in $static_refs; do
if ! grep -qE "(class|interface|trait)\s+${static_ref}" "${file}"; then
include_check=$(grep -nE "(require|include|use).*${static_ref}" "${file}" | head -1 || true)
if [[ -z "$include_check" ]]; then
echo -e "${YELLOW} ⚠ POTENTIAL MISSING STATIC CLASS in ${file}:${NC}"
echo " Static class '${static_ref}' referenced but may not be loaded."
CLASS_WARNINGS=$((CLASS_WARNINGS + 1))
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
done
fi
done
if [[ $CLASS_ERRORS -eq 0 ]] && [[ $CLASS_WARNINGS -eq 0 ]]; then
echo -e "${GREEN} ✓ No class loading errors detected${NC}"
elif [[ $CLASS_ERRORS -gt 0 ]]; then
echo -e "${RED}${CLASS_ERRORS} class loading error(s) found${NC}"
else
echo -e "${YELLOW}${CLASS_WARNINGS} class loading warning(s) found${NC}"
fi
else
echo -e "${YELLOW} ⚠ PHP not available - skipping class loading validation${NC}"
fi
echo ""
# ============================================
# 13. TOO FEW ARGUMENTS ERROR DETECTION
# ============================================
echo -e "${MAGENTA}[13/13] Checking for Too Few Arguments Errors...${NC}"
ARG_ERRORS=0
for file in "${php_files[@]}"; do
# Check for array_key_exists with single argument (requires 2)
if grep -nE "array_key_exists\s*\(\s*['\"][^'\"]+['\"]\s*\)" "${file}" >/dev/null 2>&1; then
if ! grep -nE "array_key_exists\s*\(\s*['\"][^'\"]+['\"]\s*,\s*\\$" "${file}" >/dev/null 2>&1; then
matches=$(grep -nE "array_key_exists\s*\(\s*['\"][^'\"]+['\"]\s*\)" "${file}" | head -1)
if [[ -n "$matches" ]] && ! grep -E "array_key_exists\s*\(\s*['\"][^'\"]+['\"]\s*,\s*[a-zA-Z_]" "${file}" >/dev/null 2>&1; then
echo -e "${RED} ✗ TOO FEW ARGUMENTS in ${file}:${NC}"
echo " array_key_exists() called with only 1 argument (requires 2)"
echo "$matches"
ARG_ERRORS=$((ARG_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
fi
# Check for function calls with missing required arguments
# str_replace requires 3 arguments
if grep -nE "str_replace\s*\(\s*[^)]+,\s*[^)]+\s*\)" "${file}" >/dev/null 2>&1; then
if ! grep -E "str_replace\s*\(\s*[^)]+,\s*[^)]+,\s*[^)]+\s*\)" "${file}" >/dev/null 2>&1; then
matches=$(grep -nE "str_replace\s*\(\s*[^)]+,\s*[^)]+\s*\)" "${file}" | head -1)
if [[ -n "$matches" ]]; then
echo -e "${RED} ✗ TOO FEW ARGUMENTS in ${file}:${NC}"
echo " str_replace() called with only 2 arguments (requires 3: search, replace, subject)"
echo "$matches"
ARG_ERRORS=$((ARG_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
fi
# Check for sprintf/printf with format but no arguments
if grep -nE "(sprintf|printf)\s*\(\s*['\"][%].*['\"]\s*\)" "${file}" >/dev/null 2>&1; then
matches=$(grep -nE "(sprintf|printf)\s*\(\s*['\"][%].*['\"]\s*\)" "${file}" | head -1)
if [[ -n "$matches" ]]; then
echo -e "${YELLOW} ⚠ POSSIBLE TOO FEW ARGUMENTS in ${file}:${NC}"
echo " sprintf/printf has format specifiers but may be missing arguments"
echo "$matches"
ARG_ERRORS=$((ARG_ERRORS + 1))
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
# Check for array_slice with only array argument (requires at least 2)
if grep -nE "array_slice\s*\(\s*\\\$[a-zA-Z_]+\s*\)" "${file}" >/dev/null 2>&1; then
matches=$(grep -nE "array_slice\s*\(\s*\\\$[a-zA-Z_]+\s*\)" "${file}" | head -1)
if [[ -n "$matches" ]]; then
echo -e "${RED} ✗ TOO FEW ARGUMENTS in ${file}:${NC}"
echo " array_slice() called with only 1 argument (requires at least 2: array, offset)"
echo "$matches"
ARG_ERRORS=$((ARG_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
# Check for in_array with only haystack argument (requires 2)
if grep -nE "in_array\s*\(\s*\\\$[a-zA-Z_]+\s*\)" "${file}" >/dev/null 2>&1; then
matches=$(grep -nE "in_array\s*\(\s*\\\$[a-zA-Z_]+\s*\)" "${file}" | head -1)
if [[ -n "$matches" ]]; then
echo -e "${RED} ✗ TOO FEW ARGUMENTS in ${file}:${NC}"
echo " in_array() called with only 1 argument (requires 2: needle, haystack)"
echo "$matches"
ARG_ERRORS=$((ARG_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
# Check for explode with only delimiter argument (requires 2)
if grep -nE "explode\s*\(\s*['\"][^'\"]+['\"]\s*\)" "${file}" >/dev/null 2>&1; then
if ! grep -E "explode\s*\(\s*['\"][^'\"]+['\"]\s*,\s*\\$" "${file}" >/dev/null 2>&1; then
matches=$(grep -nE "explode\s*\(\s*['\"][^'\"]+['\"]\s*\)" "${file}" | head -1)
if [[ -n "$matches" ]] && ! grep -E "explode\s*\(\s*['\"][^'\"]+['\"]\s*,\s*[^)]+\)" "${file}" >/dev/null 2>&1; then
echo -e "${RED} ✗ TOO FEW ARGUMENTS in ${file}:${NC}"
echo " explode() called with only 1 argument (requires 2: delimiter, string)"
echo "$matches"
ARG_ERRORS=$((ARG_ERRORS + 1))
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
fi
fi
# Check for implode with only array argument (requires 1, but checking for wrong order)
if grep -nE "implode\s*\(\s*\\\$[a-zA-Z_]+\s*,\s*['\"][^'\"]+['\"]\s*\)" "${file}" >/dev/null 2>&1; then
matches=$(grep -nE "implode\s*\(\s*\\\$[a-zA-Z_]+\s*,\s*['\"][^'\"]+['\"]\s*\)" "${file}" | head -1)
if [[ -n "$matches" ]]; then
echo -e "${YELLOW} ⚠ POSSIBLE ARGUMENT ORDER ERROR in ${file}:${NC}"
echo " implode() arguments may be in wrong order (expected glue, array)"
echo "$matches"
ARG_ERRORS=$((ARG_ERRORS + 1))
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
# Check for array_merge with single argument (unusual pattern)
if grep -nE "array_merge\s*\(\s*\\\$[a-zA-Z_]+\s*\)" "${file}" >/dev/null 2>&1; then
matches=$(grep -nE "array_merge\s*\(\s*\\\$[a-zA-Z_]+\s*\)" "${file}" | head -1)
if [[ -n "$matches" ]]; then
echo -e "${YELLOW} ⚠ POSSIBLE TOO FEW ARGUMENTS in ${file}:${NC}"
echo " array_merge() with single argument may indicate missing array parameter"
echo "$matches"
ARG_ERRORS=$((ARG_ERRORS + 1))
WARN_COUNT=$((WARN_COUNT + 1))
fi
fi
done
if [[ $ARG_ERRORS -eq 0 ]]; then
echo -e "${GREEN} ✓ No too few arguments errors detected${NC}"
fi
echo ""
# ============================================
# 14. FINAL REPORT
# ============================================
echo -e "${BLUE}========================================================${NC}"
echo -e "${BLUE} AUDIT RESULTS ${NC}"
echo -e "${BLUE}========================================================${NC}"
if [[ "${ERROR_COUNT}" -gt 0 ]]; then
echo -e "${RED}FAIL: ${ERROR_COUNT} Critical Security/Stability Issues Found.${NC}"
echo -e "${RED}This plugin is DANGEROUS or BROKEN. Do not deploy.${NC}"
if [[ "${WARN_COUNT}" -gt 0 ]]; then
echo -e "${YELLOW}(Plus ${WARN_COUNT} warnings requiring review)${NC}"
fi
exit 1
elif [[ "${WARN_COUNT}" -gt 0 ]]; then
echo -e "${YELLOW}WARNING: ${WARN_COUNT} Potential issues found.${NC}"
echo "Manual review required to verify false positives."
echo "Strict mode does not allow clean pass with warnings."
exit 1
else
echo -e "${GREEN}SUCCESS: Plugin passed all strict checks.${NC}"
exit 0
fi