diff --git a/lang/wc-invoice-pdf-de_DE.mo b/lang/wc-invoice-pdf-de_DE.mo index 43e04b8..38c8d42 100644 Binary files a/lang/wc-invoice-pdf-de_DE.mo and b/lang/wc-invoice-pdf-de_DE.mo differ diff --git a/lang/wc-invoice-pdf-de_DE.po b/lang/wc-invoice-pdf-de_DE.po index 1e7f788..ea39ed9 100644 --- a/lang/wc-invoice-pdf-de_DE.po +++ b/lang/wc-invoice-pdf-de_DE.po @@ -76,12 +76,33 @@ msgstr "jährlich" msgid "monthly" msgstr "monatlich" +msgid "month" +msgstr "Monat" + +msgid "months" +msgstr "Monate" + msgid "per year" msgstr "pro Jahr" msgid "per month" msgstr "pro Monat" +msgid "Hour" +msgstr "Stunde" + +msgid "Hours" +msgstr "Stunden" + +msgid "minute" +msgstr "Minute" + +msgid "minutes" +msgstr "Minuten" + +msgid "per" +msgstr "pro" + msgid "Invoice" msgstr "Rechnung" @@ -160,3 +181,11 @@ msgstr "Vorschau" msgid "Settings saved" msgstr "Einstellungen gespeichert" +msgid "To the minute calculation" +msgstr "Minuten-genaue Berechnung" + +msgid "Working hours" +msgstr "Arbeitszeiten" + +msgid "Webspace" +msgstr "Webspeicher" \ No newline at end of file diff --git a/model/invoice-pdf.php b/model/invoice-pdf.php index 1b36cda..418d869 100644 --- a/model/invoice-pdf.php +++ b/model/invoice-pdf.php @@ -94,8 +94,8 @@ public function BuildInvoice($invoice, $isOffer = false, $stream = false){ $colOptions = [ 'num' => ['width' => 32], 'desc' => [], - 'qty' => ['justification' => 'right', 'width' => 64], - 'price' => ['justification' => 'right', 'width' => 80], + 'qty' => ['justification' => 'right', 'width' => 75], + 'price' => ['justification' => 'right', 'width' => 70], 'total' => ['justification' => 'right', 'width' => 80], ]; @@ -130,13 +130,13 @@ public function BuildInvoice($invoice, $isOffer = false, $stream = false){ // overwrite the QTY to be 1 MONTH $next->add(new \DateInterval('P12M')); } - $qtyStr = number_format($v['qty'], 0, ',',' ') . ' Monat(e)'; + $qtyStr = number_format($v['qty'], 0, ',',' ') . ' ' . $product->get_price_suffix('', $v['qty']); //if(!$isOffer) $product_name .= "\nZeitraum: " . $current->format('d.m.Y')." - ".$next->format('d.m.Y') . ''; } else if($product instanceof \WC_Product_Hour) { // check if product type is "hour" to output hours instead of Qty $qtyStr = number_format($v['qty'], 1, ',',' '); - $qtyStr .= ' Std.'; + $qtyStr.= ' ' . $product->get_price_suffix('', $v['qty']); } else { $qtyStr = number_format($v['qty'], 2, ',',' '); } diff --git a/readme.txt b/readme.txt index c67d2ef..858809a 100644 --- a/readme.txt +++ b/readme.txt @@ -17,14 +17,16 @@ It also allows to setup WC Orders to be recurring and submits the invoices to th **Features** -* save invoice as pdf from any order created by WooCommerce -* export invoices into CSV (primary GnuCash, more may follow...) -* preview quote as pdf to submit provisional offer to customers -* schedule recurring invoices directly send to customers email address -* send kindly reminders for over due invoices -* show payable invoices in customers "My Account" -* individual the PDF template in text and picture -* remind delegate person about pending payments from customers +* Save invoice as pdf from any order created by WooCommerce +* Export invoices into CSV (primary GnuCash, more may follow...) +* Preview quote as pdf to submit provisional offer to customers +* Schedule recurring invoices directly send to customers email address +* Send kindly reminders for over due invoices +* Show payable invoices in customers "My Account" +* Individual the PDF template in text and picture +* Remind delegate person about pending payments from customers +* A "Webspace" product type for recurring payment support +* A "Working hours" product type to support service hours being invoiced to customers [RELEASE NOTES]( https://github.com/ole1986/wc-invoice-pdf/releases) @@ -40,7 +42,7 @@ For testing the recuring payments (submission of invoices) the "Test recuring" s = DEVELOPMENT = -To set the *recurring state* of an order while receiving an action hook can be used to achieve this +To set the *recurring state* of an order while receiving a request, an action hook can be used to achieve this Example: @@ -68,5 +70,5 @@ You should have received a copy of the GNU General Public License along with WP == Changelog == -Please click on "Plugin Homepage" the for all release notes. -You will be refered to the github project page. \ No newline at end of file +Please click on "Plugin Homepage" for release notes. +You will be redirected to the github project page. \ No newline at end of file diff --git a/wc-invoice-pdf.php b/wc-invoice-pdf.php index a7464a1..b8367cc 100644 --- a/wc-invoice-pdf.php +++ b/wc-invoice-pdf.php @@ -2,14 +2,14 @@ /* * Plugin Name: WC Recurring Invoice PDF * Description: WooCommerce invoice pdf plugin with recurring payments (scheduled) - * Version: 1.1.0 + * Version: 1.4.0 * Author: ole1986 * Author URI: https://github.com/ole1986/wc-invoice-pdf * Plugin URI: https://github.com/ole1986/wc-invoice-pdf/releases * Text Domain: wc-invoice-pdf * * WC requires at least: 3.0.0 - * WC tested up to: 3.4.4 + * WC tested up to: 3.5 */ namespace WCInvoicePdf; @@ -74,9 +74,10 @@ class WCInvoicePdf { public static function init() { self::load_textdomain_file(); - if(file_exists(WCINVOICEPDF_PLUGIN_DIR . '../wp-ispconfig3/wc/ispconfig_wc_product.php')) { - include_once WCINVOICEPDF_PLUGIN_DIR . '../wp-ispconfig3/wc/ispconfig_wc_product.php'; - include_once WCINVOICEPDF_PLUGIN_DIR . '../wp-ispconfig3/wc/ispconfig_wc_product_webspace.php'; + if(file_exists(WCINVOICEPDF_PLUGIN_DIR . 'wc/wc_product.php')) { + include_once WCINVOICEPDF_PLUGIN_DIR . 'wc/wc_product.php'; + include_once WCINVOICEPDF_PLUGIN_DIR . 'wc/wc_product_webspace.php'; + include_once WCINVOICEPDF_PLUGIN_DIR . 'wc/wc_product_service.php'; } self::load_options(); diff --git a/wc/wc_product.php b/wc/wc_product.php new file mode 100644 index 0000000..e602e1b --- /dev/null +++ b/wc/wc_product.php @@ -0,0 +1,74 @@ +get_regular_price(); + } + + /** + * Get the add to url used mainly in loops. + * + * @return string + */ + public function add_to_cart_url() + { + $url = $this->is_purchasable() && $this->is_in_stock() ? remove_query_arg('added-to-cart', add_query_arg('add-to-cart', $this->id)) : get_permalink($this->id); + return $url; + } + + /** + * Get the add to cart button text. + * + * @return string + */ + public function add_to_cart_text() + { + $text = $this->is_purchasable() && $this->is_in_stock() ? __('Add to cart', 'woocommerce') : __('Read more', 'woocommerce'); + + return apply_filters('woocommerce_product_add_to_cart_text', $text, $this); + } +} diff --git a/wc/wc_product_service.php b/wc/wc_product_service.php new file mode 100644 index 0000000..d0be142 --- /dev/null +++ b/wc/wc_product_service.php @@ -0,0 +1,117 @@ +product_type = 'hour'; + parent::__construct($product); + } + + public static function register($types) + { + // Key should be exactly the same as in the class product_type parameter + $types[ 'hour' ] = __('Working hours', 'wc-invoice-pdf'); + return $types; + } + + public static function jsRegister() + { + global $product_object; + ?> + + is_purchasable() && $this->is_in_stock() ? remove_query_arg('added-to-cart', add_query_arg('add-to-cart', $this->id)) : get_permalink($this->id); + return $url; + } + + /** + * Get the add to cart button text. + * + * @return string + */ + public function add_to_cart_text() + { + $text = $this->is_purchasable() && $this->is_in_stock() ? __('Add to cart', 'woocommerce') : __('Read more', 'woocommerce'); + + return apply_filters('woocommerce_product_add_to_cart_text', $text, $this); + } + + public static function hour_product_data_tab($product_data_tabs) + { + $product_data_tabs['general']['class'][] = 'show_if_hour'; + $product_data_tabs['linked_product']['class'][] = 'hide_if_hour'; + $product_data_tabs['attribute']['class'][] = 'hide_if_hour'; + $product_data_tabs['advanced']['class'][] = 'hide_if_hour'; + $product_data_tabs['shipping']['class'][] = 'hide_if_hour'; + + $product_data_tabs['hour_tab'] = array( + 'label' => __('Working hours', 'wc-invoice-pdf'), + 'target' => 'hour_data_tab', + 'class' => 'show_if_hour' + ); + + return $product_data_tabs; + } + + + public static function product_data_fields() + { + echo '
'; + woocommerce_wp_checkbox(['id' => '_hour_useminute', 'label' => __('minutes', 'wc-invoice-pdf'), 'description' => __("To the minute calculation", 'wc-invoice-pdf')]); + echo '
'; + } + + /** + * BACKEND: Used to save the template ID for later use (Cart/Order) + */ + public static function metadata_save($post_id) + { + update_post_meta($post_id, '_hour_useminute', $_POST['_hour_useminute']); + } + + public function get_price_suffix($price = '', $qty = 1) + { + $plural = $qty > 1 ? 's' : ''; + $suffix = __('Hour' . $plural, 'wc-invoice-pdf'); + + if($this->get_meta('_hour_useminute', true)) + { + $suffix = __('minute' . $plural, 'wc-invoice-pdf'); + } + + return ' ' . $suffix; + } + + public function get_price_html($price = '') + { + $price = wc_price(wc_get_price_to_display($this, array( 'price' => $this->get_regular_price() ))) . ' ' . __('per', 'wc-invoice-pdf') . $this->get_price_suffix('', 1); + return apply_filters('woocommerce_get_price_html', $price, $this); + } +} \ No newline at end of file diff --git a/wc/wc_product_webspace.php b/wc/wc_product_webspace.php new file mode 100644 index 0000000..189d2c7 --- /dev/null +++ b/wc/wc_product_webspace.php @@ -0,0 +1,232 @@ + __('monthly', 'wc-invoice-pdf'), 'y' => __('yearly', 'wc-invoice-pdf') ]; + + $this->supports[] = 'ajax_add_to_cart'; + //$this->product_type = "webspace"; + parent::__construct($product); + } + + public static function register($types) + { + $types[ 'webspace' ] = __('Webspace', 'wc-invoice-pdf'); + return $types; + } + + public static function jsRegister() + { + global $product_object; + ?> + + __('ISPConfig 3', 'wp-ispconfig3'), + 'target' => 'ispconfig_data_tab', + 'class' => 'show_if_webspace' + ); + } + + return $product_data_tabs; + } + + public static function ispconfig_product_data_fields() + { + if (!class_exists("Ispconfig")) return; + echo '
'; + try { + $templates = Ispconfig::$Self->withSoap()->GetClientTemplates(); + + $options = [0 => 'None']; + foreach ($templates as $v) { + $options[$v['template_id']] = $v['template_name']; + } + woocommerce_wp_select(['id' => '_ispconfig_template_id', 'label' => 'Client Limit Template', 'options' => $options]); + + Ispconfig::$Self->closeSoap(); + } catch (SoapFault $e) { + echo "
ISPConfig SOAP Request failed: " . $e->getMessage() . '
'; + } + + echo '
'; + } + + /** + * BACKEND: Used to save the template ID for later use (Cart/Order) + */ + public static function webspace_metadata_save($post_id) + { + if (!empty($_POST['_ispconfig_template_id'])) { + update_post_meta($post_id, '_ispconfig_template_id', $_POST['_ispconfig_template_id']); + } + } + + public function getISPConfigTemplateID() + { + return get_post_meta($this->get_id(), '_ispconfig_template_id', true); + } + + public function OnCheckout($checkout) + { + $templateID = $this->getISPConfigTemplateID(); + + if ($templateID >= 1 && $templateID <= 3) { + echo "

" . __('Your desired domain', 'wp-ispconfig3') . "

"; + woocommerce_form_field( + 'order_domain', + [ + 'type' => 'text', + 'placeholder' => '', + 'custom_attributes' => ['data-ispconfig-checkdomain'=>'1'] + ], + $checkout->get_value('order_domain') + ); + } + + echo ''; + echo '
Bitte beachten Sie das die Domainregistrierung innerhalb von 24 Stunden nach Zahlungseingang erfolgt
'; + } + + public function OnCheckoutValidate() + { + $templateID = $this->getISPConfigTemplateID(); + + // all products require a DOMAIN to be entered + if ($templateID >= 1 && $templateID <= 3) { + try { + $dom = Ispconfig::validateDomain($_POST['order_domain']); + $available = Ispconfig::isDomainAvailable($dom); + if ($available == 0) { + wc_add_notice(__("The domain is not available", 'wp-ispconfig3'), 'error'); + } elseif ($available == -1) { + wc_add_notice(__("The domain might not be available", 'wp-ispconfig3'), 'notice'); + } + } catch (Exception $e) { + wc_add_notice($e->getMessage(), 'error'); + } + } + } + + public function OnCheckoutSubmit($order_id, $item_key, $item) + { + if (! empty($_POST['order_domain'])) { + update_post_meta($order_id, 'Domain', sanitize_text_field($_POST['order_domain'])); + + $templateID = $this->getISPConfigTemplateID(); + // no ispconfig product found in order - so skip doing ispconfig related stuff + if (empty($templateID)) { + return; + } + + // WC-InvoicePDF: use external plugin to set the recurring properly + if ($item['quantity'] == 12) { + do_action('wcinvoicepdf_order_period', $order_id, 'yearly'); + } else { + do_action('wcinvoicepdf_order_period', $order_id, 'monthly'); + } + } + } + + public function get_price_suffix($price = '', $qty = 1) + { + $plural = $qty > 1 ? 's' : ''; + $suffix = __('month' . $plural, 'wc-invoice-pdf'); + + return ' ' . $suffix; + } + + public function get_price_html($price = '') + { + $price = wc_price(wc_get_price_to_display($this, array( 'price' => $this->get_regular_price() ))) . ' ' . __('per month', 'wc-invoice-pdf'); + return apply_filters('woocommerce_get_price_html', $price, $this); + } + + public static function AddToCart($item, $item_key) + { + if (get_class($item['data']) != 'WC_Product_Webspace') { + return $item; + } + // empty cart when a webspace product is being added + // ONLY ONE webspace product is allowed in cart + WC()->cart->empty_cart(); + return $item; + } + + /** + * Display a DropDown (per webspace product) for selecting the period (month / year / ...) + * Can be customized in $OPTIONS property + */ + public static function Period($item_qty, $item_key, $item) + { + if (get_class($item['data']) != 'WC_Product_Webspace') { + return $item_qty; + } + + $period = ($item['quantity'] == 12)?'y':'m'; + + ?> + + $v) { + $qty = ($v == 'y')?12:1; + // update the qty of the product + WC()->cart->set_quantity($item_key, $qty, false); + } + return $isUpdated; + } +} \ No newline at end of file