/home/tuzdhajd/ablacktime.com/wp-content/plugins/ninja-forms/includes/Fields/Signature.php
<?php if ( ! defined( 'ABSPATH' ) ) exit;


/**
 * Class NF_Fields_Signature
 *
 * Provides signature capture functionality with both typed and drawn signature options.
 * This field is intended for collecting acknowledgments, confirmations, and user consent
 * in a visual format. It is NOT designed to create legally binding signatures or meet
 * legal requirements for electronic signatures (e.g., e-signature laws, digital certificates).
 *
 * Features:
 * - Typed signatures: User types their name, displayed with custom fonts
 * - Drawn signatures: User draws signature on a canvas
 * - Method selection: Can be configured for typed-only, drawn-only, or both
 * 
 * Data Storage:
 * - Signatures are stored as JSON with the following structure:
 *   {
 *     "signature_type": "typed" | "drawn",
 *     "typed_name": "John Doe" (for typed),
 *     "signature_font": "dancing-script" (for typed),
 *     "signature_data": "data:image/png;base64,..." (for drawn),
 *     "canvas_dimensions": {"width": 400, "height": 150} (for drawn),
 *     "timestamp": "2024-01-01T12:00:00Z"
 *   }
 * 
 * Export Formats:
 * - CSV: "Method: typed/drawn | Signed: true/false | Value: [name if typed]"
 * - PDF: Text representation (e.g., "John Doe (Typed signature - dancing-script font)")
 * - Email/HTML: Full HTML display with styled text or image
 * 
 * Usage in HTML fields:
 * - Use merge tag format: {field:signature_field_key} or {field:signature_field_id}
 * - In HTML emails: Displays styled text (typed) or base64 image (drawn)
 * - In plain text emails: Shows text representation only
 * - Example: <p>Customer Signature: {field:signature_1}</p>
 * 
 * Repeater Field Support:
 * - Fully supports signature fields within repeater fields
 * - Maintains consistent formatting across all export types
 * 
 * @since 3.x
 */
class NF_Fields_Signature extends NF_Abstracts_Input
{
    protected $_name = 'signature';
    protected $_type = 'signature';
    protected $_section = 'common';
    protected $_icon = 'pencil-square-o';
    protected $_templates = 'signature';
    protected $_test_value = '{"signature_type":"typed","typed_name":"John Doe","signature_font":"dancing-script"}';
    
    // Constants for validation and limits
    const MAX_SIGNATURE_SIZE = 137000; // ~100KB in base64
    const MAX_NAME_LENGTH = 100;
    const VALID_SIGNATURE_TYPES = array( 'drawn', 'typed' );
    const VALID_FONTS = array( 'dancing-script', 'satisfy', 'cursive' );

    protected $_settings = array(
        'signature_method',
        'signature_font',
        'typed_placeholder',
        'drawn_placeholder',
        'canvas_width',
        'canvas_height',
        'pen_color',
        'background_color'
    );

    protected $_settings_all_fields = array(
        'key', 'label', 'label_pos', 'required', 'classes', 'manual_key', 'admin_label', 'help', 'description',
        'signature_method',
        'signature_font',
        'typed_placeholder',
        'drawn_placeholder',
        'canvas_width',
        'canvas_height',
        'pen_color',
        'background_color'
    );

    public function __construct()
    {
        parent::__construct();

        $this->_nicename = esc_html__( 'Signature', 'ninja-forms' );

        // Add default field settings
        $this->_settings[ 'label_pos' ][ 'value' ] = 'above';
        $this->_settings[ 'required' ][ 'value' ] = 0;

        // Add filter to localize field settings
        add_filter( 'ninja_forms_localize_field_' . $this->_name, array( $this, 'localize_field_settings' ) );
        
        // Primary filter for merge tag display (handles all signature merge tag conversions)
        add_filter( 'ninja_forms_merge_tag_value_' . $this->_type, array( $this, 'filter_merge_tag_value' ), 10, 2 );
        
        // Add filter for CSV export
        add_filter( 'ninja_forms_subs_export_field_value_' . $this->_type, array( $this, 'filter_csv_export_value' ), 10, 2 );
        
        // Add filter for PDF export
        add_filter( 'ninja_forms_pdf_field_value_' . $this->_type, array( $this, 'filter_pdf_value' ), 10, 2 );
        
        // Add filter for PDF field value display - this is the main handler
        add_filter( 'ninja_forms_pdf_field_value', array( $this, 'filter_pdf_field_display' ), 10, 3 );
        
        // Disable wpautop for signature fields in PDF
        add_filter( 'ninja_forms_pdf_field_value_wpautop', array( $this, 'disable_wpautop_for_signatures' ), 10, 3 );
        
        // Add signature to the list of HTML-safe fields
        add_filter( 'ninja_forms_get_html_safe_fields', array( $this, 'add_to_safe_fields' ), 10, 1 );

        // Add filter for CPT custom columns display
        add_filter( 'ninja_forms_custom_columns', array( $this, 'custom_columns' ), 10, 3 );

    }

