settings->get_totals_field_options(); $totals_table_data = array(); foreach ($total_settings as $total_key => $total_setting) { // reset possibly absent vars $method = $percent = $base = $show_unit = $only_discounted = $label = $single_total = NULL; // extract vars extract($total_setting); // remove label if empty! if( empty($total_setting['label']) ) { unset($total_setting['label']); } elseif ( !in_array( $type, array( 'fees' ) ) ) { $label = $total_setting['label'] = __( $total_setting['label'], 'woocommerce-pdf-invoices-packing-slips' ); // not proper gettext, but it makes it possible to reuse po translations! } switch ($type) { case 'subtotal': // $tax, $discount, $only_discounted $order_discount = $document->get_order_discount( 'total', 'incl' ); if ( !$order_discount && isset($only_discounted) ) { break; } switch ($discount) { case 'before': $totals_table_data[$total_key] = (array) $total_setting + $document->get_order_subtotal( $tax ); break; case 'after': // avoid recalculating if we don't have a discount anyway if ( !$order_discount ) { $totals_table_data[$total_key] = (array) $total_setting + $document->get_order_subtotal( $tax ); } else { $subtotal_value = 0; $items = $document->order->get_items(); if( sizeof( $items ) > 0 ) { foreach( $items as $item ) { $item_price = $item['line_total']; if ( $tax == 'incl' ) { // follow WC settings for tax rounding if ( 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ) ) { $item_price += wc_round_tax_total( $item['line_tax'] ); } else { $item_price += $item['line_tax']; } } // allow rounding if ( apply_filters( 'wpo_wcpdf_templates_subtotal_round_line_items', false ) ) { $item_price = round( $item_price, 2 ); } $subtotal_value += $item_price; } } $subtotal_data = array( 'label' => __('Subtotal', 'woocommerce-pdf-invoices-packing-slips'), 'value' => $document->format_price( $subtotal_value ), ); $totals_table_data[$total_key] = (array) $total_setting + $subtotal_data; } break; } break; case 'discount': // $tax, $show_codes, $show_percentage, $breakdown_coupons if ( $discount = $document->get_order_discount( 'total', $tax ) ) { if ( isset( $discount['raw_value'] ) ) { // support for positive discount (=more expensive/price corrections) $discount['value'] = $document->format_price( $discount['raw_value'] * -1 ); } else { $discount['value'] = '-'.$discount['value']; } $discount['label'] = $raw_label = ! empty( $label ) ? $label : $discount['label']; unset($total_setting['label']); $discount_percentage = $this->get_discount_percentage( $document->order ); if (isset($show_percentage) && $discount_percentage) { $precision = apply_filters( 'wpo_wcpdf_discount_percentage_precision', 0 ); $discount_percentage = number_format( $discount_percentage, $precision, wc_get_price_decimal_separator(), '' ); $discount['label'] = "{$discount['label']} ({$discount_percentage}%)"; } if ( version_compare( WOOCOMMERCE_VERSION, '3.7', '<' ) ) { // backwards compatibility $used_coupons = implode(', ', $document->order->get_used_coupons() ); } else { $used_coupons = implode(', ', $document->order->get_coupon_codes() ); } // breakdown coupon discounts $coupons = $document->order->get_items( 'coupon' ); if ( isset( $breakdown_coupons ) && ! empty( $coupons ) ) { $round_precision = apply_filters( 'wpo_wcpdf_discount_round_precision', 2 ); $coupon_row = array(); $discount_value = isset( $discount['raw_value'] ) ? floatval( $discount['raw_value'] ) : floatval( $discount['value'] ); foreach( $coupons as $coupon_id => $coupon ) { $coupon_discount = floatval( $coupon->get_discount() ); $coupon_discount_tax = floatval( $coupon->get_discount_tax() ); $coupon_data = $coupon->get_meta( 'coupon_data' ); $coupon_value = ( $tax == 'incl' ) ? $coupon_discount + $coupon_discount_tax : $coupon_discount; $discount_value = round( $discount_value - $coupon_value, $round_precision ); // coupon row $coupon_row['label'] = $discount['label']; $coupon_row['label'] .= isset( $show_percentage ) && isset( $coupon_data['discount_type'] ) && $coupon_data['discount_type'] == 'percent' ? " ({$coupon_data['amount']}%)" : ''; $coupon_row['label'] .= isset( $show_codes ) ? " ({$coupon->get_code()})" : ''; $coupon_row['label'] = apply_filters( 'wpo_wcpdf_templates_coupon_total_label', $coupon_row['label'], $coupon, $document ); $coupon_row['value'] = $document->format_price( $coupon_value * -1 ); $totals_table_data[$total_key.$coupon_id] = (array) $total_setting + $coupon_row; } // fix $discount value removing coupon values if( $discount_value != 0 ) { $discount['value'] = $document->format_price( $discount_value * -1 ); // recalculate percentage $percentage = $this->get_discount_percentage( $document->order, $discount_value ); if( isset( $show_percentage ) && $percentage ) { $precision = apply_filters( 'wpo_wcpdf_discount_percentage_precision', 0 ); $percentage = number_format( $percentage, $precision, wc_get_price_decimal_separator(), '' ); $discount['label'] = "{$raw_label} ({$percentage}%)"; } } else { break; } } elseif ( isset( $show_codes ) && ! empty( $used_coupons ) ) { $discount['label'] = "{$discount['label']} ({$used_coupons})"; } $totals_table_data[$total_key] = (array) $total_setting + $discount; } break; case 'shipping': // $tax, $method, $hide_free $shipping_cost = $document->order->get_shipping_total(); if ( !(round( $shipping_cost, 3 ) == 0 && isset($hide_free)) ) { $totals_table_data[$total_key] = (array) $total_setting + $document->get_order_shipping( $tax ); if (!empty($method)) { $totals_table_data[$total_key]['value'] = $document->order->get_shipping_method(); } if ( strpos($totals_table_data[$total_key]['label'], '{{method}}') !== false ) { $totals_table_data[$total_key]['label'] = str_replace('{{method}}', $document->order->get_shipping_method(), $totals_table_data[$total_key]['label']); } } break; case 'fees': // $tax if ( $fees = $document->get_order_fees( $tax ) ) { // WooCommerce Checkout Add-Ons compatibility if ( function_exists('wc_checkout_add_ons')) { $wc_checkout_add_ons = wc_checkout_add_ons(); // we're adding a 'fee_' prefix because that's what woocommerce does in its // order total keys and wc_checkout_add_ons uses this to determine the total type (fee) $fees = $this->array_keys_prefix($fees, 'fee_', 'add'); if (method_exists($wc_checkout_add_ons, 'get_frontend_instance')) { $wc_checkout_add_ons_frontend = $wc_checkout_add_ons->get_frontend_instance(); $fees = $wc_checkout_add_ons_frontend->append_order_add_on_fee_meta( $fees, $document->order ); } elseif ( is_object(wc_checkout_add_ons()->frontend) && method_exists(wc_checkout_add_ons()->frontend, 'append_order_add_on_fee_meta') ) { $fees = wc_checkout_add_ons()->frontend->append_order_add_on_fee_meta( $fees, $document->order ); } $fees = $this->array_keys_prefix($fees, 'fee_', 'remove'); } reset($fees); $first = key($fees); end($fees); $last = key($fees); foreach( $fees as $fee_key => $fee ) { $class = 'fee-line'; if ($fee_key == $first) $class .= ' first'; if ($fee_key == $last) $class .= ' last'; $totals_table_data[$total_key.$fee_key] = (array) $total_setting + $fee; $totals_table_data[$total_key.$fee_key]['class'] = $class; } } break; case 'vat': // $percent, $base $total_tax = $document->order->get_total_tax(); $shipping_tax = $document->order->get_shipping_tax(); if ( isset ( $single_total ) ) { $tax = array(); // override label if set // unset($total_setting['label']); $tax['label'] = !empty($label) ? $label : __( 'VAT', 'wpo_wcpdf_templates' ); if ( isset($tax_type) && $tax_type == 'product' ) { $tax['value'] = $document->format_price( $total_tax - $shipping_tax ); } elseif ( isset($tax_type) && $tax_type == 'shipping' ) { $tax['value'] = $document->format_price( $shipping_tax ); } else { $tax['value'] = $document->format_price( $total_tax ); } $totals_table_data[$total_key] = (array) $total_setting + (array) $tax; $totals_table_data[$total_key]['class'] = 'vat tax-line'; } elseif ($taxes = $document->get_order_taxes()) { $taxes = $this->add_tax_base( $taxes, $document->order ); reset($taxes); $first = key($taxes); end($taxes); $last = key($taxes); foreach( $taxes as $tax_key => $tax ) { $class = 'tax-line'; if ($tax_key == $first) $class .= ' first'; if ($tax_key == $last) $class .= ' last'; // prepare label format based on settings $label_format = '{{label}}'; if (isset($percent)) $label_format .= ' {{rate}}'; // prevent errors if base not set if ( empty( $tax['base'] ) ) $tax['base'] = 0; // override label if set $tax_label = !empty($label) ? $label : $tax['label']; unset($total_setting['label']); if ( isset($tax_type) && $tax_type == 'product' ) { if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) && $tax['tax_amount'] == 0 ) { continue; } $tax_amount = $tax['tax_amount']; } elseif ( isset($tax_type) && $tax_type == 'shipping' ) { if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) && $tax['shipping_tax_amount'] == 0 ) { continue; } $tax_amount = $tax['shipping_tax_amount']; } else { $tax_amount = $tax['tax_amount'] + $tax['shipping_tax_amount']; if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) && $tax_amount == 0 ) { continue; } if (isset($base) && !empty($tax['base'])) $label_format .= ' ({{base}})'; // add base to label } $tax['value'] = $document->format_price( $tax_amount ); // fallback to tax calculation if we have no rate // if ( empty( $tax['rate'] ) && method_exists( $document, 'calculate_tax_rate' ) ) { // $tax['rate'] = $document->calculate_tax_rate( $tax['base'], $tax_amount ); // } $label_format = apply_filters( 'wpo_wcpdf_templates_tax_total_label_format', $label_format ); if ( isset( $tax['stored_rate'] ) ) { $tax_rate = $tax['stored_rate']; } else { $tax_rate = isset($tax['calculated_rate']) ? $tax['calculated_rate'] : null; } $tax['label'] = apply_filters( 'wpo_wcpdf_templates_tax_total_label', str_replace( array( '{{label}}', '{{rate}}', '{{base}}', '{{name}}', ), array( $tax_label, $tax_rate, $document->format_price( $tax['base'] ), $tax['label'], ), $label_format ), $tax, $document ); $totals_table_data[$total_key.$tax_key] = (array) $total_setting + $tax; $totals_table_data[$total_key.$tax_key]['class'] = $class; } } break; case 'vat_base': // $percent if ($taxes = $document->get_order_taxes()){ $taxes = $this->add_tax_base( $taxes, $document->order ); reset($taxes); $first = key($taxes); end($taxes); $last = key($taxes); if (empty($total_setting['label'])) { $total_setting['label'] = $label = __( 'Total ex. VAT', 'woocommerce-pdf-invoices-packing-slips' ); } foreach( $taxes as $tax_key => $tax ) { // prevent errors if base not set if ( empty( $tax['base'] ) ) continue; $class = 'tax-base-line'; if ($tax_key == $first) $class .= ' first'; if ($tax_key == $last) $class .= ' last'; // prepare label format based on settings $label_format = '{{label}}'; if (isset($percent)) $label_format .= ' ({{rate}})'; $label_format = apply_filters( 'wpo_wcpdf_templates_tax_base_total_label_format', $label_format, $tax ); $tax['value'] = $document->format_price( $tax['base'] ); $total_setting['label'] = str_replace( array( '{{label}}', '{{rate}}', '{{rate_label}}' ) , array( $label, $tax['rate'], $tax['label'] ), $label_format ); $totals_table_data[$total_key.$tax_key] = (array) $total_setting + $tax; $totals_table_data[$total_key.$tax_key]['class'] = $class; } } break; case 'total': // $tax if ( $tax == 'excl' && apply_filters( 'wpo_wcpdf_add_up_grand_total_excl', false ) ) { // alternative calculation method that adds up product prices, fees & shipping // rather than subtracting tax from the grand total => WC3.0+ only! $grand_total_ex = 0; foreach ( $document->order->get_items() as $item_id => $item ) { $grand_total_ex += $item->get_total(); // total = after discount! } foreach ( $document->order->get_fees() as $item_id => $item ) { $grand_total_ex += $item->get_total(); // total = after discount! } $grand_total_ex += $document->order->get_shipping_total(); $grand_total_row = array( 'label' => __( 'Total ex. VAT', 'woocommerce-pdf-invoices-packing-slips' ), 'value' => wc_price( $grand_total_ex, array( 'currency' => $document->order->get_currency() ) ), ); $totals_table_data[$total_key] = (array) $total_setting + $grand_total_row; } else { $totals_table_data[$total_key] = (array) $total_setting + $document->get_order_grand_total( $tax ); } if ( $tax == 'incl') { $totals_table_data[$total_key]['class'] = 'total grand-total'; } break; case 'order_weight': // $show_unit $order_weight = array ( 'label' => __( 'Total weight', 'wpo_wcpdf_templates' ), 'value' => $this->get_order_weight( $document->order, $document, isset( $show_unit) ), ); $totals_table_data[$total_key] = (array) $total_setting + $order_weight; break; case 'total_qty': $total_qty_total = array ( 'label' => __( 'Total quantity', 'wpo_wcpdf_templates' ), 'value' => $this->get_order_total_qty( $document->order, $document ), ); $totals_table_data[$total_key] = (array) $total_setting + $total_qty_total; break; case 'text': $static_text = array ( 'label' => isset( $total_setting['label'] ) ? $this->make_replacements( $total_setting['label'], $document->order, $document ) : '', 'value' => isset( $total_setting['text'] ) ? $this->make_replacements( $total_setting['text'], $document->order, $document ) : '', ); $totals_table_data[$total_key] = (array) $total_setting + $static_text; break; case 'custom_function': default: // third party total blocks pass the function to the block parameters, not the settings if ( $type !== 'custom_function' && ! empty( $customizer_total_blocks[$type]['callback'] ) ) { $total_setting['function'] = $customizer_total_blocks[$type]['callback']; } if ( ! empty ( $total_setting['function'] ) ) { $callback = is_string( $total_setting['function'] ) ? trim( $total_setting['function'] ) : $total_setting['function']; if ( is_callable( $callback ) ) { $class = is_string( $callback ) ? $callback : $type; $total_setting['class'] = sprintf( 'custom %s', sanitize_html_class( $class ) ); $custom_total = call_user_func( $callback, $document, $total_setting ); if ( is_array( $custom_total ) ) { // we only use the label from the function unset( $total_setting['label'] ); if ( isset( $custom_total['label'] ) && isset( $custom_total['value'] ) ) { // single row $totals_table_data[$total_key] = (array) $total_setting + $custom_total; } else { // assume multiple rows foreach ( $custom_total as $custom_total_key => $custom_total_row ) { if ( isset( $custom_total_row['label'] ) && isset( $custom_total_row['value'] ) ) { $totals_table_data["{$total_key}_{$custom_total_key}"] = (array) $total_setting + $custom_total_row; } } } } } } break; } } $totals_table_data = $this->exclude_hidden_products( $totals_table_data, $document ); // apply filters and defaults foreach ( $totals_table_data as $total_key => $total_setting ) { // set class if not set. note that fees and taxes have modified keys! if ( ! isset( $total_setting['class'] ) ) { $totals_table_data[ $total_key ]['class'] = $total_setting['type']; } $totals_table_data[ $total_key ] = apply_filters( 'wpo_wcpdf_templates_total_row_data', $totals_table_data[ $total_key ], $total_setting, $document ); } return $totals_table_data; } /** * Excludes virtual and downloadable products from the provided fields for a specific document. * * @param array $fields * @param \WPO\WC\PDF_Invoices\Documents\Order_Document_Methods $document * * @return array */ private function exclude_hidden_products( $fields, $document ) { if ( empty( $document ) || 'packing-slip' !== $document->get_type() ) { return $fields; } $packing_slip_settings = get_option( 'wpo_wcpdf_documents_settings_packing-slip' ); if ( ! isset( $packing_slip_settings['hide_virtual_products'] ) ) { return $fields; } $hidden_items_total = 0; foreach ( $fields as $field_key => $field_data ) { foreach ( $document->order->get_items() as $item ) { $product = $item->get_product(); if ( $product && ( $product->is_virtual() || $product->is_downloadable() ) ) { $hidden_items_total += $item->get_total() + $item->get_total_tax(); switch ( $field_data['type'] ) { case 'formatted_order_total_ex': case 'formatted_subtotal_ex': case 'vat_base': $fields[ $field_key ]['value'] = $this->decrement_total_value( $fields[ $field_key ]['value'], $item->get_total(), true ); break; case 'total': case 'subtotal': case 'formatted_order_total': case 'formatted_subtotal': $fields[ $field_key ]['value'] = $this->decrement_total_value( $fields[ $field_key ]['value'], $item->get_total() + $item->get_total_tax(), true ); break; case 'vat': $fields[ $field_key ]['value'] = $this->decrement_total_value( $fields[ $field_key ]['value'], $item->get_total_tax(), true ); break; case 'total_qty': case 'order_qty': $fields[ $field_key ]['value'] = $this->decrement_total_value( $fields[ $field_key ]['value'], $item->get_quantity() ); break; case 'order_weight': $fields[ $field_key ]['value'] = $this->decrement_total_value( $fields[ $field_key ]['value'], $product->get_weight() * $item->get_quantity() ); break; } } } if ( 'spelled_out_total' === $field_data['type'] && extension_loaded( 'intl' ) && class_exists( 'NumberFormatter' ) ) { $number_formatter = new \NumberFormatter( determine_locale(), \NumberFormatter::SPELLOUT ); $fields[ $field_key ]['value'] = $number_formatter->format( number_format( $document->order->get_total() - $hidden_items_total, wc_get_price_decimals() ) ); } } return $fields; } /** * Decrements a given value and returns the result. * If the value is formatted, it parses the HTML content and updates the price accordingly. * * @param string $field_value * @param string $decrement_value * @param bool $is_formatted * * @return string */ private function decrement_total_value( $field_value, $decrement_value, $is_formatted = false ) { if ( ! $is_formatted ) { return strval( floatval( $field_value ) - floatval( $decrement_value ) ); } $dom = new DOMDocument(); libxml_use_internal_errors( true ); if ( ! $dom->loadHTML( $field_value, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ) ) { return $field_value; } libxml_clear_errors(); $xpath = new DOMXPath( $dom ); $node_list = $xpath->query( '//span[@class="woocommerce-Price-amount amount"]' ); if ( $node_list->length <= 0 ) { return trim( $dom->saveHTML() ); } $bdi_nodes = $xpath->query( './bdi', $node_list->item( 0 ) ); if ( $bdi_nodes->length <= 0 ) { return trim( $dom->saveHTML() ); } foreach ( $bdi_nodes->item( 0 )->childNodes as $child_node ) { if ( $child_node->nodeType !== XML_TEXT_NODE ) { continue; } $node_value = $child_node->wholeText; if ( ! preg_match( '/(\d+[^0-9]*\d*)/', $node_value, $match ) && ! empty( $match ) ) { continue; } $current_total = floatval( str_replace( wc_get_price_decimal_separator(), '.', $match[0] ) ); $new_value = $current_total - floatval( $decrement_value ); $formatted_price_dom = new DOMDocument(); if ( function_exists( 'wcpdf_convert_encoding' ) ) { $formatted_price_dom->loadHTML( wcpdf_convert_encoding( wc_price( $new_value ) ), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ); } else { $formatted_price_dom->loadHTML( mb_convert_encoding( wc_price( $new_value ), 'HTML-ENTITIES', 'UTF-8' ), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD ); } $formatted_price_node = $dom->importNode( $formatted_price_dom->getElementsByTagName( 'bdi' )->item( 0 ), true ); $node_list->item( 0 )->replaceChild( $formatted_price_node, $node_list->item( 0 )->firstChild ); break; } return trim( $dom->saveHTML() ); } public function get_order_details_header ( $column_setting, $document ) { extract( $column_setting ); if ( ! empty( $label ) ) { $header['title'] = __( $label, 'woocommerce-pdf-invoices-packing-slips' ); // not proper gettext, but it makes it possible to reuse po translations! } else { switch ( $type ) { case 'position': $header['title'] = ''; break; case 'sku': $header['title'] = __( 'SKU', 'woocommerce-pdf-invoices-packing-slips' ); break; case 'thumbnail': $header['title'] = ''; break; case 'description': $header['title'] = __( 'Product', 'woocommerce-pdf-invoices-packing-slips' ); break; case 'quantity': $header['title'] = __( 'Quantity', 'woocommerce-pdf-invoices-packing-slips' ); break; case 'price': switch ($price_type) { case 'single': $header['title'] = __( 'Price', 'woocommerce-pdf-invoices-packing-slips' ); $header['class'] = 'price'; break; case 'total': $header['title'] = __( 'Total', 'woocommerce-pdf-invoices-packing-slips' ); $header['class'] = 'total'; break; } break; case 'regular_price': $header['title'] = __( 'Regular price', 'wpo_wcpdf_templates' ); break; case 'discount': $header['title'] = __( 'Discount', 'woocommerce-pdf-invoices-packing-slips' ); break; case 'vat': $header['title'] = ( isset( $split ) && isset( $title ) ) ? __( $title, 'wpo_wcpdf_templates' ) : __( 'VAT', 'wpo_wcpdf_templates' ); break; case 'tax_rate': $header['title'] = __( 'Tax rate', 'wpo_wcpdf_templates' ); break; case 'weight': $header['title'] = __( 'Weight', 'woocommerce-pdf-invoices-packing-slips' ); break; case 'dimensions': $header['title'] = __( 'Dimensions', 'wpo_wcpdf_templates' ); break; case 'product_attribute': $header['title'] = ''; break; case 'product_custom': $header['title'] = ''; break; case 'product_description': $header['title'] = __( 'Product description', 'wpo_wcpdf_templates' ); break; case 'product_categories': $header['title'] = __( 'Categories', 'wpo_wcpdf_templates' ); break; case 'all_meta': $header['title'] = __( 'Variation', 'wpo_wcpdf_templates' ); break; case 'item_meta': $header['title'] = isset( $meta_key ) ? $meta_key : ''; break; case 'cb': $header['title'] = ''; break; case 'static_text': $header['title'] = ''; break; case 'custom_function': $header['title'] = ''; break; default: $header['title'] = $type; break; } } // set class if not set; if ( ! isset( $header['class'] ) ) { $header['class'] = $type; } // column specific classes switch ( $type ) { case 'product_attribute': if ( ! empty( $attribute_name ) ) { $attribute_name_class = sanitize_html_class( $attribute_name ); $header['class'] = "{$type} {$attribute_name_class}"; } break; case 'product_custom': if ( ! empty( $field_name ) ) { $field_name_class = sanitize_html_class( $field_name ); $header['class'] .= " {$field_name_class}"; } break; case 'custom_function': if ( ! empty( $function ) ) { $function_class = sanitize_html_class( $function ); $header['class'] .= " {$function_class}"; } break; default: break; } // mark first and last column if ( isset( $position ) ) { $header['class'] .= " {$position}-column"; } return $header; } public function get_order_details_data ( $column_setting, $item, $document ) { extract( $column_setting ); $item_dependent_columns = array( 'regular_price', 'discount', 'dimensions', 'product_attribute', 'product_custom', 'product_description', 'product_categories', 'all_meta', 'item_meta', 'static_text', 'custom_function', ); if ( in_array( $type, $item_dependent_columns ) && empty( $item['item'] ) ) { $column[$type]['data'] = null; } else { switch ($type) { case 'position': $column['data'] = $line_number; break; case 'sku': $column['data'] = isset($item['sku']) ? $item['sku'] : ''; break; case 'thumbnail': $column['data'] = isset($item['thumbnail']) ? $item['thumbnail'] : ''; break; case 'description': // $show_sku, $show_weight, $show_meta, $show_external_plugin_meta, $custom_text ob_start(); ?>
order, false ); ?>
get_type(), $item, $document->order ); ?>
format_weight ( $item['weight'] ); ?>
get_type(), $item, $document->order ); ?>
order, false ); ?>
make_item_replacements( $custom_text, $item, $document ) ) ); ?>
1 ) { $column['class'] = "{$type} multiple"; } break; case 'price': // $price_type, $tax, $discount // using a combined value to make this more readable... $price_type_full = "{$price_type}_{$tax}_{$discount}"; switch ($price_type_full) { // before discount case 'single_incl_before': $column['data'] = $item['single_price']; break; case 'single_excl_before': $column['data'] = $item['ex_single_price']; break; case 'total_incl_before': $column['data'] = $item['price']; break; case 'total_excl_before': $column['data'] = $item['ex_price']; break; // after discount case 'single_incl_after': if ( ! empty( $item['item'] ) ) { $price = ( $item['item']['line_total'] + $item['item']['line_tax'] ) / max( 1, abs( $item['quantity'] ) ); $column['data'] = $document->format_price( $price ); } else { $column['data'] = ''; } break; case 'single_excl_after': $column['data'] = $item['single_line_total']; break; case 'total_incl_after': if ( ! empty( $item['item'] ) ) { $price = $item['item']['line_total'] + $item['item']['line_tax']; $column['data'] = $document->format_price( $price ); } else { $column['data'] = ''; } break; case 'total_excl_after': $column['data'] = $item['line_total']; break; } if ($price_type == 'total') { $column['class'] = 'total'; } break; case 'regular_price': // $price_type, $tax, $only_sale $regular_prices = $this->get_regular_item_price( $item['item'], $item['item_id'], $document->order ); // check if item price is different from sale price $single_item_price = ( $item['item']['line_subtotal'] + $item['item']['line_subtotal_tax'] ) / max( 1, $item['quantity'] ); if ( isset($only_sale) && round( $single_item_price, 2 ) == round( $regular_prices['incl'], 2 ) ) { $column['data'] = ''; } else { // get including or excluding tax $regular_price = $regular_prices[$tax]; // single or total if ($price_type == 'total') { $regular_price = (float) $regular_price * $item['quantity']; } $column['data'] = $document->format_price( $regular_price ); } break; case 'discount': // $price_type, $tax if ($price_type == 'percent') { $subtotal = $item['item']['line_subtotal'] + $item['item']['line_subtotal_tax']; if( $subtotal != 0 ) { $discount = $subtotal - ( $item['item']['line_total'] + $item['item']['line_tax'] ); if ($discount > 0) { $percent = ( $discount / $subtotal ) * 100; $precision = apply_filters( 'wpo_wcpdf_discount_percentage_precision', 0 ); $percent = number_format( $percent, $precision, wc_get_price_decimal_separator(), '' ); $column['data'] = "{$percent}%"; } else { $column['data'] = ""; } } else { $column['data'] = ""; } break; } $price_type = "{$price_type}_{$tax}"; switch ($price_type) { case 'single_incl': $price = ( ($item['item']['line_subtotal'] + $item['item']['line_subtotal_tax']) - ( $item['item']['line_total'] + $item['item']['line_tax'] ) ) / max( 1, abs( $item['quantity'] ) ); $column['data'] = $document->format_price( (float) $price * -1 ); break; case 'single_excl': $price = ( $item['item']['line_subtotal'] - $item['item']['line_total'] ) / max( 1, abs( $item['quantity'] ) ); $column['data'] = $document->format_price( (float) $price * -1 ); break; case 'total_incl': $price = ($item['item']['line_subtotal'] + $item['item']['line_subtotal_tax']) - ( $item['item']['line_total'] + $item['item']['line_tax'] ); $column['data'] = $document->format_price( (float) $price * -1 ); break; case 'total_excl': $price = $item['item']['line_subtotal'] - $item['item']['line_total']; $column['data'] = $document->format_price( (float) $price * -1 ); break; } break; case 'vat': $column['data'] = ''; // $price_type, $discount if ( isset( $price_type ) && isset( $discount ) ) { $price_type = "{$price_type}_{$discount}"; $split = isset( $split ) && is_array( $split ) ? $split : array(); $dash_for_zero = isset( $dash_for_zero ); $column['data'] = $this->get_item_vat_column_data( $price_type, $item, $document, $split, $dash_for_zero ); } break; case 'tax_rate': $show_name = isset( $show_tax_name ) ? true : false; if ( version_compare( WOOCOMMERCE_VERSION, '3.7', '<' ) || ! $show_name ) { $column['data'] = $item['tax_rates']; } else { $column['data'] = $this->get_item_tax_rate_name( $item['item'], $document->order ); } break; case 'weight': if ( !isset($qty) ) { $qty = 'single'; } switch ($qty) { case 'single': $column['data'] = !empty($item['weight']) ? $item['weight'] : ''; break; case 'total': $column['data'] = !empty($item['weight']) ? $item['weight'] * $item['quantity'] : ''; break; } if (isset($show_unit) && !empty($item['weight'])) { $column['data'] = $this->format_weight ( $column['data'] ); } break; case 'dimensions': $column['data'] = $this->get_product_dimensions( $item['product'] ); break; case 'product_attribute': if (isset($item['product'])) { $attribute_name_class = sanitize_title( $attribute_name ); $column['class'] = "{$type} {$attribute_name_class}"; $column['data'] = nl2br( wptexturize ( $document->get_product_attribute( $attribute_name, $item['product'] ) )); } else { $column['data'] = ''; } break; case 'product_custom': // setup $meta_key_class = sanitize_title( $field_name ); $column['class'] = "{$type} {$meta_key_class}"; $column['data'] = nl2br( wptexturize ( $this->get_product_custom_field( $item['product'], $field_name ) )); break; case 'product_description': $column['data'] = nl2br( wptexturize( $this->get_product_description( $item['product'], $description_type, isset( $use_variation_description ) ? 1 : null ) ) ); break; case 'product_categories': $column['data'] = $this->get_product_categories( $item['product'] ); break; case 'all_meta': // $product_fallback // For an order added through the admin we can display // the formatted variation data (if fallback enabled) if ( isset( $product_fallback ) && empty( $item['meta'] ) && isset( $item['product'] ) && $item['product']->get_type() == 'variation' && function_exists( 'wc_get_formatted_variation' ) ) { $item['meta'] = wc_get_formatted_variation( $item['product'], true ); } $column['data'] = ''.$item['meta'].''; break; case 'item_meta': // $field_name if ( !empty($field_name) ) { $column['data'] = nl2br( wptexturize ( $this->get_order_item_meta( $item, $field_name ) )); } else { $column['data'] = ''; } break; case 'cb': $column['data'] = ''; break; case 'static_text': // $text $column['data'] = !empty( $text ) ? nl2br( wptexturize( $this->make_item_replacements( $text, $item, $document ) ) ) : ''; break; case 'custom_function': $function_name = trim( $function ); if ( function_exists( $function_name ) ) { $column['data'] = nl2br( wptexturize ( call_user_func( $function_name, $item, $document ) ) ); } else { $column['data'] = ''; } break; default: $column['data'] = ''; break; } } // set class if not set; if (!isset($column['class'])) { $column['class'] = $type; } // mark first and last column if (isset($position)) { $column['class'] .= " {$position}-column"; } return apply_filters( 'wpo_wcpdf_templates_item_column_data', $column, $column_setting, $item, $document ); } /** * Output custom blocks (if set for template) */ public function custom_blocks_data( $template_type, $order = null ) { $custom_blocks = WPO_WCPDF_Templates()->settings->get_settings( $template_type, 'custom', null ); if ( ! empty($custom_blocks) ) { foreach ($custom_blocks as $key => $custom_block) { // echo "
";var_dump($custom_block);echo "
";die(); if ( current_filter() != $custom_block['position']) { continue; } // only process blocks with input if ( ( $custom_block['type'] == 'custom_field' || $custom_block['type'] == 'user_meta' ) && empty( $custom_block['meta_key'] ) ) { continue; } elseif ( $custom_block['type'] == 'text' && empty( $custom_block['text'] ) ) { continue; } switch ($custom_block['type']) { case 'custom_field': if ( empty( $order ) ) { continue 2; } if ( $this->check_custom_block_condition( $custom_block, $order ) == false ) { continue 2; } $class = $custom_block['meta_key']; // support for array data $array_key_position = strpos( $custom_block['meta_key'], '[' ); if ( $array_key_position !== false ) { $array_key = trim( substr( $custom_block['meta_key'], $array_key_position), "[]'"); $meta_key = strtok( $custom_block['meta_key'], '[' ); $array_data = $order->get_meta( $meta_key ); // parent order fallback if ( empty( $array_data ) && 'shop_order_refund' == $order->get_type() && is_callable( array( $order, 'get_parent_id' ) ) ) { $parent_order = wc_get_order( $order->get_parent_id() ); $array_data = $parent_order->get_meta( $meta_key ); } if ( is_array( $array_data ) && ! empty( $array_data[$array_key] ) ) { $data = $array_data[$array_key]; break; } } $data = $order->get_meta( $custom_block['meta_key'] ); // parent order fallback if ( empty( $data ) && 'shop_order_refund' == $order->get_type() && is_callable( array( $order, 'get_parent_id' ) ) ) { $parent_order = wc_get_order( $order->get_parent_id() ); $data = $parent_order->get_meta( $custom_block['meta_key'] ); } // format date fields with WC format automatically $data = $this->maybe_format_date_field( $data, $custom_block['meta_key'] ); // format array data if ( is_array( $data ) ) { $data_strings = array(); foreach ($data as $key => $value) { if ( !is_array($value) && !is_object($value) ) { $data_strings[] = "$key: $value"; } } $data = implode(', ', $data_strings); } // WC3.0+ fallback to properties $property = str_replace('-', '_', sanitize_title( ltrim( $custom_block['meta_key'], '_' ) ) ); if ( empty( $data ) && is_callable( array( $order, "get_{$property}" ) ) ) { $data = $order->{"get_{$property}"}( 'view' ); } break; case 'user_meta': if ( empty( $order ) ) { continue 2; } if ( $this->check_custom_block_condition( $custom_block, $order ) == false ) { continue 2; } if ( 'shop_order_refund' == $order->get_type() && is_callable( array( $order, 'get_parent_id' ) ) ) { $parent_order = wc_get_order( $order->get_parent_id() ); $user_id = $parent_order->get_user_id(); } else { $user_id = $order->get_user_id(); } if ( ! empty( $user_id ) ) { $meta_key = $custom_block['meta_key']; $user_properties = array( 'user_login', 'user_nicename', 'user_email', 'user_url', 'user_registered', 'user_status', 'display_name', ); // check properties first if ( in_array( $meta_key, $user_properties ) && $user = get_user_by( 'id', $user_id ) ) { $data = $user->{"$meta_key"}; } else { $data = get_user_meta( $user_id, $meta_key, true ); } } else { $data = ''; } $class = $custom_block['meta_key']; break; case 'text': if ( !empty( $order ) && $this->check_custom_block_condition( $custom_block, $order ) == false ) { continue 2; } if ( !empty( $order ) ) { $document = wcpdf_get_document( $template_type, $order ); $formatted_text = $this->make_replacements( $custom_block['text'], $order, $document ); } else { $formatted_text = $custom_block['text']; } if ( empty( $custom_block['html_mode'] ) ) { $data = nl2br( wptexturize( $formatted_text ) ); } else { $data = $formatted_text; } $class = 'custom-block-text'; break; } // Hide if empty option if ( !empty($custom_block['hide_if_empty']) ) { if ( $custom_block['type'] == 'text' && empty( strip_tags( $data ) ) ) { continue; } elseif ( $custom_block['type'] != 'text' && empty( $data ) ){ continue; } } // output table rows if in order data table if ( in_array( current_filter(), array( 'wpo_wcpdf_before_order_data', 'wpo_wcpdf_after_order_data') ) ) { printf('%s%s', $class, $custom_block['label'], $data ); } else { if (!empty($custom_block['label'])) { printf('

%s

', $class, $custom_block['label'] ); } // only apply div wrapper if not already in div if ( stripos($data, '%s', $class, $data ); } } }; } } public function check_custom_block_condition( $custom_block, $order ) { // we're always checking against the parent order data for refunds if ( $order->get_type() == 'shop_order_refund' ) { $order = wc_get_order( $order->get_parent_id() ); } // var_dump( !empty($custom_block['order_statuses']) && is_array($custom_block['order_statuses']) );die(); // Order status if ( !empty($custom_block['order_statuses']) && is_array($custom_block['order_statuses']) && is_callable(array($order,'get_status')) ) { // Standardise status names (make sure wc-prefix is used) $order_status = 'wc-' === substr( $order->get_status(), 0, 3 ) ? $order->get_status() : 'wc-' . $order->get_status(); if ( !in_array($order_status, $custom_block['order_statuses']) ) { return false; } } // Payment Method if ( !empty($custom_block['payment_methods']) && is_array($custom_block['payment_methods']) && is_callable(array($order,'get_payment_method')) ) { if ( !in_array($order->get_payment_method(), $custom_block['payment_methods']) ) { return false; } } // Billing Country if ( !empty($custom_block['billing_country']) && is_array($custom_block['billing_country']) && is_callable(array($order,'get_billing_country')) ) { if ( !in_array($order->get_billing_country(), $custom_block['billing_country']) ) { return false; } } // Shipping Country if ( !empty($custom_block['shipping_country']) && is_array($custom_block['shipping_country']) && is_callable(array($order,'get_shipping_country')) ) { if ( !in_array($order->get_shipping_country(), $custom_block['shipping_country']) ) { return false; } } // VAT reverse charge if ( !empty($custom_block['vat_reverse_charge']) ) { $is_eu_vat = in_array( $order->get_billing_country(), WC()->countries->get_european_union_countries( 'eu_vat' ) ); if ( $is_eu_vat && $order->get_total() > 0 && $order->get_total_tax() == 0 ) { // Try fetching VAT Number from meta $vat_meta_keys = array ( '_vat_number', // WooCommerce EU VAT Number '_billing_vat_number', // WooCommerce EU VAT Number 2.3.21+ 'VAT Number', // WooCommerce EU VAT Compliance '_eu_vat_evidence', // Aelia EU VAT Assistant '_billing_eu_vat_number', // EU VAT Number for WooCommerce (WP Whale/former Algoritmika) 'yweu_billing_vat', // YITH WooCommerce EU VAT 'billing_vat', // German Market '_billing_vat_id', // Germanized Pro '_shipping_vat_id', // Germanized Pro (alternative) ); foreach ($vat_meta_keys as $meta_key) { if ( $vat_number = $order->get_meta( $meta_key ) ) { // Aelia EU VAT Assistant stores the number in a multidimensional array if ($meta_key == '_eu_vat_evidence' && is_array($vat_number)) { $vat_number = !empty($vat_number['exemption']['vat_number']) ? $vat_number['exemption']['vat_number'] : ''; } break; } } } // if we got here and we don't have a VAT number, // this is NOT a 0 tax order from the EU either if ( ! apply_filters( 'wpo_wcpdf_vat_reverse_charge_order', ! empty( $vat_number ), $order ) ) { return false; } } // 's all good man return apply_filters( 'wpo_wcpdf_custom_block_condition', true, $custom_block, $order ); } public function settings_fields_replacements( $text, $document ) { // make replacements if placeholders present if ( strpos( $text, '{{' ) !== false ) { $text = $this->make_replacements( $text, $document->order, $document ); } return $text; } public function make_replacements ( $text, $order, $document = null ) { // load parent order for refunds if ( 'shop_order_refund' == $order->get_type() && is_callable( array( $order, 'get_parent_id' ) ) ) { $parent_order = wc_get_order( $order->get_parent_id() ); } $text = ! is_null( $text ) ? $text : ''; // make an index of placeholders used in the text preg_match_all('/\{\{.*?\}\}/', $text, $placeholders_used); $placeholders_used = array_shift($placeholders_used); // we only need the first match set // load countries & states $countries = new WC_Countries; // loop through placeholders and make replacements foreach ($placeholders_used as $placeholder) { $placeholder_clean = trim($placeholder,"{{}}"); $ignore = array( '{{PAGE_NUM}}', '{{PAGE_COUNT}}' ); if (in_array($placeholder, $ignore)) { continue; } // first try to read data from order, fallback to parent order (for refunds) $data_sources = array( 'order', 'parent_order' ); foreach ($data_sources as $data_source) { if (empty($$data_source)) { continue; } // custom/third party filters if ( strpos($placeholder_clean, '|') !== false ) { $filter = "wpo_wcpdf_templates_replace_".sanitize_title( substr( $placeholder_clean, 0, strpos($placeholder_clean, '|') ) ); } else { $filter = "wpo_wcpdf_templates_replace_".sanitize_title( $placeholder_clean ); } if ( has_filter( $filter ) ) { $custom_filtered = ''; // we always want to replace these tags, regardless of errors/output ob_start(); // in case a plugin outputs data instead of returning it try { $custom_filtered = apply_filters( $filter, $custom_filtered, $$data_source, $placeholder_clean ); } catch (\Throwable $e) { // For PHP 7 if (function_exists('wcpdf_log_error')) { wcpdf_log_error( $e->getMessage(), 'critical', $e ); } } catch (\Exception $e) { // For PHP 5 if (function_exists('wcpdf_log_error')) { wcpdf_log_error( $e->getMessage(), 'critical', $e ); } } ob_get_clean(); $text = str_replace($placeholder, $custom_filtered, $text); continue 2; } // special treatment for country & state $country_placeholders = array( 'shipping_country', 'billing_country' ); $state_placeholders = array( 'shipping_state', 'billing_state' ); foreach ( array_merge( $country_placeholders, $state_placeholders ) as $country_state_placeholder ) { if ( strpos( $placeholder_clean, $country_state_placeholder ) !== false ) { // check if formatting is needed if ( strpos( $placeholder_clean, '_code' ) !== false ) { // no country or state formatting $placeholder_clean = str_replace( '_code', '', $placeholder_clean ); $format = false; } else { $format = true; } $country_or_state = call_user_func( array( $$data_source, "get_{$placeholder_clean}" ) ); if ( $format === true ) { // format country or state if ( in_array( $placeholder_clean, $country_placeholders ) ) { $country_or_state = ( $country_or_state && isset( $countries->countries[ $country_or_state ] ) ) ? $countries->countries[ $country_or_state ] : $country_or_state; } elseif ( in_array( $placeholder_clean, $state_placeholders ) ) { // get country for address $callback = 'get_'.str_replace( 'state', 'country', $placeholder_clean ); $country = call_user_func( array( $$data_source, $callback ) ); $country_or_state = ( $country && $country_or_state && isset( $countries->states[ $country ][ $country_or_state ] ) ) ? $countries->states[ $country ][ $country_or_state ] : $country_or_state; } } if ( ! empty( $country_or_state ) ) { $text = str_replace( $placeholder, $country_or_state, $text ); continue 3; } } } // date offset placeholders if ( strpos( $placeholder_clean, '|+' ) !== false ) { $calculated_date = ''; $placeholder_args = explode( '|+', $placeholder_clean ); if ( ! empty( $placeholder_args[1] ) ) { $date_name = $placeholder_args[0]; $date_offset = $placeholder_args[1]; switch ( $date_name ) { case 'order_date': $order_date = $$data_source->get_date_created(); $date_format = function_exists( 'wcpdf_date_format' ) ? wcpdf_date_format( $document, 'order_date_created' ) : wc_date_format(); $calculated_date = date_i18n( $date_format, strtotime( $order_date->date_i18n('Y-m-d H:i:s') . " + {$date_offset}") ); break; case 'invoice_date': $invoice_date_set = $$data_source->get_meta( '_wcpdf_invoice_date' ); // prevent creating invoice date when not already set if ( ! empty( $invoice_date_set ) && ! empty( $document ) ) { $invoice_date = $document->get_date( 'invoice' ); $date_format = function_exists( 'wcpdf_date_format' ) ? wcpdf_date_format( $document, 'invoice_date' ) : wc_date_format(); $calculated_date = date_i18n( $date_format, strtotime( $invoice_date->date_i18n('Y-m-d H:i:s') . " + {$date_offset}" ) ); } break; } } if ( ! empty( $calculated_date ) ) { $text = str_replace( $placeholder, $calculated_date, $text ); continue 2; } } // Custom placeholders $custom = ''; switch ($placeholder_clean) { case 'invoice_number': if (!empty($document)) { $custom = $document->get_invoice_number(); } break; case 'invoice_date': $invoice_date = $$data_source->get_meta( '_wcpdf_invoice_date' ); // prevent creating invoice date when not already set if (!empty($invoice_date) && !empty($document)) { $custom = $document->get_invoice_date(); } break; case 'invoice_notes': if ( ! empty( $document ) ) { if( $document->get_type() != 'invoice' ) { $invoice = wcpdf_get_invoice( $order ); if( ! empty( $invoice ) && is_callable( array( $invoice, 'get_document_notes' ) ) ) { $custom = $invoice->get_document_notes(); } } elseif( is_callable( array( $document, 'get_document_notes' ) ) ) { $custom = $document->get_document_notes(); } } break; case 'document_notes': if ( ! empty( $document ) && is_callable( array( $document, 'get_document_notes' ) ) ) { $custom = $document->get_document_notes(); } break; case 'document_number': if (!empty($document)) { if ( $number = $document->get_number() ) { $custom = $number->get_formatted(); } } break; case 'document_date': if (!empty($document)) { if ( $date = $document->get_date() ) { $date_format = function_exists( 'wcpdf_date_format' ) ? wcpdf_date_format( $document, 'document_date' ) : wc_date_format(); $custom = $date->date_i18n( $date_format ); } } break; case 'site_title': $custom = get_bloginfo(); break; case 'shipping_notes': case 'customer_notes': case 'customer_note': $custom = $$data_source->get_customer_note(); if (!empty($custom)) { $custom = wpautop( wptexturize( $custom ) ); } break; case 'order_notes': $custom = $this->get_order_notes( $$data_source ); break; case 'private_order_notes': $custom = $this->get_order_notes( $$data_source, 'private' ); break; case 'order_number': if ( method_exists( $$data_source, 'get_order_number' ) ) { $custom = ltrim($$data_source->get_order_number(), '#'); } break; case 'order_status': $custom = wc_get_order_status_name( $$data_source->get_status() ); break; case 'payment_status': if ( is_callable( array( $$data_source, 'is_paid' ) ) ) { $custom = $$data_source->is_paid() ? __( 'Paid', 'wpo_wcpdf_templates' ) : __( 'Unpaid', 'wpo_wcpdf_templates' ); } break; case 'order_date': $order_date = $$data_source->get_date_created(); $date_format = function_exists( 'wcpdf_date_format' ) ? wcpdf_date_format( $document, 'order_date_created' ) : wc_date_format(); $custom = $order_date->date_i18n( $date_format ); break; case 'order_time': $order_date = $$data_source->get_date_created(); $custom = $order_date->date_i18n( wc_time_format() ); break; case 'order_weight': $custom = $this->get_order_weight( $$data_source, $document ); break; case 'order_qty': $custom = $this->get_order_total_qty( $$data_source, $document ); break; case 'date_paid': case 'paid_date': case 'time_paid': case 'paid_time': case 'date_completed': case 'completed_date': $custom = $this->get_date( $placeholder_clean, $$data_source ); break; case 'current_date': $date_format = function_exists( 'wcpdf_date_format' ) ? wcpdf_date_format( $document, 'current_date' ) : wc_date_format(); $custom = date_i18n( $date_format ); break; case 'payment_method_title': $custom = $document->get_payment_method(); break; case 'payment_method_description': if ( $payment_gateway = wc_get_payment_gateway_by_order( $$data_source ) ) { $custom = $payment_gateway->get_description(); } break; case 'payment_method_instructions': if ( $payment_gateway = wc_get_payment_gateway_by_order( $$data_source ) ) { if ( isset( $payment_gateway->instructions ) ) { $custom = $payment_gateway->instructions; } } break; case 'payment_method_thankyou_page_text': if ( $payment_gateway = wc_get_payment_gateway_by_order( $$data_source ) ) { if ( method_exists( $payment_gateway, 'thankyou_page' ) ) { ob_start(); $payment_gateway->thankyou_page( $$data_source->get_id() ); $custom = ob_get_clean(); if (!empty($custom)) { $custom = str_replace( PHP_EOL, '', $custom ); } } } break; case 'used_coupons': if ( version_compare( WOOCOMMERCE_VERSION, '3.7', '<' ) ) { // backwards compatibility $custom = implode(', ', $$data_source->get_used_coupons() ); } else { $custom = implode(', ', $$data_source->get_coupon_codes() ); } $text = str_replace($placeholder, $custom, $text); continue 3; // do not fallback to parent order case 'current_user_name': $user = wp_get_current_user(); if ( $user instanceof \WP_User ) { $custom = $user->display_name; } break; case 'formatted_order_total': if (!empty($document)) { $grand_total = $document->get_order_grand_total('incl'); $custom = $grand_total['value']; } break; case 'formatted_subtotal': if (!empty($document)) { $subtotal = $document->get_order_subtotal('incl'); $custom = $subtotal['value']; } break; case 'formatted_discount': if (!empty($document)) { $discount = $document->get_order_discount('total', 'incl'); $custom = isset($discount['value']) ? $discount['value'] : ''; } break; case 'formatted_shipping': if (!empty($document)) { $shipping = $document->get_order_shipping('incl'); $custom = $shipping['value']; } break; case 'formatted_order_total_ex': if (!empty($document)) { $grand_total = $document->get_order_grand_total('excl'); $custom = $grand_total['value']; } break; case 'formatted_subtotal_ex': if (!empty($document)) { $subtotal = $document->get_order_subtotal('excl'); $custom = $subtotal['value']; } break; case 'formatted_discount_ex': if (!empty($document)) { $discount = $document->get_order_discount('total', 'excl'); $custom = isset($discount['value']) ? $discount['value'] : ''; } break; case 'formatted_shipping_ex': if (!empty($document)) { $shipping = $document->get_order_shipping('excl'); $custom = $shipping['value']; } break; case 'document_barcode': if ( ! empty( $document ) && function_exists( 'wcub_get_barcode' ) ) { $barcode = wcub_get_barcode( $document ); if( $barcode->exists() ) { $custom = '
'.$barcode->get_output().'
'; } } break; case 'order_barcode': if ( ! empty( $$data_source ) && function_exists( 'wcub_get_barcode' ) ) { $barcode = wcub_get_barcode( $$data_source ); if( $barcode->exists() ) { $custom = '
'.$barcode->get_output().'
'; } } break; case 'wc_order_barcode': $barcode_text = $$data_source->get_meta( '_barcode_text' ); if ( function_exists( 'WC_Order_Barcodes' ) && ! empty( $barcode_text ) ) { if ( is_callable( array( WC_Order_Barcodes(), 'barcode_url' ) ) ) { $src = WC_Order_Barcodes()->barcode_url( $$data_source->get_id() ); } else { $src = trailingslashit( get_site_url() ) . '?wc_barcode=' . $barcode_text; } if ( apply_filters( 'wpo_wcpdf_templates_wc_order_barcodes_use_http', false ) ) { $src = str_replace( 'https://', 'http://', $src ); } if ( apply_filters( 'wpo_wcpdf_templates_wc_order_barcodes_prefetch_image_data', true ) ) { try { $barcode_request = wp_remote_get( $src ); if ( $barcode_request && ! is_wp_error( $barcode_request ) && $barcode_image_data = wp_remote_retrieve_body( $barcode_request ) ) { $src = sprintf( 'data:image/png;base64,%s', base64_encode( $barcode_image_data ) ); } } catch (\Throwable $th) { wcpdf_log_error( 'Error trying to fetch barcode data: ' . $th->getMessage(), 'critical', $th ); } } if ( WC_Order_Barcodes()->barcode_type == 'qr' ) { $css = 'height: 40mm; width: 40mm; position:relative'; } else { $css = 'height: 10mm; width: 40mm; overflow:hidden; position:relative'; } $custom = sprintf('
%s
', $css, $src, $barcode_text ); } break; case 'local_pickup_plus_pickup_details': $custom = $this->get_local_pickup_plus_pickup_details( $$data_source ); break; case 'wpo_wcpdf_shop_name': if (!empty($document)) { $custom = $document->get_shop_name(); } break; case 'wpo_wcpdf_shop_address': if ( ! empty( $document ) ) { $custom = $document->get_shop_address(); } break; case 'wpo_wcpdf_footer': if ( ! empty( $document ) ) { $custom = $document->get_footer(); } break; case 'wpo_wcpdf_extra_1': if ( ! empty( $document ) ) { $custom = $document->get_extra_1(); } break; case 'wpo_wcpdf_extra_2': if ( ! empty( $document ) ) { $custom = $document->get_extra_2(); } break; case 'wpo_wcpdf_extra_3': if ( ! empty( $document ) ) { $custom = $document->get_extra_3(); } break; case 'spelled_out_total': if( extension_loaded( 'intl' ) && class_exists( 'NumberFormatter' ) ) { $number_formatter = new \NumberFormatter( determine_locale(), \NumberFormatter::SPELLOUT ); $custom = $number_formatter->format( $document->order->get_total() ); } break; case 'checkout_payment_url': case 'payment_url': if (is_callable(array($$data_source,'get_checkout_payment_url'))) { $custom = $$data_source->get_checkout_payment_url(); } break; case 'customer_order_count': if ( is_callable( array( $$data_source, 'get_customer_id' ) ) && function_exists( 'wc_get_customer_order_count' ) && ! empty( $customer_id = $$data_source->get_customer_id() ) ) { $custom = wc_get_customer_order_count( $customer_id ); } break; case 'customer_total_spent': if ( is_callable( array( $$data_source, 'get_customer_id' ) ) && function_exists( 'wc_get_customer_total_spent' ) && ! empty( $customer_id = $$data_source->get_customer_id() ) ) { $spent = wc_get_customer_total_spent( $customer_id ); $custom = $document->format_price( $spent ); } break; case 'customer_registered_date': if ( is_callable( array( $$data_source, 'get_user' ) ) && function_exists( 'wc_string_to_datetime' ) && ! empty( $user = $$data_source->get_user() ) ) { $registered_date = wc_string_to_datetime( $user->user_registered ); $date_format = function_exists( 'wcpdf_date_format' ) ? wcpdf_date_format( $document, 'customer_registered_date' ) : wc_date_format(); $custom = $registered_date->date_i18n( $date_format ); } break; case 'ABSPATH': if ( defined( 'ABSPATH' ) ) { $custom = ABSPATH; } break; case 'WP_CONTENT_DIR': if ( defined( 'WP_CONTENT_DIR' ) ) { $custom = WP_CONTENT_DIR; } break; default: break; } if ( ! empty( $custom ) ) { $custom_with_excluded_hidden = $this->exclude_hidden_products( array( array( 'type' => trim( $placeholder, '{}' ), 'value' => $custom ) ), $document ); $text = str_replace( $placeholder, $custom_with_excluded_hidden[0]['value'], $text ); continue 2; } // Order Properties if ( in_array( $placeholder_clean, array( 'shipping_address', 'billing_address' ) ) ) { $placeholder_clean = "formatted_{$placeholder_clean}"; } $property_meta_keys = array( '_order_currency' => 'currency', '_order_tax' => 'total_tax', '_order_total' => 'total', '_order_version' => 'version', '_order_shipping' => 'shipping_total', '_order_shipping_tax' => 'shipping_tax', ); if ( in_array( $placeholder_clean, array_keys( $property_meta_keys ) ) ) { $property_name = $property_meta_keys[$placeholder_clean]; } else { $property_name = str_replace( '-', '_', sanitize_title( ltrim( $placeholder_clean, '_' ) ) ); } if ( is_callable( array( $$data_source, "get_{$property_name}" ) ) ) { $prop = call_user_func( array( $$data_source, "get_{$property_name}" ) ); if ( ! empty( $prop ) ) { $text = str_replace( $placeholder, $prop, $text ); continue 2; } } // Order Meta if ( ! $this->is_order_prop( $placeholder_clean ) ) { $meta = $$data_source->get_meta( $placeholder_clean ); if ( ! empty( $meta ) ) { // format date fields with WC format automatically $meta = $this->maybe_format_date_field( $meta, $placeholder_clean ); $text = str_replace( $placeholder, $meta, $text ); continue 2; } else { // Fallback to hidden meta $meta = $$data_source->get_meta( "_{$placeholder_clean}" ); if ( ! empty( $meta ) ) { $text = str_replace( $placeholder, $meta, $text ); continue 2; } } } } // remove placeholder if no replacement was made $text = str_replace( $placeholder, '', $text ); } return $text; } public function maybe_format_date_field( $date_value, $meta_key ) { $known_date_fields = apply_filters( 'wpo_wcpdf_templates_format_date_fields', array( '_local_pickup_time_select', // WooCommerce Local Pickup Time Select - array with timestamp 'ywcdd_order_delivery_date', // YITH WooCommerce Delivery Date Premium '_orddd_timestamp', // Order Delivery Date Pro (Tyche) '_orddd_lite_timestamp', // Order Delivery Date Lite (Tyche) '_delivery_date', // WooCommerce Order Delivery ... or generic ) ); if ( in_array( $meta_key, $known_date_fields ) ) { if ( $meta_key == '_local_pickup_time_select' && is_array( $date_value ) ) { $date_value = array_shift( $date_value ); } // could be timestamp or formatted date if ( is_numeric( $date_value ) && intval( $date_value ) > 30000101 ) { // avoid colision with Ymd date strings until January 1st, 3000 $timestamp = intval( $date_value ); } elseif ( is_string( $date_value ) ) { $timestamp = strtotime( $date_value ); } else { // not something we can use return $date_value; } // sanity check (party like it's 1999, huh?) if ( $timestamp > strtotime( '1999-12-31' ) ) { $wcpdf_date_format = function_exists( 'wcpdf_date_format' ) ? wcpdf_date_format() : wc_date_format(); // determine whether to include time in formatted date (if the original format had it) if ( $meta_key == '_local_pickup_time_select' || ( !is_numeric( $date_value ) && strpos( (string) $date_value, ':' ) !== false ) ) { $date_format = $wcpdf_date_format . ' ' . wc_time_format(); } else { $date_format = $wcpdf_date_format; } $date_value = date_i18n( apply_filters( 'wpo_wcpdf_templates_date_field_format', $date_format ), $timestamp ); } } return $date_value; } public function is_order_prop( $key ) { // Taken from WC class $order_props = array( // Abstract order props 'parent_id', 'status', 'currency', 'version', 'prices_include_tax', 'date_created', 'date_modified', 'discount_total', 'discount_tax', 'shipping_total', 'shipping_tax', 'cart_tax', 'total', 'total_tax', // Order props 'customer_id', 'order_key', 'billing_first_name', 'billing_last_name', 'billing_company', 'billing_address_1', 'billing_address_2', 'billing_city', 'billing_state', 'billing_postcode', 'billing_country', 'billing_email', 'billing_phone', 'shipping_first_name', 'shipping_last_name', 'shipping_company', 'shipping_address_1', 'shipping_address_2', 'shipping_city', 'shipping_state', 'shipping_postcode', 'shipping_country', 'payment_method', 'payment_method_title', 'transaction_id', 'customer_ip_address', 'customer_user_agent', 'created_via', 'customer_note', 'date_completed', 'date_paid', 'cart_hash', ); return in_array($key, $order_props); } public function make_item_replacements( $text, $item, $document ) { // make replacements if placeholders present if ( strpos( $text, '{{' ) === false ) { return $text; } // make an index of placeholders used in the text preg_match_all('/\{\{.*?\}\}/', $text, $placeholders_used); $placeholders_used = array_shift($placeholders_used); // we only need the first match set // loop through placeholders and make replacements foreach ($placeholders_used as $placeholder) { $replacement = null; $placeholder_clean = trim($placeholder,"{{}}"); // custom product field placeholders if ( strpos($placeholder_clean, 'product_custom_field::') !== false ) { $meta_key = trim(str_replace('product_custom_field::', '', $placeholder_clean)); if (!empty($meta_key)) { $replacement = $this->get_product_custom_field( $item['product'], $meta_key ); if (!empty($replacement)) { $text = str_replace($placeholder, $replacement, $text); continue; } } } // custom product field placeholders if ( strpos($placeholder_clean, 'item_meta::') !== false ) { $meta_key = trim(str_replace('item_meta::', '', $placeholder_clean)); if (!empty($meta_key)) { $replacement = $this->get_order_item_meta( $item, $meta_key ); if (!empty($replacement)) { $text = str_replace($placeholder, $replacement, $text); continue; } } } // product attribute placeholders if ( strpos($placeholder_clean, 'product_attribute::') !== false && !empty( $item['product'] ) ) { $attribute_name = trim(str_replace('product_attribute::', '', $placeholder_clean)); if (!empty($attribute_name)) { $replacement = $document->get_product_attribute( $attribute_name, $item['product'] ); if (!empty($replacement)) { $text = str_replace($placeholder, $replacement, $text); continue; } } } switch ($placeholder_clean) { case 'item_id': $replacement = $item['item_id'] != 0 ? $item['item_id'] : ''; break; case 'product_id': $replacement = $item['product_id'] != 0 ? $item['product_id'] : ''; break; case 'variation_id': $replacement = $item['variation_id'] != 0 ? $item['variation_id'] : ''; break; case 'product_description': $replacement = $this->get_product_description( $item['product'] ); break; case 'product_description_short': $replacement = $this->get_product_description( $item['product'], 'short' ); break; case 'product_description_long': $replacement = $this->get_product_description( $item['product'], 'long' ); break; case 'product_description_variation': $replacement = $this->get_product_description( $item['product'], 'variation' ); break; case 'product_categories': $replacement = $this->get_product_categories( $item['product'] ); break; case 'product_tags': $replacement = $this->get_product_tags( $item['product'] ); break; case 'purchase_note': $replacement = $this->get_product_purchase_note( $item['product'] ); break; case 'product_dimensions': $replacement = $this->get_product_dimensions( $item['product'] ); break; case 'product_length': $replacement = is_callable( array( $item['product'], 'get_length' ) ) ? wc_format_dimensions( array( $item['product']->get_length() ) ) : ''; break; case 'product_width': $replacement = is_callable( array( $item['product'], 'get_width' ) ) ? wc_format_dimensions( array( $item['product']->get_width() ) ) : ''; break; case 'product_height': $replacement = is_callable( array( $item['product'], 'get_height' ) ) ? wc_format_dimensions( array( $item['product']->get_height() ) ) : ''; break; case 'product_weight': $replacement = is_callable( array( $item['product'], 'get_weight' ) ) ? wc_format_weight( $item['product']->get_weight() ) : ''; break; case 'sale_price_discount_excl_tax': $replacement = $this->get_sale_price_discount( $item['item'], $item['item_id'], $document->order, 'price_excl_tax' ); break; case 'sale_price_discount_incl_tax': $replacement = $this->get_sale_price_discount( $item['item'], $item['item_id'], $document->order, 'price_incl_tax' ); break; case 'sale_price_discount_percent': $replacement = $this->get_sale_price_discount( $item['item'], $item['item_id'], $document->order, 'percent' ); break; case 'wc_brands': $replacement = $this->get_product_brands( $item['product'] ); break; case 'sku': $replacement = $item['sku']; break; case 'wpo_batch_number': if ( function_exists( 'wpo_wcpbn_get_item_batch_numbers' ) ) { $replacement = wpo_wcpbn_get_item_batch_numbers( $item['item'] ); } break; case 'wpo_batch_expiry_date': if ( function_exists( 'wpo_wcpbn_get_item_batch_expiry_dates' ) ) { $replacement = wpo_wcpbn_get_item_batch_expiry_dates( $item['item'] ); } break; case 'product_barcode': if ( function_exists( 'wcub_get_barcode' ) ) { $barcode = wcub_get_barcode( $item['product'] ); if( $barcode->exists() ) { $replacement = '
'.$barcode->get_output().'
'; } } break; } $replacement = apply_filters( 'wpo_wcpdf_custom_item_placeholder_' . sanitize_title( $placeholder_clean ), $replacement, $text, $item, $document ); if (!empty($replacement)) { $text = str_replace($placeholder, $replacement, $text); continue; } // remove placeholder if no replacement was made $text = str_replace($placeholder, '', $text); } return $text; } public function get_product_custom_field( $product, $meta_key ) { if ( isset( $product ) && ! empty( $meta_key ) ) { // backwards compatible meta keys of properties $property_meta_keys = array( '_stock' => 'stock_quantity', ); $property = in_array( $meta_key, array_keys( $property_meta_keys ) ) ? $property_meta_keys[$meta_key] : str_replace( '-', '_', sanitize_title( ltrim( $meta_key, '_' ) ) ); // try actual product first, starting with properties if ( is_callable( array( $product, "get_{$property}" ) ) ) { $custom = $product->{"get_{$property}"}( 'view' ); } if ( empty( $custom ) ) { $custom = $product->get_meta( $meta_key ); } // fallback to parent for variations if ( empty( $custom ) && $product->is_type( 'variation' ) ) { $_product = wc_get_product( $product->get_parent_id() ); // try actual product first, starting with properties if ( is_callable( array( $_product, "get_{$property}" ) ) ) { $custom = $_product->{"get_{$property}"}( 'view' ); } if ( empty( $custom ) ) { $custom = $_product->get_meta( $meta_key ); } } return $custom; } else { return ''; } } public function get_order_item_meta( $document_item, $meta_key ) { return wc_get_order_item_meta( $document_item['item_id'], $meta_key, true ); } public function get_product_description( $product, $type = 'short', $use_variation_description = true ) { if (! empty( $product ) ) { if ( $type == 'variation' ) { $description = $product->is_type( 'variation' ) ? $product->get_description() : ''; } elseif ( isset( $use_variation_description ) && $product->is_type( 'variation' ) ) { $description = $product->get_description(); } else { if ( $product->is_type( 'variation' ) ) { $_product = wc_get_product( $product->get_parent_id() ); } else { $_product = $product; } switch ( $type ) { case 'short': if ( method_exists( $_product, 'get_short_description' ) ) { $description = $_product->get_short_description(); } else { $description = $_product->post->post_excerpt; } break; case 'long': if ( method_exists( $_product, 'get_description' ) ) { $description = $_product->get_description(); } else { $description = $_product->post->post_content; } break; } } } else { $description = ''; } return $description; } public function get_product_categories( $product ) { if (isset($product)) { if (function_exists('wc_get_product_category_list')) { // WC3.0+ if ( $product->is_type( 'variation' ) ) { // variations don't have categories so we take the parent $category_list = wc_get_product_category_list( $product->get_parent_id() ); } else { $category_list = wc_get_product_category_list( $product->get_id() ); } } else { $category_list = $product->get_categories(); } $product_categories = strip_tags( $category_list ); } else { $product_categories = ''; } return $product_categories; } public function get_product_tags( $product ) { if (isset($product)) { if (function_exists('wc_get_product_tag_list')) { // WC3.0+ if ( $product->is_type( 'variation' ) ) { // variations don't have tags so we take the parent $tag_list = wc_get_product_tag_list( $product->get_parent_id() ); } else { $tag_list = wc_get_product_tag_list( $product->get_id() ); } } else { $tag_list = $product->get_tags(); } $product_tags = strip_tags( $tag_list ); } else { $product_tags = ''; } return $product_tags; } public function get_product_purchase_note( $product ) { if (!empty($product)) { $purchase_note = method_exists($product, 'get_purchase_note') ? $product->get_purchase_note() : $product->purchase_note; $purchase_note = do_shortcode( wp_kses_post( $purchase_note ) ); } else { $purchase_note = ''; } return $purchase_note; } public function get_product_dimensions( $product ) { if ( !empty($product) && function_exists('wc_format_dimensions') && is_callable( array( $product, 'get_dimensions' ) ) ) { return wc_format_dimensions( $product->get_dimensions( false ) ); } else { return ''; } } public function get_sale_price_discount( $item, $item_id, $order, $type = null ) { $regular_prices = $this->get_regular_item_price( $item, $item_id, $order ); if ( round( $item['line_total'], 2 ) == round( $regular_prices['excl'] * $item['qty'], 2 ) ) { return ''; } switch ( $type ) { default: case 'price_excl_tax': $item_price = $item['line_total']; // before coupon discounts $regular_price = $regular_prices['excl'] * $item['qty']; return wc_price( $regular_price - $item_price, array ( 'currency' => $order->get_currency() ) ); case 'price_incl_tax': $item_price = $item['line_total'] + $item['line_tax']; // before coupon discounts $regular_price = $regular_prices['incl'] * $item['qty']; return wc_price( $regular_price - $item_price, array ( 'currency' => $order->get_currency() ) ); case 'percent': $item_price = $item['line_total'] + $item['line_tax']; // before coupon discounts $regular_price = $regular_prices['incl'] * $item['qty']; if ( $regular_price > 0 ) { $percent = ( ( $regular_price - $item_price ) / $regular_price ) * 100; $precision = apply_filters( 'wpo_wcpdf_discount_percentage_precision', 0 ); $percent = number_format( $percent, $precision, wc_get_price_decimal_separator(), '' ); return "{$percent}%"; } else { return ''; } } } public function get_product_brands( $product ) { if ( function_exists( 'get_brands ') && ! empty( $product ) ) { if ( $product->is_type( 'variation' ) && is_callable( array( $product, 'get_parent_id' ) ) ) { $product_id = $product->get_parent_id(); } else { $product_id = $product->get_id(); } $terms = get_the_terms( $product_id, 'product_brand' ); $brand_count = is_array( $terms ) ? sizeof( $terms ) : 0; if ( $brand_count == 0 ) { return ''; } $taxonomy = get_taxonomy( 'product_brand' ); $labels = $taxonomy->labels; $brands = get_brands( $product_id, ', ' ); $label = '' . sprintf( _n( '%1$s: ', '%2$s: ', $brand_count ), $labels->singular_name, $labels->name ). ''; return sprintf( '
%s %s
', $label, $brands ); } else { return ''; } } public function get_order_notes( $order, $filter = 'customer' ) { if ( 'shop_order_refund' == $order->get_type() && is_callable( array( $order, 'get_parent_id' ) ) ) { $post_id = $order->get_parent_id(); } else { $post_id = $order->get_id(); } $args = array( 'post_id' => $post_id, 'approve' => 'approve', 'type' => 'order_note' ); remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 ); $notes = get_comments( $args ); add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 ); if ( $notes ) { $formatted_notes = array(); foreach( $notes as $key => $note ) { if ( $filter == 'customer' && !get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) { unset($notes[$key]); continue; } if ( $filter == 'private' && get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ) { unset($notes[$key]); continue; } $note_classes = array( 'note_content' ); $note_classes[] = ( __( 'WooCommerce', 'woocommerce' ) === $note->comment_author ) ? 'system-note' : ''; $formatted_notes[$key] = sprintf( '
%s
', esc_attr( implode( ' ', $note_classes ) ), wpautop( wptexturize( wp_kses_post( $note->comment_content ) ) ) ); } return implode("\n", $formatted_notes); } else { return false; } } public function add_tax_base( $taxes, $order ) { $tax_rates_base = $this->get_tax_rates_base( $order ); foreach ($taxes as $item_id => $tax) { if ( isset( $tax_rates_base[$tax['rate_id']] ) ) { $taxes[$item_id]['base'] = $tax_rates_base[$tax['rate_id']]->base; $taxes[$item_id]['calculated_rate'] = $tax_rates_base[$tax['rate_id']]->calculated_rate; } $created_via = is_callable( array( $order, 'get_created_via' ) ) ? $order->get_created_via() : false; if ( $created_via == 'subscription' ) { // subscription renewals didn't properly record the rate_percent property between WC3.7 and WCS3.0.1 // so we use a fallback if the rate_percent = 0 // if we the tax is bigger than 0 stored the rate percentage in the past, use that $tax_amount = $tax['tax_amount'] + $tax['shipping_tax_amount']; if ( $tax_amount > 0 && isset($tax_rates_base[$tax['rate_id']]->rate_percent) && $tax_rates_base[$tax['rate_id']]->rate_percent > 0 ) { $taxes[$item_id]['stored_rate'] = $this->format_tax_rate( $tax_rates_base[$tax['rate_id']]->rate_percent ); } elseif ( is_numeric($item_id) && $tax_amount > 0 && $stored_rate = wc_get_order_item_meta( absint($item_id), '_wcpdf_rate_percentage', true ) ) { $taxes[$item_id]['stored_rate'] = $this->format_tax_rate( $stored_rate ); } // not setting 'stored_rate' will let the plugin fall back to the calculated_rate } elseif ( method_exists( $order, 'get_version' ) && version_compare( $order->get_version(), '3.7', '>=' ) && version_compare( WC_VERSION, '3.7', '>=' ) && isset( $tax_rates_base[$tax['rate_id']] ) ) { $taxes[$item_id]['stored_rate'] = $this->format_tax_rate( $tax_rates_base[$tax['rate_id']]->rate_percent ); } elseif ( is_numeric($item_id) && $stored_rate = wc_get_order_item_meta( absint($item_id), '_wcpdf_rate_percentage', true ) ) { $taxes[$item_id]['stored_rate'] = $this->format_tax_rate( $stored_rate ); } } return $taxes; } public function get_tax_rates_base( $order ) { // get tax totals from order and preset base $taxes = $this->get_tax_totals( $order ); foreach ($taxes as $rate_id => $tax) { $tax->base = $tax->shipping_tax_amount = 0; } $hide_zero_tax = apply_filters( 'wpo_wcpdf_tax_rate_base_hide_zero', apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ); // get subtotals from regular line items and fees $items = $order->get_items( array( 'fee', 'line_item', 'shipping' ) ); foreach ($items as $item_id => $item) { // get tax data if ( $item['type'] == 'shipping' ) { $line_taxes = maybe_unserialize( $item['taxes'] ); // WC3.0 stores taxes as 'total' (like line items); if (isset($line_taxes['total'])) { $line_taxes = $line_taxes['total']; } } else { $line_tax_data = maybe_unserialize( $item['line_tax_data'] ); $line_taxes = $line_tax_data['total']; } foreach ( $line_taxes as $rate_id => $tax ) { if ( isset( $taxes[$rate_id] ) ) { // convert tax to float, but only if numeric $tax = (is_numeric($tax)) ? (float) $tax : $tax; if ( ( is_float( $tax ) && abs( $tax ) > 0.0 ) || ( $tax === 0.0 && $hide_zero_tax === false ) ) { $taxes[$rate_id]->base += apply_filters( 'wpo_wcpdf_item_tax_rate_base', ($item['type'] == 'shipping') ? floatval( $item['cost'] ) : floatval( $item['line_total'] ), $item, $taxes[$rate_id], $order ); if ($item['type'] == 'shipping') { $taxes[$rate_id]->shipping_tax_amount += floatval( $tax ); } } } } } // add calculated rate foreach ($taxes as $rate_id => $tax) { $calculated_rate = $this->calculate_tax_rate( $tax->base, $tax->amount ); if (function_exists('wc_get_price_decimal_separator')) { $tax_rate = str_replace('.', wc_get_price_decimal_separator(), strval($calculated_rate) ); } $taxes[$rate_id]->calculated_rate = $calculated_rate; } return $taxes; } public function get_tax_totals( $order ) { $taxes = array(); $merge_by_code = apply_filters( 'wpo_wcpdf_tax_rate_base_merge_by_code', false ); if ( $merge_by_code ) { // get taxes from WC $tax_totals = $order->get_tax_totals(); // put taxes in new array with tax_id as key foreach ( $tax_totals as $code => $tax ) { $tax->code = $code; $taxes[$tax->rate_id] = $tax; } } else { // DON'T MERGE BY CODE foreach ( $order->get_items( 'tax' ) as $key => $tax ) { $code = $tax->get_rate_code(); $rate_id = $tax->get_rate_id(); if ( ! isset( $taxes[ $rate_id ] ) ) { $taxes[ $rate_id ] = new stdClass(); $taxes[ $rate_id ]->amount = 0; } $taxes[ $rate_id ]->id = $key; $taxes[ $rate_id ]->base = 0; $taxes[ $rate_id ]->code = $code; $taxes[ $rate_id ]->rate_id = $rate_id; $taxes[ $rate_id ]->is_compound = $tax->is_compound(); $taxes[ $rate_id ]->label = $tax->get_label(); $taxes[ $rate_id ]->amount += (float) $tax->get_tax_total() + (float) $tax->get_shipping_tax_total(); $taxes[ $rate_id ]->formatted_amount = wc_price( wc_round_tax_total( $taxes[ $rate_id ]->amount ), array( 'currency' => $order->get_currency() ) ); // WC3.7 stores rate percent if ( is_callable( array( $tax, 'get_rate_percent' ) ) ) { $taxes[ $rate_id ]->rate_percent = $tax->get_rate_percent(); } } if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) { $amounts = array_filter( wp_list_pluck( $taxes, 'amount' ) ); $taxes = array_intersect_key( $taxes, $amounts ); } } return $taxes; } public function calculate_tax_rate( $price_ex_tax, $tax ) { if ( $price_ex_tax != 0) { $tax_rate = $this->format_tax_rate( ($tax / $price_ex_tax)*100 ); } else { $tax_rate = '-'; } return $tax_rate; } public function format_tax_rate( $tax_rate ) { $precision = apply_filters( 'wpo_wcpdf_calculate_tax_rate_precision', 1 ); $formatted_tax_rate = round( (float) $tax_rate , $precision ).' %'; return apply_filters( 'wpo_wcpdf_formatted_tax_rate', $formatted_tax_rate, $tax_rate ); } public function save_regular_item_price( $order_id, $posted = array() ) { if ( $order = wc_get_order( $order_id ) ) { $items = $order->get_items(); if (empty($items)) { return; } foreach ($items as $item_id => $item) { // this function will directly store the item price $regular_price = $this->get_regular_item_price( $item, $item_id, $order ); } } } public function get_item_vat_column_data( $price_type, $item, $document, $split = array(), $dash_for_zero = false ) { $column_data = ''; switch ( $price_type ) { case 'single_before': if ( ! empty( $item['item'] ) ) { $line_subtotal_tax = isset( $split['subtotal'] ) ? $split['subtotal'] : $item['item']['line_subtotal_tax']; $price = ( $line_subtotal_tax ) / max( 1, $item['quantity'] ); $column_data = ( 0 == $price && $dash_for_zero ) ? '—' : $document->format_price( $price ); } break; case 'single_after': if ( ! empty( $item['item'] ) ) { if ( isset( $split['subtotal'] ) && isset( $split['discount_tax'] ) && isset( $split['multiple'] ) ) { $discount_tax = ( 0 != $split['discount_tax'] && $split['multiple'] ) ? $split['discount_tax'] / max( 1, $item['quantity'] ) : $split['discount_tax']; $line_tax = ( 0 != $split['subtotal'] ) ? $split['subtotal'] - $discount_tax : $split['subtotal']; } else { $price = $item['item']['line_tax']; $line_tax = ( false !== strpos( $price, '0,00 ' ) && $dash_for_zero ) ? '—' : $price; } $price = ( $line_tax ) / max( 1, $item['quantity'] ); $column_data = ( 0 == $price && $dash_for_zero ) ? '—' : $document->format_price( $price ); } break; case 'total_before': if ( isset( $split['total'] ) && isset( $split['discount_tax'] ) && isset( $split['multiple'] ) ) { $discount_tax = ( 0 != $split['discount_tax'] && $split['multiple'] ) ? $split['discount_tax'] / max( 1, $item['quantity'] ) : $split['discount_tax']; $price = ( 0 != $split['total'] ) ? $split['total'] + $discount_tax : $split['total']; $column_data = ( 0 == $price && $dash_for_zero ) ? '—' : $document->format_price( $price ); } else { $price = $item['line_subtotal_tax']; $column_data = ( false !== strpos( $price, '0,00 ' ) && $dash_for_zero ) ? '—' : $price; } break; case 'total_after': if ( isset( $split['total'] ) ) { $price = $split['total']; $column_data = ( 0 == $price && $dash_for_zero ) ? '—' : $document->format_price( $price ); } else { $price = $item['line_tax']; $column_data = ( false !== strpos( $price, '0,00 ' ) && $dash_for_zero ) ? '—' : $price; } break; } return $column_data; } public function get_item_tax_rate_name( $item, $order ) { $tax_names = ''; $order_taxes = $this->get_tax_totals( $order ); if ( is_callable( array( $item, 'get_taxes' ) ) && $taxes = $item->get_taxes() ) { if ( ! empty( $taxes['total'] ) ) { $formatted_taxes = array(); foreach ( $taxes['total'] as $tax_rate_id => $tax_amount ) { if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) && $tax_amount == 0 ) { continue; } if ( $tax_amount != '' ) { $label = $order_taxes[$tax_rate_id]->label; $rate = $this->format_tax_rate( $order_taxes[$tax_rate_id]->rate_percent ); $formatted_taxes[] = "{$label} {$rate}"; } } $tax_names = implode( '
', $formatted_taxes ); } } return $tax_names; } // get regular price from item - query product when not stored in item yet public function get_regular_item_price( $item, $item_id, $order ) { // first check if we alreay have stored the regular price of this item $regular_price = wc_get_order_item_meta( $item_id, '_wcpdf_regular_price', true ); if ( !empty( $regular_price ) && is_array( $regular_price ) && array_key_exists( 'incl', $regular_price ) && array_key_exists( 'excl', $regular_price ) ) { return $regular_price; } if ( is_callable( array( $item, 'get_product' ) ) ) { // WC4.4+ $product = $item->get_product(); } else { $product = $order->get_product_from_item( $item ); } if ($product) { $product_regular_price = $product->get_regular_price(); // get different incarnations $regular_price = array( 'incl' => wc_get_price_including_tax( $product, array( 'qty' => 1, 'price' => $product_regular_price ) ), 'excl' => wc_get_price_excluding_tax( $product, array( 'qty' => 1, 'price' => $product_regular_price ) ), ); } else { // fallback to item price $regular_price = array( 'incl' => $order->get_line_subtotal( $item, true /* $inc_tax */, false ) / max( 1, abs( $item->get_quantity() ) ), 'excl' => $order->get_line_subtotal( $item, false /* $inc_tax */, false ) / max( 1, abs( $item->get_quantity() ) ), ); } wc_update_order_item_meta( $item_id, '_wcpdf_regular_price', $regular_price ); return $regular_price; } public function get_discount_percentage( $order, $discount = null ) { if( is_null( $discount ) ) { if ( method_exists( $order, 'get_total_discount' ) ) { // WC2.3 introduced an $ex_tax parameter $ex_tax = false; $discount = $order->get_total_discount( $ex_tax ); } elseif ( method_exists( $order, 'get_discount_total' ) ) { // was this ever included in a release? $discount = $order->get_discount_total(); } else { return false; } } $order_total = $order->get_total(); // shipping and fees are not discounted $shipping_total = $order->get_total_shipping() + $order->get_shipping_tax(); $fee_total = 0; if (method_exists($order, 'get_fees')) { // old versions of WC don't support fees foreach ( $order->get_fees() as $fees ) { $fee_total += $fees['line_total'] + $fees['line_tax']; } } $percentage = ( $discount / ( $order_total + $discount - $shipping_total - $fee_total) ) * 100; if ( round($percentage) > 0 ) { return $percentage; } else { return false; } } public function get_order_weight( $order, $document = null, $add_unit = true ) { $items = $order->get_items(); $weight = 0; if( sizeof( $items ) > 0 ) { foreach( $items as $item_id => $item ) { if ( is_callable( array( $item, 'get_product' ) ) ) { // WC4.4+ $product = $item->get_product(); } else { $product = $order->get_product_from_item( $item ); } if ( $this->subtract_refunded_qty( $document ) && $refunded_qty = $order->get_qty_refunded_for_item( $item_id ) ) { $qty = (int) $item['qty'] + $refunded_qty; } else { $qty = (int) $item['qty']; } if ( is_callable( array( $product, 'get_weight' ) ) && is_numeric($product->get_weight()) ) { $weight += $product->get_weight() * $qty; } } } if ( $add_unit == true ) { $weight = $this->format_weight ( $weight ); } return apply_filters( 'wpo_wcpdf_templates_order_weight', $weight, $order, $document ); } public function get_order_total_qty( $order, $document = null ) { $items = $order->get_items(); $total_qty = 0; if( sizeof( $items ) > 0 ) { foreach( $items as $item_id => $item ) { // only count visible items (product bundles compatibility) if ( ! apply_filters( 'woocommerce_order_item_visible', true, $item ) ) { continue; } $total_qty += $item['qty']; if ( $this->subtract_refunded_qty( $document ) && $refunded_qty = $order->get_qty_refunded_for_item( $item_id ) ) { $total_qty += $refunded_qty; } } } return apply_filters( 'wpo_wcpdf_templates_order_qty', $total_qty, $order, $document ); } public function get_date( $placeholder, $order ) { // parent order fallback if ( 'shop_order_refund' == $order->get_type() && is_callable( array( $order, 'get_parent_id' ) ) ) { $order = wc_get_order( $order->get_parent_id() ); } switch ( $placeholder ) { case 'date_paid': case 'paid_date': $date_paid = $order->get_date_paid(); $date_format = function_exists( 'wcpdf_date_format' ) ? wcpdf_date_format( $document, 'order_date_paid' ) : wc_date_format(); $date = ! empty( $date_paid ) ? $date_paid->date_i18n( $date_format ) : '-'; break; case 'time_paid': case 'paid_time': $date_paid = $order->get_date_paid(); $date = ! empty( $date_paid ) ? $date_paid->date_i18n( wc_time_format() ) : '-'; break; case 'date_completed': case 'completed_date': $date_completed = $order->get_date_completed(); $date_format = function_exists( 'wcpdf_date_format' ) ? wcpdf_date_format( $document, 'order_date_completed' ) : wc_date_format(); $date = ! empty( $date_completed ) ? $date_completed->date_i18n( $date_format ) : '-'; break; default: $date = '-'; break; } return $date; } public function subtract_refunded_qty( $document ) { $subtract_refunded_qty = false; if (!empty($document) && $document->get_type() == 'packing-slip') { $packing_slip_settings = get_option( 'wpo_wcpdf_documents_settings_packing-slip' ); if ( isset($packing_slip_settings['subtract_refunded_qty'] ) ) { $subtract_refunded_qty = true; } } return $subtract_refunded_qty; } // hide regular price item eta public function hide_regular_price_itemmeta( $hidden_keys ) { $hidden_keys[] = '_wcpdf_regular_price'; return $hidden_keys; } public function array_keys_prefix( $array, $prefix, $add_or_remove = 'add' ) { if (empty($array) || !is_array($array) ) { return $array; } foreach ($array as $key => $value) { if ( $add_or_remove == 'add' ) { $array[$prefix.$key] = $value; unset($array[$key]); } else { // remove $new_key = str_replace($prefix, '', $key); $array[$new_key] = $value; unset($array[$key]); } } return $array; } public function get_local_pickup_plus_pickup_details( $order ) { if ( function_exists('wc_local_pickup_plus') ) { $local_pickup = wc_local_pickup_plus(); $orders_handler = $local_pickup->get_orders_instance(); if ( $orders_handler && ( $pickup_data = $orders_handler->get_order_pickup_data( $order ) ) ) { ob_start(); $shipping_method = $local_pickup->get_shipping_method_instance(); $package_number = 1; $packages_count = count( $pickup_data ); ?>

get_method_title() ); ?>

1 ) : ?>
get_method_title() ), $package_number ); ?>
    $value ) : ?>
  • :
