Restore to commit 74e578279624c6045ca440a3459ebfa1f8d54191
This commit is contained in:
762
scripts/validate-woocommerce.sh
Normal file
762
scripts/validate-woocommerce.sh
Normal file
@@ -0,0 +1,762 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ==============================================================================
|
||||
# STRICT WooCommerce Plugin Compatibility Validator
|
||||
# ==============================================================================
|
||||
# This script performs aggressive static analysis to ensure WooCommerce plugins
|
||||
# meet modern standards, specifically HPOS compatibility and security.
|
||||
# ==============================================================================
|
||||
|
||||
PLUGIN_DIR="${1:-}"
|
||||
|
||||
# Color codes
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
MAGENTA='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Counters
|
||||
CRITICAL_COUNT=0
|
||||
WARNING_COUNT=0
|
||||
|
||||
# Usage check
|
||||
if [[ -z "${PLUGIN_DIR}" ]]; then
|
||||
echo "Usage: $(basename "$0") /path/to/plugin" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d "${PLUGIN_DIR}" ]]; then
|
||||
echo "Plugin directory not found: ${PLUGIN_DIR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}========================================================${NC}"
|
||||
echo -e "${BLUE} STRICT WOOCOMMERCE COMPATIBILITY VALIDATION ${NC}"
|
||||
echo -e "${BLUE}========================================================${NC}"
|
||||
echo "Target: ${PLUGIN_DIR}"
|
||||
echo ""
|
||||
|
||||
# Gather files
|
||||
mapfile -t php_files < <(find "${PLUGIN_DIR}" -type f -name "*.php" ! -path "*/vendor/*" ! -path "*/node_modules/*" 2>/dev/null | sort || true)
|
||||
|
||||
if [[ "${#php_files[@]}" -eq 0 ]]; then
|
||||
echo -e "${RED}✗ FATAL: No PHP files found.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# 1. HPOS STRICT MODE (CRITICAL)
|
||||
# ============================================
|
||||
echo -e "${MAGENTA}[1/9] HPOS (High-Performance Order Storage) Strict Analysis...${NC}"
|
||||
|
||||
hpos_declared=false
|
||||
hpos_compatible=false
|
||||
|
||||
# 1.1 Check Declaration
|
||||
for file in "${php_files[@]}"; do
|
||||
if grep -q "FeaturesUtil::declare_compatibility" "${file}"; then
|
||||
hpos_declared=true
|
||||
if grep -q "'custom_order_tables'" "${file}" || grep -q '"custom_order_tables"' "${file}"; then
|
||||
hpos_compatible=true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if ! $hpos_declared; then
|
||||
echo -e "${RED} ✗ FAILURE: HPOS compatibility not declared.${NC}"
|
||||
echo " Must use: \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility"
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
elif ! $hpos_compatible; then
|
||||
echo -e "${RED} ✗ FAILURE: HPOS declared but 'custom_order_tables' missing.${NC}"
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
else
|
||||
echo -e "${GREEN} ✓ HPOS compatibility declared.${NC}"
|
||||
fi
|
||||
|
||||
# 1.2 Check for Legacy Direct DB Access (Strict)
|
||||
# Flag any direct SQL queries that mention posts table AND shop_order in the same file
|
||||
for file in "${php_files[@]}"; do
|
||||
# Check for SQL queries joining posts/postmeta looking for orders
|
||||
if grep -E "wp_posts|wp_postmeta" "${file}" | grep -qE "shop_order|_order_"; then
|
||||
# Exclude if comments explicitly say it's legacy fallback
|
||||
if ! grep -q "HPOS usage" "${file}"; then
|
||||
echo -e "${RED} ✗ HPOS VIOLATION in ${file}:${NC}"
|
||||
echo " Detected direct 'wp_posts'/'wp_postmeta' access potentially for orders."
|
||||
echo " HPOS requires using WC_Order CRUD methods or specific HPOS tables."
|
||||
grep -nE "wp_posts|wp_postmeta" "${file}" | head -3
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for legacy metadata functions on orders
|
||||
# We flag usage of get_post_meta where the variable name implies an order
|
||||
if grep -nE "(get|update|add|delete)_post_meta\s*\(\s*\\$(order|wc_order|item)" "${file}"; then
|
||||
echo -e "${RED} ✗ HPOS VIOLATION in ${file}:${NC}"
|
||||
echo " Detected legacy meta function on order variable."
|
||||
echo " Use \$order->get_meta(), \$order->update_meta_data(), etc."
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
|
||||
# Check for direct ID access (e.g., $order->id)
|
||||
if grep -nE "\\\$[a-zA-Z0-9_]*order->id" "${file}" | grep -v "get_id()"; then
|
||||
echo -e "${RED} ✗ DEPRECATED ACCESS in ${file}:${NC}"
|
||||
echo " Detected direct access to \$order->id. Use \$order->get_id()."
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 2. WOOCOMMERCE VERSION HEADERS
|
||||
# ============================================
|
||||
echo -e "${MAGENTA}[2/9] Validating Version Headers...${NC}"
|
||||
|
||||
# Find main plugin file
|
||||
main_file=""
|
||||
for file in "${php_files[@]}"; do
|
||||
if grep -q "Plugin Name:" "${file}"; then
|
||||
main_file="${file}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "${main_file}" ]]; then
|
||||
echo -e "${RED} ✗ FAILURE: Main plugin file not found.${NC}"
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
else
|
||||
wc_tested=$(grep "WC tested up to:" "${main_file}" | sed 's/.*://' | tr -d '[:space:]')
|
||||
wc_required=$(grep "WC requires at least:" "${main_file}" | sed 's/.*://' | tr -d '[:space:]')
|
||||
|
||||
if [[ -z "$wc_tested" ]]; then
|
||||
echo -e "${RED} ✗ FAILURE: Missing 'WC tested up to' header.${NC}"
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
else
|
||||
# Simple check if tested version is reasonably recent (starts with 8 or 9)
|
||||
if [[ ! "$wc_tested" =~ ^(8|9)\. ]]; then
|
||||
echo -e "${YELLOW} ⚠ WARNING: 'WC tested up to' ($wc_tested) seems old (pre-8.0).${NC}"
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$wc_required" ]]; then
|
||||
echo -e "${RED} ✗ FAILURE: Missing 'WC requires at least' header.${NC}"
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 3. DEPRECATED FUNCTIONS & HOOKS (Strict)
|
||||
# ============================================
|
||||
echo -e "${MAGENTA}[3/9] Scanning for Deprecated WooCommerce Code...${NC}"
|
||||
|
||||
DEPRECATED_PATTERNS=(
|
||||
"woocommerce_get_page_id:Use wc_get_page_id()"
|
||||
"wc_get_page_permalink:Use wc_get_page_permalink()"
|
||||
"woocommerce_show_messages:Use wc_print_notices()"
|
||||
"wc_current_theme_is_fse_theme:Use wp_is_block_theme() (WC 9.9+)"
|
||||
"global \$woocommerce:Use WC()"
|
||||
"\$woocommerce->cart:Use WC()->cart"
|
||||
"\$woocommerce->customer:Use WC()->customer"
|
||||
"store_api_validate_add_to_cart:Use woocommerce_store_api_validate_add_to_cart"
|
||||
"jquery-blockui:Use wc-jquery-blockui"
|
||||
"woocommerce_coupon_error:Use wc_add_notice()"
|
||||
"woocommerce_add_notice:Use wc_add_notice()"
|
||||
"woocommerce_get_template:Use wc_get_template()"
|
||||
"woocommerce_get_template_part:Use wc_get_template_part()"
|
||||
"woocommerce_locate_template:Use wc_locate_template()"
|
||||
"WC()->cart->get_cart_from_session:Removed in WC 8.2+"
|
||||
"woocommerce_calculate_totals:Use recalculate_totals()"
|
||||
"woocommerce_get_product_from_item:Use \$item->get_product()"
|
||||
"woocommerce_get_order_item_meta:Use \$item->get_meta()"
|
||||
"woocommerce_downloadable_file_permission:Use wc_downloadable_file_permission()"
|
||||
"woocommerce_sanitize_taxonomy_name:Use wc_sanitize_taxonomy_name()"
|
||||
"woocommerce_clean:Use wc_clean()"
|
||||
"woocommerce_array_overlay:Use wc_array_overlay()"
|
||||
)
|
||||
|
||||
for file in "${php_files[@]}"; do
|
||||
for pattern_set in "${DEPRECATED_PATTERNS[@]}"; do
|
||||
pattern="${pattern_set%%:*}"
|
||||
advice="${pattern_set##*:}"
|
||||
|
||||
if grep -q "$pattern" "${file}"; then
|
||||
echo -e "${RED} ✗ DEPRECATED CODE in ${file}:${NC}"
|
||||
echo " Found: '$pattern'"
|
||||
echo " Fix: $advice"
|
||||
grep -n "$pattern" "${file}" | head -1
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
done
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 4. DATABASE & SCHEMA SAFETY
|
||||
# ============================================
|
||||
echo -e "${MAGENTA}[4/9] Validating Database Usage...${NC}"
|
||||
|
||||
for file in "${php_files[@]}"; do
|
||||
# Check for creating tables without dbDelta
|
||||
if grep -q "CREATE TABLE" "${file}"; then
|
||||
if ! grep -q "dbDelta" "${file}"; then
|
||||
echo -e "${RED} ✗ DB SAFETY in ${file}:${NC}"
|
||||
echo " 'CREATE TABLE' found without 'dbDelta'. This is a WP standard violation."
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Strict check: Using standard WP tables for order data
|
||||
if grep -E "INSERT INTO.*wp_options" "${file}" | grep -q "woocommerce"; then
|
||||
echo -e "${YELLOW} ⚠ PERFORMANCE: Inserting WooCommerce data directly into wp_options in ${file}.${NC}"
|
||||
echo " Avoid cluttering the autoloaded options table."
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 5. TEMPLATE OVERRIDES
|
||||
# ============================================
|
||||
echo -e "${MAGENTA}[5/9] Checking Template Structure...${NC}"
|
||||
|
||||
if [[ -d "${PLUGIN_DIR}/templates" ]]; then
|
||||
echo -e "${GREEN} ✓ '/templates' directory found.${NC}"
|
||||
|
||||
# Check for outdated template versions in comments
|
||||
find "${PLUGIN_DIR}/templates" -name "*.php" | while read -r tpl; do
|
||||
if ! grep -q "@version" "${tpl}"; then
|
||||
echo -e "${YELLOW} ⚠ WARNING: Template ${tpl} missing @version tag.${NC}"
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 6. CART/CHECKOUT BLOCKS
|
||||
# ============================================
|
||||
echo -e "${MAGENTA}[6/9] Checking Blocks Compatibility...${NC}"
|
||||
|
||||
has_blocks_integration=false
|
||||
for file in "${php_files[@]}"; do
|
||||
if grep -qE "woocommerce_blocks|StoreApi|IntegrationInterface" "${file}"; then
|
||||
has_blocks_integration=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if ! $has_blocks_integration; then
|
||||
echo -e "${YELLOW} ⚠ WARNING: No Cart/Checkout Blocks integration detected.${NC}"
|
||||
echo " Your plugin may break in Block-based checkouts."
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
else
|
||||
echo -e "${GREEN} ✓ Blocks integration detected.${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 7. ORDER NOTES & LOGGING
|
||||
# ============================================
|
||||
echo -e "${MAGENTA}[7/9] Checking Logging Standards...${NC}"
|
||||
|
||||
for file in "${php_files[@]}"; do
|
||||
# Check for error_log
|
||||
if grep -q "error_log" "${file}"; then
|
||||
echo -e "${YELLOW} ⚠ WARNING: 'error_log' found in ${file}. Use 'WC_Logger' instead.${NC}"
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
|
||||
# Check for adding order notes directly via comments
|
||||
if grep -q "wp_insert_comment" "${file}" | grep -q "order"; then
|
||||
echo -e "${RED} ✗ FAILURE: inserting comments directly for orders in ${file}.${NC}"
|
||||
echo " Use \$order->add_order_note() instead."
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 8. AJAX ERROR DETECTION
|
||||
# ============================================
|
||||
echo -e "${MAGENTA}[8/9] Detecting AJAX Errors & Security Issues...${NC}"
|
||||
|
||||
for file in "${php_files[@]}"; do
|
||||
# Check for AJAX actions without nonce verification
|
||||
if grep -qE "add_action.*wp_ajax_.*add_action.*wp_ajax_nopriv_" "${file}"; then
|
||||
if ! grep -q "check_ajax_referer\|wp_verify_nonce" "${file}"; then
|
||||
echo -e "${RED} ✗ AJAX SECURITY VIOLATION in ${file}:${NC}"
|
||||
echo " AJAX action found without nonce verification."
|
||||
grep -nE "add_action.*wp_ajax_" "${file}" | head -1
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for wp_send_json_error without proper status codes
|
||||
if grep -q "wp_send_json_error" "${file}"; then
|
||||
if ! grep -q "wp_send_json_error.*[45][0-9][0-9]" "${file}"; then
|
||||
echo -e "${YELLOW} ⚠ AJAX ERROR HANDLING in ${file}:${NC}"
|
||||
echo " wp_send_json_error() should include HTTP status code (400, 403, 500)."
|
||||
grep -n "wp_send_json_error" "${file}" | head -1
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for AJAX handlers using wp_die() or die()
|
||||
if grep -qE "wp_die\(|die\(" "${file}"; then
|
||||
if grep -qE "wp_ajax_.*{" "${file}"; then
|
||||
echo -e "${RED} ✗ AJAX ERROR in ${file}:${NC}"
|
||||
echo " Direct wp_die() or die() in AJAX handler breaks proper error handling."
|
||||
grep -nE "(wp_die|die)\(" "${file}" | head -1
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for AJAX actions missing capability checks
|
||||
if grep -qE "add_action.*wp_ajax_" "${file}"; then
|
||||
if ! grep -qE "current_user_can|check_ajax_referer" "${file}"; then
|
||||
echo -e "${RED} ✗ AJAX SECURITY in ${file}:${NC}"
|
||||
echo " AJAX action missing capability or nonce checks."
|
||||
grep -nE "add_action.*wp_ajax_" "${file}" | head -1
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for AJAX handlers returning data without proper JSON encoding
|
||||
if grep -qE "wp_ajax_" "${file}"; then
|
||||
if grep -q "echo.*json_encode" "${file}"; then
|
||||
echo -e "${YELLOW} ⚠ AJAX ERROR HANDLING in ${file}:${NC}"
|
||||
echo " Manual JSON encoding in AJAX. Use wp_send_json_success/error()."
|
||||
grep -n "echo.*json_encode" "${file}" | head -1
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for AJAX handlers catching errors without proper logging
|
||||
if grep -qE "wp_ajax_.*try.*catch" "${file}"; then
|
||||
if ! grep -qE "wc_get_logger|wc_log_error|error_log.*AJAX" "${file}"; then
|
||||
echo -e "${YELLOW} ⚠ AJAX ERROR HANDLING in ${file}:${NC}"
|
||||
echo " AJAX try-catch block may not be logging errors properly."
|
||||
grep -nE "wp_ajax_.*try" "${file}" | head -1
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 10. CLASS LOADING ERRORS & MISSING CLASSES
|
||||
# ============================================
|
||||
echo -e "${MAGENTA}[10/10] Detecting Class Loading Errors and Missing Classes...${NC}"
|
||||
|
||||
for file in "${php_files[@]}"; do
|
||||
# Check for deprecated order properties
|
||||
if grep -qE "\\\$order->(order_type|customer_id|customer_note|post_status)" "${file}"; then
|
||||
echo -e "${RED} ✗ DEPRECATED ORDER PROPERTY in ${file}${NC}"
|
||||
echo " Direct property access removed. Use getter methods."
|
||||
grep -nE "\\\$order->(order_type|customer_id|customer_note|post_status)" "${file}" | head -1
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
|
||||
# Check for legacy meta handling on products
|
||||
if grep -E "(get|update|add|delete)_post_meta.*product_id" "${file}"; then
|
||||
if ! grep -q "HPOS" "${file}"; then
|
||||
echo -e "${YELLOW} ⚠ POTENTIAL LEGACY META HANDLING in ${file}${NC}"
|
||||
echo " Verify proper CRUD methods are used for products."
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for proper webhook implementation
|
||||
if grep -q "wc_get_webhook" "${file}" || grep -q "WC_Webhook" "${file}"; then
|
||||
echo -e "${GREEN} ✓ Webhook implementation detected${NC}"
|
||||
fi
|
||||
|
||||
# Check for payment gateway implementation
|
||||
if grep -q "WC_Payment_Gateway" "${file}"; then
|
||||
if ! grep -qE "process_payment|transaction_result" "${file}"; then
|
||||
echo -e "${YELLOW} ⚠ Incomplete payment gateway in ${file}${NC}"
|
||||
echo " Missing process_payment() method."
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for shipping method implementation
|
||||
if grep -q "WC_Shipping_Method" "${file}"; then
|
||||
if ! grep -qE "calculate_shipping" "${file}"; then
|
||||
echo -e "${YELLOW} ⚠ Incomplete shipping method in ${file}${NC}"
|
||||
echo " Missing calculate_shipping() method."
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for proper cart handling
|
||||
if grep -qE "\\\$woocommerce->cart|WC()->cart->add_to_cart" "${file}"; then
|
||||
if grep -qE "add_to_cart\(\s*\\\$_POST|add_to_cart\(\s*\\\$_GET" "${file}"; then
|
||||
echo -e "${RED} ✗ SECURITY: Direct user input in add_to_cart in ${file}${NC}"
|
||||
echo " Must validate and sanitize product data."
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for proper product query usage
|
||||
if grep -q "wp_get_post_terms.*product" "${file}"; then
|
||||
echo -e "${YELLOW} ⚠ Use wc_get_product_terms() instead of wp_get_post_terms() in ${file}${NC}"
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
|
||||
# Check for proper price handling (should use wc_get_price_including_tax/excluding_tax)
|
||||
if grep -qE "get_post_meta.*_price" "${file}" && ! grep -q "wc_get_price"; then
|
||||
echo -e "${YELLOW} ⚠ Use WC price functions in ${file}${NC}"
|
||||
echo " Prefer wc_get_price_including_tax() over direct meta access."
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
|
||||
# Check for proper action/filter prefixes
|
||||
if grep -qE "add_action.*'woocommerce_|add_filter.*'woocommerce_" "${file}"; then
|
||||
echo -e "${GREEN} ✓ Proper WooCommerce hook usage${NC}"
|
||||
fi
|
||||
|
||||
# Check for Block-based checkout integration
|
||||
if grep -qE "woocommerce_store_api|CheckoutBlocks|StoreApi" "${file}"; then
|
||||
echo -e "${GREEN} ✓ Block-based checkout support detected${NC}"
|
||||
fi
|
||||
|
||||
# Check for product attribute handling
|
||||
if grep -qE "get_the_terms.*product" "${file}" && grep -qE "pa_" "${file}"; then
|
||||
echo -e "${GREEN} ✓ Product attribute handling detected${NC}"
|
||||
fi
|
||||
|
||||
# Check for tax handling
|
||||
if grep -qE "WC_Tax|calculate_tax|get_tax_rate" "${file}"; then
|
||||
echo -e "${GREEN} ✓ Tax calculation handling detected${NC}"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 10. CLASS LOADING ERRORS & MISSING CLASSES
|
||||
# ============================================
|
||||
echo -e "${MAGENTA}[10/10] 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
|
||||
if [[ -f "${SCRIPT_DIR}/check-duplicate-classes.php" ]]; 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))
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
elif [[ "$line" == *"MISSING CLASS/TYPE"* ]]; then
|
||||
echo -e "${RED} ✗ CLASS LOADING ERROR: ${line}${NC}"
|
||||
CLASS_ERRORS=$((CLASS_ERRORS + 1))
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
elif [[ "$line" == *"DUPLICATE CLASS"* ]]; then
|
||||
echo -e "${RED} ✗ CLASS LOADING ERROR: ${line}${NC}"
|
||||
CLASS_ERRORS=$((CLASS_ERRORS + 1))
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
elif [[ "$line" == *"DUPLICATE INTERFACE"* ]]; then
|
||||
echo -e "${RED} ✗ CLASS LOADING ERROR: ${line}${NC}"
|
||||
CLASS_ERRORS=$((CLASS_ERRORS + 1))
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
elif [[ "$line" == *"DUPLICATE TRAIT"* ]]; then
|
||||
echo -e "${RED} ✗ CLASS LOADING ERROR: ${line}${NC}"
|
||||
CLASS_ERRORS=$((CLASS_ERRORS + 1))
|
||||
CRITICAL_COUNT=$((CRITICAL_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))
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
elif [[ "$line" == *"MISSING INCLUDE"* ]]; then
|
||||
echo -e "${RED} ✗ MISSING INCLUDE: ${line}${NC}"
|
||||
CLASS_ERRORS=$((CLASS_ERRORS + 1))
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
elif [[ "$line" == *"ACTIVATION HOOK ISSUE"* ]]; then
|
||||
echo -e "${RED} ✗ ACTIVATION HOOK CLASS ERROR: ${line}${NC}"
|
||||
CLASS_ERRORS=$((CLASS_ERRORS + 1))
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
elif [[ "$line" == *"UNDEFINED FUNCTION"* ]]; then
|
||||
echo -e "${RED} ✗ UNDEFINED FUNCTION: ${line}${NC}"
|
||||
CLASS_ERRORS=$((CLASS_ERRORS + 1))
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
elif [[ "$line" == *"EARLY FUNCTION CALL"* ]]; then
|
||||
echo -e "${YELLOW} ⚠ CLASS LOADING WARNING: ${line}${NC}"
|
||||
CLASS_WARNINGS=$((CLASS_WARNINGS + 1))
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
elif [[ "$line" == *"static_class_not_loaded"* ]]; then
|
||||
echo -e "${RED} ✗ STATIC CLASS NOT LOADED: ${line}${NC}"
|
||||
CLASS_ERRORS=$((CLASS_ERRORS + 1))
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
elif [[ "$line" == *"Checking for"* ]] || [[ "$line" == *"No"* ]] || [[ "$line" == *"✓"* ]] || [[ "$line" == *"PASS"* ]] || [[ "$line" == *"FAIL"* ]] || [[ "$line" == *"Scanned"* ]] || [[ "$line" == *"found"* ]]; then
|
||||
:
|
||||
else
|
||||
echo " ${line}"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo -e "${YELLOW} ⚠ check-duplicate-classes.php not found - using static analysis${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${MAGENTA}[10.1/10] WooCommerce Class Loading Pattern Analysis...${NC}"
|
||||
|
||||
for file in "${php_files[@]}"; do
|
||||
if grep -qE "(WC_[A-Z][a-zA-Z0-9_]*|woocommerce_[a-zA-Z0-9_]+)" "${file}"; then
|
||||
wc_classes=$(grep -oE "(WC_[A-Z][a-zA-Z0-9_]*|woocommerce_[a-zA-Z0-9_]+)" "${file}" | sort -u)
|
||||
for wc_class in $wc_classes; do
|
||||
if [[ "$wc_class" == "WC_"* ]] || [[ "$wc_class" == "woocommerce_"* ]]; then
|
||||
if ! grep -qE "(class|interface|trait)\s+${wc_class}" "${file}"; then
|
||||
if ! grep -qE "(function_exists|class_exists|interface_exists).*${wc_class}" "${file}"; then
|
||||
echo -e "${YELLOW} ⚠ WooCommerce Class Reference in ${file}:${NC}"
|
||||
echo " '${wc_class}' referenced but may require WooCommerce dependency check."
|
||||
CLASS_WARNINGS=$((CLASS_WARNINGS + 1))
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
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 WooCommerce dependency check."
|
||||
CLASS_WARNINGS=$((CLASS_WARNINGS + 1))
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
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
|
||||
if [[ "$class_ref" != "WC_"* ]] && [[ "$class_ref" != "WP_"* ]]; 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))
|
||||
CRITICAL_COUNT=$((CRITICAL_COUNT + 1))
|
||||
fi
|
||||
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
|
||||
if [[ "$static_ref" != "WC_"* ]] && [[ "$static_ref" != "WP_"* ]]; 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))
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if grep -qE "add_action\s*\(\s*['\"]woocommerce_" "${file}"; then
|
||||
if ! grep -qE "(class_exists|function_exists).*woocommerce" "${file}"; then
|
||||
echo -e "${YELLOW} ⚠ WooCommerce Hook Without Dependency Check in ${file}:${NC}"
|
||||
echo " WooCommerce hooks detected without dependency check."
|
||||
CLASS_WARNINGS=$((CLASS_WARNINGS + 1))
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
if grep -qE "add_filter\s*\(\s*['\"]woocommerce_" "${file}"; then
|
||||
if ! grep -qE "(class_exists|function_exists).*woocommerce" "${file}"; then
|
||||
echo -e "${YELLOW} ⚠ WooCommerce Filter Without Dependency Check in ${file}:${NC}"
|
||||
echo " WooCommerce filters detected without dependency check."
|
||||
CLASS_WARNINGS=$((CLASS_WARNINGS + 1))
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
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 ""
|
||||
|
||||
# ============================================
|
||||
# 11. TOO FEW ARGUMENTS ERROR DETECTION
|
||||
# ============================================
|
||||
echo -e "${MAGENTA}[11/11] 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))
|
||||
CRITICAL_COUNT=$((CRITICAL_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))
|
||||
CRITICAL_COUNT=$((CRITICAL_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))
|
||||
WARNING_COUNT=$((WARNING_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))
|
||||
CRITICAL_COUNT=$((CRITICAL_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))
|
||||
CRITICAL_COUNT=$((CRITICAL_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))
|
||||
CRITICAL_COUNT=$((CRITICAL_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))
|
||||
WARNING_COUNT=$((WARNING_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))
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for WooCommerce-specific functions with missing arguments
|
||||
if grep -nE "wc_get_order\s*\(\s*\)" "${file}" >/dev/null 2>&1; then
|
||||
matches=$(grep -nE "wc_get_order\s*\(\s*\)" "${file}" | head -1)
|
||||
if [[ -n "$matches" ]]; then
|
||||
echo -e "${YELLOW} ⚠ POSSIBLE TOO FEW ARGUMENTS in ${file}:${NC}"
|
||||
echo " wc_get_order() called without order ID argument"
|
||||
echo "$matches"
|
||||
ARG_ERRORS=$((ARG_ERRORS + 1))
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
if grep -nE "wc_get_product\s*\(\s*\)" "${file}" >/dev/null 2>&1; then
|
||||
matches=$(grep -nE "wc_get_product\s*\(\s*\)" "${file}" | head -1)
|
||||
if [[ -n "$matches" ]]; then
|
||||
echo -e "${YELLOW} ⚠ POSSIBLE TOO FEW ARGUMENTS in ${file}:${NC}"
|
||||
echo " wc_get_product() called without product ID argument"
|
||||
echo "$matches"
|
||||
ARG_ERRORS=$((ARG_ERRORS + 1))
|
||||
WARNING_COUNT=$((WARNING_COUNT + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $ARG_ERRORS -eq 0 ]]; then
|
||||
echo -e "${GREEN} ✓ No too few arguments errors detected${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 12. FINAL SUMMARY
|
||||
# ============================================
|
||||
|
||||
echo -e "${BLUE}========================================================${NC}"
|
||||
echo -e "${BLUE} VALIDATION SUMMARY ${NC}"
|
||||
echo -e "${BLUE}========================================================${NC}"
|
||||
|
||||
if [[ "${CRITICAL_COUNT}" -gt 0 ]]; then
|
||||
echo -e "${RED}FAILED: ${CRITICAL_COUNT} critical issues found.${NC}"
|
||||
echo "This plugin is NOT safely compatible with modern WooCommerce."
|
||||
echo "Fix all Critical failures before usage."
|
||||
exit 1
|
||||
elif [[ "${WARNING_COUNT}" -gt 0 ]]; then
|
||||
echo -e "${YELLOW}PASSED WITH WARNINGS: 0 critical, ${WARNING_COUNT} warnings.${NC}"
|
||||
echo "Review warnings for future-proofing."
|
||||
exit 0
|
||||
else
|
||||
echo -e "${GREEN}PASSED: 0 critical, 0 warnings.${NC}"
|
||||
echo "Plugin appears fully compatible with strict standards."
|
||||
exit 0
|
||||
fi
|
||||
Reference in New Issue
Block a user