    /**
     * Localize field settings for frontend
     */
    public function localize_field_settings( $settings )
    {
        // Ensure signature-specific settings are passed to frontend
        $signature_settings = [
            'signature_method',
            'signature_font',
            'typed_placeholder',
            'drawn_placeholder',
            'canvas_width',
            'canvas_height',
            'pen_color',
            'background_color'
        ];

        foreach ( $signature_settings as $setting ) {
            if ( ! isset( $settings[ $setting ] ) && isset( $this->_settings[ $setting ][ 'value' ] ) ) {
                $settings[ $setting ] = $this->_settings[ $setting ][ 'value' ];
            }
        }
        
        // If there's a value that looks like JSON, process it for display
        if ( isset( $settings['value'] ) && is_string( $settings['value'] ) && strpos( $settings['value'], '{' ) === 0 ) {
            // Store the raw value for potential merge tag processing
            $settings['raw_signature_value'] = $settings['value'];
        }

        return $settings;
    }

    /**
     * Validate field
     */
    public function validate( $field, $data )
    {
        $errors = parent::validate( $field, $data );

        // Get field value
        $value = $field[ 'value' ];

        // If field is required and empty
        if ( 1 == $field[ 'required' ] && empty( $value ) ) {
            $errors[] = esc_html__( 'Please provide a signature', 'ninja-forms' );
            return $errors;
        }

        // If not empty, validate signature data
        if ( ! empty( $value ) ) {
            $signature_data = json_decode( $value, true );
            
            if ( ! is_array( $signature_data ) || ! isset( $signature_data[ 'signature_type' ] ) ) {
                $errors[] = esc_html__( 'Invalid signature data', 'ninja-forms' );
                return $errors;
            }
            
            // Validate signature type
            if ( ! in_array( $signature_data[ 'signature_type' ], self::VALID_SIGNATURE_TYPES ) ) {
                $errors[] = esc_html__( 'Invalid signature type', 'ninja-forms' );
                return $errors;
            }

            // Validate based on signature type
            if ( 'typed' === $signature_data[ 'signature_type' ] ) {
                if ( empty( $signature_data[ 'typed_name' ] ) ) {
                    if ( 1 == $field[ 'required' ] ) {
                        $errors[] = esc_html__( 'Please provide a signature', 'ninja-forms' );
                    } else {
                        $errors[] = esc_html__( 'Please type your name', 'ninja-forms' );
                    }
                }
                
                // Check name length
                if ( isset( $signature_data[ 'typed_name' ] ) && strlen( $signature_data[ 'typed_name' ] ) > self::MAX_NAME_LENGTH ) {
                    $errors[] = esc_html__( 'Name is too long', 'ninja-forms' );
                }
            } elseif ( 'drawn' === $signature_data[ 'signature_type' ] ) {
                if ( empty( $signature_data[ 'signature_data' ] ) || 
                     ! preg_match( '/^data:image\/(png|jpeg);base64,/', $signature_data[ 'signature_data' ] ) ) {
                    if ( 1 == $field[ 'required' ] ) {
                        $errors[] = esc_html__( 'Please provide a signature', 'ninja-forms' );
                    } else {
                        $errors[] = esc_html__( 'Please draw your signature', 'ninja-forms' );
                    }
                }
                
                // Check signature size
                if ( isset( $signature_data[ 'signature_data' ] ) && 
                     strlen( $signature_data[ 'signature_data' ] ) > self::MAX_SIGNATURE_SIZE ) {
                    $errors[] = esc_html__( 'Signature data is too large', 'ninja-forms' );
                }
            }
        }

        return $errors;
    }

    /**
     * Sanitize field value
     */
    public function sanitize_field_value( $value )
    {
        if ( empty( $value ) ) {
            return '';
        }

        // Decode JSON
        $signature_data = json_decode( $value, true );
        
        if ( ! is_array( $signature_data ) ) {
            return '';
        }

        $sanitized = [];

        // Validate and sanitize signature type
        $sanitized[ 'signature_type' ] = in_array( $signature_data[ 'signature_type' ], self::VALID_SIGNATURE_TYPES ) 
            ? $signature_data[ 'signature_type' ] 
            : 'typed';

        // Sanitize based on type
        if ( 'drawn' === $sanitized[ 'signature_type' ] ) {
            // Validate base64 image data
            if ( isset( $signature_data[ 'signature_data' ] ) && 
                 preg_match( '/^data:image\/(png|jpeg);base64,(.+)/', $signature_data[ 'signature_data' ], $matches ) ) {
                // Limit image data size
                if ( strlen( $signature_data[ 'signature_data' ] ) < self::MAX_SIGNATURE_SIZE ) {
                    // Additional validation: verify base64 can be decoded
                    $decoded = base64_decode( $matches[2], true );
                    if ( false !== $decoded && ! empty( $decoded ) ) {
                        $sanitized[ 'signature_data' ] = $signature_data[ 'signature_data' ];
                    }
                }
            }
            
            // Sanitize canvas dimensions
            if ( isset( $signature_data[ 'canvas_dimensions' ] ) && is_array( $signature_data[ 'canvas_dimensions' ] ) ) {
                $width = isset( $signature_data[ 'canvas_dimensions' ][ 'width' ] ) ? 
                    absint( $signature_data[ 'canvas_dimensions' ][ 'width' ] ) : 400;
                $height = isset( $signature_data[ 'canvas_dimensions' ][ 'height' ] ) ? 
                    absint( $signature_data[ 'canvas_dimensions' ][ 'height' ] ) : 150;
                
                // Clamp to reasonable ranges
                $width = max( 100, min( 2000, $width ) );
                $height = max( 50, min( 1000, $height ) );
                
                $sanitized[ 'canvas_dimensions' ] = [
                    'width' => $width,
                    'height' => $height
                ];
            }
        } elseif ( 'typed' === $sanitized[ 'signature_type' ] ) {
            // Sanitize typed name
            if ( isset( $signature_data[ 'typed_name' ] ) ) {
                $sanitized[ 'typed_name' ] = sanitize_text_field( $signature_data[ 'typed_name' ] );
            }
            // Sanitize font
            if ( isset( $signature_data[ 'signature_font' ] ) ) {
                $sanitized[ 'signature_font' ] = in_array( $signature_data[ 'signature_font' ], self::VALID_FONTS )
                    ? $signature_data[ 'signature_font' ]
                    : 'dancing-script';
            }
        }

        // Add metadata
        if ( isset( $signature_data[ 'timestamp' ] ) ) {
            // Validate timestamp format (ISO 8601 or Unix timestamp)
            $timestamp = $signature_data[ 'timestamp' ];
            if ( is_numeric( $timestamp ) || preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/', $timestamp ) ) {
                $sanitized[ 'timestamp' ] = sanitize_text_field( $timestamp );
            }
        }

        return wp_json_encode( $sanitized );
    }

