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

1478 lines
53 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
namespace WPO\IPS;
use WPO\IPS\Documents\SequentialNumberStore;
use WPO\IPS\Settings\SettingsCallbacks;
use WPO\IPS\Settings\SettingsGeneral;
use WPO\IPS\Settings\SettingsDocuments;
use WPO\IPS\Settings\SettingsDebug;
use WPO\IPS\Settings\SettingsEDI;
use WPO\IPS\Settings\SettingsUpgrade;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
if ( ! class_exists( '\\WPO\\IPS\\Settings' ) ) :
class Settings {
/** @var string|false */
public $options_page_hook;
public SettingsCallbacks $callbacks;
public SettingsGeneral $general;
public SettingsDocuments $documents;
public SettingsDebug $debug;
public SettingsUpgrade $upgrade;
public SettingsEDI $edi;
public array $general_settings;
public array $debug_settings;
public array $edi_settings;
private array $installed_templates = array();
private array $installed_templates_cache = array();
private array $template_list_cache = array();
protected static ?self $_instance = null;
public static function instance(): self {
if ( is_null( self::$_instance ) ) {
self::$_instance = new self();
}
return self::$_instance;
}
public function __construct() {
$this->callbacks = SettingsCallbacks::instance();
$this->general = SettingsGeneral::instance();
$this->documents = SettingsDocuments::instance();
$this->debug = SettingsDebug::instance();
$this->edi = SettingsEDI::instance();
$this->upgrade = SettingsUpgrade::instance();
$this->load_settings();
// Settings menu item
add_action( 'admin_menu', array( $this, 'menu' ), 999 ); // Add menu
// Links on plugin page
add_filter( 'plugin_action_links_'.WPO_WCPDF()->plugin_basename, array( $this, 'add_settings_link' ) );
add_filter( 'plugin_row_meta', array( $this, 'add_support_links' ), 10, 2 );
// settings capabilities
add_filter( 'option_page_capability_wpo_wcpdf_general_settings', array( $this, 'user_settings_capability' ) );
// AJAX set number store
add_action( 'wp_ajax_wpo_wcpdf_set_next_number', array( $this, 'set_number_store' ) );
// AJAX get header logo setting HTML
add_action( 'wp_ajax_wpo_wcpdf_get_media_upload_setting_html', array( $this, 'get_media_upload_setting_html' ) );
// refresh template path cache each time the general settings are updated
add_action( "update_option_wpo_wcpdf_settings_general", array( $this, 'general_settings_updated' ), 10, 3 );
// sets transient to flush rewrite rules
add_action( "update_option_wpo_wcpdf_settings_debug", array( $this, 'debug_settings_updated' ), 10, 3 );
add_action( 'init', array( $this, 'maybe_delete_flush_rewrite_rules_transient' ) );
// migrate old template paths to template IDs before loading settings page
add_action( 'wpo_wcpdf_settings_output_general', array( $this, 'maybe_migrate_template_paths' ), 9, 2 );
// AJAX preview
add_action( 'wp_ajax_wpo_wcpdf_preview', array( $this, 'ajax_preview' ) );
// AJAX preview order search
add_action( 'wp_ajax_wpo_wcpdf_preview_order_search', array( $this, 'preview_order_search' ) );
// schedule yearly reset numbers
add_action( 'wpo_wcpdf_schedule_yearly_reset_numbers', array( $this, 'yearly_reset_numbers' ) );
// Apply categories to document settings.
add_action( 'wpo_wcpdf_init_documents', array( $this, 'update_documents_settings_categories' ), 999 );
// Apply categories to general settings.
add_filter( 'wpo_wcpdf_settings_fields_general', array( $this, 'update_general_settings_categories' ), 999, 5 );
// Apply categories to debug (Advanced) settings.
add_filter( 'wpo_wcpdf_settings_fields_debug', array( $this, 'update_debug_settings_categories' ), 999, 4 );
// Sync address from WooCommerce address.
add_action( 'wp_ajax_wpo_wcpdf_sync_address', array( $this, 'sync_shop_address_with_woo' ) );
}
public function menu() {
$parent_slug = 'woocommerce';
$this->options_page_hook = add_submenu_page(
$parent_slug,
esc_html__( 'PDF Invoices', 'woocommerce-pdf-invoices-packing-slips' ),
esc_html__( 'PDF Invoices', 'woocommerce-pdf-invoices-packing-slips' ),
$this->user_settings_capability(),
'wpo_wcpdf_options_page',
array( $this, 'settings_page' )
);
}
/**
* Add settings link to plugins page
*/
public function add_settings_link( $links ) {
$action_links = array(
'settings' => '<a href="admin.php?page=wpo_wcpdf_options_page">'. esc_html__( 'Settings', 'woocommerce-pdf-invoices-packing-slips' ) . '</a>',
);
return array_merge( $action_links, $links );
}
/**
* Add various support links to plugin page
* after meta (version, authors, site)
*/
public function add_support_links( $links, $file ) {
if ( $file == WPO_WCPDF()->plugin_basename ) {
$row_meta = array(
'docs' => '<a href="https://docs.wpovernight.com/topic/woocommerce-pdf-invoices-packing-slips/" target="_blank" title="' . esc_html__( 'Documentation', 'woocommerce-pdf-invoices-packing-slips' ) . '">' . esc_html__( 'Documentation', 'woocommerce-pdf-invoices-packing-slips' ) . '</a>',
'support' => '<a href="https://wordpress.org/support/plugin/woocommerce-pdf-invoices-packing-slips" target="_blank" title="' . esc_html__( 'Support Forum', 'woocommerce-pdf-invoices-packing-slips' ) . '">' . esc_html__( 'Support Forum', 'woocommerce-pdf-invoices-packing-slips' ) . '</a>',
);
return array_merge( $links, $row_meta );
}
return (array) $links;
}
/**
* Returns the first capability from a filterable list that the current user has access to.
* Falls back to the default capability if none match.
*
* @return string The matched or default user capability.
*/
public function user_settings_capability() {
$manage_woocommerce = 'manage_woocommerce';
// Get the default capability
$default_capability = apply_filters( 'wpo_wcpdf_settings_default_user_capability', $manage_woocommerce );
$default_capability = ( empty( $default_capability ) || ! is_string( $default_capability ) ) ? $manage_woocommerce : $default_capability;
// Get the list of capabilities
$capabilities = (array) apply_filters( 'wpo_wcpdf_settings_user_role_capabilities', array( $default_capability ) );
// Loop through the list
foreach ( $capabilities as $capability ) {
if ( is_string( $capability ) && current_user_can( $capability ) ) {
return $capability;
}
}
// Fallback
return ! empty( $default_capability ) ? $default_capability : $manage_woocommerce;
}
/**
* Check if user role can manage settings.
* @return bool
*/
public function user_can_manage_settings() {
return current_user_can( $this->user_settings_capability() );
}
public function settings_page() {
// feedback on settings save
settings_errors();
$settings_tabs = apply_filters( 'wpo_wcpdf_settings_tabs', array(
'general' => array(
'title' => __( 'General', 'woocommerce-pdf-invoices-packing-slips' ),
'preview_states' => 3,
),
'documents' => array(
'title' => __( 'Documents', 'woocommerce-pdf-invoices-packing-slips' ),
'preview_states' => 3,
),
'edi' => array(
'title' => __( 'E-Documents', 'woocommerce-pdf-invoices-packing-slips' ),
'preview_states' => 1,
),
) );
// add status and upgrade tabs last in row
$settings_tabs['debug'] = array(
'title' => __( 'Advanced', 'woocommerce-pdf-invoices-packing-slips' ),
'preview_states' => 1,
);
$settings_tabs['upgrade'] = array(
'title' => __( 'Upgrade', 'woocommerce-pdf-invoices-packing-slips' ),
'preview_states' => 1,
);
$settings_tabs = $this->maybe_disable_preview_on_settings_tabs( $settings_tabs ); // disable preview on debug setting
$default_tab = apply_filters( 'wpo_wcpdf_settings_tabs_default', ! empty( $settings_tabs['general'] ) ? 'general' : key( $settings_tabs ) );
$nonce = wp_create_nonce( 'wp_wcpdf_settings_page_nonce' );
include WPO_WCPDF()->plugin_path() . '/views/settings-page.php';
}
public function maybe_disable_preview_on_settings_tabs( $settings_tabs ) {
$this->load_settings();
if ( isset( $this->debug_settings['disable_preview'] ) ) {
foreach ( $settings_tabs as $tab_key => &$tab ) {
if ( is_array( $tab ) && ! empty( $tab['preview_states'] ) ) {
$tab['preview_states'] = 1;
}
}
}
return $settings_tabs;
}
public function ajax_preview() {
check_ajax_referer( 'wpo_wcpdf_preview', 'security' );
try {
// check permissions
if ( ! $this->user_can_manage_settings() ) {
throw new \Exception( esc_html__( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ), 403 );
}
// get document type
if ( ! empty( $_POST['document_type'] ) ) {
$document_type = sanitize_text_field( wp_unslash( $_POST['document_type'] ) );
} else {
$document_type = 'invoice';
}
// get order ID
if ( ! empty( $_POST['order_id'] ) ) {
$order_id = sanitize_text_field( wp_unslash( $_POST['order_id'] ) );
if ( 'credit-note' === $document_type ) {
// get last refund ID of the order if available
$refund = wc_get_orders(
array(
'type' => 'shop_order_refund',
'parent' => $order_id,
'limit' => 1,
)
);
$order_id = ! empty( $refund ) ? $refund[0]->get_id() : $order_id;
}
} else {
// default to last order
$default_order_id = wc_get_orders( apply_filters( 'wpo_wcpdf_preview_default_order_id_query_args', array(
'limit' => 1,
'return' => 'ids',
'type' => 'shop_order',
), $document_type ) );
$order_id = apply_filters( 'wpo_wcpdf_preview_default_order_id', ! empty( $default_order_id ) ? reset( $default_order_id ) : false );
}
// get PDF data for preview
if ( $order_id ) {
$order = apply_filters( 'wpo_wcpdf_preview_order_object', wc_get_order( $order_id ), $order_id, $document_type );
if ( empty( $order ) ) {
wp_send_json_error( array( 'error' => esc_html__( 'Order not found!', 'woocommerce-pdf-invoices-packing-slips' ) ) );
}
if ( ! in_array( $order->get_type(), array( 'shop_order', 'shop_order_refund' ) ) ) {
wp_send_json_error( array( 'error' => esc_html__( 'Object found is not an order!', 'woocommerce-pdf-invoices-packing-slips' ) ) );
}
// process settings data
if ( ! empty( $_POST['data'] ) ) {
// parse form data
parse_str( $_POST['data'], $form_data ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$form_data = stripslashes_deep( $form_data );
foreach ( $form_data as $option_key => $form_settings ) {
if ( ! empty( $option_key ) && false === apply_filters( 'wpo_wcpdf_preview_filter_option', 0 === strpos( $option_key, 'wpo_wcpdf' ), $option_key ) ) {
continue; // not our business
}
// validate option values
$form_settings = $this->callbacks->validate( $form_settings );
// filter the options
add_filter( "option_{$option_key}", function( $_value, $_option ) use ( $form_settings ) {
return $form_settings;
}, 99, 2 );
}
// reload settings
$this->load_settings();
do_action( 'wpo_wcpdf_preview_after_reload_settings' );
}
$document = wcpdf_get_document( $document_type, $order );
if ( $document ) {
if ( ! $document->exists() ) {
$document->set_date( current_time( 'timestamp', true ) );
$number_store_method = $this->get_sequential_number_store_method();
$number_store_name = apply_filters( 'wpo_wcpdf_document_sequential_number_store', "{$document->slug}_number", $document );
$number_store = new SequentialNumberStore( $number_store_name, $number_store_method );
$document->set_number( $number_store->get_next() );
}
// Update document date.
$document->initiate_date();
// Update document number only if it is not already set.
if ( empty( $document->get_number() ) ) {
$document_number = $document->get_document_number();
if ( ! empty( $document_number ) ) {
$document->set_number( $document_number );
}
}
$document_number = $document->get_number( $document->get_type() );
// Apply document number formatting.
if ( $document_number ) {
if ( ! empty( $document->settings['number_format'] ) && is_array( $document->settings['number_format'] ) ) {
$document_number->load_number_data( $document->settings['number_format'] );
}
$document_number->apply_formatting( $document, $order );
}
// preview
$output_format = ( ! empty( $_REQUEST['output_format'] ) && $_REQUEST['output_format'] != 'pdf' && in_array( $_REQUEST['output_format'], $document->output_formats ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['output_format'] ) ) : 'pdf';
switch ( $output_format ) {
default:
case 'pdf':
$preview_data = base64_encode( $document->preview_pdf() );
break;
case 'xml':
$preview_data = $document->preview_xml();
break;
}
wp_send_json_success( array(
'preview_data' => $preview_data,
'output_format' => $output_format,
) );
} else {
wp_send_json_error(
array(
'error' => sprintf(
/* translators: 1. order ID, 2. documentation page link, 3. documentation page link closing tag */
esc_html__( 'The PDF preview for order #%1$d is not available. This can happen if some settings prevent the document from being generated. Please review your configuration or check the %2$sdocumentation%3$s for more details.', 'woocommerce-pdf-invoices-packing-slips' ),
$order_id,
'<a href="https://docs.wpovernight.com/woocommerce-pdf-invoices-packing-slips/troubleshooting-pdf-preview-unavailability/" target="_blank">',
'</a>'
)
)
);
}
} else {
wp_send_json_error( array( 'error' => esc_html__( 'No WooCommerce orders found! Please consider adding your first order to see this preview.', 'woocommerce-pdf-invoices-packing-slips' ) ) );
}
} catch ( \Throwable $th ) {
wcpdf_log_error( 'Error trying to generate document: ' . $th->getTraceAsString(), 'critical' );
wp_send_json_error(
array(
'error' => sprintf(
/* translators: error message */
esc_html__( 'Error trying to generate document: %s', 'woocommerce-pdf-invoices-packing-slips' ),
$th->getMessage()
)
)
);
}
wp_die();
}
public function preview_order_search() {
check_ajax_referer( 'wpo_wcpdf_preview', 'security' );
try {
// check permissions
if ( ! $this->user_can_manage_settings() ) {
throw new \Exception( esc_html__( 'You do not have sufficient permissions to access this page.', 'woocommerce-pdf-invoices-packing-slips' ), 403 );
}
if ( ! empty( $_POST['search'] ) && ! empty( $_POST['document_type'] ) ) {
$search = sanitize_text_field( wp_unslash( $_POST['search'] ) );
$document_type = sanitize_text_field( wp_unslash( $_POST['document_type'] ) );
$results = array();
// we have an order ID
if ( is_numeric( $search ) && wc_get_order( $search ) ) {
$results = [ $search ];
// no order ID, let's try with customer
} else {
$default_args = apply_filters( 'wpo_wcpdf_preview_order_search_args', array(
'type' => 'shop_order',
'limit' => 10,
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
), $document_type );
// search by email
if ( is_email( $search ) ) {
$args = array( 'customer' => $search );
$args = $args + $default_args;
$results = wc_get_orders( $args );
// search by names
} else {
$names = array( 'billing_first_name', 'billing_last_name', 'billing_company' );
foreach ( $names as $name ) {
$args = array( $name => $search );
$args = $args + $default_args;
$results = wc_get_orders( $args );
if ( count( $results ) > 0 ) {
break;
}
}
}
}
// filter results
$results = apply_filters( 'wpo_wcpdf_preview_order_search_results', $results, $search, $document_type );
// if we got here we have results!
if ( ! empty( $results ) ) {
$data = array();
foreach ( $results as $value ) {
$order = wc_get_order( $value );
if ( empty( $order ) ) {
continue;
}
$order_id = is_callable( array( $order, 'get_id' ) ) ? $order->get_id() : 0;
$data[$order_id]['order_number'] = is_callable( array( $order, 'get_order_number' ) ) ? $order->get_order_number() : '';
$data[$order_id]['billing_first_name'] = is_callable( array( $order, 'get_billing_first_name' ) ) ? wpo_wcpdf_sanitize_html_content( $order->get_billing_first_name(), 'first_name' ) : '';
$data[$order_id]['billing_last_name'] = is_callable( array( $order, 'get_billing_last_name' ) ) ? wpo_wcpdf_sanitize_html_content( $order->get_billing_last_name(), 'last_name' ) : '';
$data[$order_id]['billing_company'] = is_callable( array( $order, 'get_billing_company' ) ) ? wpo_wcpdf_sanitize_html_content( $order->get_billing_company(), 'company' ) : '';
$data[$order_id]['date_created'] = is_callable( array( $order, 'get_date_created' ) ) ? '<strong>' . esc_attr__( 'Date', 'woocommerce-pdf-invoices-packing-slips' ) . ':</strong> ' . $order->get_date_created()->format( 'Y/m/d' ) : '';
$data[$order_id]['total'] = is_callable( array( $order, 'get_total' ) ) ? '<strong>' . esc_attr__( 'Total', 'woocommerce-pdf-invoices-packing-slips' ) . ':</strong> ' . wc_price( $order->get_total() ) : '';
}
$data = apply_filters( 'wpo_wcpdf_preview_order_search_data', $data, $results );
wp_send_json_success( $data );
} else {
wp_send_json_error( array( 'error' => esc_html__( 'No order(s) found!', 'woocommerce-pdf-invoices-packing-slips' ) ) );
}
} else {
wp_send_json_error( array( 'error' => esc_html__( 'An error occurred when trying to process your request!', 'woocommerce-pdf-invoices-packing-slips' ) ) );
}
} catch ( \Throwable $th ) {
wp_send_json_error(
array(
'error' => sprintf(
/* translators: error message */
esc_html__( 'Error trying to get orders: %s', 'woocommerce-pdf-invoices-packing-slips' ),
$th->getMessage()
)
)
);
}
wp_die();
}
public function add_settings_fields( $settings_fields, $page, $option_group, $option_name ) {
foreach ( $settings_fields as $settings_field ) {
if ( ! isset( $settings_field['callback'] ) ) {
continue;
} elseif ( is_callable( array( $this->callbacks, $settings_field['callback'] ) ) ) {
$callback = array( $this->callbacks, $settings_field['callback'] );
} elseif ( is_callable( $settings_field['callback'] ) ) {
$callback = $settings_field['callback'];
} else {
continue;
}
if ( $settings_field['type'] == 'section' ) {
add_settings_section(
$settings_field['id'],
$settings_field['title'],
$callback,
$page,
$settings_field['args'] ?? array()
);
} else {
add_settings_field(
$settings_field['id'],
$settings_field['title'],
$callback,
$page,
$settings_field['section'],
$settings_field['args']
);
// register option separately for singular options
if ( is_string( $settings_field['callback'] ) && $settings_field['callback'] == 'singular_text_element') {
register_setting( $option_group, $settings_field['args']['option_name'], array( $this->callbacks, 'validate' ) ); // phpcs:ignore PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic
}
}
}
// $page, $option_group & $option_name are all the same...
register_setting( $option_group, $option_name, array( $this->callbacks, 'validate' ) ); // phpcs:ignore PluginCheck.CodeAnalysis.SettingSanitization.register_settingDynamic
add_filter( 'option_page_capability_'.$page, array( $this, 'user_settings_capability' ) );
}
/**
* Get document general settings.
*
* @return array
*/
public function get_common_document_settings(): array {
return array(
'paper_size' => $this->general_settings['paper_size'] ?? '',
'font_subsetting' => isset( $this->general_settings['font_subsetting'] ) || ( defined( "DOMPDF_ENABLE_FONTSUBSETTING" ) && DOMPDF_ENABLE_FONTSUBSETTING === true ),
'header_logo' => $this->general_settings['header_logo'] ?? '',
'header_logo_height' => $this->general_settings['header_logo_height'] ?? '',
'vat_number' => $this->general_settings['vat_number'] ?? '',
'coc_number' => $this->general_settings['coc_number'] ?? '',
'shop_name' => $this->general_settings['shop_name'] ?? '',
'shop_phone_number' => $this->general_settings['shop_phone_number'] ?? '',
'shop_email_address' => $this->general_settings['shop_email_address'] ?? '',
'shop_address_line_1' => $this->general_settings['shop_address_line_1'] ?? '',
'shop_address_line_2' => $this->general_settings['shop_address_line_2'] ?? '',
'shop_address_country' => $this->general_settings['shop_address_country'] ?? '',
'shop_address_state' => $this->general_settings['shop_address_state'] ?? '',
'shop_address_city' => $this->general_settings['shop_address_city'] ?? '',
'shop_address_postcode' => $this->general_settings['shop_address_postcode'] ?? '',
'shop_address_additional' => $this->general_settings['shop_address_additional'] ?? '',
'footer' => $this->general_settings['footer'] ?? '',
'extra_1' => $this->general_settings['extra_1'] ?? '',
'extra_2' => $this->general_settings['extra_2'] ?? '',
'extra_3' => $this->general_settings['extra_3'] ?? '',
);
}
public function get_document_settings( $document_type, $output_format = 'pdf' ) {
if ( ! empty( $document_type ) ) {
$option_name = ( 'pdf' === $output_format || 'xml' === $output_format ) // In 5.0.0 and later, EDocuments settings are isolated from document settings, so PDF is the default.
? "wpo_wcpdf_documents_settings_{$document_type}"
: "wpo_wcpdf_documents_settings_{$document_type}_{$output_format}";
return get_option( $option_name, array() );
} else {
return false;
}
}
public function get_output_format( $document = null, $request = null ) {
$output_format = 'pdf'; // default
if ( isset( $this->debug_settings['html_output'] ) || ( isset( $request['output'] ) && 'html' === $request['output'] ) ) {
$output_format = 'html';
} elseif ( isset( $request['output'] ) && ! empty( $request['output'] ) && ! empty( $document ) && in_array( $request['output'], $document->output_formats ) ) {
$output_format = esc_attr( $request['output'] );
}
return apply_filters( 'wpo_wcpdf_output_format', $output_format, $document );
}
public function get_output_mode() {
if ( isset( $this->general_settings['download_display'] ) ) {
switch ( $this->general_settings['download_display'] ) {
case 'display':
$output_mode = 'inline';
break;
case 'download':
default:
$output_mode = 'download';
break;
}
} else {
$output_mode = 'download';
}
return $output_mode;
}
/**
* Get installed templates list as options.
*
* @return array
*/
public function get_installed_templates_list(): array {
$installed_templates = $this->get_installed_templates();
$template_list = array();
foreach ( $installed_templates as $path => $template_id ) {
$template_name = basename( $template_id );
$group = dirname( $template_id );
// check if this is an extension template
if ( false !== strpos( $group, 'extension::' ) ) {
$extension = explode( '::', $group );
$group = 'extension';
}
switch ( $group ) {
case 'default':
case 'premium_plugin':
// no suffix
break;
case 'extension':
$template_name = sprintf( '%s (%s) [%s]', $template_name, __( 'Extension', 'woocommerce-pdf-invoices-packing-slips' ), $extension[1] );
break;
case 'theme':
default:
$template_name = sprintf( '%s (%s)', $template_name, __( 'Custom', 'woocommerce-pdf-invoices-packing-slips' ) );
break;
}
$template_list[ $template_id ] = $template_name;
}
return $template_list;
}
public function get_template_path( string $template_path = '' ) {
$selected_template = $template_path
? sanitize_text_field( $template_path )
: ( $this->general_settings['template_path'] ?? '' );
// return default path if no template selected
if ( empty( $selected_template ) ) {
return wp_normalize_path( WPO_WCPDF()->plugin_path() . '/templates/Simple' );
}
$installed_templates = $this->get_installed_templates();
if ( in_array( $selected_template, $installed_templates ) ) {
return array_search( $selected_template, $installed_templates );
} else {
// unknown template or full template path (filter override)
$template_path = wp_normalize_path( $selected_template );
// add base path, checking if it's not already there
// alternative setups like Bedrock have WP_CONTENT_DIR & ABSPATH separated
if ( defined( 'WP_CONTENT_DIR' ) && ! empty( WP_CONTENT_DIR ) && false !== strpos( WP_CONTENT_DIR, ABSPATH ) ) {
$base_path = wp_normalize_path( ABSPATH );
} else {
$base_path = wp_normalize_path( WP_CONTENT_DIR );
}
if ( ! empty( $template_path ) && false === strpos( $template_path, $base_path ) ) {
$template_path = wp_normalize_path( $base_path . $template_path );
}
}
return $template_path;
}
public function get_installed_templates( $force_reload = false ) {
// because this method can be called (too) early we load from a cached list in those cases
// this cache is updated each time the template settings are saved/updated
if ( ! did_action( 'wpo_wcpdf_init_documents' ) && ( $cached_template_list = $this->get_template_list_cache() ) ) {
return $cached_template_list;
}
// to save resources on the disk operations we only do this once
if ( $force_reload === false && ! empty ( $this->installed_templates ) ) {
return $this->installed_templates;
}
$installed_templates = array();
// get base paths
$template_base_path = ( function_exists( 'WC' ) && is_callable( array( WC(), 'template_path' ) ) ) ? WC()->template_path() : apply_filters( 'woocommerce_template_path', 'woocommerce/' );
$template_base_path = untrailingslashit( $template_base_path );
$template_paths = array (
// note the order: theme before child-theme, so that child theme is always preferred (overwritten)
'default' => WPO_WCPDF()->plugin_path() . '/templates/',
'theme' => get_template_directory() . "/{$template_base_path}/pdf/",
'child-theme' => get_stylesheet_directory() . "/{$template_base_path}/pdf/",
);
$template_paths = apply_filters( 'wpo_wcpdf_template_paths', $template_paths );
foreach ( $template_paths as $template_source => $template_path ) {
$dirs = (array) glob( $template_path . '*' , GLOB_ONLYDIR );
foreach ( $dirs as $dir ) {
$clean_dir = wp_normalize_path( $dir );
$template_name = basename( $clean_dir );
// let child theme override parent theme
$group = ( $template_source == 'child-theme' ) ? 'theme' : $template_source;
$installed_templates[ $clean_dir ] = "{$group}/{$template_name}" ;
}
}
if ( empty( $installed_templates ) ) {
// fallback to Simple template for servers with glob() disabled
$simple_template_path = wp_normalize_path( $template_paths['default'] . 'Simple' );
$installed_templates[$simple_template_path] = 'default/Simple';
}
$installed_templates = apply_filters( 'wpo_wcpdf_installed_templates', $installed_templates );
$this->installed_templates = $installed_templates;
if ( ! empty( $this->template_list_cache ) && array_diff_assoc( $this->template_list_cache, $this->installed_templates ) ) {
$this->set_template_list_cache( $this->installed_templates );
}
return $installed_templates;
}
public function get_template_list_cache() {
$template_list = get_option( 'wpo_wcpdf_installed_template_paths', array() );
if ( ! empty( $template_list ) ) {
$checked_list = array();
$outdated = false;
// cache could be outdated, so we check whether the folders exist
foreach ( $template_list as $path => $template_id ) {
if ( WPO_WCPDF()->file_system->is_dir( $path ) ) {
$checked_list[$path] = $template_id; // folder exists
continue;
}
$outdated = true;
// folder does not exist, try replacing base if we can locate wp-content
$wp_content_folder = 'wp-content';
if ( ! empty( $path ) && false !== strpos( $path, $wp_content_folder ) && defined( WP_CONTENT_DIR ) ) {
// try wp-content
$relative_path = substr( $path, strrpos( $path, $wp_content_folder ) + strlen( $wp_content_folder ) );
$new_path = WP_CONTENT_DIR . $relative_path;
if ( WPO_WCPDF()->file_system->is_dir( $new_path ) ) {
$checked_list[$new_path] = $template_id;
}
}
}
if ( $outdated ) {
$this->set_template_list_cache( $checked_list );
}
$this->installed_templates_cache = $checked_list;
return $checked_list;
} else {
return array();
}
}
public function set_template_list_cache( $template_list ) {
$this->template_list_cache = $template_list;
update_option( 'wpo_wcpdf_installed_template_paths', $template_list );
}
public function delete_template_list_cache() {
delete_option( 'wpo_wcpdf_installed_template_paths' );
}
public function general_settings_updated( $old_settings, $settings, $option ) {
if ( is_array( $settings ) && ! empty ( $settings['template_path'] ) ) {
$this->delete_template_list_cache();
$this->set_template_list_cache( $this->get_installed_templates() );
}
}
public function debug_settings_updated( $old_settings, $settings, $option ) {
if ( is_array( $settings ) && is_array( $old_settings ) && empty( $old_settings['pretty_document_links'] ) && ! empty ( $settings['pretty_document_links'] ) ) {
set_transient( 'wpo_wcpdf_flush_rewrite_rules', 'yes', HOUR_IN_SECONDS );
}
}
public function maybe_delete_flush_rewrite_rules_transient() {
if ( get_transient( 'wpo_wcpdf_flush_rewrite_rules' ) ) {
flush_rewrite_rules();
delete_transient( 'wpo_wcpdf_flush_rewrite_rules' );
}
}
public function get_relative_template_path( $absolute_path ) {
if ( empty( $absolute_path ) ) {
return '';
}
if ( defined( 'WP_CONTENT_DIR' ) && ! empty( WP_CONTENT_DIR ) && false !== strpos( WP_CONTENT_DIR, ABSPATH ) ) {
$base_path = wp_normalize_path( ABSPATH );
} else {
$base_path = wp_normalize_path( WP_CONTENT_DIR );
}
return str_replace( $base_path, '', wp_normalize_path( $absolute_path ) );
}
public function maybe_migrate_template_paths( $settings_section = null, $nonce = null ) {
if ( ! wp_verify_nonce( $nonce, 'wp_wcpdf_settings_page_nonce' ) ) {
return;
}
// bail if no template is selected yet (fresh install)
if ( empty( $this->general_settings['template_path'] ) ) {
return;
}
$installed_templates = $this->get_installed_templates( true );
$selected_template = wp_normalize_path( $this->general_settings['template_path'] );
$template_match = '';
if ( ! in_array( $selected_template, $installed_templates ) && substr_count( $selected_template, '/' ) > 1 ) {
// search for path match
foreach ( $installed_templates as $path => $template_id ) {
$path = wp_normalize_path( $path );
// check if the last part of the path matches
if ( substr( $path, -strlen( $selected_template ) ) === $selected_template ) {
$template_match = $template_id;
break;
}
}
// fallback to template name if no path match
if ( empty( $template_match ) ) {
$template_ids = array_flip( array_unique( array_combine( $installed_templates, array_map( 'basename', $installed_templates ) ) ) );
$template_name = basename( $selected_template );
if ( ! empty ( $template_ids[$template_name] ) ) {
$template_match = $template_ids[$template_name];
}
}
// migrate setting if we have a match
if ( ! empty( $template_match ) ) {
$this->general_settings['template_path'] = $template_match;
update_option( 'wpo_wcpdf_settings_general', $this->general_settings );
/* translators: 1. path, 2. template ID */
wcpdf_log_error( sprintf( 'Template setting migrated from %1$s to %2$s', $path, $template_id ), 'info' );
}
}
}
public function set_number_store() {
$store = ! empty( $_POST['store'] ) ? sanitize_text_field( wp_unslash( $_POST['store'] ) ) : '';
check_ajax_referer( "wpo_wcpdf_next_{$store}", 'security' );
// check permissions
if ( ! $this->user_can_manage_settings() ) {
die();
}
$number = ! empty( $_POST['number'] ) ? (int) $_POST['number'] : 0;
if ( $number > 0 ) {
$number_store_method = $this->get_sequential_number_store_method();
$number_store = new SequentialNumberStore( $store, $number_store_method );
$number_store->set_next( $number );
echo wp_kses_post( "next number ({$store}) set to {$number}" );
}
die();
}
public function get_sequential_number_store_method() {
global $wpdb;
$method = isset( $this->debug_settings['calculate_document_numbers'] ) ? 'calculate' : 'auto_increment';
// safety first - always use calculate when auto_increment_increment is not 1
$row = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
"SHOW VARIABLES LIKE 'auto_increment_increment'"
);
if ( ! empty( $row ) && ! empty( $row->Value ) && $row->Value != 1 ) {
$method = 'calculate';
}
return $method;
}
public function schedule_yearly_reset_numbers() {
if ( ! $this->maybe_schedule_yearly_reset_numbers() ) {
return;
}
// checks AS functions existence
if ( ! function_exists( '\\as_schedule_single_action' ) || ! function_exists( '\\as_get_scheduled_actions' ) ) {
wcpdf_log_error( 'Action Scheduler functions not available. Cannot schedule yearly document number reset.', 'critical' );
return;
}
$next_year = strval( intval( current_time( 'Y' ) ) + 1 );
$datetime = new \WC_DateTime( "{$next_year}-01-01 00:00:01", new \DateTimeZone( wc_timezone_string() ) );
$semaphore = new Semaphore( 'schedule_yearly_reset_numbers' );
$hook = 'wpo_wcpdf_schedule_yearly_reset_numbers';
// checks if there are pending actions
$scheduled_actions = count( \as_get_scheduled_actions( array(
'hook' => $hook,
'status' => \ActionScheduler_Store::STATUS_PENDING,
) ) );
// if no concurrent actions sets the action
if ( $scheduled_actions < 1 ) {
if ( $semaphore->lock() ) {
$semaphore->log( 'Lock acquired for yearly reset numbers schedule.', 'info' );
try {
$action_id = \as_schedule_single_action( $datetime->getTimestamp(), $hook );
if ( ! empty( $action_id ) ) {
wcpdf_log_error(
"Yearly document numbers reset scheduled with the action id: {$action_id}",
'info'
);
} else {
wcpdf_log_error(
'The yearly document numbers reset action schedule failed!',
'critical'
);
}
} catch ( \Exception $e ) {
$semaphore->log( $e, 'critical' );
} catch ( \Error $e ) {
$semaphore->log( $e, 'critical' );
}
if ( $semaphore->release() ) {
$semaphore->log( 'Lock released for yearly reset numbers schedule.', 'info' );
}
} else {
$semaphore->log( 'Couldn\'t get the lock for yearly reset numbers schedule.', 'critical' );
}
} else {
wcpdf_log_error(
"Number of concurrent yearly document numbers reset actions found: {$scheduled_actions}",
'error'
);
if ( function_exists( '\\as_unschedule_all_actions' ) ) {
\as_unschedule_all_actions( $hook );
} else {
wcpdf_log_error( 'Action Scheduler functions not available. Cannot unschedule yearly document number reset.', 'critical' );
}
// reschedule
$this->schedule_yearly_reset_numbers();
}
}
public function yearly_reset_numbers() {
$semaphore = new Semaphore( 'yearly_reset_numbers' );
if ( $semaphore->lock() ) {
$semaphore->log( 'Lock acquired for yearly reset numbers.', 'info' );
try {
// reset numbers
$documents = WPO_WCPDF()->documents->get_documents( 'all' );
$number_stores = array();
foreach ( $documents as $document ) {
if ( is_callable( array( $document, 'get_sequential_number_store' ) ) ) {
$number_stores[$document->get_type()] = $document->get_sequential_number_store();
}
}
// log reset number events
if ( ! empty( $number_stores ) ) {
foreach( $number_stores as $document_type => $number_store ) {
if ( $number_store->get_next() === 1 ) {
wcpdf_log_error(
"Yearly number reset succeed for '{$document_type}' with database table name: {$number_store->table_name}",
'info'
);
} else {
wcpdf_log_error(
"An error occurred while trying to reset yearly number for '{$document_type}' with database table name: {$number_store->table_name}",
'error'
);
}
}
}
} catch ( \Exception $e ) {
$semaphore->log( $e, 'critical' );
} catch ( \Error $e ) {
$semaphore->log( $e, 'critical' );
}
if ( $semaphore->release() ) {
$semaphore->log( 'Lock release for yearly reset numbers.', 'info' );
}
} else {
$semaphore->log( 'Couldn\'t get the lock for yearly reset numbers.', 'critical' );
}
// reschedule the action for the next year
$this->schedule_yearly_reset_numbers();
}
public function maybe_schedule_yearly_reset_numbers() {
$schedule = false;
foreach ( WPO_WCPDF()->documents->get_documents( 'all' ) as $document ) {
if ( isset( $document->settings['reset_number_yearly'] ) ) {
$schedule = true;
break;
}
}
// unschedule existing actions
if ( ! $schedule ) {
if ( function_exists( '\\as_unschedule_all_actions' ) ) {
\as_unschedule_all_actions( 'wpo_wcpdf_schedule_yearly_reset_numbers' );
} else {
wcpdf_log_error( 'Action Scheduler functions not available. Cannot unschedule yearly document number reset.', 'critical' );
}
}
return $schedule;
}
public function yearly_reset_action_is_scheduled() {
$is_scheduled = false;
if ( ! function_exists( '\\as_get_scheduled_actions' ) ) {
wcpdf_log_error( 'Action Scheduler function not available. Cannot check if the yearly numbering reset is scheduled.', 'critical' );
return $is_scheduled;
}
$scheduled_actions = \as_get_scheduled_actions( array(
'hook' => 'wpo_wcpdf_schedule_yearly_reset_numbers',
'status' => \ActionScheduler_Store::STATUS_PENDING,
) );
if ( ! empty( $scheduled_actions ) ) {
$total_actions = count( $scheduled_actions );
if ( $total_actions === 1 ) {
$is_scheduled = true;
} else {
$message = sprintf(
/* translators: total scheduled actions */
__( 'Only 1 scheduled action should exist for the yearly reset of the numbering system, but %s were found', 'woocommerce-pdf-invoices-packing-slips' ),
$total_actions
);
wcpdf_log_error( $message );
}
}
return $is_scheduled;
}
public function get_media_upload_setting_html() {
check_ajax_referer( 'wpo_wcpdf_get_media_upload_setting_html', 'security' );
$request = stripslashes_deep( $_POST );
// check permissions
if ( ! $this->user_can_manage_settings() ) {
wp_send_json_error();
}
// get previous (default) args and preset current
$args = isset( $request['args'] ) ? $request['args'] : array();
$args['current'] = isset( $request['attachment_id'] ) ? absint( $request['attachment_id'] ) : 0;
if ( isset( $args['translatable'] ) ) {
$args['translatable'] = wc_string_to_bool( $args['translatable'] );
}
// get settings HTML
ob_start();
$this->callbacks->media_upload( $args );
$html = ob_get_clean();
return wp_send_json_success( $html );
}
public function move_setting_after_id( $settings, $insert_settings, $after_setting_id ) {
$pos = 1; // this is already +1 to insert after the actual pos
foreach ( $settings as $setting ) {
if ( isset( $setting['id'] ) && $setting['id'] == $after_setting_id ) {
$section = $setting['section'];
break;
} else {
$pos++;
}
}
// replace section
if ( isset( $section ) ) {
foreach ( $insert_settings as $key => $insert_setting ) {
$insert_settings[$key]['section'] = $section;
}
} else {
$empty_section = array(
array(
'type' => 'section',
'id' => 'custom',
'title' => '',
'callback' => 'section',
),
);
$insert_settings = array_merge( $empty_section, $insert_settings );
}
// insert our api settings
$new_settings = array_merge( array_slice( $settings, 0, $pos, true ), $insert_settings, array_slice( $settings, $pos, NULL, true ) );
return $new_settings;
}
/**
* Applies categories to document settings.
*
* @return void
*/
public function update_documents_settings_categories(): void {
$documents = WPO_WCPDF()->documents->get_documents( 'all' );
foreach ( $documents as $document ) {
foreach ( $document->output_formats as $output_format ) {
add_filter(
"wpo_wcpdf_settings_fields_documents_{$document->get_type()}_{$output_format}",
array( $this, 'apply_document_settings_categories' ),
999
);
}
}
}
/**
* Apply categories to documents settings fields.
*
* @param array $settings_fields
*
* @return array
*/
public function apply_document_settings_categories( array $settings_fields ): array {
$current_filter = explode( '_', current_filter() );
$output_format = end( $current_filter );
$document_type = prev( $current_filter );
$document = wcpdf_get_document( $document_type, null );
if ( ! $document ) {
return $settings_fields;
}
$settings_categories = is_callable( array( $document, 'get_settings_categories' ) )
? $document->get_settings_categories( $output_format )
: array();
// Return if no category found!
if ( empty( $settings_categories ) ) {
return $settings_fields;
}
return $this->apply_setting_categories( $settings_fields, $settings_categories );
}
/**
* Apply categories to general settings.
*
* @param array $settings_fields
* @param string $page
* @param string $option_group
* @param string $option_name
* @param SettingsGeneral $general_settings
*
* @return array
*/
public function update_general_settings_categories( array $settings_fields, string $page, string $option_group, string $option_name, \WPO\IPS\Settings\SettingsGeneral $general_settings ): array {
$settings_categories = is_callable( array( $general_settings, 'get_settings_categories' ) )
? $general_settings->get_settings_categories()
: array();
if ( empty( $settings_categories ) ) {
return $settings_fields;
}
return $this->apply_setting_categories( $settings_fields, $settings_categories );
}
/**
* Apply categories to debug settings.
*
* @param array $settings_fields
* @param string $page
* @param string $option_group
* @param string $option_name
*
* @return array
*/
public function update_debug_settings_categories( array $settings_fields, string $page, string $option_group, string $option_name ): array {
$settings_categories = is_callable( array( $this->debug, 'get_settings_categories' ) )
? $this->debug->get_settings_categories()
: array();
if ( empty( $settings_categories ) ) {
return $settings_fields;
}
return $this->apply_setting_categories( $settings_fields, $settings_categories );
}
/**
* Apply categories to settings fields.
*
* @param array $settings_fields
* @param array $settings_categories
*
* @return array
*/
public function apply_setting_categories( array $settings_fields, array $settings_categories ): array {
if ( empty( $settings_fields ) || empty( $settings_categories ) ) {
return $settings_fields;
}
$modified_settings_fields = array();
$settings_lookup = array();
$processed_keys = array();
foreach ( $settings_fields as $key => $settings_field ) {
if ( 'section' === $settings_field['type'] ) {
// Remove all sections.
unset( $settings_fields[ $key ] );
continue;
}
// Create a lookup array for settings fields by id.
// This allows for quick access to settings fields by their id, reducing the time complexity
// of finding a settings field from O(n*m) to O(n+m), where n is the number of category members
// and m is the number of settings fields.
$settings_lookup[ $settings_field['id'] ] = $key;
}
// Update settings fields.
foreach ( $settings_categories as $category_name => $category_details ) {
$category_title = isset( $category_details['title'] ) && is_string( $category_details['title'] )
? $category_details['title']
: '';
$category_members = isset( $category_details['members'] ) && is_array( $category_details['members'] )
? $category_details['members']
: array();
// Add section for each category.
$modified_settings_fields[] = $this->create_section( $category_name, $category_title );
// Add settings fields based on the order in the members array.
foreach ( $category_members as $member ) {
if ( isset( $settings_lookup[ $member ] ) ) {
$key = $settings_lookup[ $member ];
// Skip if the key has already been processed.
if ( in_array( $key, $processed_keys, true ) ) {
continue;
}
$settings_field = $settings_fields[ $key ];
$settings_field['section'] = $category_name;
$modified_settings_fields[] = $settings_field;
$processed_keys[] = $key;
}
}
}
// Check for any unprocessed settings fields.
$unprocessed_settings_fields = array_diff_key( $settings_fields, array_flip( $processed_keys ) );
// Create an "Additional settings" section for uncategorized settings fields.
if ( ! empty( $unprocessed_settings_fields ) ) {
$category_name = 'additional';
$modified_settings_fields[] = $this->create_section(
$category_name,
__( 'Additional settings', 'woocommerce-pdf-invoices-packing-slips' )
);
// Add rest of settings to the $modified_settings_fields array under "More" category
foreach ( $unprocessed_settings_fields as $settings_field ) {
$settings_field['section'] = $category_name;
$modified_settings_fields[] = $settings_field;
}
}
return $modified_settings_fields;
}
/**
* Initializes settings by loading them from the database.
*
* @return void
*/
public function load_settings(): void {
$general_settings = get_option( 'wpo_wcpdf_settings_general', array() );
$debug_settings = get_option( 'wpo_wcpdf_settings_debug', array() );
$edi_settings = get_option( 'wpo_ips_edi_settings', array() );
$this->general_settings = is_array( $general_settings ) ? $general_settings : array();
$this->debug_settings = is_array( $debug_settings ) ? $debug_settings : array();
$this->edi_settings = is_array( $edi_settings ) ? $edi_settings : array();
}
/**
* Creates a section array for settings fields.
*
* @param string $category_name The ID of the category.
* @param string $category_title The title of the section.
*
* @return array The section configuration array.
*/
public function create_section( string $category_name, string $category_title ): array {
return array(
'type' => 'section',
'id' => $category_name,
'title' => $category_title,
'callback' => 'section',
'args' => array(
'before_section' => '<div class="settings_category" id="' . esc_attr( $category_name ) . '">',
'after_section' => '</div>',
),
);
}
/**
* Helper method to add a single setting field to a category.
*
* @param array $settings_categories Array of existing settings categories, with category names as keys.
* @param string $new_setting_id The new setting ID to add to the specified category.
* @param string $category_name Name of the category to which the settings will be added.
* @param int|null $position Optional. The position at which to insert the new settings (starts from 1). Defaults to appending at the end.
*
* @return array
*/
public function add_single_setting_field_to_category( array $settings_categories, string $new_setting_id, string $category_name, ?int $position = null ): array {
return $this->add_setting_field_to_category( $settings_categories, array( $new_setting_id ), $category_name, $position );
}
/**
* Helper method to add multiple setting fields to a category.
*
* @param array $settings_categories Array of existing settings categories, with category names as keys.
* @param array $new_setting_ids Array of new setting IDs to add to the specified category.
* @param string $category_name Name of the category to which the settings will be added.
* @param int|null $position Optional. The position at which to insert the new settings (starts from 1). Defaults to appending at the end.
*
* @return array
*/
public function add_multiple_setting_fields_to_category( array $settings_categories, array $new_setting_ids, string $category_name, ?int $position = null ): array {
return $this->add_setting_field_to_category( $settings_categories, $new_setting_ids, $category_name, $position );
}
/**
* Internal method to handle adding setting fields to a category.
*
* @param array $settings_categories Array of existing settings categories, with category names as keys.
* @param array $new_setting_ids Array of new setting IDs to add to the specified category.
* @param string $category_name Name of the category to which the settings will be added.
* @param int|null $position Optional. The position at which to insert the new settings (1-based index). Defaults to appending at the end.
*
* @return array
*/
public function add_setting_field_to_category( array $settings_categories, array $new_setting_ids, string $category_name, ?int $position = null ): array {
if ( ! isset( $settings_categories[ $category_name ] ) ) {
return $settings_categories;
}
$members = &$settings_categories[ $category_name ]['members'];
if ( is_null( $position ) || 0 === $position ) {
$members = array_merge( $members, $new_setting_ids );
} else {
array_splice( $members, $position - 1, 0, $new_setting_ids );
}
return $settings_categories;
}
/**
* Get the position of a specific setting in the settings array.
*
* @param array $settings_categories Array of settings categories where the setting name is searched.
* @param string $category Name of the category to search in.
* @param string $setting_name Name of the setting to find in the settings array.
*
* @return int Position of the setting (1-based index) if found; otherwise, returns 0.
*/
public function get_setting_position( array $settings_categories, string $category, string $setting_name ): int {
if ( empty( $settings_categories[ $category ]['members'] ) ) {
return 0;
}
$key = array_search( $setting_name, $settings_categories[ $category ]['members'], true );
return $key !== false ? absint( $key ) + 1: 0;
}
/**
* Helper method to add a setting category.
*
* @param array $settings_categories
* @param string $category_name
* @param string $title
* @param array $members
*
* @return array
*/
public function add_settings_category( array $settings_categories, string $category_name, string $title, array $members ): array {
// Do not override if the category already exist.
if ( isset( $settings_categories[ $category_name ] ) ) {
return $settings_categories;
}
$settings_categories[ $category_name ] = array(
'title' => $title,
'members' => $members,
);
return $settings_categories;
}
/**
* Syncs the address from WooCommerce settings.
*
* @return void
*/
public function sync_shop_address_with_woo(): void {
check_ajax_referer( 'wpo_wcpdf_admin_nonce', 'security' );
$address_field = ! empty( $_POST['address_field'] )
? sanitize_text_field( wp_unslash( $_POST['address_field'] ) )
: '';
if ( empty( $address_field ) ) {
wp_send_json_error( array( 'message' => __( 'Address field is required.', 'woocommerce-pdf-invoices-packing-slips' ) ) );
return;
}
// Map address fields to the option keys used by WooCommerce.
$address_map = array(
'shop_address_line_1' => 'woocommerce_store_address',
'shop_address_line_2' => 'woocommerce_store_address_2',
'shop_address_country' => 'woocommerce_default_country',
'shop_address_state' => 'woocommerce_default_country',
'shop_address_city' => 'woocommerce_store_city',
'shop_address_postcode' => 'woocommerce_store_postcode',
);
// Validate the address field against the map.
if ( ! array_key_exists( $address_field, $address_map ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid address field.', 'woocommerce-pdf-invoices-packing-slips' ) ) );
return;
}
$option_key = $address_map[ $address_field ];
$raw_value = get_option( $option_key, '' );
// Return, if the value is not set.
if ( empty( $raw_value ) ) {
wp_send_json_error( array( 'message' => __( 'The field is empty.', 'woocommerce-pdf-invoices-packing-slips' ) ) );
return;
}
// Parse the value based on the address field.
switch ( $address_field ) {
case 'shop_address_state':
$parsed = wc_format_country_state_string( $raw_value );
$value = $parsed['state'] ?? null;
break;
default:
$value = $raw_value;
}
wp_send_json_success( array( 'value' => $value ) );
}
}
endif; // class_exists