AlkantarClanX12
Your IP : 216.73.217.24
<?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, '"' ) !== false || strpos( $value, '&' ) !== 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'];
// imagettfbbox() excludes descenders (letters like y, g, j below the baseline).
// Add an allowance so cursive script fonts render without clipping.
$descender_allowance = (int) ceil( $font_size * 0.30 );
$img_width = $text_width + ( $padding * 2 );
$img_height = $text_height + ( $padding * 2 ) + $descender_allowance;
// 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>';
}
}
Home - Capital GMC Buick Regina
Skip to content
{{ $t(category) }}
Error
{{vehicle.modelData.year}} {{vehicle.modelData.make}} {{vehicle.modelData.model}}
Starting from {{vehicle.modelData.startingPrice | moneyFormat(lang)}}
Welcome to Capital GMC BUICK – REGINA
Thank you for choosing Capital GMC Buick | Regina, your premier certified Buick and GMC dealership proudly serving drivers in Regina and the surrounding communities. Whether you’re searching for a brand-new Buick or GMC vehicle or a meticulously inspected pre-owned model, we have a diverse selection to match your needs and lifestyle.
Beyond our impressive inventory, we offer a seamless and stress-free financing experience through our well-connected finance centre, where our team of experts is dedicated to securing the best loan or lease options for you, quickly, transparently, and hassle-free.
But our commitment to you doesn’t stop at the sale. Our state-of-the-art service centre is staffed with skilled Buick and GMC technicians who use the latest equipment and genuine OEM parts to keep your vehicle running at its best. From routine maintenance to complex repairs, we’ve got you covered.
Experience top-tier customer service, quality vehicles, and expert care, all in one place. Visit Capital GMC Buick | Regina today or call us at 306-205-8072 with any questions. We’re here to help!
Ask a Question
Capital GMC Buick – Regina
Contact Us
Have a question or need assistance? Fill out the form and we will reach out to you as soon as possible.
Notice: JavaScript is required for this content.
CLOSE
Schedule a Visit
Let us know when you are coming and how we can assist you. We can ensure someone will be on hand to help you out at the desired date and time.
Notice: JavaScript is required for this content.
CLOSE
Find a Career
Have a look at our list of available positions and apply online today to join our team!
×
Opportunities to Grow
The auto industry is constantly changing and we want to continue to grow. We offer growth, leadership & mentorship programs to allow our staff to grow with us.
×
Competitive Salary
We have a significant earning potential with incentive-based pay in most roles. We also offer an employee referral bonus with paid bonuses.
×
Health & Dental
We offer a comprehensive benefits package including extended health, dental, and vision care. We also include paramedical, life insurance, paid sick leave, short & long-term disability coverages.
×
Vacation
We value our employees and want everyone to take their vacation time. We offer a minimum of 2 weeks vacation each year.
×
Training & Development
We have many opportunities for paid education and training in-house as well as training from the Manufacturer.
×
$10,000 Cash Giveaway – Terms & Conditions
All October long, stop by Capital GMC Buick Cadillac, to enter for your chance to win $10,000 cash. No purchase is required, but entries must be made in-store.
The contest is open to residents of Saskatchewan who are 18+. Dealership employees and their households are not eligible. Entries will be accepted from October 1 to October 31, 2025. A random draw will take place on November 1, 2025.
The prize is one $10,000 award, paid by cheque, and must be accepted as awarded. Winner will be contacted by phone or email and must respond within 7 days or another entry may be drawn. Odds of winning depend on the number of entries received.By entering, you agree that Capital Automotive Group may use your name and photo for winner announcements. The contest is governed by the laws of Saskatchewan.
Notice: JavaScript is required for this content.
CLOSE