Files
2026-04-28 15:13:50 +02:00

987 lines
28 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/*
|--------------------------------------------------------------------------
| EDI Document global functions
|--------------------------------------------------------------------------
*/
/**
* Sanitizes a string for use in EDI documents by stripping all HTML tags and decoding HTML entities to plain text.
*
* @param string $string
*
* @return string
*/
function wpo_ips_edi_sanitize_string( string $string ): string {
$string = wp_strip_all_tags( $string );
return htmlspecialchars_decode( $string, ENT_QUOTES );
}
/**
* Get EDI tax data from fallback
*
* @param string $key Can be category, scheme, or reason
* @param int|null $rate_id The tax rate ID
* @param \WC_Abstract_Order|null $order The order object
* @return string
*/
function wpo_ips_edi_get_tax_data_from_fallback( string $key, ?int $rate_id, ?\WC_Abstract_Order $order ): string {
$result = '';
if ( ! in_array( $key, array( 'category', 'scheme', 'reason' ) ) ) {
return $result;
}
$tax_rate_class = '';
$edi_tax_settings = wpo_ips_edi_get_tax_settings();
if ( ! is_null( $rate_id ) && class_exists( '\WC_TAX' ) && is_callable( array( '\WC_TAX', '_get_tax_rate' ) ) ) {
$tax_rate = \WC_Tax::_get_tax_rate( $rate_id, OBJECT );
if ( ! empty( $tax_rate ) && is_numeric( $tax_rate->tax_rate ) ) {
$result = isset( $edi_tax_settings['rate'][ $tax_rate->tax_rate_id ][ $key ] ) ? $edi_tax_settings['rate'][ $tax_rate->tax_rate_id ][ $key ] : '';
$tax_rate_class = $tax_rate->tax_rate_class;
}
}
if ( empty( $tax_rate_class ) ) {
$tax_rate_class = 'standard';
}
if ( empty( $result ) || 'default' === $result ) {
$result = isset( $edi_tax_settings['class'][ $tax_rate_class ][ $key ] ) ? $edi_tax_settings['class'][ $tax_rate_class ][ $key ] : '';
}
// check if order is tax exempt
if ( wpo_wcpdf_order_is_vat_exempt( $order ) ) {
switch ( $key ) {
case 'scheme':
$result = 'VAT';
break;
case 'category':
$result = 'AE';
break;
case 'reason':
$result = 'VATEX-EU-AE';
break;
}
$result = apply_filters( 'wpo_ips_edi_get_tax_data_from_fallback_vat_exempt', $result, $key, $rate_id, $order );
}
return $result;
}
/**
* Save EDI order taxes
*
* @param \WC_Abstract_Order $order
* @return void
*/
function wpo_ips_edi_save_order_taxes( \WC_Abstract_Order $order ): void {
foreach ( $order->get_taxes() as $item_id => $tax_item ) {
if ( is_a( $tax_item, '\WC_Order_Item_Tax' ) && is_callable( array( $tax_item, 'get_rate_id' ) ) ) {
// get tax rate id from item
$tax_rate_id = $tax_item->get_rate_id();
// read tax rate data from db
if ( class_exists( '\WC_TAX' ) && is_callable( array( '\WC_TAX', '_get_tax_rate' ) ) ) {
$tax_rate = \WC_Tax::_get_tax_rate( $tax_rate_id, OBJECT );
if ( ! empty( $tax_rate ) && is_numeric( $tax_rate->tax_rate ) ) {
// store percentage in tax item meta
wc_update_order_item_meta( $item_id, '_wcpdf_rate_percentage', $tax_rate->tax_rate );
$edi_tax_settings = wpo_ips_edi_get_tax_settings();
$tax_fields = array( 'category', 'scheme', 'reason' );
foreach ( $tax_fields as $field ) {
$value = isset( $edi_tax_settings['rate'][ $tax_rate->tax_rate_id ][ $field ] ) ? $edi_tax_settings['rate'][ $tax_rate->tax_rate_id ][ $field ] : '';
if ( empty( $value ) || 'default' === $value ) {
$value = wpo_ips_edi_get_tax_data_from_fallback( $field, $tax_rate_id, $order );
}
wc_update_order_item_meta( $item_id, '_wpo_ips_edi_tax_' . $field, $value );
}
}
}
}
}
}
/**
* Maybe save EDI order Peppol data.
*
* @param \WC_Abstract_Order $order
* @param array $data
* @return void
*/
function wpo_ips_edi_maybe_save_order_peppol_data( \WC_Abstract_Order $order, array $data = array() ): void {
if ( ! wpo_ips_edi_peppol_is_available() ) {
return; // only save for Peppol formats
}
$identifier = '';
$scheme = '';
$save_meta_data = false;
if ( ! empty( $data ) ) {
$mode = wpo_ips_edi_peppol_identifier_input_mode();
$raw = trim( sanitize_text_field( wp_unslash( $data['peppol_endpoint_id'] ?? '' ) ) );
$scheme = $identifier = '';
// Determine parts
if ( 'full' === $mode && false !== strpos( $raw, ':' ) ) {
[ $scheme, $identifier ] = array_map(
'trim',
explode( ':', $raw, 2 ) + array( '', '' )
);
// Select mode, plain identifier
} else {
$identifier = $raw;
}
}
if ( empty( $identifier ) || empty( $scheme ) ) {
$customer_id = is_callable( array( $order, 'get_customer_id' ) )
? $order->get_customer_id()
: 0;
if ( $customer_id <= 0 ) {
return;
}
$identifier = get_user_meta( $customer_id, 'peppol_endpoint_id', true );
$scheme = get_user_meta( $customer_id, 'peppol_endpoint_eas', true );
}
if ( ! empty( $identifier ) ) {
$order->update_meta_data( '_peppol_endpoint_id', $identifier );
$save_meta_data = true;
}
if ( ! empty( $scheme ) ) {
$order->update_meta_data( '_peppol_endpoint_eas', $scheme );
$save_meta_data = true;
}
if ( ! $save_meta_data ) {
return;
}
$order->save_meta_data();
}
/**
* Get EDI Maker
* Use `wpo_ips_edi_maker` filter to change the EDI class (which can wrap another EDI library).
*
* @return WPO\IPS\Makers\EDIMaker|null
*/
function wpo_ips_edi_get_maker(): ?WPO\IPS\Makers\EDIMaker {
$class = '\\WPO\\IPS\\Makers\\EDIMaker';
if ( ! class_exists( $class ) ) {
include_once WPO_WCPDF()->plugin_path() . '/includes/Makers/EDIMaker.php';
}
$class = apply_filters( 'wpo_ips_edi_maker', $class );
if ( ! class_exists( $class ) ) {
wcpdf_error_handling( 'EDI Maker class not found: ' . $class );
return null;
}
return new $class();
}
/**
* Get EDI settings
*
* @param string|null $key
* @return array|string|null
*/
function wpo_ips_edi_get_settings( ?string $key = null ) {
$settings = get_option( 'wpo_ips_edi_settings', array() );
return $key ? ( $settings[ $key ] ?? null ) : $settings;
}
/**
* Get EDI Tax settings
*
* @return array
*/
function wpo_ips_edi_get_tax_settings(): array {
return get_option( 'wpo_ips_edi_tax_settings', array() );
}
/**
* Check if EDI is available
*
* @return bool
*/
function wpo_ips_edi_is_available(): bool {
// Check `sabre/xml` library here: https://packagist.org/packages/sabre/xml
return apply_filters( 'wpo_ips_edi_is_available', WPO_WCPDF()->is_dependency_version_supported( 'php' ) && ! empty( wpo_ips_edi_get_settings( 'enabled' ) ) );
}
/**
* Check if EDI Peppol is available
*
* @return bool
*/
function wpo_ips_edi_peppol_is_available(): bool {
$format = wpo_ips_edi_get_current_format();
return apply_filters( 'wpo_ips_edi_peppol_is_available', wpo_ips_edi_is_available() && ! empty( $format ) && false !== strpos( $format, 'peppol' ) );
}
/**
* Write EDI file
*
* @param \WPO\IPS\Documents\OrderDocument $document
* @param bool $attachment
* @param bool $contents_only
*
* @return string|false
*/
function wpo_ips_edi_write_file( \WPO\IPS\Documents\OrderDocument $document, bool $attachment = false, bool $contents_only = false ) {
$edi_maker = wpo_ips_edi_get_maker();
if ( ! $edi_maker ) {
return wcpdf_error_handling( 'EDI Maker not available. Cannot write EDI file.' );
}
if ( $attachment ) {
$tmp_path = WPO_WCPDF()->main->get_tmp_path( 'attachments' );
if ( ! $tmp_path ) {
return wcpdf_error_handling( 'Temporary path not available. Cannot write EDI file.' );
}
$edi_maker->set_file_path( $tmp_path );
}
$format = wpo_ips_edi_get_current_format();
if ( empty( $format ) ) {
return wcpdf_error_handling( 'EDI format not set. Cannot write EDI file.' );
}
$syntax = wpo_ips_edi_get_current_syntax();
if ( empty( $syntax ) ) {
return wcpdf_error_handling( 'EDI syntax not set. Cannot write EDI file.' );
}
$edi_document = new \WPO\IPS\EDI\Document( $syntax, $format, $document );
$builder = new \WPO\IPS\EDI\SabreBuilder();
$contents = apply_filters( 'wpo_ips_edi_contents',
$builder->build( $edi_document ),
$edi_document,
$document
);
if ( empty( $contents ) ) {
return wcpdf_error_handling( 'Failed to build EDI contents.' );
}
if ( $contents_only ) {
return $contents;
}
$filename = apply_filters( 'wpo_ips_edi_filename',
$document->get_filename(
'download',
array( 'output' => 'xml' )
),
$document
);
return $edi_maker->write( $filename, $contents );
}
/**
* EDI file headers
*
* @param string $filename
* @param int|false $size
* @return void
*/
function wpo_ips_edi_file_headers( string $filename, $size ): void {
$charset = apply_filters( 'wpo_ips_edi_file_header_content_type_charset', 'UTF-8' );
header( 'Content-Description: File Transfer' );
header( 'Content-Type: application/xml; charset=' . $charset );
header( 'Content-Disposition: attachment; filename=' . $filename );
header( 'Content-Transfer-Encoding: binary' );
header( 'Expires: 0' );
header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' );
header( 'Pragma: public' );
header( 'Content-Length: ' . $size );
do_action( 'wpo_ips_edi_after_headers', $filename, $size );
}
/**
* Get the current EDI syntax
*
* @return string|null
*/
function wpo_ips_edi_get_current_syntax(): ?string {
$syntax = wpo_ips_edi_get_settings( 'syntax' );
return apply_filters( 'wpo_ips_edi_current_syntax', $syntax ?: null );
}
/**
* Get the current EDI format
*
* @param bool $full_details Optional. If true, returns full format details.
* @return string|array|null
*/
function wpo_ips_edi_get_current_format( bool $full_details = false ) {
$syntax = wpo_ips_edi_get_settings( 'syntax' );
$format = null;
if ( ! empty( $syntax ) ) {
$format = wpo_ips_edi_get_settings( "{$syntax}_format" );
if ( ! empty( $format ) && $full_details ) {
$format = wpo_ips_edi_syntax_formats( $syntax, $format );
}
}
return apply_filters( 'wpo_ips_edi_current_format', $format ?: null, $syntax, $full_details );
}
/**
* Check if EDI attachments should be sent
*
* @return bool
*/
function wpo_ips_edi_send_attachments(): bool {
return ! empty( wpo_ips_edi_get_settings( 'send_attachments' ) );
}
/**
* Check if EDI encrypted PDF should be embedded
*
* @return bool
*/
function wpo_ips_edi_embed_encrypted_pdf(): bool {
return apply_filters( 'wpo_ips_edi_embed_encrypted_pdf', ! empty( wpo_ips_edi_get_settings( 'embed_encrypted_pdf' ) ) );
}
/**
* Check if EDI item meta should be included
*
* @return bool
*/
function wpo_ips_edi_include_item_meta(): bool {
return apply_filters( 'wpo_ips_edi_include_item_meta', ! empty( wpo_ips_edi_get_settings( 'include_item_meta' ) ) );
}
/**
* Check if EDI preview is enabled
*
* @return bool
*/
function wpo_ips_edi_preview_is_enabled(): bool {
return ! empty( wpo_ips_edi_get_settings( 'enabled_preview' ) );
}
/**
* Get the list of syntaxes (slug ⇒ humanreadable name).
*
* @return array
*/
function wpo_ips_edi_syntaxes(): array {
$syntaxes = array();
foreach ( wpo_ips_edi_syntax_formats() as $slug => $data ) {
$syntaxes[ $slug ] = $data['name'];
}
return apply_filters( 'wpo_ips_edi_syntaxes', $syntaxes );
}
/**
* Get the complete "syntax → formats" map, or a slice of it.
*
* `wpo_ips_edi_syntax_formats()` → everything
* `wpo_ips_edi_syntax_formats( 'ubl' )` → only the UBL block
* `wpo_ips_edi_syntax_formats( 'ubl', 'ubl-2p1' )` → just that format
*
* @param string $syntax Optional. Syntax key (e.g. 'ubl', 'cii').
* @param string $format Optional. Format key (e.g. 'ubl2p1').
*
* @return array
*/
function wpo_ips_edi_syntax_formats( string $syntax = '', string $format = '' ): array {
$map = apply_filters(
'wpo_ips_edi_syntax_formats',
array(
'ubl' => array(
'name' => 'Universal Business Language (UBL)',
'formats' => array(
'ubl-2p1' => array(
'name' => 'UBL 2.1',
'hybrid' => false,
'documents' => array(
'invoice' => \WPO\IPS\EDI\Syntaxes\Ubl\Formats\Ubl2p1\Invoice::class,
),
),
'peppol-bis-3p0' => array(
'name' => 'PEPPOL BIS 3.0',
'hybrid' => false,
'documents' => array(
'invoice' => \WPO\IPS\EDI\Syntaxes\Ubl\Formats\PeppolBis3p0\Invoice::class,
),
),
),
),
'cii' => array(
'name' => 'Cross Industry Invoice (CII)',
'formats' => array(
'cii-d16b' => array(
'name' => 'CII D16B',
'hybrid' => false,
'documents' => array(
'invoice' => \WPO\IPS\EDI\Syntaxes\Cii\Formats\CiiD16B\Invoice::class,
),
),
'factur-x-1p0' => array(
'name' => 'Factur-X 1.0',
'hybrid' => true,
'documents' => array(
'invoice' => \WPO\IPS\EDI\Syntaxes\Cii\Formats\FacturX1p0\Invoice::class,
),
),
'zugferd-1p0' => array(
'name' => 'ZUGFeRD 1.0',
'hybrid' => true,
'documents' => array(
'invoice' => \WPO\IPS\EDI\Syntaxes\Cii\Formats\Zugferd1p0\Invoice::class,
),
),
'zugferd-2p0' => array(
'name' => 'ZUGFeRD 2.0',
'hybrid' => true,
'documents' => array(
'invoice' => \WPO\IPS\EDI\Syntaxes\Cii\Formats\Zugferd2p0\Invoice::class,
),
),
),
),
)
);
// Slicing
if ( '' === $syntax ) {
return $map;
}
if ( ! isset( $map[ $syntax ] ) ) {
return array();
}
if ( '' === $format ) {
return $map[ $syntax ];
}
return $map[ $syntax ]['formats'][ $format ] ?? array();
}
/**
* Log EDI messages
*
* @param string $message The log message.
* @param string $level The log level (default: 'info').
* @param \Throwable|null $e Optional. Exception to log.
* @return void
*/
function wpo_ips_edi_log( string $message, string $level = 'info', ?\Throwable $e = null ): void {
if ( empty( wpo_ips_edi_get_settings( 'enabled_logs' ) ) ) {
return;
}
wcpdf_log_error( $message, $level, $e, 'wpo-ips-edi' );
}
/**
* Check if a VAT number starts with a valid country prefix (ISO 3166-1 alpha-2).
*
* @param string $vat_number The VAT number to check.
* @return bool True if the prefix is valid, false otherwise.
*/
function wpo_ips_edi_vat_number_has_country_prefix( string $vat_number ): bool {
$vat_number = strtoupper( trim( $vat_number ) );
// Special handling for Greece
if ( 'EL' === substr( $vat_number, 0, 2 ) ) {
return true;
}
$valid_prefixes = array(
'AD','AE','AF','AG','AI','AL','AM','AO','AQ','AR','AS','AT','AU','AW','AX','AZ',
'BA','BB','BD','BE','BF','BG','BH','BI','BJ','BL','BM','BN','BO','BQ','BR','BS',
'BT','BV','BW','BY','BZ','CA','CC','CD','CF','CG','CH','CI','CK','CL','CM','CN',
'CO','CR','CU','CV','CW','CX','CY','CZ','DE','DJ','DK','DM','DO','DZ','EC','EE',
'EG','EH','ES','ET','FI','FJ','FK','FM','FO','FR','GA','GB','GD','GE','GF','GG',
'GH','GI','GL','GM','GN','GP','GQ','GR','GS','GT','GU','GW','GY','HK','HM','HN',
'HR','HT','HU','ID','IE','IL','IM','IN','IO','IQ','IR','IS','IT','JE','JM','JO',
'JP','KE','KG','KH','KI','KM','KN','KP','KR','KW','KY','KZ','LA','LB','LC','LI',
'LK','LR','LS','LT','LU','LV','LY','MA','MC','MD','ME','MF','MG','MH','MK','ML',
'MM','MN','MO','MP','MQ','MR','MS','MT','MU','MV','MW','MX','MY','MZ','NA','NC',
'NE','NF','NG','NI','NL','NO','NP','NR','NU','NZ','OM','PA','PE','PF','PG','PH',
'PK','PL','PM','PN','PR','PS','PT','PW','PY','QA','RE','RO','RS','RU','RW','SA',
'SB','SC','SD','SE','SG','SH','SI','SJ','SK','SL','SM','SN','SO','SR','SS','ST',
'SV','SX','SY','SZ','TC','TD','TF','TG','TH','TJ','TK','TL','TM','TN','TO','TR',
'TT','TV','TW','TZ','UA','UG','UM','US','UY','UZ','VA','VC','VE','VG','VI','VN',
'VU','WF','WS','XI','YE','YT','ZA','ZM','ZW'
);
$prefix = substr( $vat_number, 0, 2 );
return in_array( $prefix, $valid_prefixes, true );
}
/**
* Build PEPPOL endpoint ID from VAT number.
*
* @param string $billing_country The billing country code (ISO 3166-1 alpha-2).
* @param string $vat_number The VAT number.
* @return array Array with 'eas' and 'endpoint_id' keys, or empty array on failure.
*/
function wpo_ips_edi_build_peppol_endpoint_from_vat( string $billing_country, string $vat_number ): array {
$billing_country = strtoupper( trim( $billing_country ) );
$vat_number = strtoupper( trim( $vat_number ) );
if ( '' === $billing_country || '' === $vat_number ) {
return array();
}
// If VAT looks like it has a prefix, ensure it's a plausible ISO prefix.
if (
strlen( $vat_number ) >= 2 &&
ctype_alpha( substr( $vat_number, 0, 2 ) ) &&
! wpo_ips_edi_vat_number_has_country_prefix( $vat_number )
) {
return array();
}
$mappings = wpo_ips_edi_get_peppol_vat_mappings();
if ( empty( $mappings[ $billing_country ] ) ) {
return array();
}
$cfg = $mappings[ $billing_country ];
// Normalize separators first.
$normalized = preg_replace( '/[\s\.\-]/', '', $vat_number ) ?? $vat_number;
// Strip configured prefixes.
if ( ! empty( $cfg['strip_prefixes'] ) && is_array( $cfg['strip_prefixes'] ) ) {
foreach ( $cfg['strip_prefixes'] as $prefix ) {
$prefix = strtoupper( (string) $prefix );
if ( '' !== $prefix && 0 === strpos( $normalized, $prefix ) ) {
$normalized = substr( $normalized, strlen( $prefix ) );
break;
}
}
}
// Keep only what the country expects.
$value = $normalized;
if ( ! empty( $cfg['keep_pattern'] ) && is_string( $cfg['keep_pattern'] ) ) {
if ( preg_match_all( $cfg['keep_pattern'], $value, $m ) ) {
$value = implode( '', $m[0] );
} else {
$value = '';
}
}
$value = trim( $value );
if ( '' === $value ) {
return array();
}
// Sanity constraints.
if ( ! empty( $cfg['length'] ) && is_int( $cfg['length'] ) && strlen( $value ) !== $cfg['length'] ) {
return array();
}
$eas = (string) ( $cfg['eas'] ?? '' );
if ( '' === $eas ) {
return array();
}
$endpointid = sprintf( '%s:%s', $eas, $value );
return array(
'eas' => $eas,
'endpoint_id' => $endpointid,
);
}
/**
* Get PEPPOL VAT number mappings.
*
* @return array
*/
function wpo_ips_edi_get_peppol_vat_mappings(): array {
$mappings = array(
'BE' => array(
'name' => 'Belgium',
'eas' => '0208',
'strip_prefixes' => array( 'BE' ),
'keep_pattern' => '/\d+/',
'length' => 10,
),
);
return apply_filters( 'wpo_ips_edi_peppol_vat_mappings', $mappings );
}
/**
* Get supplier identifiers data for EDI.
*
* @return array
*/
function wpo_ips_edi_get_supplier_identifiers_data(): array {
$general_settings = WPO_WCPDF()->settings->general;
$language = wpo_ips_edi_get_settings( 'supplier_identifiers_language' );
$data = array();
if ( empty( $language ) ) {
$language = 'default';
}
$data[ $language ] = array(
'name' => array(
'label' => __( 'Name', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $general_settings->get_setting( 'shop_name', $language ),
'required' => true,
),
'address' => array(
'label' => __( 'Street address', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $general_settings->get_setting( 'shop_address_line_1', $language ),
'required' => true,
),
'postcode' => array(
'label' => __( 'Postcode', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $general_settings->get_setting( 'shop_address_postcode', $language ),
'required' => true,
),
'city' => array(
'label' => __( 'City', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $general_settings->get_setting( 'shop_address_city', $language ),
'required' => true,
),
'state' => array(
'label' => __( 'State', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $general_settings->get_setting( 'shop_address_state', $language ),
'required' => false,
),
'country' => array(
'label' => __( 'Country Code', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $general_settings->get_setting( 'shop_address_country', $language ),
'required' => true,
),
'vat_number' => array(
'label' => __( 'VAT number', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $general_settings->get_setting( 'vat_number', $language ),
'required' => true,
),
'coc_number' => array(
'label' => __( 'Registration number', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $general_settings->get_setting( 'coc_number', $language ),
'required' => false,
),
'email' => array(
'label' => __( 'Email', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $general_settings->get_setting( 'shop_email_address', $language ),
'required' => true,
),
);
if ( wpo_ips_edi_peppol_is_available() ) {
$endpoint_id = wpo_ips_edi_get_settings( 'peppol_endpoint_id' );
$endpoint_scheme = wpo_ips_edi_get_settings( 'peppol_endpoint_eas' );
$data[ $language ]['peppol_endpoint_id'] = array(
'label' => __( 'PEPPOL Endpoint ID', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => ! empty( $endpoint_scheme ) && ! empty( $endpoint_id )
? sprintf( '%s:%s', $endpoint_scheme, $endpoint_id )
: '',
'required' => true,
);
}
return apply_filters(
'wpo_ips_edi_supplier_identifier_data',
$data,
$language,
$general_settings
);
}
/**
* Get order customer identifiers data for EDI.
*
* @param \WC_Order $order The order object.
* @return array
*/
function wpo_ips_edi_get_order_customer_identifiers_data( \WC_Order $order ): array {
$data = array(
'name' => array(
'label' => __( 'Name', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $order->get_billing_company() ?: $order->get_formatted_billing_full_name(),
'required' => true,
),
'address' => array(
'label' => __( 'Address', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => trim( $order->get_billing_address_1() . ' ' . $order->get_billing_address_2() ),
'required' => true,
),
'postcode' => array(
'label' => __( 'Postcode', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $order->get_billing_postcode(),
'required' => true,
),
'city' => array(
'label' => __( 'City', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $order->get_billing_city(),
'required' => true,
),
'state' => array(
'label' => __( 'State', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $order->get_billing_state(),
'required' => false,
),
'country' => array(
'label' => __( 'Country Code', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $order->get_billing_country(),
'required' => true,
),
'vat_number' => array(
'label' => __( 'VAT number', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => apply_filters( 'wpo_ips_edi_order_customer_vat_number', wpo_wcpdf_get_order_customer_vat_number( $order ), $order ),
'required' => true,
),
'email' => array(
'label' => __( 'Email', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => $order->get_billing_email(),
'required' => true,
),
);
if ( wpo_ips_edi_peppol_is_available() ) {
$user_id = $order->get_customer_id();
$endpoint_id = $order->get_meta( '_peppol_endpoint_id' );
$endpoint_scheme = $order->get_meta( '_peppol_endpoint_eas' );
if ( empty( $endpoint_id ) && $user_id ) {
$endpoint_id = get_user_meta( $user_id, 'peppol_endpoint_id', true );
}
if ( empty( $endpoint_scheme ) && $user_id ) {
$endpoint_scheme = get_user_meta( $user_id, 'peppol_endpoint_eas', true );
}
$data['peppol_endpoint_id'] = array(
'label' => __( 'Endpoint ID', 'woocommerce-pdf-invoices-packing-slips' ),
'value' => ! empty( $endpoint_scheme ) && ! empty( $endpoint_id )
? sprintf( '%s:%s', $endpoint_scheme, $endpoint_id )
: ( ! empty( $endpoint_id ) ? $endpoint_id : '' ),
'required' => true,
);
}
return apply_filters(
'wpo_ips_edi_order_customer_identifier_data',
$data,
$order
);
}
/**
* Check if EDI Peppol customer fields are enabled for a specific location.
*
* @param string $location Can be 'checkout' or 'my_account'.
* @return bool True if fields should be shown in the given location.
*/
function wpo_ips_edi_peppol_enabled_for_location( string $location ): bool {
if ( ! wpo_ips_edi_peppol_is_available() ) {
return false;
}
$location_setting = wpo_ips_edi_get_settings( 'peppol_endpoint_id_field_location' );
// Valid options
$valid = array( 'checkout', 'my_account', 'both', 'none' );
// Always return false if the field is not properly set
if ( empty( $location_setting ) || ! in_array( $location_setting, $valid, true ) ) {
return false;
}
// Explicitly disabled everywhere
if ( 'none' === $location_setting ) {
return false;
}
// Return true if location matches or if both locations are enabled
return $location === $location_setting || 'both' === $location_setting;
}
/**
* Get the input mode for customer Peppol identifiers.
*
* @return string 'select' or 'full'. Defaults to 'full'.
*/
function wpo_ips_edi_peppol_identifier_input_mode(): string {
return 'full'; // Default is full mode; may change in the future.
// $mode = wpo_ips_edi_get_settings( 'peppol_customer_identifiers_input_mode' );
// return 'select' === $mode ? 'select' : 'full';
}
/**
* Save Peppol identifiers to usermeta (only if new or different).
*
* - full mode: text = scheme:identifier
* - select mode: text = identifier; scheme from <select>
* (but if user typed scheme:identifier we respect that)
*
* @param int $user_id User ID.
* @param array $request $_POST / REST payload.
*/
function wpo_ips_edi_peppol_save_customer_identifiers( int $user_id, array $request ): void {
if ( $user_id <= 0 ) {
return;
}
$mode = wpo_ips_edi_peppol_identifier_input_mode();
// [ textfield , schemefield ]
$pairs = array(
array(
'peppol_endpoint_id',
'peppol_endpoint_eas',
),
);
foreach ( $pairs as list( $id_key, $scheme_key ) ) {
if ( ! isset( $request[ $id_key ] ) ) {
continue;
}
$raw = trim( sanitize_text_field( wp_unslash( $request[ $id_key ] ) ) );
$scheme = $identifier = '';
// Determine parts
if ( 'full' === $mode && false !== strpos( $raw, ':' ) ) {
[ $scheme, $identifier ] = array_map(
'trim',
explode( ':', $raw, 2 ) + array( '', '' )
);
// Select mode, plain identifier
} else {
$identifier = $raw;
}
// Fallback to select mode value when scheme still empty
if ( empty( $scheme ) && isset( $request[ $scheme_key ] ) ) {
$scheme = trim( sanitize_text_field( wp_unslash( $request[ $scheme_key ] ) ) );
}
// Validate scheme list
$valid_schemes = false !== strpos( $scheme_key, '_eas' )
? \WPO\IPS\EDI\Standards\EN16931::get_eas()
: \WPO\IPS\EDI\Standards\EN16931::get_icd();
if ( ! empty( $scheme ) && ! isset( $valid_schemes[ $scheme ] ) ) {
$scheme = ''; // invalid scheme, discard
}
// Save identifier if changed
if ( ! empty( $identifier ) ) {
$existing_identifier = get_user_meta( $user_id, $id_key, true );
if ( $existing_identifier !== $identifier ) {
update_user_meta( $user_id, $id_key, $identifier, $existing_identifier );
}
}
// Save scheme if changed
if ( ! empty( $scheme ) ) {
$existing_scheme = get_user_meta( $user_id, $scheme_key, true );
if ( $existing_scheme !== $scheme ) {
update_user_meta( $user_id, $scheme_key, $scheme, $existing_scheme );
}
}
}
}
/**
* Get the parent order for refunds.
*
* @return \WC_Order
*/
function wpo_ips_edi_get_parent_order( \WC_Abstract_Order $order ): \WC_Order {
if ( is_a( $order, 'WC_Order_Refund' ) ) {
$parent_id = $order->get_parent_id();
if ( $parent_id ) {
$parent_order = wc_get_order( $parent_id );
if ( $parent_order ) {
return $parent_order;
}
}
}
return $order;
}
/**
* Small helper to generate action button HTML.
*
* @param string $url
* @param string $class
* @param string $label
* @param string $icon
*
* @return string
*/
function wpo_ips_edi_generate_action_button_html( string $url, string $class, string $label, string $icon ): string {
if ( empty( $url ) ) {
return '';
}
$atts = apply_filters(
'wpo_ips_edi_generate_action_button_html_atts',
array(
'url' => $url,
'class' => $class,
'label' => $label,
'icon' => $icon,
)
);
return sprintf(
'<a href="%1$s" class="%2$s" alt="%3$s" title="%3$s">
<span class="dashicons %4$s"></span>
</a>',
esc_url( $atts['url'] ),
esc_attr( $atts['class'] ),
esc_attr( $atts['label'] ),
esc_attr( $atts['icon'] )
);
}