Category Archives: Woocommerce

Di chuyển nút add to cart đến sát phần giá sản phẩm trong Woocommerce

Di chuyen nut add to cart Woocommerce

Trong trang single product page, Woocommerce mặc định sẽ trình bày “tên sản phẩm – product tittle” => “giá sản phẩm” => “mô tả ngắn” => “add to cart – thêm vào giỏ hàng”. Cách bố trí này có vẻ không dễ dùng & tiện bằng cách đưa nút “add to cart” lên sát với giá sản phẩm.

Để xử lý vấn đề này, có 1 cách rất đơn giản là chúng ta thay đổi trật tự ưu tiên thực thi nút “add to cart” như sau: Remove action rồi re-add lại với priority phù hợp như sau:

 /** woocommerce: change position of add-to-cart on single product **/
    remove_action( 'woocommerce_single_product_summary', 
               'woocommerce_template_single_add_to_cart', 30 );
    add_action( 'woocommerce_single_product_summary', 
            'woocommerce_template_single_add_to_cart', 9 ); 

Kết quả có được như sau: 

Di chuyen nut add to cart Woocommerce

Xem thêm: Trật tự xử lý các actions trong page sản phẩm:

 /**
         * woocommerce_single_product_summary hook
         *
         * @hooked woocommerce_template_single_title - 5
         * @hooked <strong>woocommerce_template_single_price - 10 => đưa add to cart vào sát phần này
         * @hooked woocommerce_template_single_excerpt - 20
         * @hooked woocommerce_template_single_add_to_cart - 30
         * @hooked woocommerce_template_single_meta - 40
         * @hooked woocommerce_template_single_sharing - 50
         */ 

Ref: https://www.businessbloomer.com/woocommerce-visual-hook-guide-single-product-page/

https://wordpress.stackexchange.com/questions/114734/how-to-customize-position-of-add-to-cart-of-woocommerce-on-product-page

Hiển thị phương thức thanh toán dựa trên phương thức ship với Woocommerce

Tuy bien phuong thuc thanh toan Woocommerce

Issue: Tùy biến hiển thị phương thức thanh toán dựa trên lựa chọn phương thức giao hàng của khách hàng

Ví dụ:
+ Khi khách hàng lựa chọn phương thức giao hàng COD -giao hàng thu tiền => trang Checkout sẽ ẩn phương thức thanh toán “chuyển khoản”
+ Khi đơn hàng đạt mức free ship & khách hàng lựa chọn phương thức giao hàng miễn phí => trang Checkout sẽ ẩn phương thức thanh toán “COD”

Giải pháp:

+ Can thiệp vào hook “woocommerce_available_payment_gateways”
Code cụ thể:

//hide payment method based on shipping method selected<br />
add_filter( 'woocommerce_available_payment_gateways', 'payment_gateways_based_on_chosen_shipping_method' );<br />
function payment_gateways_based_on_chosen_shipping_method( $available_gateways ) {<br />
// Not in backend (admin)<br />
if( is_admin() )<br />
return $available_gateways;</p>
<p>// Lay phuong thuc ship duoc KH chon<br />
$chosen_shipping_methods = (array) WC()-&gt;session-&gt;get( 'chosen_shipping_methods' );</p>
<p>if ( in_array( 'free_shipping:6', $chosen_shipping_methods ) ) //free_shipping:6 - phuong thuc ship duoc chon<br />
{<br />
unset( $available_gateways['nganluong'] ); //nganluong - phuong thuc thanh toan bi loai bo<br />
unset( $available_gateways['cod'] );//COD - phuong thuc thanh toan bi loai bo<br />
}<br />
elseif ( in_array( 'ghtk_shipping_method', $chosen_shipping_methods ) )//ghtk_shipping_method - phuong thuc giao hang qua GHTK - plugin Le Van Toan<br />
{<br />
unset( $available_gateways['bacs'] );<br />
}<br />
return $available_gateways;</p>
<p>}

Cách lấy value của phương thức vận chuyển & phương thức thanh toán: 

Từ browser console, lấy giá trị trường value của phương thức tương ứng.

Tuy bien phuong thuc thanh toan Woocommerce dua tren phuong thuc van chuyen

Các bạn có thể tùy biến theo nhu cầu.

Reference:

1. Show hide payment methods based on selected shipping method in Woocommerce

2. Hide shipping and payment methods in WooCommerce

Integrate Relevanssi into Flatsome

By default, Flatsome only displays shop result for its search page. To integrate Relevanssi into Flatsome, we need to add the below code to child theme function.php:

remove_action( 'woocommerce_after_main_content', 'flatsome_pages_in_search_results', 10 );
// Add Pages and blog posts to top of search results if set.
function relevanssi_pages_in_search_results() {
if ( ! is_search() || ! get_theme_mod( 'search_result', 1 ) ) {
return;
}
global $post;
if ( get_search_query() ) {
$args = array(
'post_type' =&amp;gt; 'post',
's' =&amp;gt; get_search_query(),
);
$query = new WP_Query();
		$query-&amp;gt;parse_query( $args );
		relevanssi_do_query( $query );
		$posts = array();
		while ( $query-&amp;gt;have_posts() ) {
			$query-&amp;gt;the_post();
			array_push( $posts, $post-&amp;gt;ID );
		}
		$args = array(
			'post_type' =&amp;gt; 'page',
			's'         =&amp;gt; get_search_query(),
		);
		$query = new WP_Query();
		$query-&amp;gt;parse_query( $args );
		relevanssi_do_query( $query );
		$pages = array();
		while ( $query-&amp;gt;have_posts() ) {
			$query-&amp;gt;the_post();
			$wc_page = false;
			if ( 'page' === $post-&amp;gt;post_type ) {
				foreach ( array( 'shop', 'cart', 'checkout', 'view_order', 'terms' ) as $wc_page_type ) {
					if ( $post-&amp;gt;ID === wc_get_page_id( $wc_page_type ) ) {
						$wc_page = true;
					}
				}
			}
			if ( ! $wc_page ) {
				array_push( $pages, $post-&amp;gt;ID );
			}
		}
		do_action( 'flatsome_products_page_loader' );
                if ( ! empty( $posts ) || ! empty( $pages ) ) {
                     $list_type = get_theme_mod( 'search_result_style', 'slider' );
                     if ( ! empty( $posts ) ) {    
		            
                             
add_action( 'woocommerce_after_main_content', 'relevanssi_pages_in_search_results', 10 );

Full code: ref: https://www.relevanssi.com/knowledge-base/flatsome-theme-product-search-results-page/

Update woocommerce product via google sheet API

To update woocommerce products (price, sale price, quantity…), we can use Google sheet as database & Google sheet API to automatically update woocommerce. Below is the workflow: 

Prepare the product template => update the template with real data => upload to woocommerce (manual/API): 

+ Download from woocommerce (export product data – built-in function of woocommerce)

+ Put the template product data to Google sheet

+ Update the product data in Google sheet (edit price, quantity…)

+ Upload edited data to Woocommerce: 

           . For manual upload: Download file from Goolge sheet as csv file (default file downloaded from Google sheet is encoded UTF8 csv file – supported by Woo). Note: Excel doesnt support the encoded utf 8 csv file => Dont use Excel.

           . For API work:

Ref: https://wisdmlabs.com/blog/google-apps-script-fetch-databases-to-google-spreadsheet/

Woocommerce Rest API – link data với google sheet

1. Generate Keys – tạo API key trong Woocommerce setting

To start using REST API, you first need to generate API keys.

  1. Go to WooCommerce > Settings > Advanced
  2. Go to the REST API tab and click Add key.
  3. Give the key a description for your own reference, choose a user with access to orders etc, and give the key read/write permissions.
  4. Click Generate api key.
  5. Your keys will be shown – do not close this tab yet, the secret will be hidden if you try to view the key again.

To test API in test tool (ex: https://reqbin.com/), username = consumer key, password = consumer secret 
Note: + Using https, header with authentication (username & password) can not be read.

+ If error 401 => check security measures (server firewall and/or security plugin – ex: ithemes security … to block API access)

2. Woocommerce Rest API endpoints: (orders/products)

Endpoints are located at WooCommerce > Settings > Advanced.

List all products: domain//wp-json/wc/v3/products

Ref: https://woocommerce.github.io/woocommerce-rest-api-docs/#introduction

WooCommerce GET request examples

+ GET request to view all orders
https://yourcompany.com/wp-json/wc/v3/orders/
+ GET request to view a single order
https://yourcompany.com/wp-json/wc/v3/orders/{insert order ID}
+ GET request to view all products
https://yourcompany.com/wp-json/wc/v3/products/
https://yourcompany.com/wp-json/wc/v3/products?type=variable&per_page=50     //Type = variable products & 1<per_page<100

+ Get all variations of a product: => to get all variations of all products, we need to loop through all variable product IDs.
/wp-json/wc/v3/products/<product_id>/variations

+ GET request to view a single product
https://yourcompany.com/wp-json/wc/v3/products/{insert product ID}
+ GET request to view a single product variation
https://yourcompany.com/wp-json/wc/v3/products/{insert product ID}/variations/{insert variation ID}
+ GET request to view all customers
https://yourcompany.com/wp-json/wc/v3/customers/
+ GET request to view a single customer
https://yourcompany.com/wp-json/wc/v3/customers/{insert customer ID}

Ref: https://sgwebpartners.com/how-to-use-woocommerce-api/

Routes vs Endpoints

A route is the “name” you use to access endpoints, used in the URL. A route can have multiple endpoints associated with it, and which is used depends on the HTTP verb.

For example, with the URL http://example.com/wp-json/wp/v2/posts/123:

  • The “route” is wp/v2/posts/123 – The route doesn’t include wp-json because wp-json is the base path for the API itself.
  • This route has 3 endpoints:
    • GET triggers a get_item method, returning the post data to the client.
    • PUT triggers an update_item method, taking the data to update, and returning the updated post data.
    • DELETE triggers a delete_item method, returning the now-deleted post data to the client.

3. Authentication with google scripts:

Solution from google – basic authentication

var USERNAME = 'your_username';
var PASSWORD = 'your_password';
var API_URL = 'http://<place_api_url_here>';

var authHeader = 'Basic ' + Utilities.base64Encode(USERNAME + ':' + PASSWORD);
var options = {
  headers: {Authorization: authHeader}
}
// Include 'options' object in every request
var response = UrlFetchApp.fetch(API_URL, options);

Ref: https://developers.google.com/google-ads/scripts/docs/features/third-party-apis
Ref: https://stackoverflow.com/questions/23546255/how-to-use-urlfetchapp-with-credentials-google-scripts
Ref: https://badlywired.com/2018/01/how-to-authenticate-wordpress-rest-api-from-google-sheets-scripts/

https://stackoverflow.com/questions/16027002/google-apps-script-and-external-api-authorization-failing-in-header

4. Output Woocommerce data to Google Sheet

After all 3 above steps, we need to output data to Google Sheet.

Issue 1: To protect server, Woocommerce API returns 100 result/page (Ref: Rest API pagination)

=> Solution: loop to all API URL để lấy toàn bộ các API URL  => Loop through all Url returned data to append each product/order to Google Sheet.

Issue 2: Setup the script to run on a specific time (ex: at 00:00 each day)

=> Solution: Using trigger

4.1 Woo API Pagination

Woocommerce API limit per page: maximum 100 results/page => If the result set is 500 products/order => 5 API URLs 

Ex: URL 1: yourdomain/wp-json/wc/v3/products?per_page=100&page=1 (first 100 results)

      URL 2: yourdomain/wp-json/wc/v3/products?per_page=100&page=2 (2nd 100 results)

      URL 3: yourdomain/wp-json/wc/v3/products?per_page=100&page=3 (3rd 100 results)

      URL 4: yourdomain/wp-json/wc/v3/products?per_page=100&page=4 (4th 100 results)

      URL 5: yourdomain/wp-json/wc/v3/products?per_page=100&page=5 (5th 100 results)

How to know how many pages & products/orders does the data set have:  

based on the API header below:  

  • X-WP-Total: Total returned records – Using Postman or reqbin.com to see 
  • X-WP-TotalPages: Total returned pages

Ref: Retrieve entire data from paginated API recursively 

4.2 Actual code:

Script example:

function myFunction() {
var USERNAME = 'comsumer key';
var PASSWORD = 'secret key';
var headers = {
"Accept": "application/xml",
"Content-Type": "application/xml",
"Authorization": "Basic "+ Utilities.base64Encode(USERNAME+":"+PASSWORD)
};
var options = {
"method" : "get",
"headers" : headers 
};
Logger.clear();
for (page = 1; page < 4; page++) { //see header X-WP-TotalPages to know total how many pages returned
var URL = 'Yourdomain/wp-json/wc/v3/products?per_page=20'+'&page='+page;
var response = UrlFetchApp.fetch(URL,options);
var data = JSON.parse(response); 
Logger.log(data.length);
var sheet = SpreadsheetApp.getActiveSheet();
// loop through the map and output to sheet
for (i = 0; i < data.length; i++) { 
sheet.appendRow([data[i].id,
data[i].name,
data[i].price,
data[i].stock_quantity,
data[i].stock_status,
]);
}
}
}

Ref: https://badlywired.com/2018/01/linking-wordpress-to-a-spreadsheet-using-wp-rest-api-and-google-sheets-scripts/

Ref: get all api url& fetch: https://stackoverflow.com/questions/56671010/how-can-i-iterate-through-multiple-urls-to-fetch-json-response-in-google-apps-sc

5. Using time-based script to automate script: 

+ Using time-driven trigger to wake up the main script everyday/week…

+ Clear the spreadsheet everytime the main script run to get fresh data

function startTimeTrigger() {
ScriptApp.newTrigger('main')
.timeBased()
.atHour(7)
.everDays(1)
.create();
};

6. Cell formatting in google sheet: 

Format a single column: 

var column = sheet.getRange("B2:B"); //select column B from cell B2
column.setNumberFormat("M/d/yy");

Ref: https://www.blackcj.com/blog/2015/05/18/cell-number-formatting-with-google-apps-script/  

 

Tùy biến Woocommerce review

Issue: Tùy biến Woocommerce review (text xuất hiện bên cạnh * chứ không phải ở dòng dưới)

=> Nghiên cứu các file sau:

+ woocommerce/templates/single-product/rating.php

+ woocommerce/assets/css/woocommerce.scss: 

.star-rating span {
    font-family: "star";
}

Chú ý: Nhớ copy vào child theme

 

 

Source: 

https://wordpress.stackexchange.com/questions/290572/woocommerce-showing-star-rating-review-instead-of-text-review-string

https://stackoverflow.com/questions/32027481/woocommerce-change-product-rating-in-loop-from-textual-format-to-star-display

 

Woocommerce email customize – Lưu ý

Customizing Your Email Templates

Woocommerce Email templates được tạo từ PHP, HTML, CSS. Cần chú ý một số điều khi tùy biến Woocommerce email template:

No Javascript

Emails are not dynamic. Everything that is output needs to be static. That means that jQuery and Javascript will have no effect on these.

WooCommerce Globals and Hooks

Phần lớn content được sử dụng trong email được lấy từ dữ liệu lưu trữ trong woocommerce. Dưới đây là 1 số class & action mà Woocommerce sử dụng để lấy dữ liệu điền vào email:

    • `WC_Emails` (Documentation) This class contains pretty much all of the actions that you’ll need to deal with adjusting the email layouts or moving content to other places. For example, the `email_header` and `email_footer` and the `customer_invoice->order` are all in there and available to be extended. If you are going to be making serious layout changes you’ll want to know how to output these into your new layout
    • `wc_get_template` (Documentation) This is how the general framework of each template is structured. The `email-header.php` and `email-footer.php` are called with this.
    • `do_action` (Codex Documentation) This is your friend, but it’s a WordPress Core function. You’ll use this to call up any of the WooCommerce available actions.

CSS

WooCommerce uses `emails\email-styles.php` to output the color styles from the WooCommerce Email Settings tab directly into the template as inline styles. Inline styles are best for email. WooCommerce takes the header styles and dynamically inserts them into the template as inline styles. It’s pretty impressive how they do that. This also applies to any custom styles you add, even to new class or id names.

One quirk I found though is make sure you don’t add any inline comments in your styles. For some reason that breaks this feature and your styles won’t get applied. The bottomline is that you can add all the styles you want into the email-header.php file, and WooCommerce will take care of adding them into your template as inline styles. Pretty awesome work.

Source: https://impress.org/customize-and-preview-woocommerce-emails/

Conditional Customization with Actions/Filters – Woocommerce email

Conditional Customization with Actions/Filters

The final and most effective approach to customizing emails is to work with WooCommerce custom code. This obviously requires a high-level of expertise in PHP. The good news is that the process is straightforward because the original WooCommerce layouts are still in use. The process involves changing portions of the content.

Filter Functions

Actions Functions

Activation/Deactivation/Uninstall Functions

For this example, I will add some helpful payment instructions to the email, based on the checkout payment type used.

To start, add the following to the theme’s functions.php:

add_action( 'woocommerce_before_email_order', 'add_order_instruction_email', 10, 2 );
 
function add_order_instruction_email( $order, $sent_to_admin ) {
  
  if ( ! $sent_to_admin ) {
 
    if ( 'cod' == $order->payment_method ) {
      // cash on delivery method
      echo '<p><strong>Instructions:</strong> Full payment is due immediately upon delivery: <em>cash only, no exceptions</em>.</p>';
    } else {
      // other methods (ie credit card)
      echo '<p><strong>Instructions:</strong> Please look for "Madrigal Electromotive GmbH" on your next credit card statement.</p>';
    }
  }
}


Source: https://www.cloudways.com/blog/how-to-customize-woocommerce-order-emails/

Thêm thông tin vào woocommerce email

Add Product Images to WooCommerce Emails

We’ll need to use the woocommerce_email_order_items_table filter

Let’s first enable product images within our email order items template. We can do so my taking the data passed into this filter and returning it, but changing the show_image value to true instead with this snippet (in a custom plugin or functions.php):

// Edit order items table template defaults
function sww_add_wc_order_email_images( $table, $order ) {
  
	ob_start();
	
	$template = $plain_text ? 'emails/plain/email-order-items.php' : 'emails/email-order-items.php';
	wc_get_template( $template, array(
		'order'                 => $order,
		'items'                 => $order->get_items(),
		'show_download_links'   => $show_download_links,
		'show_sku'              => $show_sku,
		'show_purchase_note'    => $show_purchase_note,
		'show_image'            => true,
		'image_size'            => $image_size
	) );
   
	return ob_get_clean();
}
add_filter( 'woocommerce_email_order_items_table', 'sww_add_wc_order_email_images', 10, 2 );

When this code is used, a 32px by 32px product image is automatically inserted in the order items table with WooCommerce emails:

Source: https://jilt.com/blog/how-to-add-information-to-woocommerce-emails/

Customer reviews plugin for Woocommerce – Customization

Good strategy from this plugin: 

After purchase => Reminder email of product review sending to customer emails => Collecting reviews (hosted on plugin’s author host) => Shown in shop page (product pages) => Discount email sending (discount code).

Những issues cần giải quyết khi sử dụng plugin này:

  • Nếu khách hàng thực hiện review 2 hoặc nhiều lần theo link từ email review => discount coupon sẽ được gửi nhiều lần. 
  • Tùy biến Email template (default template rất đơn giản, unprofessional): 

Cần tìm email template file mà plugin này sử dụng: class-ivole-email.php & email.php & email_items.php – footer. 

Class-ivole-email.php:

Line 225

foreach ( $order->get_items() as $order_item ) {
if( $excl_free && 0 >= $order_item['line_total'] ) {
continue;
}
$list_products .= $order_item['name'] . ' / ' . wc_price( $order_item['line_total'], $price_args ) . '<br/>';
}

line 319:

$this->replace['list-products'] = sprintf( '%s / %s<br/>%s / %s<br/>',
__( 'Item 1 Test', IVOLE_TEXT_DOMAIN ),
wc_price( 15 ),
__( 'Item 2 Test', IVOLE_TEXT_DOMAIN ),
wc_price( 150 )
);

Cách enable image của sản phẩm đã mua:

You can go to the “Review Reminder” Tab move towards the “Email Body” section. you can found there is wordpress editor with two option “View/Text” tab For making CSS changes click on the “Text” Tab and remove the inline style sheet and click on test mail.

I want review reminder email to show the picture of purchased product: 

Go to the folder containing email templates: wp-content/themes/your-child-theme-name/woocommerce/emails/ then turn image on as follows: 

<?php echo $order->email_order_items_table( array(
	'show_sku'      => $sent_to_admin,
	'show_image'    => false,
	'image_size'   => array( 32, 32 ),
	'plain_text'    => $plain_text,
	'sent_to_admin' => $sent_to_admin
) ); ?>

Change it from 'show_image' => false, to 'show_image' => true, If u want to change the size of image, pls change the third row: ‘image_size’ => array( 32, 32 ), Source: https://gist.github.com/SiR-DanieL/59080a7d1146aa6b2844#file-email-order-details-php

  • Can we get all reviews hosted on the author’s host if we stop using this plugin.

the plugin hosts all reviews as static html files on AWS for page speed optimization. 

  • Show reviews on Google: using feed via google merchant => Google only shows review with comments  

Other source for reference: 

https://www.8theme.com/topic/how-to-add-thumbnail-of-the-purchase-product-at-the-checkout-order-summary/

https://stackoverflow.com/questions/30813815/show-products-image-in-orders-page-woocommerce

https://nicola.blog/2016/02/02/show-the-product-thumbnail-in-woocommerce-emails/

 

Detail class-ivole-email.php:

<?php

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

if ( ! class_exists( 'Ivole_Email' ) ) :

/**
* Reminder email for product reviews
*/
class Ivole_Email {

public $id;
public $to;
public $heading;
public $subject;
public $template_html;
public $template_items_html;
public $from;
public $from_name;
public $bcc;
public $replyto;
public $language;
public $footer;
public $find = array();
public $replace = array();
public static $default_body = "Hi {customer_first_name},\n\nThank you for shopping with us!\n\nWe would love if you could help us and other customers by reviewing products that you recently purchased in order #{order_id}. It only takes a minute and it would really help others. Click the button below and leave your review!\n\nBest wishes,\n{site_title}";
public static $default_body_coupon = "Hi {customer_first_name},\n\nThank you for reviewing your order!\n\nAs a token of appreciation, we’d like to offer you a discount coupon for your next purchases in our shop. Please apply the following coupon code during checkout to receive {discount_amount} discount.\n\n<strong>{coupon_code}</strong>\n\nBest wishes,\n{site_title}";
/**
* Constructor.
*/
public function __construct( $order_id = 0 ) {
$this->id = 'ivole_reminder';
$this->heading = strval( get_option( 'ivole_email_heading', __( 'How did we do?', IVOLE_TEXT_DOMAIN ) ) );
$this->subject = strval( get_option( 'ivole_email_subject', '[{site_title}] ' . __( 'Review Your Experience with Us', IVOLE_TEXT_DOMAIN ) ) );
$this->form_header = strval( get_option( 'ivole_form_header', __( 'How did we do?', IVOLE_TEXT_DOMAIN ) ) );
$this->form_body = strval( get_option( 'ivole_form_body', __( 'Please review your experience with products and services that you purchased at {site_title}.', IVOLE_TEXT_DOMAIN ) ) );
$this->template_html = Ivole_Email::plugin_path() . '/templates/email.php';
$this->template_items_html = Ivole_Email::plugin_path() . '/templates/email_items.php';
$this->language = get_option( 'ivole_language', 'EN' );
$this->from_name = get_option( 'ivole_email_from_name', Ivole_Email::get_blogname() );
$this->footer = get_option( 'ivole_email_footer', '' );

$this->find['site-title'] = '{site_title}';
$this->replace['site-title'] = Ivole_Email::get_blogname();

//qTranslate integration
if( function_exists( 'qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage' ) ) {
$this->heading = qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage( $this->heading );
$this->subject = qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage( $this->subject );
$this->form_header = qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage( $this->form_header );
$this->form_body = qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage( $this->form_body );
$this->from_name = qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage( $this->from_name );
$this->footer = qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage( $this->footer );
if( 'QQ' === $this->language ) {
global $q_config;
$this->language = strtoupper( $q_config['language'] );
}
}

//WPML integration
if ( has_filter( 'wpml_translate_single_string' ) && defined( 'ICL_LANGUAGE_CODE' ) && ICL_LANGUAGE_CODE ) {
$wpml_current_language = apply_filters( 'wpml_current_language', NULL );
if ( $order_id ) {
$wpml_current_language = get_post_meta( $order_id, 'wpml_language', true );
}
$this->heading = apply_filters( 'wpml_translate_single_string', $this->heading, 'ivole', 'ivole_email_heading', $wpml_current_language );
$this->subject = apply_filters( 'wpml_translate_single_string', $this->subject, 'ivole', 'ivole_email_subject', $wpml_current_language );
$this->form_header = apply_filters( 'wpml_translate_single_string', $this->form_header, 'ivole', 'ivole_form_header', $wpml_current_language );
$this->form_body = apply_filters( 'wpml_translate_single_string', $this->form_body, 'ivole', 'ivole_form_body', $wpml_current_language );
$this->from_name = apply_filters( 'wpml_translate_single_string', $this->from_name, 'ivole', 'ivole_email_from_name', $wpml_current_language );
$this->footer = apply_filters( 'wpml_translate_single_string', $this->footer, 'ivole', 'ivole_email_footer', $wpml_current_language );
if ( empty( $this->from_name ) ) {
$this->from_name = Ivole_Email::get_blogname();
}

if ( 'WPML' === $this->language ) {
$this->language = strtoupper( $wpml_current_language );
}
}

//a safety check if some translation plugin removed language
if ( empty( $this->language ) ) {
$this->language = 'EN';
}

$this->footer = strval( $this->footer );
}

/**
* Trigger version 2.
*/
public function trigger2( $order_id, $to = null ) {
$this->find['customer-first-name'] = '{customer_first_name}';
$this->find['customer-name'] = '{customer_name}';
$this->find['order-id'] = '{order_id}';
$this->find['order-date'] = '{order_date}';
$this->find['list-products'] = '{list_products}';
$api_url = '';

$this->from = get_option( 'ivole_email_from', '' );

// check if Reply-To address needs to be added to email
$this->replyto = get_option( 'ivole_email_replyto', get_option( 'admin_email' ) );
if( filter_var( $this->replyto, FILTER_VALIDATE_EMAIL ) ) {
$this->replyto = $this->replyto;
} else {
$this->replyto = get_option( 'admin_email' );
}

$comment_required = get_option( 'ivole_form_comment_required', 'no' );
if( 'no' === $comment_required ) {
$comment_required = 0;
} else {
$comment_required = 1;
}

$shop_rating = 'yes' === get_option( 'ivole_form_shop_rating', 'no' ) ? true : false;
$allowMedia = 'yes' === get_option( 'ivole_form_attach_media', 'no' ) ? true : false;
$ratingBar = 'star' === get_option( 'ivole_form_rating_bar', 'smiley' ) ? 'star' : 'smiley';
$geolocation = 'yes' === get_option( 'ivole_form_geolocation', 'no' ) ? true : false;

if ( $order_id ) {
//check if Limit Number of Reviews option is used
if( 'yes' === get_option( 'ivole_limit_reminders', 'yes' ) ) {
//check how many reminders have already been sent for this order (if any)
$reviews = get_post_meta( $order_id, '_ivole_review_reminder', true );
if( $reviews >= 1 ) {
//if more than one, then we cannot send email
return 3;
}
}
//check if registered customers option is used
$registered_customers = false;
if( 'yes' === get_option( 'ivole_registered_customers', 'no' ) ) {
$registered_customers = true;
}
$order = new WC_Order( $order_id );

//check customer roles
$for_role = get_option( 'ivole_enable_for_role', 'all' );
$enabled_roles = get_option( 'ivole_enabled_roles', array() );

// check if taxes should be included in list_products variable
$tax_displ = get_option( 'woocommerce_tax_display_cart' );
$incl_tax = false;
if ( 'excl' === $tax_displ ) {
$incl_tax = false;
} else {
$incl_tax = true;
}

//check if free products should be excluded from list_products variable
$excl_free = false;
if( 'yes' == get_option( 'ivole_exclude_free_products', 'no' ) ) {
$excl_free = true;
}

// check if we are dealing with old WooCommerce version
$customer_first_name = '';
$customer_last_name = '';
$order_date = '';
$order_currency = '';
$order_items = array();
$user = NULL;
if( method_exists( $order, 'get_billing_email' ) ) {
// Woocommerce version 3.0 or later
$user = $order->get_user();
if( $registered_customers ) {
if( $user ) {
$this->to = $user->user_email;
} else {
$this->to = $order->get_billing_email();
}
} else {
$this->to = $order->get_billing_email();
}
$this->replace['customer-first-name'] = $order->get_billing_first_name();
$customer_first_name = $order->get_billing_first_name();
$customer_last_name = $order->get_billing_last_name();
$this->replace['customer-name'] = $order->get_billing_first_name() . ' ' . $order->get_billing_last_name();
//$this->replace['order-id'] = $order_id;
$this->replace['order-id'] = $order->get_order_number();
$this->replace['order-date'] = date_i18n( wc_date_format(), strtotime( $order->get_date_created() ) );
$order_date = date_i18n( 'd.m.Y', strtotime( $order->get_date_created() ) );
$order_currency = $order->get_currency();

$price_args = array( 'currency' => $order_currency );
$list_products = '';
foreach ( $order->get_items() as $order_item ) {
if( $excl_free && 0 >= $order->get_line_subtotal( $order_item, $incl_tax ) ) {
continue;
}
$order_item->get_name() . ' / ' . wc_price( $order->get_line_subtotal( $order_item, $incl_tax ), $price_args ) . '<br/>';
}
$this->replace['list-products'] = $list_products;
} else {
// Woocommerce before version 3.0
$user_id = get_post_meta( $order_id, '_customer_user', true );
if( $user_id ) {
$user = get_user_by( 'id', $user_id );
}
if( $registered_customers ) {
if( $user ) {
$this->to = $user->user_email;
} else {
$this->to = get_post_meta( $order_id, '_billing_email', true );
}
} else {
$this->to = get_post_meta( $order_id, '_billing_email', true );
}
$this->replace['customer-first-name'] = get_post_meta( $order_id, '_billing_first_name', true );
$customer_first_name = get_post_meta( $order_id, '_billing_first_name', true );
$customer_last_name = get_post_meta( $order_id, '_billing_last_name', true );
$this->replace['customer-name'] = get_post_meta( $order_id, '_billing_first_name', true ) . ' ' . get_post_meta( $order_id, '_billing_last_name', true );
//$this->replace['order-id'] = $order_id;
$this->replace['order-id'] = $order->get_order_number();
$this->replace['order-date'] = date_i18n( wc_date_format(), strtotime( $order->order_date ) );
$order_date = date_i18n( 'd.m.Y', strtotime( $order->order_date ) );
$order_currency = $order->get_order_currency();

$price_args = array( 'currency' => $order_currency );
$list_products = '';
foreach ( $order->get_items() as $order_item ) {
if( $excl_free && 0 >= $order_item['line_total'] ) {
continue;
}
$list_products .= $order_item['name'] . ' / ' . wc_price( $order_item['line_total'], $price_args ) . '<br/>';
}
$this->replace['list-products'] = $list_products;
}
//check customer roles if there is a restriction to which roles reminders should be sent
if( 'roles' === $for_role ) {
if( isset( $user ) && !empty( $user ) ) {
$roles = $user->roles;
$intersection = array_intersect( $enabled_roles, $roles );
if( count( $intersection ) < 1 ){
//customer has no allowed roles
return 5;
}
}
}
// check if BCC address needs to be added to email
$bcc_address = get_option( 'ivole_email_bcc', '' );
if( filter_var( $bcc_address, FILTER_VALIDATE_EMAIL ) ) {
$this->bcc = $bcc_address;
} else {
$this->bcc = '';
}

$message = $this->get_content();
$message = $this->replace_variables( $message );

$secret_key = get_post_meta( $order_id, 'ivole_secret_key', true );
if( !$secret_key ) {
//generate and save a secret key for callback to DB
$secret_key = bin2hex(openssl_random_pseudo_bytes(16));
if( false === update_post_meta( $order_id, 'ivole_secret_key', $secret_key ) ) {
//could not save the secret key to DB, so a customer will not be able to submit the review form
return 6;
}
}

$data = array(
'token' => '164592f60fbf658711d47b2f55a1bbba',
'shop' => array( "name" => Ivole_Email::get_blogname(),
'domain' => Ivole_Email::get_blogurl(),
'country' => apply_filters( 'woocommerce_get_base_location', get_option( 'woocommerce_default_country' ) ) ),
'email' => array( 'to' => $this->to,
'from' => $this->from,
'fromText' => $this->from_name,
'bcc' => $this->bcc,
'replyTo' => $this->replyto,
'subject' => $this->replace_variables( $this->subject ),
'header' => $this->replace_variables( $this->heading ),
'body' => $message,
'footer' => $this->footer ),
'customer' => array( 'firstname' => $customer_first_name,
'lastname' => $customer_last_name ),
'order' => array( 'id' => strval( $order_id ),
'date' => $order_date,
'currency' => $order_currency,
'items' => Ivole_Email::get_order_items2( $order ) ),
'callback' => array( //'url' => get_option( 'home' ) . '/wp-json/ivole/v1/review',
'url' => get_rest_url( null, '/ivole/v1/review' ),
'key' => $secret_key ),
'form' => array('header' => $this->replace_variables( $this->form_header ),
'description' => $this->replace_variables( $this->form_body ),
'commentRequired' => $comment_required,
'allowMedia' => $allowMedia,
'shopRating' => $shop_rating,
'ratingBar' => $ratingBar,
'geoLocation' => $geolocation ),
'colors' => array(
'form' => array(
'bg' => get_option( 'ivole_form_color_bg', '#0f9d58' ),
'text' => get_option( 'ivole_form_color_text', '#ffffff' ),
'el' => get_option( 'ivole_form_color_el', '#1AB394' )
),
'email' => array(
'bg' => get_option( 'ivole_email_color_bg', '#0f9d58' ),
'text' => get_option( 'ivole_email_color_text', '#ffffff' )
)
),
'language' => $this->language
);
//check that array of items is not empty
if( 1 > count( $data['order']['items'] ) ) {
return 4;
}
$api_url = 'https://api.cusrev.com/v1/production/review-reminder';
} else {
// no order number means this is a test and we should provide some dummy information
$this->replace['customer-first-name'] = __( 'Jane', IVOLE_TEXT_DOMAIN );
$this->replace['customer-name'] = __( 'Jane Doe', IVOLE_TEXT_DOMAIN );
$this->replace['order-id'] = 12345;
$this->replace['order-date'] = date_i18n( wc_date_format(), time() );
$this->replace['list-products'] = sprintf(
'%s / %s<br/>%s / %s<br/>',
__( 'Item 1 Test', IVOLE_TEXT_DOMAIN ),
wc_price( 15 ),
__( 'Item 2 Test', IVOLE_TEXT_DOMAIN ),
wc_price( 150 )
);

$message = $this->get_content();
$message = $this->replace_variables( $message );

$data = array(
'token' => '164592f60fbf658711d47b2f55a1bbba',
'shop' => array( "name" => Ivole_Email::get_blogname(),
'domain' => Ivole_Email::get_blogurl() ),
'email' => array( 'to' => $to,
'from' => $this->from,
'fromText' => $this->from_name,
'replyTo' => $this->replyto,
'subject' => $this->replace_variables( $this->subject ),
'header' => $this->replace_variables( $this->heading ),
'body' => $message,
'footer' => $this->footer ),
'customer' => array( 'firstname' => __( 'Jane', IVOLE_TEXT_DOMAIN ),
'lastname' => __( 'Doe', IVOLE_TEXT_DOMAIN ) ),
'order' => array( 'id' => '12345',
'date' => date_i18n( wc_date_format(), time() ),
'currency' => get_woocommerce_currency(),
'items' => array( array( 'id' => 1,
'name' => __( 'Item 1 Test', IVOLE_TEXT_DOMAIN ),
'price' => 15,
'image' => ''),
array( 'id' => 1,
'name' => __( 'Item 1 Test', IVOLE_TEXT_DOMAIN ),
'price' => 150,
'image' => '') ) ),
'form' => array( 'header' => $this->replace_variables( $this->form_header ),
'description' => $this->replace_variables( $this->form_body ),
'commentRequired' => $comment_required,
'allowMedia' => $allowMedia,
'shopRating' => $shop_rating,
'ratingBar' => $ratingBar,
'geoLocation' => $geolocation ),
'colors' => array(
'form' => array(
'bg' => get_option( 'ivole_form_color_bg', '#0f9d58' ),
'text' => get_option( 'ivole_form_color_text', '#ffffff' ),
'el' => get_option( 'ivole_form_color_el', '#1AB394' )
),
'email' => array(
'bg' => get_option( 'ivole_email_color_bg', '#0f9d58' ),
'text' => get_option( 'ivole_email_color_text', '#ffffff' )
)
),
'language' => $this->language
);
$api_url = 'https://api.cusrev.com/v1/production/test-email';
}
$license = get_option( 'ivole_license_key', '' );
if( strlen( $license ) > 0 ) {
$data['licenseKey'] = $license;
}
$data_string = json_encode( $data );
//error_log( $data_string );
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $api_url );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "POST" );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $data_string );
curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen( $data_string ) )
);
$result = curl_exec( $ch );
if( false === $result ) {
return array( 2, curl_error( $ch ) );
}
//error_log( $result );
$result = json_decode( $result );
if( isset( $result->status ) && $result->status === 'OK' ) {
//update count of review reminders sent in order meta
if( $order_id ) {
$count = get_post_meta( $order_id, '_ivole_review_reminder', true );
$new_count = 0;
if( '' === $count ) {
$new_count = 1;
} else {
$count = intval( $count );
$new_count = $count + 1;
}
update_post_meta( $order_id, '_ivole_review_reminder', $new_count );
}
return 0;
} elseif( isset( $result->status ) && $result->status === 'Error' ) {
if( isset( $result->details ) && 0 === strcmp( 'Too many review invitations for a single order', $result->details ) ) {
//we shouldn't send one than more reminder per order because customers will be annoyed
return array( 7, __( 'Error: only one review invitation per order is allowed.', IVOLE_TEXT_DOMAIN ) . ' <a href="https://cusrev.freshdesk.com/support/solutions/articles/43000511299-error-only-one-review-invitation-per-order-is-allowed" target="_blank" rel="noopener noreferrer">' . __( 'View additional information.', IVOLE_TEXT_DOMAIN ) . '</a>' );
} else {
return 8;
}
} else {
//error_log( print_r( $result, true) );
return 1;
}
}

/**
* Get content
*
* @access public
* @return string
*/
public function get_content() {
ob_start();
//$email_heading = $this->heading;
$def_body = Ivole_Email::$default_body;
$lang = $this->language;
include( $this->template_html );
return ob_get_clean();
}

public static function plugin_path() {
return untrailingslashit( plugin_dir_path( __FILE__ ) );
}

public function get_from_address() {
$from_address = apply_filters( 'woocommerce_email_from_address', get_option( 'woocommerce_email_from_address' ), $this );
return sanitize_email( $from_address );
}

public function get_from_name() {
$from_name = apply_filters( 'woocommerce_email_from_name', get_option( 'woocommerce_email_from_name' ), $this );
return wp_specialchars_decode( esc_html( $from_name ), ENT_QUOTES );
}

public function get_content_type() {
return 'text/html';
}

public function replace_variables( $input ) {
return str_replace( $this->find, $this->replace, __( $input ) );
}

public static function get_blogname() {
$blog_name = get_option( 'ivole_shop_name', get_option( 'blogname' ) );
if( !$blog_name ) {
$blog_name = get_option( 'blogname' );
if( !$blog_name ) {
$blog_name = Ivole_Email::get_blogurl();
}
}
return wp_specialchars_decode( $blog_name, ENT_QUOTES );
}

public static function get_blogurl() {
$temp = get_option( 'home' );
$disallowed = array('http://', 'https://');
foreach($disallowed as $d) {
if(strpos($temp, $d) === 0) {
return str_replace($d, '', $temp);
}
}
return $temp;
}

public static function get_blogdomain() {
$temp = get_option( 'home' );
$temp = parse_url( $temp, PHP_URL_HOST );
//error_log( print_r( $temp, true ) );
if( !$temp ) {
//error_log( 'AA' );
$temp = '';
}
return $temp;
}

public static function get_order_items2( $order ) {
$items_return = array();
$enabled_for = get_option( 'ivole_enable_for', 'all' );
$enabled_categories = get_option( 'ivole_enabled_categories', array() );
$items = $order->get_items();
// check if taxes should be included in line items prices
$tax_display = get_option( 'woocommerce_tax_display_cart' );
$inc_tax = false;
if ( 'excl' == $tax_display ) {
$inc_tax = false;
} else {
$inc_tax = true;
}
//error_log( 'items' );
//error_log( print_r( $items, true) );
foreach ( $items as $item_id => $item ) {
// check if an item needs to be skipped because none of categories it belongs to has been enabled for reminders
if( $enabled_for === 'categories' ) {
$skip = true;
$categories = get_the_terms( $item['product_id'], 'product_cat' );
foreach ( $categories as $category_id => $category ) {
if( in_array( $category->term_id, $enabled_categories ) ) {
$skip = false;
break;
}
}
if( $skip ) {
continue;
}
}
if ( apply_filters( 'woocommerce_order_item_visible', true, $item ) ) {
//create WC_Product to use its function for getting name of the product
$prod_temp = new WC_Product( $item['product_id'] );
$image = wp_get_attachment_image_url( $prod_temp->get_image_id(), 'full', false );
if( !$image ) {
$image = '';
}
$q_name = $prod_temp->get_title();

//qTranslate integration
$ivole_language = get_option( 'ivole_language' );
if( function_exists( 'qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage' ) && $ivole_language === 'QQ' ) {
$q_name = qtranxf_useCurrentLanguageIfNotFoundUseDefaultLanguage( $q_name );
}

//WPML integration
if ( has_filter( 'translate_object_id' ) && $ivole_language === 'WPML' ) {
$wpml_current_language = get_post_meta( $order->get_id(), 'wpml_language', true );
$translated_product_id = apply_filters( 'translate_object_id', $item['product_id'], 'product', true, $wpml_current_language );
$q_name = get_the_title( $translated_product_id );
}

$q_name = strip_tags( $q_name );

//check if name of the product is empty (this could happen if a product was deleted)
if( strlen( $q_name ) === 0 ) {
continue;
}

//check if we have several variations of the same product in our order
//review requests should be sent only once per each product
$same_product_exists = false;
for($i = 0; $i < sizeof( $items_return ); $i++ ) {
if( isset( $items_return[$i]['id'] ) && $item['product_id'] === $items_return[$i]['id'] ) {
$same_product_exists = true;
$items_return[$i]['price'] += $order->get_line_subtotal( $item, $inc_tax );
}
}
if( !$same_product_exists ) {
$items_return[] = array( 'id' => $item['product_id'], 'name' => $q_name, 'price' => $order->get_line_subtotal( $item, $inc_tax ),
'image' => $image );
}
}
}
//check if free products should be excluded
if( 'yes' == get_option( 'ivole_exclude_free_products', 'no' ) ) {
$items_return_excl_free = array();
foreach ($items_return as $item_return) {
if( $item_return['price'] > 0 ) {
$items_return_excl_free[] = $item_return;
}
}
//error_log( print_r( $items_return_excl_free, true) );
return $items_return_excl_free;
}
//error_log( print_r( $items_return, true) );
return $items_return;
}
}

endif;