get_version(), '3.7', '>=' ) ) { return; // WC3.7 already stores the rate in the tax lines } else { $this->save_tax_rate_percentage( $order ); } } public function save_tax_rate_percentage_frontend( $order_id, $posted ) { if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '3.7', '<' ) ) { $order = wc_get_order( $order_id ); if ( !empty( $order ) ) { $this->save_tax_rate_percentage( $order ); } } } public function save_tax_rate_percentage( $order ) { 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 ( $tax_rate && !empty( $tax_rate->tax_rate ) ) { // store percentage in tax item meta wc_update_order_item_meta( $item_id, '_wcpdf_rate_percentage', $tax_rate->tax_rate ); } } } } } /** * PHP Intl extension used by the spell out total placeholder (PHP NumberFormatter class) * @param array $server_configs * @return array */ public function php_intl_check( $server_configs ) { $server_configs['Intl'] = array( 'required' => __( 'Required when using the {{spelled_out_total}} placeholder', 'wpo_wcpdf_templates' ), 'value' => null, 'result' => extension_loaded('intl'), ); return $server_configs; } /** * Helper function to format the item weight * @param float $weight * @return string */ public function format_weight( $weight ) { return wc_format_weight( $weight ); } } // end class } // end class_exists return new WooCommerce_PDF_IPS_Templates_Main();