    /**
     * Format value for display in the legacy admin interface
     * 
     * Note: This method is used by the old WordPress admin submission view interface
     * (WordPress Admin → Ninja Forms → Submissions → View individual submission).
     * The modern submission management interface uses a React component instead,
     * but this method is kept for backwards compatibility.
     * 
     * @param array $field Field data
     * @param string $value Field value (JSON string)
     * @return string HTML formatted signature for display
     */
    public function admin_form_element( $field, $value )
    {
        if ( empty( $value ) ) {
            return '<div style="color: #999; font-style: italic;">' . esc_html__( 'Not signed', 'ninja-forms' ) . '</div>';
        }

        // Decode HTML entities if present
        $decoded_value = html_entity_decode( $value, ENT_QUOTES, 'UTF-8' );
        $signature_data = json_decode( $decoded_value, true );

        // Try with stripslashes if needed
        if ( ! is_array( $signature_data ) && is_string( $value ) ) {
            $signature_data = json_decode( stripslashes( $value ), true );
        }

        // If still not valid, try original value
        if ( ! is_array( $signature_data ) ) {
            $signature_data = json_decode( $value, true );
        }

        if ( ! is_array( $signature_data ) || ! isset( $signature_data['signature_type'] ) ) {
            return '<div style="color: #999; font-style: italic;">' . esc_html__( 'Invalid signature data', 'ninja-forms' ) . '</div>';
        }

        $output = '';
        $method = $signature_data['signature_type'];

        // Display typed signature
        if ( 'typed' === $method && isset( $signature_data['typed_name'] ) && ! empty( $signature_data['typed_name'] ) ) {
            $font = isset( $signature_data['signature_font'] ) ? $signature_data['signature_font'] : 'dancing-script';
            $font_family = $this->get_signature_font_family( $font );

            $output .= '<div style="margin-bottom: 15px;">';
            $output .= '<div style="font-family: ' . esc_attr( $font_family ) . '; font-size: 32px; color: #000; padding: 15px; background: transparent; display: inline-block;">';
            $output .= esc_html( $signature_data['typed_name'] );
            $output .= '</div>';
            $output .= '<div style="margin-top: 8px; font-size: 12px; color: #666;">';
            $output .= '<strong>' . esc_html__( 'Type:', 'ninja-forms' ) . '</strong> ' . esc_html__( 'Typed signature', 'ninja-forms' );
            $output .= '</div>';
            $output .= '</div>';
        }
        // Display drawn signature
        elseif ( 'drawn' === $method && isset( $signature_data['signature_data'] ) && ! empty( $signature_data['signature_data'] ) ) {
            if ( preg_match( '/^data:image\/(png|jpeg);base64,/', $signature_data['signature_data'] ) ) {
                $output .= '<div style="margin-bottom: 15px;">';
                $output .= '<img src="' . esc_attr( $signature_data['signature_data'] ) . '" ';
                $output .= 'alt="' . esc_attr__( 'Signature', 'ninja-forms' ) . '" ';
                $output .= 'style="max-width: 400px; height: auto; display: block;" />';
                $output .= '<div style="margin-top: 8px; font-size: 12px; color: #666;">';
                $output .= '<strong>' . esc_html__( 'Type:', 'ninja-forms' ) . '</strong> ' . esc_html__( 'Drawn signature', 'ninja-forms' );
                $output .= '</div>';
                $output .= '</div>';
            } else {
                return '<div style="color: #999; font-style: italic;">' . esc_html__( 'Invalid signature image data', 'ninja-forms' ) . '</div>';
            }
        }

        // Add non-editable notice
        $output .= '<div style="font-size: 12px; color: #666; font-style: italic; margin-top: 8px;">';
        $output .= esc_html__( 'Signatures are not editable', 'ninja-forms' );
        $output .= '</div>';

        return $output;
    }


