# contact-forms/trunk/assets/js/admin/theme-helper.js

Contact Forms by Cimatti, version trunk. 446 lines.

- Page: https://pluginprobe.com/plugins/contact-forms/trunk/code/assets/js/admin/theme-helper.js
- Raw: https://pluginprobe.com/plugins/contact-forms/trunk/raw/assets/js/admin/theme-helper.js
- Modified: 2026-04-08T09:49:58+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/contact-forms/trunk/code/assets/js/admin/theme-helper.js#L10-L20`.

```javascript
/**
 * Contact Forms - Theme Helper
 * 
 * CSS conflict scanner that detects theme rules overriding Contact Forms styling.
 * Shows file, line number, selector, and conflicting properties.
 * 
 * @package ContactForms
 * @since 2.0.0-beta.6
 */
(function($) {
    'use strict';
    
    // Configuration from PHP
    var config = window.accuaThemeHelper || {};
    var monitoredClasses = config.monitoredClasses || [];
    var criticalProperties = config.criticalProperties || [];
    var i18n = config.i18n || {};
    var pluginCssUrl = config.pluginCssUrl || '';
    
    // Build regex pattern for monitored classes
    var classPattern = new RegExp('\\b(' + monitoredClasses.join('|').replace(/-/g, '\\-') + ')\\b');
    
    /**
     * Extract filename from URL
     */
    function getFilename(url) {
        if (!url) return '(inline)';
        try {
            var urlObj = new URL(url, window.location.origin);
            var pathname = urlObj.pathname;
            var filename = pathname.split('/').pop() || pathname;
            // Remove query string for display but keep for identification
            return filename.split('?')[0];
        } catch (e) {
            return url.substring(0, 50) + '...';
        }
    }
    
    // Remote site Contact Forms version info (set during URL scan)
    var remoteCfInfo = null;
    
    /**
     * Check if URL is the Contact Forms plugin CSS (skip it if same version)
     */
    function isPluginCss(url) {
        if (!url) return false;
        // Match both old and new CSS paths
        var isPlugin = url.indexOf('contact-forms/assets/css/frontend.css') !== -1 ||
                       url.indexOf('contact-forms/assets/css/admin.css') !== -1 ||
                       url.indexOf('contact-forms/assets/css/accua-form-api.css') !== -1 ||
                       url.indexOf('contact-forms/accua-form-api.css') !== -1; // Old path
        return isPlugin;
    }
    
    /**
     * Check if plugin CSS should be skipped (same version as current)
     */
    function shouldSkipPluginCss(url) {
        if (!isPluginCss(url)) return false;
        
        // If we have remote version info and it matches current, skip it
        if (remoteCfInfo && remoteCfInfo.remoteCssVersion) {
            return remoteCfInfo.remoteCssVersion === remoteCfInfo.currentCssVersion;
        }
        
        // For admin page scanning, always skip plugin CSS
        return true;
    }
    
    /**
     * Estimate line number from CSS text and rule position
     */
    function estimateLineNumber(cssText, ruleText) {
        if (!cssText || !ruleText) return '?';
        
        var selectorMatch = ruleText.match(/^[^{]+/);
        if (!selectorMatch) return '?';
        
        var selector = selectorMatch[0].trim();
        var index = cssText.indexOf(selector);
        
        if (index === -1) return '?';
        
        // Count newlines before this position
        var textBefore = cssText.substring(0, index);
        var lineNumber = (textBefore.match(/\n/g) || []).length + 1;
        
        return lineNumber;
    }
    
    /**
     * Parse a CSS rule and extract conflicting properties
     */
    function parseConflictingProperties(rule) {
        var conflicts = [];
        var style = rule.style;
        
        if (!style) return conflicts;
        
        for (var i = 0; i < style.length; i++) {
            var prop = style[i];
            // Check if this property is in our critical list
            if (criticalProperties.indexOf(prop) !== -1) {
                var value = style.getPropertyValue(prop);
                var priority = style.getPropertyPriority(prop);
                conflicts.push({
                    property: prop,
                    value: value,
                    important: priority === 'important'
                });
            }
        }
        
        return conflicts;
    }
    
    /**
     * Format conflicting properties for display
     */
    function formatConflicts(conflicts) {
        return conflicts.map(function(c) {
            var important = c.important ? ' <span style="color:#d63638;font-weight:bold;">!important</span>' : '';
            return '<code>' + c.property + ': ' + c.value + important + '</code>';
        }).join('<br>');
    }
    
    /**
     * Scan a single stylesheet for conflicts
     */
    function scanStylesheet(sheet, cssText) {
        var results = [];
        var url = sheet.href || '';
        
        // Skip Contact Forms own CSS if same version
        if (shouldSkipPluginCss(url)) {
            return results;
        }
        
        try {
            var rules = sheet.cssRules || sheet.rules || [];
            
            for (var i = 0; i < rules.length; i++) {
                var rule = rules[i];
                
                // Only process style rules (not @media, @keyframes, etc.)
                if (rule.type !== CSSRule.STYLE_RULE) continue;
                
                var selector = rule.selectorText || '';
                
                // Check if selector contains any monitored class
                if (!classPattern.test(selector)) continue;
                
                // Get conflicting properties
                var conflicts = parseConflictingProperties(rule);
                
                if (conflicts.length > 0) {
                    // Estimate line number
                    var lineNumber = '?';
                    if (cssText) {
                        lineNumber = estimateLineNumber(cssText, rule.cssText);
                    }
                    
                    results.push({
                        url: url,
                        filename: getFilename(url),
                        line: lineNumber,
                        selector: selector,
                        conflicts: conflicts,
                        ruleText: rule.cssText
                    });
                }
            }
        } catch (e) {
            // Cross-origin stylesheets throw security errors
            console.log('Cannot access stylesheet:', url, e.message);
        }
        
        return results;
    }
    
    /**
     * Scan CSS text string for conflicts (for URL scanning)
     */
    function scanCssText(cssText, sourceUrl) {
        var results = [];
        
        // Skip Contact Forms own CSS if same version
        if (shouldSkipPluginCss(sourceUrl)) {
            return results;
        }
        
        // Parse CSS text manually with regex
        // Match rules: selector { properties }
        var rulePattern = /([^{}]+)\{([^{}]+)\}/g;
        var match;
        var position = 0;
        
        while ((match = rulePattern.exec(cssText)) !== null) {
            var selector = match[1].trim();
            var properties = match[2].trim();
            
            // Check if selector contains any monitored class
            if (!classPattern.test(selector)) continue;
            
            // Parse properties
            var conflicts = [];
            var propPattern = /([\w-]+)\s*:\s*([^;]+);?/g;
            var propMatch;
            
            while ((propMatch = propPattern.exec(properties)) !== null) {
                var prop = propMatch[1].trim();
                var value = propMatch[2].trim();
                
                if (criticalProperties.indexOf(prop) !== -1) {
                    var isImportant = value.indexOf('!important') !== -1;
                    value = value.replace(/\s*!important\s*/i, '');
                    
                    conflicts.push({
                        property: prop,
                        value: value,
                        important: isImportant
                    });
                }
            }
            
            if (conflicts.length > 0) {
                // Estimate line number
                var textBefore = cssText.substring(0, match.index);
                var lineNumber = (textBefore.match(/\n/g) || []).length + 1;
                
                results.push({
                    url: sourceUrl,
                    filename: getFilename(sourceUrl),
                    line: lineNumber,
                    selector: selector,
                    conflicts: conflicts,
                    ruleText: match[0]
                });
            }
        }
        
        return results;
    }
    
    /**
     * Scan all stylesheets on current page
     */
    function scanCurrentPage() {
        var allResults = [];
        var sheets = document.styleSheets;
        
        for (var i = 0; i < sheets.length; i++) {
            var sheet = sheets[i];
            
            // Try to get CSS text for line number estimation
            var cssText = '';
            try {
                if (sheet.cssRules) {
                    cssText = Array.from(sheet.cssRules).map(function(r) {
                        return r.cssText;
                    }).join('\n');
                }
            } catch (e) {
                // Cross-origin
            }
            
            var results = scanStylesheet(sheet, cssText);
            allResults = allResults.concat(results);
        }
        
        return allResults;
    }
    
    /**
     * Display results in the table
     */
    function displayResults(results) {
        var $table = $('#theme-helper-results');
        var $tbody = $('#conflicts-tbody');
        var $summary = $('#results-summary');
        
        $tbody.empty();
        
        if (results.length === 0) {
            $summary.html(
                '<span style="color:#00a32a;font-weight:bold;">✓ ' + i18n.noConflicts + '</span>' +
                '<p style="margin-top:10px;">All scanned stylesheets appear compatible with Contact Forms.</p>'
            );
            $table.show();
            return;
        }
        
        // Group by file
        var byFile = {};
        results.forEach(function(r) {
            var key = r.filename;
            if (!byFile[key]) byFile[key] = [];
            byFile[key].push(r);
        });
        
        var fileCount = Object.keys(byFile).length;
        
        $summary.html(
            '<span style="color:#d63638;font-weight:bold;">⚠ ' + i18n.conflictsFound + '</span>' +
            '<p style="margin-top:10px;">' +
            results.length + ' conflicting rule(s) found in ' + fileCount + ' file(s).' +
            '</p>'
        );
        
        // Populate table
        results.forEach(function(r) {
            var $row = $('<tr>');
            
            // Stylesheet column - make it a clickable link
            var urlTitle = r.url || '(unknown)';
            var filenameHtml;
            if (r.url && r.url.indexOf('(inline') === -1) {
                // External URL - make it clickable
                filenameHtml = '<a href="' + encodeURI(r.url) + '" target="_blank" title="' + urlTitle + '" style="text-decoration:none;">' +
                    '<strong>' + r.filename + '</strong> <span class="dashicons dashicons-external" style="font-size:14px;vertical-align:middle;"></span></a>';
            } else {
                filenameHtml = '<strong title="' + urlTitle + '">' + r.filename + '</strong>';
            }
            $row.append('<td>' + filenameHtml + '</td>');
            
            // Line column
            $row.append('<td>' + r.line + '</td>');
            
            // Selector column
            $row.append('<td><code>' + r.selector + '</code></td>');
            
            // Properties column
            $row.append('<td>' + formatConflicts(r.conflicts) + '</td>');
            
            $tbody.append($row);
        });
        
        $table.show();
    }
    
    /**
     * Show status message
     */
    function showStatus(message, type) {
        var $status = $('#theme-helper-status');
        var borderColor = type === 'error' ? '#d63638' : (type === 'success' ? '#00a32a' : '#0073aa');
        var bgColor = type === 'error' ? '#fcf0f1' : (type === 'success' ? '#edfaef' : '#f0f6fc');
        
        $status.css({
            'border-left-color': borderColor,
            'background': bgColor
        });
        
        $('#status-message').html(message);
        $status.show();
    }
    
    /**
     * Scan current page button handler
     */
    function handleScanCurrentPage() {
        showStatus('<span class="spinner is-active" style="float:none;margin:0 5px 0 0;"></span> ' + i18n.scanning, 'info');
        
        // Small delay to allow spinner to render
        setTimeout(function() {
            var results = scanCurrentPage();
            displayResults(results);
            showStatus('✓ ' + i18n.scanComplete + ' - ' + results.length + ' conflict(s) found', results.length > 0 ? 'error' : 'success');
        }, 100);
    }
    
    /**
     * Scan URL button handler
     */
    function handleScanUrl() {
        var url = $('#scan-url').val().trim();
        
        if (!url) {
            showStatus('⚠ ' + i18n.enterUrl, 'error');
            return;
        }
        
        showStatus('<span class="spinner is-active" style="float:none;margin:0 5px 0 0;"></span> ' + i18n.fetchingCss, 'info');
        
        $.ajax({
            url: config.ajaxUrl,
            method: 'POST',
            data: {
                action: 'accua_forms_fetch_url_css',
                nonce: config.nonce,
                url: url
            },
            success: function(response) {
                if (!response.success) {
                    showStatus('⚠ ' + (response.data?.message || i18n.scanError), 'error');
                    return;
                }
                
                var stylesheets = response.data.stylesheets || [];
                var allResults = [];
                
                // Store remote CF version info for version comparison
                remoteCfInfo = {
                    remoteCssVersion: response.data.remoteCssVersion,
                    remoteVersion: response.data.remoteContactFormsVersion,
                    currentCssVersion: response.data.currentCssVersion,
                    currentVersion: response.data.currentVersion
                };
                
                // Scan each stylesheet
                stylesheets.forEach(function(sheet) {
                    var results = scanCssText(sheet.content, sheet.url);
                    allResults = allResults.concat(results);
                });
                
                // Build version info message
                var versionInfo = '';
                if (remoteCfInfo.remoteCssVersion && remoteCfInfo.remoteCssVersion !== remoteCfInfo.currentCssVersion) {
                    versionInfo = '<br><span style="color:#dba617;">⚠ Remote site uses Contact Forms CSS v' + remoteCfInfo.remoteCssVersion + 
                        ' (you have v' + remoteCfInfo.currentCssVersion + ')</span>';
                } else if (remoteCfInfo.remoteCssVersion) {
                    versionInfo = '<br><span style="color:#00a32a;">✓ Remote site uses same Contact Forms CSS version (v' + remoteCfInfo.remoteCssVersion + ')</span>';
                }
                
                displayResults(allResults);
                showStatus('✓ ' + i18n.scanComplete + ' - Scanned ' + stylesheets.length + ' stylesheet(s), ' + allResults.length + ' conflict(s) found' + versionInfo, allResults.length > 0 ? 'error' : 'success');
            },
            error: function() {
                showStatus('⚠ ' + i18n.scanError, 'error');
            }
        });
    }
    
    // Initialize on document ready
    $(function() {
        $('#scan-current-page').on('click', handleScanCurrentPage);
        $('#scan-url-button').on('click', handleScanUrl);
        $('#scan-url').on('keypress', function(e) {
            if (e.which === 13) {
                handleScanUrl();
            }
        });
    });
    
})(jQuery);

```
