' . __( 'Warning!', 'woocommerce-pdf-invoices-packing-slips' ) . '' . ' ' . __( 'The settings below are meant for debugging/development only. Do not use them on a live website!' , 'woocommerce-pdf-invoices-packing-slips' ) ); } /** * Custom fields section callback. * * @return void */ public function custom_fields_section(): void { echo wp_kses_post( sprintf( /* translators: %s Modern (Premium) */ __( 'These are used for the (optional) footer columns in the %s template, but can also be used for other elements in your custom template.' , 'woocommerce-pdf-invoices-packing-slips' ), 'Modern (Premium)' ) ); } /** * HTML section callback. * * @param array $args Field arguments. * @return void */ public function html_section( array $args ): void { extract( $this->normalize_settings_args( $args ) ); // output HTML echo wp_kses_post( $html ); } /** * Checkbox callback. * * args: * option_name - name of the main option * id - key of the setting * value - value if not 1 (optional) * default - default setting (optional) * description - description (optional) * custom_attributes - custom attributes (optional) * * @param array $args Field arguments. * @return void */ public function checkbox( array $args ): void { extract( $this->normalize_settings_args( $args ) ); // output checkbox printf( '', esc_attr( $id ), esc_attr( $setting_name ), esc_attr( $value ), checked( $value, $current, false ), ! empty( $disabled ) ? 'disabled="disabled"' : '', wp_kses_post( $custom_attributes ) ); if ( ! empty( $title ) ) { printf( '', esc_attr( $id ), esc_html( $title ) ); } // print store empty input if true if ( $store_unchecked ) { printf( '', esc_attr( $option_name ), esc_attr( $id ) ); } // output description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } } /** * Text input callback. * * args: * title - secondary title of the input (optional) * option_name - name of the main option * id - key of the setting * size - size of the text input (em) * default - default setting (optional) * description - description (optional) * type - type (optional) * custom_attributes - custom attributes (optional) * * @param array $args Field arguments. * @return void */ public function text_input( array $args ): void { extract( $this->normalize_settings_args( $args ) ); if ( empty( $type ) ) { $type = 'text'; } if ( ! empty( $action_button ) ) { echo '
'; } if ( ! empty( $title ) ) { printf( '', esc_attr( $id ), esc_html( $title ) ); } $size = ! empty( $size ) ? sprintf( 'size="%s"', esc_attr( $size ) ) : ''; printf( '', esc_attr( $type ), esc_attr( $id ), esc_attr( $setting_name ), esc_attr( $current ), esc_attr( $size ), esc_attr( $placeholder ), ! empty( $disabled ) ? 'disabled="disabled"' : '', wp_kses_post( $custom_attributes ) ); // Output action button. if ( ! empty( $action_button ) ) { $this->output_action_button( $action_button, $id ); echo '
'; } // output description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } } /** * URL input callback. * * args: * option_name - name of the main option * id - key of the setting * size - size of the text input (em) * default - default setting (optional) * description - description (optional) * type - type (optional) * custom_attributes - custom attributes (optional) * * @param array $args Field arguments. * @return void */ public function url_input( array $args ): void { extract( $this->normalize_settings_args( $args ) ); if ( empty( $type ) ) { $type = 'url'; } $size = ! empty( $size ) ? sprintf( 'size="%s"', esc_attr( $size ) ) : ''; printf( '', esc_attr( $type ), esc_attr( $id ), esc_attr( $setting_name ), esc_attr( $current ), esc_attr( $size ), esc_attr( $placeholder ), ! empty( $disabled ) ? 'disabled="disabled"' : '', wp_kses_post( $custom_attributes ) ); // output description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } } /** * Email input callback. * * args: * option_name - name of the main option * id - key of the setting * size - size of the text input (em) * default - default setting (optional) * description - description (optional) * type - type (optional) * custom_attributes - custom attributes (optional) * * @param array $args Field arguments. * @return void */ public function email_input( array $args ): void { extract( $this->normalize_settings_args( $args ) ); if ( empty( $type ) ) { $type = 'email'; } $size = ! empty( $size ) ? sprintf( 'size="%s"', esc_attr( $size ) ) : ''; printf( '', esc_attr( $type ), esc_attr( $id ), esc_attr( $setting_name ), esc_attr( sanitize_email( $current ) ), esc_attr( $size ), esc_attr( $placeholder ), ! empty( $disabled ) ? 'disabled="disabled"' : '', wp_kses_post( $custom_attributes ) ); // output description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } } /** * Combined checkbox & text input callback. * * args: * option_name - name of the main option * id - key of the setting * value - value if not 1 (optional) * default - default setting (optional) * description - description (optional) * * @param array $args Field arguments. * @return void */ public function checkbox_text_input( array $args ): void { $args = $this->normalize_settings_args( $args ); extract( $args ); unset( $args['description'] ); // already extracted, should only be used here // get checkbox ob_start(); $this->checkbox( $args ); $checkbox = ob_get_clean(); // get text input for insertion in wrapper $input_args = array( 'id' => $args['text_input_id'], 'default' => isset( $args['text_input_default'] ) ? (string) $args['text_input_default'] : null, 'size' => isset( $args['text_input_size'] ) ? $args['text_input_size'] : null, ) + $args; unset( $input_args['current'] ); unset( $input_args['setting_name'] ); ob_start(); $this->text_input( $input_args ); $text_input = ob_get_clean(); $allowed_html = array( 'input' => array( 'type' => true, 'name' => true, 'id' => true, 'value' => true, 'class' => true, 'placeholder' => true, 'disabled' => true, 'checked' => true, 'size' => true, ), ); if ( ! empty( $text_input_wrap ) ) { $text_input = sprintf( $text_input_wrap, $text_input ); } echo wp_kses( "{$checkbox} {$text_input}", $allowed_html ); // output description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } } /** * Single text option (not part of any settings array) * * args: * option_name - name of the main option * id - key of the setting * default - default setting (optional) * description - description (optional) * custom_attributes - custom attributes (optional) * * @param array $args Field arguments. * @return void */ public function singular_text_element( array $args ): void { $args = $this->normalize_settings_args( $args ); extract( $args ); $size = $size ?? '25'; $class = isset( $translatable ) && true === $translatable ? 'translatable' : ''; $option = get_option( $option_name ?? '' ); if ( ! empty( $option ) ) { $current = $option; } else { $current = $default ?? ''; } printf( '', esc_attr( $id ), esc_attr( $option_name ), esc_attr( $current ), esc_attr( $size ), esc_attr( $class ), wp_kses_post( $custom_attributes ) ); // output description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } } /** * Textarea callback. * * args: * title - secondary title of the input (optional) * option_name - name of the main option * id - key of the setting * width - width of the text input (em) * height - height of the text input (lines) * default - default setting (optional) * description - description (optional) * custom_attributes - custom attributes (optional) * * @param array $args Field arguments. * @return void */ public function textarea( array $args ): void { extract( $this->normalize_settings_args( $args ) ); printf( '', esc_attr( $id ), esc_attr( $setting_name ), esc_textarea( $current ), esc_attr( $width ), esc_attr( $height ), esc_attr( $placeholder ), wp_kses_post( $custom_attributes ) ); // output description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } } /** * Select element callback. * * args: * title - secondary title of the input (optional) * setting_name - name of the main setting * id - key of the setting * multiple - whether the select is multiple (optional) * options - array of options for the select * current - current value(s) of the setting * disabled - whether the select is disabled (optional) * custom_attributes - custom attributes (optional) * * @param array $args Field arguments. * @return void */ public function select( array $args ): void { extract( $this->normalize_settings_args( $args ) ); if ( ! empty( $action_button ) ) { echo '
'; } if ( ! empty( $title ) ) { printf( '', esc_attr( $id ), esc_html( $title ) ); } if ( ! empty( $enhanced_select ) ) { if ( ! empty( $multiple ) ) { $setting_name = "{$setting_name}[]"; $multiple = 'multiple=multiple'; } else { $multiple = ''; } $placeholder = ! empty( $placeholder ) ? esc_attr( $placeholder ) : ''; $title = ! empty( $title ) ? esc_attr( $title ) : ''; $class = 'wc-enhanced-select wpo-wcpdf-enhanced-select'; $css = 'width:400px'; printf( '', esc_attr( $id ), esc_attr( $setting_name ), ! empty( $disabled ) ? 'disabled="disabled"' : '', wp_kses_post( $custom_attributes ) ); } if ( ! empty( $options_callback ) ) { $options = isset( $options_callback_args ) ? call_user_func_array( $options_callback, $options_callback_args ) : call_user_func( $options_callback ); } foreach ( $options as $key => $label ) { // Normalize both sides to string $key_str = (string) $key; // Determine if selected (works for both single and multiple) if ( ! empty( $multiple ) && is_array( $current ) ) { $is_selected = in_array( $key_str, array_map( 'strval', $current ), true ); $selected_attr = $is_selected ? ' selected="selected"' : ''; } else { $selected_attr = selected( (string) $current, $key_str, false ); } printf( '', esc_attr( $key_str ), esc_attr( $selected_attr ), esc_html( $label ) ); } echo ''; // Output action button. if ( ! empty( $action_button ) ) { $this->output_action_button( $action_button, $id ); echo '
'; } // output description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } if ( ! empty( $custom ) ) { printf( '
', esc_attr( $id ) ); if ( is_callable( array( $this, $custom['type'] ) ) ) { $this->{$custom['type']}( $custom['args'] ); } echo '
'; $custom_option = ! empty( $custom['custom_option'] ) ? $custom['custom_option'] : 'custom'; ?> normalize_settings_args( $args ) ); if ( ! empty( $options_callback ) ) { $options = isset( $options_callback_args ) ? call_user_func_array( $options_callback, $options_callback_args ) : call_user_func( $options_callback ); } foreach ( $options as $key => $label ) { printf( '', esc_attr( $id ), esc_attr( $setting_name ), esc_attr( $key ), checked( $current, $key, false ), wp_kses_post( $custom_attributes ) ); printf( '
', esc_attr( $id ), esc_attr( $key ), esc_html( $label ) ); } // output description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } } /** * Multiple text element callback. * * args: * id - key of the setting * setting_name - name of the main setting * fields_callback - callback function to get the fields * fields_callback_args - arguments for the fields callback (optional) * current - current values of the setting * header - header for the table (optional) * description - description for the table (optional) * custom_attributes - custom attributes for the input fields (optional) * * @param array $args Field arguments. * @return void */ public function multiple_text_input( array $args ): void { extract( $this->normalize_settings_args( $args ) ); if ( ! empty( $fields_callback ) ) { $fields = isset( $fields_callback_args ) ? call_user_func_array( $fields_callback, $fields_callback_args ) : call_user_func( $fields_callback ); } printf( '', esc_attr( $id ) ); if ( ! empty( $header ) ) { echo wp_kses_post( "" ); } foreach ( $fields as $name => $field ) { echo ''; $size = $field['size']; $placeholder = isset( $field['placeholder'] ) ? $field['placeholder'] : ''; $field_description = ! empty( $field['description'] ) ? $field['description']: ''; // output field label if ( isset( $field['label'] ) ) { printf( '', esc_attr( $id ), esc_attr( $name ), esc_html( $field['label'] ) ); } else { echo ''; } $field_current = isset( $current[ $name ] ) ? $current[ $name ] : ''; $type = isset( $field['type'] ) ? $field['type'] : 'text'; // output field printf( '', esc_attr( $type ), esc_attr( $id ), esc_attr( $setting_name ), esc_attr( $name ), esc_attr( $field_current ), esc_attr( $size ), esc_attr( $placeholder ), wp_kses_post( $custom_attributes ) ); // field description. if ( ! empty( $field_description ) ) { echo ''; } else { echo ''; } echo ''; } echo "
{$header}:
' . wp_kses_post( wc_help_tip( $field_description, true ) ) . '
"; // group description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } } /** * Multiple text element callback. * * args: * id - key of the setting * setting_name - name of the main setting * fields_callback - callback function to get the fields * fields_callback_args - arguments for the fields callback (optional) * current - current values of the setting * header - header for the table (optional) * description - description for the table (optional) * custom_attributes - custom attributes for the input fields (optional) * * @param array $args Field arguments. * @return void */ public function multiple_checkboxes( array $args ): void { extract( $this->normalize_settings_args( $args ) ); if ( ! empty( $fields_callback ) ) { $fields = isset( $fields_callback_args ) ? call_user_func_array( $fields_callback, $fields_callback_args ) : call_user_func( $fields_callback ); } foreach ( $fields as $name => $label ) { $field_current = isset( $current[ $name ] ) ? $current[ $name ] : ''; // output checkbox printf( '', esc_attr( $id ), esc_attr( $setting_name ), esc_attr( $name ), esc_attr( $value ), checked( $value, $field_current, false ), wp_kses_post( $custom_attributes ) ); // output field label printf( '
', esc_attr( $id ), esc_attr( $name ), esc_html( $label ) ); } // output description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } } /** * Media upload callback. * * args: * id - key of the setting * setting_name - name of the main setting * current - current value of the setting * uploader_title - title of the media uploader * uploader_button_text - text of the media uploader button * remove_button_text - text of the remove button * description - description for the media upload field * custom_attributes - custom attributes for the input field * * @param array $args Field arguments. * @return void */ public function media_upload( array $args ): void { extract( $this->normalize_settings_args( $args ) ); $setting_name = $this->append_language( $setting_name, $args ); $attachment = ! empty( $current ) ? wp_get_attachment_image_src( $current, 'full', false ) : ''; if ( ! empty( $attachment ) ) { $general_settings = get_option( 'wpo_wcpdf_settings_general', array() ); $attachment_src = $attachment[0]; $attachment_width = $attachment[1]; $attachment_height = $attachment[2]; // check if we have the height saved on settings $header_logo_height = ! empty( $general_settings['header_logo_height'] ) ? $general_settings['header_logo_height'] : '3cm'; if ( false !== stripos( $header_logo_height, 'mm' ) ) { $in_height = floatval( $header_logo_height ) / 25.4; } elseif ( false !== stripos( $header_logo_height, 'cm' ) ) { $in_height = floatval( $header_logo_height ) / 2.54; } elseif ( false !== stripos( $header_logo_height, 'in' ) ) { $in_height = floatval( $header_logo_height ); } else { // don't display resolution } /** * .webp support can be disabled but still showing the image in settings. * We should add a notice because this will display an error when redering the PDF using DOMPDF. */ if ( 'webp' === wp_check_filetype( $attachment_src )['ext'] && ! function_exists( 'imagecreatefromwebp' ) ) { printf( '

%s

', wp_kses_post( /* translators: %1$s: file type, %2$s: System Configurations, %3$s: Advanced */ __( 'File type %1$s is not supported by your server! Please check your %2$s under the %3$s tab.', 'woocommerce-pdf-invoices-packing-slips' ), 'webp', '' . __( 'System Configurations', 'woocommerce-pdf-invoices-packing-slips' ) . '', '' . __( 'Advanced', 'woocommerce-pdf-invoices-packing-slips' ) . '' ) ); } printf( '', esc_attr( $attachment_src ), esc_attr( $id ) ); if ( ! empty( $attachment_height ) && ! empty( $in_height ) ) { $attachment_resolution = round( absint( $attachment_height ) / $in_height ); printf( '

%s: %sdpi

', esc_html__( 'Image resolution', 'woocommerce-pdf-invoices-packing-slips' ), esc_html( $attachment_resolution ) ); // warn the user if the image is unnecessarily large if ( $attachment_resolution > 600 ) { printf( '

%s

', esc_html__( 'The image resolution exceeds the recommended maximum of 600dpi. This will unnecessarily increase the size of your PDF files and could negatively affect performance.', 'woocommerce-pdf-invoices-packing-slips' ) ); } } printf( '%2$s ', esc_attr( $id ), esc_attr( $remove_button_text ) ); } printf( '', esc_attr( $id ), esc_attr( $setting_name ), esc_attr( $current ), esc_attr( wp_json_encode( $args ) ), esc_attr( wp_create_nonce( 'wpo_wcpdf_get_media_upload_setting_html' ) ) ); printf( '%2$s', esc_attr( $uploader_title ), esc_attr( $uploader_button_text ), esc_attr( $remove_button_text ), esc_attr( $id ) ); // Displays option description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } } /** * Next document number edit callback. * * args: * store - name of the store (e.g. 'invoice', 'packing_slip') * size - size of the input field (optional) * description - description of the field (optional) * store_callback - callback function to get the store (optional) * store_callback_args - arguments for the store callback (optional) * * @param array $args Field arguments. * @return void */ public function next_number_edit( array $args ): void { extract( $args ); // $store, $size, $description if ( ! empty( $store_callback ) ) { $store = isset( $store_callback_args ) ? call_user_func_array( $store_callback, $store_callback_args ) : call_user_func( $store_callback ); } // SequentialNumberStore object if ( is_object( $store ) ) { $next_number = $store->get_next(); $store = $store->store_name; // legacy } else { $number_store_method = WPO_WCPDF()->settings->get_sequential_number_store_method(); $number_store = new SequentialNumberStore( $store, $number_store_method ); $next_number = $number_store->get_next(); } $nonce = wp_create_nonce( "wpo_wcpdf_next_{$store}" ); printf( ' ', esc_attr( $store ), esc_attr( $size ), esc_attr( $next_number ), esc_attr( $nonce ), esc_html__( 'Save', 'woocommerce-pdf-invoices-packing-slips' ) ); // Displays option description. if ( ! empty( $description ) ) { printf( '

%s

', wp_kses_post( $description ) ); } } /** * Wrapper function to create tabs for settings in different languages * * args: * option_name - name of the main option * id - key of the setting * callback - callback function to render the fields * fields - array of fields to render (optional, used for multiple_text_input) * i18n_description - description for the internationalized fields * * @param array $args * * @return void */ public function i18n_wrap( array $args ): void { extract( $this->normalize_settings_args( $args ) ); $languages = wpo_wcpdf_get_multilingual_languages(); if ( ! empty( $languages ) ) { printf( '
', esc_attr( $option_name ), esc_attr( $id ) ); ?> $language_name ) { $translation_id = "{$option_name}_{$id}_{$lang_code}"; printf( '
', esc_attr( $translation_id ) ); $args['lang'] = $lang_code; // don't use internationalized placeholders since they're not translated, // to avoid confusion (user thinking they're all the same) if ( 'multiple_text_input' === $callback ) { foreach ( $fields as $key => $field_args ) { if ( ! empty( $field_args['placeholder'] ) && isset( $field_args['i18n_placeholder'] ) ) { $args['fields'][$key]['placeholder'] = ''; } } } else { if ( ! empty( $args['placeholder'] ) && isset( $args['i18n_placeholder'] ) ) { $args['placeholder'] = ''; } } // specific description for internationalized fields (to compensate for missing placeholder) if ( ! empty( $args['i18n_description'] ) ) { $args['description'] = $args['i18n_description']; } if ( is_array( $callback ) ) { call_user_func( $callback, $args ); } else { call_user_func( array( $this, $callback ), $args ); } echo '
'; } ?>
normalize_custom_attributes( $args ); return $args; } /** * Normalize custom attributes. * * @param array $args Field arguments. * @return string */ public function normalize_custom_attributes( array $args ): string { $custom_attributes = array(); if ( ! empty( $args['custom_attributes'] ) && is_array( $args['custom_attributes'] ) ) { foreach ( $args['custom_attributes'] as $attribute => $attribute_value ) { $custom_attributes[] = esc_attr( $attribute ) . '="' . esc_attr( $attribute_value ) . '"'; } } return ! empty( $custom_attributes ) ? implode( ' ', $custom_attributes ) : ''; } /** * Validate options. * * @param array|false|null $input Options to validate. * @return array Validated options. */ public function validate( $input ): array { $output = array(); // Create our array for storing the validated options. if ( ! empty( $input ) && is_array( $input ) ) { if ( ! empty( $input['wpo_wcpdf_setting_store_empty'] ) && is_array( $input['wpo_wcpdf_setting_store_empty'] ) ) { foreach ( $input['wpo_wcpdf_setting_store_empty'] as $key ) { if ( empty( $input[ $key ] ) ) { $output[ $key ] = 0; } } unset( $input['wpo_wcpdf_setting_store_empty'] ); } // Loop through each of the incoming options. foreach ( $input as $key => $value ) { if ( is_array( $value ) ) { foreach ( $value as $sub_key => $sub_value ) { $output[ $key ][ $sub_key ] = $sub_value; } } else { // Normalize identifiers like VAT / CoC on save. if ( in_array( $key, array( 'vat_number', 'coc_number' ), true ) ) { $value = $this->normalize_identifier( (string) $value ); } $output[ $key ] = $value; } } } // Return the array processing any additional functions filtered by this action. return apply_filters( 'wpo_wcpdf_validate_input', $output, $input ); } /** * Appends language at the end of the setting provided, in case the setting is translatable * and it does not have a language set. * * @param string $setting Settings field that needs a language. * @param array $args Setting arguments. * * @return string */ public function append_language( string $setting, array $args ): string { if ( isset( $args['translatable'] ) && true === $args['translatable'] && isset( $args['lang'] ) && 'default' !== $args['lang'] && ! ( substr( $setting, -strlen( "[{$args['lang']}]" ) ) === "[{$args['lang']}]" ) ) { return $setting .= "[{$args['lang']}]"; } else { return $setting; } } /** * Output the action button. * * @param array $action_button * @param string $id * * @return void */ private function output_action_button( array $action_button, string $id ): void { printf( '', ! empty( $action_button['class'] ) ? sprintf( 'class="%s"', esc_attr( $action_button['class'] ) ) : '', sprintf( 'id="%s"', esc_attr( $action_button['id'] ?? esc_attr( $id ) ) . '_action' ), ! empty( $action_button['title'] ) ? sprintf( 'title="%s"', esc_attr( $action_button['title'] ) ) : '', esc_html( $action_button['text'] ), ! empty( $action_button['icon'] ) ? sprintf( '', esc_attr( $action_button['icon'] ) ) : '' ); } /** * Normalize identifier-like values (VAT / CoC). * * @param string $value * @return string */ protected function normalize_identifier( string $value ): string { $value = wp_strip_all_tags( $value ); $value = trim( $value ); // Uppercase for consistency (VAT formats often expect it). $value = strtoupper( $value ); // Keep only A-Z and 0-9, strip spaces, dots, dashes, etc. $value = preg_replace( '/[^A-Z0-9]/', '', $value ); return $value; } } endif; // class_exists