    /**
     * Filter merge tag value for display in various contexts
     *
     * This method handles signature display for:
     * - HTML fields (via merge tags)
     * - Email notifications
     * - PDF documents
     * - Any other merge tag usage
     *
     * @param mixed $value The field value
     * @param array $field The field data (may contain additional field settings)
     * @return string Formatted value for display
     */
    public function filter_merge_tag_value( $value, $field )
    {
        if ( empty( $value ) ) {
            return '';
        }

        // Handle HTML entity-encoded JSON (common in email contexts)
        if ( is_string( $value ) && ( strpos( $value, '&quot;' ) !== false || strpos( $value, '&amp;' ) !== false ) ) {
            $value = html_entity_decode( $value, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
        }

        $signature_data = json_decode( $value, true );

        if ( ! is_array( $signature_data ) ) {
            return '';
        }

        // Check if we need plain text output (for plain text emails only)
        $is_plain_text = isset( $field['email_format'] ) && 'plain' === $field['email_format'];

        // Also check if this is being used in a context that requires plain text
        $context = isset( $field['merge_tag_context'] ) ? $field['merge_tag_context'] : 'default';

        if ( $is_plain_text && 'email' === $context ) {
            // Plain text format for emails only
            if ( 'typed' === $signature_data[ 'signature_type' ] && ! empty( $signature_data[ 'typed_name' ] ) ) {
                return $signature_data[ 'typed_name' ] . ' (Typed Signature)';
            }

            if ( 'drawn' === $signature_data[ 'signature_type' ] ) {
                $timestamp = isset( $signature_data[ 'timestamp' ] ) ? ' - ' . $signature_data[ 'timestamp' ] : '';
                return 'Drawn Signature' . $timestamp;
            }
        } else {
            // Detect if we're in a PDF context
            $is_pdf_context = $this->is_pdf_rendering_context();

            // HTML format - return HTML for merge tags in HTML fields, emails, and PDFs
            $options = $this->get_signature_html_options( $context );

            // For PDF context with typed signatures, generate an image instead of HTML text
            if ( $is_pdf_context && 'typed' === $signature_data['signature_type'] && ! empty( $signature_data['typed_name'] ) ) {
                error_log( 'Ninja Forms Signature: PDF context detected, generating image for typed signature' );
                $html = $this->get_signature_pdf_image_html( $signature_data, $options );
            } else {
                if ( $is_pdf_context ) {
                    error_log( 'Ninja Forms Signature: PDF context but not typed signature (type: ' . ( isset( $signature_data['signature_type'] ) ? $signature_data['signature_type'] : 'unknown' ) . ')' );
                }
                // Allow filter to override options
                $options = apply_filters( 'ninja_forms_signature_merge_tag_options', $options, $field, $context );

                // Generate HTML that will display properly in HTML fields
                $html = $this->get_signature_email_html( $signature_data, $options );
            }

            // Check if we're in a repeater context (field data has different structure)
            $is_repeater_context = isset( $field['id'] ) && strpos( $field['id'], '_' ) !== false;

            // Wrap HTML for proper display (skip wrapper if in repeater context)
            if ( ! empty( $html ) ) {
                if ( $is_repeater_context ) {
                    return $html;
                }
                return $this->wrap_signature_html( $html, 'merge-tag' );
            }
        }

        return '';
    }

    /**
     * Filter CSV export value
     * 
     * Formats signature data for CSV exports in a human-readable format:
     * - Empty: "Method: none | Signed: false"
     * - Typed: "Method: typed | Signed: true | Value: John Doe"
     * - Drawn: "Method: drawn | Signed: true"
     * 
     * @param mixed $value The field value (JSON string)
     * @param object|array $field The field object or array (unused but required by filter signature)
     * @return string Formatted value for CSV export
     */
    public function filter_csv_export_value( $value, $field )
    {
        // Handle already processed values (e.g., from Email action)
        if ( is_string( $value ) && ( strpos( $value, 'Method:' ) !== false || strpos( $value, 'Signed:' ) !== false ) ) {
            return $value;
        }

        // Build CSV parts
        $csv_parts = array();
        
        // Handle empty values - field was not signed
        if ( empty( $value ) ) {
            $csv_parts[] = 'Method: none';
            $csv_parts[] = 'Signed: false';
            return implode( ' | ', $csv_parts );
        }

        // Try to decode JSON - handle potential escaping issues
        $signature_data = json_decode( $value, true );
        
        // If first decode fails, try with stripslashes
        if ( ! is_array( $signature_data ) && is_string( $value ) ) {
            $signature_data = json_decode( stripslashes( $value ), true );
        }
        
        // If still not valid, try html_entity_decode (common in email contexts)
        if ( ! is_array( $signature_data ) && is_string( $value ) ) {
            $decoded_value = html_entity_decode( $value, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
            $signature_data = json_decode( $decoded_value, true );
        }
        
        // If we can't parse the data, check if it might be just the signature type string
        if ( ! is_array( $signature_data ) ) {
            // Sometimes the value might be just the signature type as a plain string
            if ( in_array( $value, array( 'typed', 'drawn' ) ) ) {
                $csv_parts[] = 'Method: ' . $value;
                $csv_parts[] = 'Signed: false';
                return implode( ' | ', $csv_parts );
            }
            
            $csv_parts[] = 'Method: error';
            $csv_parts[] = 'Signed: false';
            return implode( ' | ', $csv_parts );
        }

        // Get the method used - signature_type contains the actual method used during submission
        $method = isset( $signature_data['signature_type'] ) ? $signature_data['signature_type'] : 'unknown';
        $csv_parts[] = 'Method: ' . $method;
        
        // Check if a value was actually provided based on the method
        $has_value = false;
        $typed_value = '';
        
        if ( 'typed' === $method ) {
            // For typed signatures, check if name was provided
            if ( isset( $signature_data['typed_name'] ) && ! empty( $signature_data['typed_name'] ) ) {
                $has_value = true;
                $typed_value = $signature_data['typed_name'];
            }
        } elseif ( 'drawn' === $method ) {
            // For drawn signatures, check if signature data exists
            if ( isset( $signature_data['signature_data'] ) && ! empty( $signature_data['signature_data'] ) ) {
                $has_value = true;
            }
        }
        
        // Add signed status based on whether value exists
        $csv_parts[] = 'Signed: ' . ( $has_value ? 'true' : 'false' );
        
        // Add the value when method is typed
        if ( 'typed' === $method && ! empty( $typed_value ) ) {
            $csv_parts[] = 'Value: ' . $typed_value;
        }
        
        return implode( ' | ', $csv_parts );
    }

    /**
     * Filter PDF export value - transforms signature data for PDF export
     * 
     * This filter is specific to the signature field type and prepares
     * the signature data in a format suitable for PDF generation.
     * 
     * @param mixed $value The field value (JSON string)
     * @param object $field The field object (unused but required by filter signature)
     * @return array Formatted value for PDF export with type and value keys
     */
    public function filter_pdf_value( $value, $field )
    {
        if ( empty( $value ) ) {
            return [
                'type' => 'empty',
                'value' => ''
            ];
        }

        $signature_data = json_decode( $value, true );
        
        if ( ! is_array( $signature_data ) ) {
            return [
                'type' => 'empty',
                'value' => ''
            ];
        }

        // Get PDF-ready data using internal method
        return $this->get_signature_pdf_data( $signature_data );
    }
    
    /**
     * Filter PDF field display to show signature
     * 
     * This is the main filter for controlling how signature fields appear in PDFs.
     * It handles both the default PDF table (which uses MultiCell and doesn't support HTML)
     * and custom templates (which can use WriteHTML for full HTML support).
     * 
     * Note: The default PDF table generation doesn't support HTML, so we provide
     * a text representation. For proper signature image display, users should
     * use a custom PDF template or the document body feature.
     * 
     * @param string $field_value The current field value
     * @param string $original_value The original field value (JSON string)
     * @param array $field The field data including type, id, etc.
     * @return string Modified field value for PDF display
     */
    public function filter_pdf_field_display( $field_value, $original_value, $field )
    {
        // Only process signature fields
        if ( ! isset( $field['type'] ) || 'signature' !== $field['type'] ) {
            return $field_value;
        }
        
        // If value is empty, return empty message
        if ( empty( $original_value ) ) {
            return esc_html__( 'Not signed', 'ninja-forms' );
        }
        
        // Try to decode JSON
        $signature_data = json_decode( $original_value, true );
        
        // If first decode fails, try with stripslashes
        if ( ! is_array( $signature_data ) && is_string( $original_value ) ) {
            $signature_data = json_decode( stripslashes( $original_value ), true );
        }
        
        // If we can't parse the data, return error message
        if ( ! is_array( $signature_data ) || ! isset( $signature_data['signature_type'] ) ) {
            return esc_html__( 'Invalid signature data', 'ninja-forms' );
        }
        
        // Handle based on signature type
        if ( 'typed' === $signature_data['signature_type'] && ! empty( $signature_data['typed_name'] ) ) {
            // For typed signatures, show name and font
            $font = isset( $signature_data['signature_font'] ) ? $signature_data['signature_font'] : 'dancing-script';
            
            // Return text representation since default PDF table doesn't support HTML
            return sprintf(
                '%s (Typed signature - %s font)',
                $signature_data['typed_name'],
                $font
            );
            
        } elseif ( 'drawn' === $signature_data['signature_type'] && ! empty( $signature_data['signature_data'] ) ) {
            // For drawn signatures in default PDF table, we can only show text
            // To display the actual image, users need to use a custom template
            // or enable the "use_document_body" option
            return esc_html__( 'Drawn signature captured', 'ninja-forms' );
        }
        
        return esc_html__( 'Not signed', 'ninja-forms' );
    }
    
    /**
     * Disable wpautop for signature fields in PDF
     * 
     * @param bool $apply_wpautop Whether to apply wpautop
     * @param string $field_value The field value
     * @param array $field The field data
     * @return bool
     */
    public function disable_wpautop_for_signatures( $apply_wpautop, $field_value, $field )
    {
        if ( isset( $field['type'] ) && 'signature' === $field['type'] ) {
            return false;
        }
        
        return $apply_wpautop;
    }
    
    /**
     * Add signature to the list of HTML-safe fields
     * 
     * This ensures signature field values are not stripped of HTML
     * when used in merge tags
     * 
     * @param array $safe_fields Current list of safe fields
     * @return array Modified list
     */
    public function add_to_safe_fields( $safe_fields )
    {
        if ( ! in_array( 'signature', $safe_fields ) ) {
            $safe_fields[] = 'signature';
        }
        
        return $safe_fields;
    }
    /**
     * Get default HTML options for signature display
     *
     * @param string $context The display context (merge_tag, email, etc.)
     * @return array HTML generation options
     */
    private function get_signature_html_options( $context = 'merge_tag' )
    {
        $default_options = [
            'max_width' => '400px',
            'font_size' => '28px',
            'typed_color' => '#000000',
            'container_style' => 'display: inline-block;'
        ];

        // Allow context-specific overrides
        return apply_filters( 'ninja_forms_signature_html_options', $default_options, $context );
    }

    /**
     * Detect if we're rendering in a PDF context
     *
     * Checks various indicators to determine if the current rendering
     * is happening within a PDF generation process.
     *
     * @return bool True if in PDF context, false otherwise
     */
    private function is_pdf_rendering_context()
    {
        // Check if this is being called from PDF generation actions
        if ( doing_action( 'nf_pdf_before_template_part' ) || doing_action( 'nf_pdf_after_template_part' ) ) {
            return true;
        }

        // Check the call stack for PDF-related classes
        $backtrace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 15 );
        foreach ( $backtrace as $trace ) {
            if ( isset( $trace['class'] ) && strpos( $trace['class'], 'NF_Pdf_Submissions' ) !== false ) {
                return true;
            }
            if ( isset( $trace['file'] ) && strpos( $trace['file'], 'ninja-forms-pdf-submissions' ) !== false ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Generate HTML for signature display in PDF using an image
     *
     * Converts typed signature text to an image to ensure proper font rendering
     * in PDF documents without affecting other text.
     *
     * @param array $signature_data Decoded signature JSON data
     * @param array $options Display options
     * @return string HTML with image tag or fallback text
     */
    private function get_signature_pdf_image_html( $signature_data, $options = [] )
    {
        // Validate we have typed signature data
        if ( 'typed' !== $signature_data['signature_type'] || empty( $signature_data['typed_name'] ) ) {
            return '';
        }

        $font = isset( $signature_data['signature_font'] ) ? $signature_data['signature_font'] : 'dancing-script';
        $text = $signature_data['typed_name'];

        // Generate image with signature font
        $image_options = [
            'font_size' => 48,
            'color' => isset( $options['typed_color'] ) ? $options['typed_color'] : '#000000',
            'padding' => 15,
        ];

        $image_data = $this->generate_signature_image( $text, $font, $image_options );

        // If image generation failed, fall back to plain text
        if ( false === $image_data ) {
            return sprintf(
                '<div style="font-family: serif; font-style: italic; font-size: 24px;">%s</div>',
                esc_html( $text )
            );
        }

        // Return image tag with appropriate styling
        $max_width = isset( $options['max_width'] ) ? $options['max_width'] : '400px';

        return sprintf(
            '<img src="%s" alt="%s" style="max-width: %s; height: auto; display: block;" />',
            esc_attr( $image_data ),
            esc_attr( sprintf( __( 'Signature: %s', 'ninja-forms' ), $text ) ),
            esc_attr( $max_width )
        );
    }
    
    /**
     * Generate HTML wrapper for signature content
     * @param string $html The signature HTML content
     * @param string $context The display context
     * @return string Wrapped HTML
     */
    private function wrap_signature_html( $html, $context = 'merge_tag' )
    {
        if ( empty( $html ) ) {
            return '';
        }

        $class = 'nf-signature-' . sanitize_html_class( $context );
        return sprintf(
            '<div class="%s" style="display: block; margin: 10px 0;">%s</div>',
            esc_attr( $class ),
            $html
        );
    }
    
    /**
     * Convert signature data to HTML for email display
     *
     * @param array $signature_data Decoded signature JSON data
     * @param array $options Display options
     * @return string HTML output
     */
    private function get_signature_email_html( $signature_data, $options = [] )
    {
        $defaults = [
            'max_width' => '400px',
            'font_size' => '28px',
            'typed_color' => '#000000',
            'container_style' => '',
        ];

        $options = array_merge( $defaults, $options );

        // For typed signatures
        if ( 'typed' === $signature_data['signature_type'] && ! empty( $signature_data['typed_name'] ) ) {
            $font = isset( $signature_data['signature_font'] ) ? $signature_data['signature_font'] : 'dancing-script';
            $font_family = $this->get_signature_font_family( $font );

            $style = sprintf(
                'font-family: %s; font-size: %s; color: %s; padding: 10px 15px; background-color: transparent; display: inline-block; %s',
                esc_attr( $font_family ),
                esc_attr( $options['font_size'] ),
                esc_attr( $options['typed_color'] ),
                esc_attr( $options['container_style'] )
            );

            return sprintf(
                '<div style="%s">%s</div>',
                $style,
                esc_html( $signature_data['typed_name'] )
            );
        }

        // For drawn signatures
        if ( 'drawn' === $signature_data['signature_type'] && ! empty( $signature_data['signature_data'] ) ) {
            $style = sprintf(
                'max-width: %s; height: auto; display: block; %s',
                esc_attr( $options['max_width'] ),
                esc_attr( $options['container_style'] )
            );

            return sprintf(
                '<img src="%s" alt="%s" style="%s" />',
                esc_attr( $signature_data['signature_data'] ),
                esc_attr__( 'Signature', 'ninja-forms' ),
                $style
            );
        }

        return '';
    }
    
    /**
     * Get font family CSS value
     *
     * @param string $font Font identifier
     * @return string CSS font-family value
     */
    private function get_signature_font_family( $font )
    {
        static $fonts = [
            'dancing-script' => '"Dancing Script", cursive',
            'satisfy' => '"Satisfy", cursive',
            'cursive' => 'cursive',
        ];

        return isset( $fonts[ $font ] ) ? $fonts[ $font ] : $fonts['dancing-script'];
    }

    /**
     * Get font file path for signature font
     *
     * @param string $font Font identifier
     * @return string|false Path to TTF font file or false if not found
     */
    private function get_signature_font_file( $font )
    {
        // Get the Ninja Forms plugin directory
        // __FILE__ is /includes/Fields/Signature.php, so we need to go up 2 levels
        $plugin_dir = dirname( dirname( dirname( __FILE__ ) ) );
        $font_dir = $plugin_dir . '/assets/fonts/signature/';

        $font_files = [
            'dancing-script' => 'dancing-script-400.ttf',
            'satisfy' => 'satisfy-400.ttf',
            'cursive' => 'dancing-script-400.ttf', // Fallback to Dancing Script
        ];

        $font_file = isset( $font_files[ $font ] ) ? $font_files[ $font ] : $font_files['dancing-script'];
        $full_path = $font_dir . $font_file;

        return file_exists( $full_path ) ? $full_path : false;
    }

    /**
     * Generate image from typed signature text
     *
     * Creates a PNG image with the signature text rendered in the selected font.
     * This is used for PDF display to ensure fonts render correctly without affecting
     * the rest of the PDF document.
     *
     * @param string $text The signature text to render
     * @param string $font Font identifier (dancing-script, satisfy, cursive)
     * @param array $options Image generation options (font_size, color, padding)
     * @return string|false Base64 encoded PNG data URL or false on failure
     */
    private function generate_signature_image( $text, $font = 'dancing-script', $options = [] )
    {
        // Check if GD library is available
        if ( ! function_exists( 'imagecreatetruecolor' ) ) {
            error_log( 'Ninja Forms Signature: GD library not available' );
            return false;
        }

        $defaults = [
            'font_size' => 48,      // Font size in points
            'color' => '#000000',   // Text color
            'padding' => 20,        // Padding around text
            'bg_color' => 'transparent', // Background color
        ];

        $options = array_merge( $defaults, $options );

        // Get font file path
        $font_file = $this->get_signature_font_file( $font );
        if ( ! $font_file ) {
            error_log( 'Ninja Forms Signature: Font file not found for font: ' . $font );
            return false;
        }

        // Log which font is being used
        error_log( 'Ninja Forms Signature: Generating image with font: ' . $font . ' (' . basename( $font_file ) . ')' );

        // Calculate text dimensions
        $font_size = $options['font_size'];
        $angle = 0;

        // Get bounding box for text
        $bbox = imagettfbbox( $font_size, $angle, $font_file, $text );
        if ( ! $bbox ) {
            return false;
        }

        // Calculate image dimensions
        $text_width = abs( $bbox[4] - $bbox[0] );
        $text_height = abs( $bbox[5] - $bbox[1] );
        $padding = $options['padding'];

        $img_width = $text_width + ( $padding * 2 );
        $img_height = $text_height + ( $padding * 2 );

        // Create image
        $image = imagecreatetruecolor( $img_width, $img_height );
        if ( ! $image ) {
            return false;
        }

        // Handle transparency
        imagesavealpha( $image, true );
        $transparent = imagecolorallocatealpha( $image, 0, 0, 0, 127 );
        imagefill( $image, 0, 0, $transparent );

        // Parse color
        $hex_color = ltrim( $options['color'], '#' );
        $r = hexdec( substr( $hex_color, 0, 2 ) );
        $g = hexdec( substr( $hex_color, 2, 2 ) );
        $b = hexdec( substr( $hex_color, 4, 2 ) );

        $text_color = imagecolorallocate( $image, $r, $g, $b );

        // Calculate text position (centered vertically, padded horizontally)
        $x = $padding;
        $y = $padding + $text_height;

        // Draw text
        imagettftext( $image, $font_size, $angle, $x, $y, $text_color, $font_file, $text );

        // Capture image as PNG
        ob_start();
        imagepng( $image, null, 9 ); // Max compression
        $image_data = ob_get_clean();

        // Clean up
        imagedestroy( $image );

        if ( ! $image_data ) {
            return false;
        }

        // Convert to base64 data URL
        return 'data:image/png;base64,' . base64_encode( $image_data );
    }
    
    /**
     * Generate PDF-ready signature data
     * 
     * @param array $signature_data Decoded signature JSON data
     * @return array PDF-compatible data structure
     */
    private function get_signature_pdf_data( $signature_data )
    {
        if ( ! is_array( $signature_data ) || ! isset( $signature_data['signature_type'] ) ) {
            return [
                'type' => 'empty',
                'value' => ''
            ];
        }
        
        // For typed signatures
        if ( 'typed' === $signature_data['signature_type'] && ! empty( $signature_data['typed_name'] ) ) {
            $font = isset( $signature_data['signature_font'] ) ? $signature_data['signature_font'] : 'dancing-script';
            
            return [
                'type' => 'styled_text',
                'value' => $signature_data['typed_name'],
                'font_family' => $this->get_signature_font_family( $font ),
                'font_size' => 24,
                'color' => '#000000',
                'style' => 'italic'
            ];
        }
        
        // For drawn signatures
        if ( 'drawn' === $signature_data['signature_type'] && ! empty( $signature_data['signature_data'] ) ) {
            // Try to extract just the base64 data part
            if ( preg_match( '/^data:image\/(png|jpeg);base64,(.+)/', $signature_data['signature_data'], $matches ) ) {
                return [
                    'type' => 'image',
                    'format' => $matches[1],
                    'data' => $matches[2],
                    'base64_full' => $signature_data['signature_data'],
                    'width' => 300,
                    'height' => 100,
                    'maintain_aspect_ratio' => true,
                    'alt' => 'Signature'
                ];
            }
        }
        
        return [
            'type' => 'empty',
            'value' => ''
        ];
    }

    /**
     * Format signature field display for CPT custom columns
     *
     * This method handles signature display in the WordPress CPT submissions table
     * (edit.php?post_type=nf_sub). It displays a compact signature representation.
     *
     * @param mixed $value Field value
     * @param object $field Field object
     * @param int $sub_id Submission ID
     * @return string Formatted HTML for table display
     */
    public function custom_columns( $value, $field, $sub_id = 0 )
    {
        // Only process signature fields
        if ( ! is_object( $field ) || $field->get_setting( 'type' ) !== 'signature' ) {
            return $value;
        }

        // Handle empty value
        if ( empty( $value ) || $value === '' ) {
            return '<span style="color: #999; font-style: italic;">' . esc_html__( 'Not signed', 'ninja-forms' ) . '</span>';
        }

        // Decode HTML entities first (common in WordPress contexts)
        $decoded_value = html_entity_decode( $value, ENT_QUOTES, 'UTF-8' );

        // Try to decode JSON from decoded value
        $signature_data = json_decode( $decoded_value, true );

        // If that didn't work, try the original value
        if ( ! is_array( $signature_data ) ) {
            $signature_data = json_decode( $value, true );
        }

        // Handle invalid JSON
        if ( ! is_array( $signature_data ) || ! isset( $signature_data['signature_type'] ) ) {
            return '<span style="color: #999; font-style: italic;">' . esc_html__( 'Invalid signature', 'ninja-forms' ) . '</span>';
        }

        // Display typed signature
        if ( 'typed' === $signature_data['signature_type'] && ! empty( $signature_data['typed_name'] ) ) {
            $font = isset( $signature_data['signature_font'] ) ? $signature_data['signature_font'] : 'dancing-script';
            $font_family = $this->get_signature_font_family( $font );

            $output = '<div style="display: inline-block;">';
            $output .= '<div style="font-family: ' . esc_attr( $font_family ) . '; font-size: 20px; color: #000; padding: 8px 12px; background: transparent;">';
            $output .= esc_html( $signature_data['typed_name'] );
            $output .= '</div>';
            $output .= '<div style="font-size: 11px; color: #666; margin-top: 3px;">' . esc_html__( 'Typed signature', 'ninja-forms' ) . '</div>';
            $output .= '</div>';

            return $output;
        }

        // Display drawn signature
        if ( 'drawn' === $signature_data['signature_type'] && ! empty( $signature_data['signature_data'] ) ) {
            if ( preg_match( '/^data:image\/(png|jpeg);base64,/', $signature_data['signature_data'] ) ) {
                $output = '<img src="' . esc_attr( $signature_data['signature_data'] ) . '" ';
                $output .= 'alt="' . esc_attr__( 'Signature', 'ninja-forms' ) . '" ';
                $output .= 'style="max-width: 150px; max-height: 50px; display: block;" />';
                $output .= '<div style="font-size: 11px; color: #666; margin-top: 3px;">' . esc_html__( 'Drawn signature', 'ninja-forms' ) . '</div>';

                return $output;
            }
        }

        return '<span style="color: #999; font-style: italic;">' . esc_html__( 'Invalid signature data', 'ninja-forms' ) . '</span>';
    }

}