In this tutorial, we’ll be taking an email template created with Postcards and adding it to a WooCommerce email template. We’ll be updating the WooCommerce email template files by adding the Postcards HTML code to them.
Prerequisites
- A Designmodo account
- A WooCommerce store set up
- Ability to edit code
- Ability to add plugins to WordPress
Video Version
Steps
1. Create and export a Postcards template
Go to the Postcards app and create a template. Since we’re using WooCommerce, let’s use an e-commerce type of template.
In the Postcards app, I’ve created a template using “Transactional 7.”
Next, we want to export this file since we need the generated HTML to add to WooCommerce. To do this, press the “Export” button in the top right corner and then “Download as zip.”
Make sure you have the “Host images online” option selected so you don’t have to download and deal with the images yourself. My screen looks like this:
Download the zip and then extract the index.html file somewhere you can work with. Make a copy of this file so you can edit it without worrying about losing the original code.
Your screen should look like this:
2. Download the WooCommerce Email Test plugin
To easily test what our custom email templates in WooCommerce will look like, we can download a free plugin called WooCommerce Email Test.
In your WordPress admin dashboard, search for this plugin. Your screen should look like:
After you install and activate this plugin, you’ll have a new submenu item in your WooCommerce menu called “Email Test” like this:
If you click “Email Test” then it’ll take you to a page that looks like this:
I’ve created a test order for this tutorial. The email template we’ll be editing is “Completed Order.”
If I press the “Completed Order” button then I’ll see the email WooCommerce sends when the order has been completed. The default one looks like this:
If you don’t already have an order to work with, then create one now.
Now it’s time to start editing the WooCommerce template email files.
3. Overview of how to edit WooCommerce email templates
A WooCommerce email template is made up of several different parts. Rather than having one PHP file for each email, WooCommerce builds each email template by combining different parts in a PHP file.
For example, the completed order email is made up of a header, order items, addresses, order details, footer, and styles that are combined in the completed order template. Each of these sections is its own PHP file.
Changing the completed order email template involves changing all of these files. The header, order details, addresses, and more need to be updated to contain code from the Postcards HTML file. Any file that uses the header or footer or anything else will be affected as well.
The files are located in the wp-content/plugins/woocommerce/templates/emails folder. This folder contains the files that are used to build the emails that get sent.
The customer-completed-order.php file is the email we are looking to use. This file is also in this folder but the convention is to override this file by placing it in your theme’s folder like in this picture:
You can create this overridden file in the WooCommerce settings.
In the WooCommerce setting’s page, go to the emails tab, and click “Completed order.” At the bottom of this page, you’ll see this option to override the HTML file. When you press “Copy file to theme” it will create the new customer-completed-order.php file in your theme’s folder.
4. Edit the email-styles.php file
The first file we want to edit is the email-styles.php file. This file is in the woocommerce/templates/emails folder.
This file looks like this:
<?php /** * Email Styles * * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-styles.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.3.0 */ if ( ! defined( 'ABSPATH' ) ) { exit; } // Load colors. $bg = get_option( 'woocommerce_email_background_color' ); $body = get_option( 'woocommerce_email_body_background_color' ); $base = get_option( 'woocommerce_email_base_color' ); $base_text = wc_light_or_dark( $base, '#202020', '#ffffff' ); $text = get_option( 'woocommerce_email_text_color' ); // Pick a contrasting color for links. $link_color = wc_hex_is_light( $base ) ? $base : $base_text; if ( wc_hex_is_light( $body ) ) { $link_color = wc_hex_is_light( $base ) ? $base_text : $base; } $bg_darker_10 = wc_hex_darker( $bg, 10 ); $body_darker_10 = wc_hex_darker( $body, 10 ); $base_lighter_20 = wc_hex_lighter( $base, 20 ); $base_lighter_40 = wc_hex_lighter( $base, 40 ); $text_lighter_20 = wc_hex_lighter( $text, 20 ); $text_lighter_40 = wc_hex_lighter( $text, 40 ); // !important; is a gmail hack to prevent styles being stripped if it doesn't like something. // body{padding: 0;} ensures proper scale/positioning of the email in the iOS native email app. ?> body { padding: 0; } #wrapper { background-color: <?php echo esc_attr( $bg ); ?>; margin: 0; padding: 70px 0; -webkit-text-size-adjust: none !important; width: 100%; } #template_container { box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1) !important; background-color: <?php echo esc_attr( $body ); ?>; border: 1px solid <?php echo esc_attr( $bg_darker_10 ); ?>; border-radius: 3px !important; } #template_header { background-color: <?php echo esc_attr( $base ); ?>; border-radius: 3px 3px 0 0 !important; color: <?php echo esc_attr( $base_text ); ?>; border-bottom: 0; font-weight: bold; line-height: 100%; vertical-align: middle; font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; } #template_header h1, #template_header h1 a { color: <?php echo esc_attr( $base_text ); ?>; } #template_header_image img { margin-left: 0; margin-right: 0; } #template_footer td { padding: 0; border-radius: 6px; } #template_footer #credit { border: 0; color: <?php echo esc_attr( $text_lighter_40 ); ?>; font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; font-size: 12px; line-height: 150%; text-align: center; padding: 24px 0; } #template_footer #credit p { margin: 0 0 16px; } #body_content { background-color: <?php echo esc_attr( $body ); ?>; } #body_content table td { padding: 48px 48px 32px; } #body_content table td td { padding: 12px; } #body_content table td th { padding: 12px; } #body_content td ul.wc-item-meta { font-size: small; margin: 1em 0 0; padding: 0; list-style: none; } #body_content td ul.wc-item-meta li { margin: 0.5em 0 0; padding: 0; } #body_content td ul.wc-item-meta li p { margin: 0; } #body_content p { margin: 0 0 16px; } #body_content_inner { color: <?php echo esc_attr( $text_lighter_20 ); ?>; font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; font-size: 14px; line-height: 150%; text-align: <?php echo is_rtl() ? 'right' : 'left'; ?>; } .td { color: <?php echo esc_attr( $text_lighter_20 ); ?>; border: 1px solid <?php echo esc_attr( $body_darker_10 ); ?>; vertical-align: middle; } .address { padding: 12px; color: <?php echo esc_attr( $text_lighter_20 ); ?>; border: 1px solid <?php echo esc_attr( $body_darker_10 ); ?>; } .text { color: <?php echo esc_attr( $text ); ?>; font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; } .link { color: <?php echo esc_attr( $base ); ?>; } #header_wrapper { padding: 36px 48px; display: block; } h1 { color: <?php echo esc_attr( $base ); ?>; font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; font-size: 30px; font-weight: 300; line-height: 150%; margin: 0; text-align: <?php echo is_rtl() ? 'right' : 'left'; ?>; text-shadow: 0 1px 0 <?php echo esc_attr( $base_lighter_20 ); ?>; } h2 { color: <?php echo esc_attr( $base ); ?>; display: block; font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; font-size: 18px; font-weight: bold; line-height: 130%; margin: 0 0 18px; text-align: <?php echo is_rtl() ? 'right' : 'left'; ?>; } h3 { color: <?php echo esc_attr( $base ); ?>; display: block; font-family: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; font-size: 16px; font-weight: bold; line-height: 130%; margin: 16px 0 8px; text-align: <?php echo is_rtl() ? 'right' : 'left'; ?>; } a { color: <?php echo esc_attr( $link_color ); ?>; font-weight: normal; text-decoration: underline; } img { border: none; display: inline-block; font-size: 14px; font-weight: bold; height: auto; outline: none; text-decoration: none; text-transform: capitalize; vertical-align: middle; margin-<?php echo is_rtl() ? 'left' : 'right'; ?>: 10px; } <?php
After the `if ( ! defined(‘ABSPATH’) ) { exit; }` line, the actual style code begins.
We want to replace everything after that line with our styles defined in the <head> section of the Postcards template.
Open your index.html file that you downloaded from Postcards in a text editor. It’ll look like:
Copy everything within the opening and closing <style type=”text/css”> tag and replace all the styles in the email-styles.php file with it and save it.
Your email-styles.php file should look like:
Make sure you close the PHP tag before you paste in your style code. The “?>” line tells the file that PHP code is stopping and HTML code is beginning.
If you refresh your email template preview, you should see that the email is now unstyled.
This is because we removed all the styles that the default template was using so now it’s just showing bare HTML code.
Once we start adding the Postcards HTML to the necessary files, the template will start looking like the one we created in the Postcards app, except with content coming from WooCommerce.
The completed email-styles.php file is here.
<?php /** * Email Styles * * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-styles.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.3.0 */ if ( ! defined( 'ABSPATH' ) ) { exit; } ?> @media screen { @font-face { font-family: 'Fira Sans'; font-style: normal; font-weight: 400; src: local('Fira Sans Regular'), local('FiraSans-Regular'), url(https://fonts.gstatic.com/s/firasans/v8/va9E4kDNxMZdWfMOD5Vvl4jLazX3dA.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: 'Fira Sans'; font-style: normal; font-weight: 400; src: local('Fira Sans Regular'), local('FiraSans-Regular'), url(https://fonts.gstatic.com/s/firasans/v8/va9E4kDNxMZdWfMOD5Vvk4jLazX3dGTP.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } @font-face { font-family: 'Fira Sans'; font-style: normal; font-weight: 500; src: local('Fira Sans Medium'), local('FiraSans-Medium'), url(https://fonts.gstatic.com/s/firasans/v8/va9B4kDNxMZdWfMOD5VnZKveRhf6Xl7Glw.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: 'Fira Sans'; font-style: normal; font-weight: 500; src: local('Fira Sans Medium'), local('FiraSans-Medium'), url(https://fonts.gstatic.com/s/firasans/v8/va9B4kDNxMZdWfMOD5VnZKveQhf6Xl7Gl3LX.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } @font-face { font-family: 'Fira Sans'; font-style: normal; font-weight: 700; src: local('Fira Sans Bold'), local('FiraSans-Bold'), url(https://fonts.gstatic.com/s/firasans/v8/va9B4kDNxMZdWfMOD5VnLK3eRhf6Xl7Glw.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: 'Fira Sans'; font-style: normal; font-weight: 700; src: local('Fira Sans Bold'), local('FiraSans-Bold'), url(https://fonts.gstatic.com/s/firasans/v8/va9B4kDNxMZdWfMOD5VnLK3eQhf6Xl7Gl3LX.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } @font-face { font-family: 'Fira Sans'; font-style: normal; font-weight: 800; src: local('Fira Sans ExtraBold'), local('FiraSans-ExtraBold'), url(https://fonts.gstatic.com/s/firasans/v8/va9B4kDNxMZdWfMOD5VnMK7eRhf6Xl7Glw.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: 'Fira Sans'; font-style: normal; font-weight: 800; src: local('Fira Sans ExtraBold'), local('FiraSans-ExtraBold'), url(https://fonts.gstatic.com/s/firasans/v8/va9B4kDNxMZdWfMOD5VnMK7eQhf6Xl7Gl3LX.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } } #outlook a { padding: 0; } .ReadMsgBody, .ExternalClass { width: 100%; } .ExternalClass, .ExternalClass p, .ExternalClass td, .ExternalClass div, .ExternalClass span, .ExternalClass font { line-height: 100%; } div[style*="margin: 14px 0"], div[style*="margin: 16px 0"] { margin: 0 !important; } table, td { mso-table-lspace: 0; mso-table-rspace: 0; } table, tr, td { border-collapse: collapse; } body, td, th, p, div, li, a, span { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-line-height-rule: exactly; } img { border: 0; outline: none; line-height: 100%; text-decoration: none; -ms-interpolation-mode: bicubic; } a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; } body { margin: 0; padding: 0; width: 100% !important; -webkit-font-smoothing: antialiased; } .pc-gmail-fix { display: none; display: none !important; } @media screen and (min-width: 621px) { .pc-email-container { width: 620px !important; } } @media screen and (max-width:620px) { .pc-sm-p-30-10 { padding: 30px 10px !important } .pc-sm-fs-30 { font-size: 30px !important } .pc-sm-fs-20 { font-size: 20px !important } .pc-sm-fs-18 { font-size: 18px !important } .pc-sm-mw-100pc { min-width: 100% !important } } @media screen and (max-width:525px) { .pc-xs-p-25-0 { padding: 25px 0 !important } .pc-xs-fs-16 { font-size: 16px !important } .pc-xs-br-disabled br { display: none !important } }
5. Edit the email-header.php file
The next file we want to edit is the email-header.php file. This file is where the beginning part of the HTML file is defined. So the opening <html> tag, the <head> tag, the opening <body> tag, and the beginning parts of the HTML code where the layout is defined.
Right now, the email-header.php file looks like this.
<?php /** * Email Header * * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-header.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 2.4.0 */ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } ?> <!DOCTYPE html> <html <?php language_attributes(); ?>> <head> <meta http-equiv="Content-Type" content="text/html; charset=<?php bloginfo( 'charset' ); ?>" /> <title><?php echo get_bloginfo( 'name', 'display' ); ?></title> </head> <body <?php echo is_rtl() ? 'rightmargin' : 'leftmargin'; ?>="0" marginwidth="0" topmargin="0" marginheight="0" offset="0"> <div id="wrapper" dir="<?php echo is_rtl() ? 'rtl' : 'ltr'; ?>"> <table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%"> <tr> <td align="center" valign="top"> <div id="template_header_image"> <?php if ( $img = get_option( 'woocommerce_email_header_image' ) ) { echo '<p style="margin-top:0;"><img src="' . esc_url( $img ) . '" alt="' . get_bloginfo( 'name', 'display' ) . '" /></p>'; } ?> </div> <table border="0" cellpadding="0" cellspacing="0" width="600" id="template_container"> <tr> <td align="center" valign="top"> <!-- Header --> <table border="0" cellpadding="0" cellspacing="0" width="600" id="template_header"> <tr> <td id="header_wrapper"> <h1><?php echo $email_heading; ?></h1> </td> </tr> </table> <!-- End Header --> </td> </tr> <tr> <td align="center" valign="top"> <!-- Body --> <table border="0" cellpadding="0" cellspacing="0" width="600" id="template_body"> <tr> <td valign="top" id="body_content"> <!-- Content --> <table border="0" cellpadding="20" cellspacing="0" width="100%"> <tr> <td valign="top"> <div id="body_content_inner">
We want to replace everything here with the code at the start of the Postcards template.
If you delete the styles in the <head> of the Postcards template, your code will look something like:
Since we want to add the opening HTML code and the beginning parts of the layout, we want to add the first part of this index.html file until the rest of the layout begins.
The email template is composed of several <tbody> tags that reside within a parent table. In the template, this part of the code is here:
The code within the yellow box is the beginning part of the HTML layout and every section below it will be defined within its own <tbody> tag until the closing which will be the email-footer.php.
So copy and paste all the code until the <table> opening but not the <tbody> below it.
Your email-header.php file should look like:
Not much should change in the email preview.
Also, to add the title, there was a line of code in the original email-header.php file that added the title.
Add this PHP code to your <title> so your page has a title instead of nothing.
The final email-header.php file is here.
6. Begin editing the customer-completed-order.php file
We have our styles and beginning HTML code set up. Now it’s time to start adding content.
The first part of the template is the beginning.
This is defined within the customer-completed-order.php file, the specific email we are updating. At the beginning of the tutorial, you should have created an overridden file for customer-completed-order.php that is in your theme’s folder.
The original file looks like this.
<?php /** * Customer completed order email * * This template can be overridden by copying it to yourtheme/woocommerce/emails/customer-completed-order.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.7.0 */ if ( ! defined( 'ABSPATH' ) ) { exit; } /* * @hooked WC_Emails::email_header() Output the email header */ do_action( 'woocommerce_email_header', $email_heading, $email ); ?> <?php /* translators: %s: Customer first name */ ?> <p><?php printf( esc_html__( 'Hi %s,', 'woocommerce' ), esc_html( $order->get_billing_first_name() ) ); ?></p> <?php /* translators: %s: Site title */ ?> <p><?php esc_html_e( 'We have finished processing your order.', 'woocommerce' ); ?></p> <?php /* * @hooked WC_Emails::order_details() Shows the order details table. * @hooked WC_Structured_Data::generate_order_data() Generates structured data. * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. * @since 2.5.0 */ do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); /* * @hooked WC_Emails::order_meta() Shows order meta data. */ do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); /* * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); /** * Show user-defined additional content - this is set in each email's settings. */ if ( $additional_content ) { echo wp_kses_post( wpautop( wptexturize( $additional_content ) ) ); } /* * @hooked WC_Emails::email_footer() Output the email footer */ do_action( 'woocommerce_email_footer', $email );
This file uses code from the template pieces like email-header.php, email-footer.php, and others by using WooCommerce actions.
In the code file on GitHub, on line 25 there is an action that uses the email-header.php file. This action adds the email-header.php code in that file at that location.
So the customer-completed-order.php file is a combination of HTML and PHP code and WooCommerce actions.
In the customer-completed-order.php file, we want to add actions where they’re needed and write HTML and PHP code where it’s unique to the customer completed order email.
The sections of the template that are unique to that file are:
Each of these sections is its own <tbody> tag.
Replace the HTML code in the customer-completed-order.php file that’s in between the email header and order details actions with these <tbody> tags.
Before:
After:
On line 30 in the above image, you may have noticed that there is new PHP code. This is the code that is required to print out the order number dynamically rather than hard coding the value for every email.
If you save the file and refresh your email preview you should see:
We are making progress!
The customer-completed-order.php file at this step can be found here.
<?php /** * Customer completed order email * * This template can be overridden by copying it to yourtheme/woocommerce/emails/customer-completed-order.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.7.0 */ if ( ! defined( 'ABSPATH' ) ) { exit; } /* * @hooked WC_Emails::email_header() Output the email header */ do_action( 'woocommerce_email_header', $email_heading, $email ); ?> <tbody> <tr> <td class="pc-fb-font" style="line-height: 20px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 500; color: #40BE65; padding: 0 20px;" valign="top"> <?php echo sprintf(__('Order #%s', 'woocommerce'), $order->get_order_number()); ?> </td> </tr> <tr> <td height="15" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td class="pc-sm-fs-30 pc-fb-font" style="font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 36px; font-weight: 800; line-height: 46px; letter-spacing: -0.6px; color: #151515; padding: 0 20px;" valign="top"> Thank you for your purchase! </td> </tr> <tr> <td height="15" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td class="pc-sm-fs-18 pc-xs-fs-16 pc-fb-font" style="font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 20px; line-height: 30px; letter-spacing: -0.2px; color: #9B9B9B; padding: 0 20px;" valign="top"> Thanks for shopping with us. <br>We've received your order and we're already getting started on it. You'll get an email soon with all the details. </td> </tr> <tr> <td height="25" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <?php /* * @hooked WC_Emails::order_details() Shows the order details table. * @hooked WC_Structured_Data::generate_order_data() Generates structured data. * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. * @since 2.5.0 */ do_action( 'woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email ); /* * @hooked WC_Emails::order_meta() Shows order meta data. */ do_action( 'woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email ); /* * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ do_action( 'woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email ); /** * Show user-defined additional content - this is set in each email's settings. */ if ( $additional_content ) { echo wp_kses_post( wpautop( wptexturize( $additional_content ) ) ); } /* * @hooked WC_Emails::email_footer() Output the email footer */ do_action( 'woocommerce_email_footer', $email );
7. Edit the email-order-details.php file
The email-order-details.php file contains the rest of the template and it includes multiple actions.
The order details and the order itself is contained within this file. It’s an action that contains other actions.
We left off by adding three <tbody> tags to the customer-completed-order.php file and below that there was an action called “woocommerce_email_order_details” and this is the email-order-details.php file.
The default email-order-details.php file looks like this.
<?php /** * Order details table shown in emails. * * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-order-details.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.7.0 */ defined( 'ABSPATH' ) || exit; $text_align = is_rtl() ? 'right' : 'left'; do_action( 'woocommerce_email_before_order_table', $order, $sent_to_admin, $plain_text, $email ); ?> <h2> <?php if ( $sent_to_admin ) { $before = '<a class="link" href="' . esc_url( $order->get_edit_order_url() ) . '">'; $after = '</a>'; } else { $before = ''; $after = ''; } /* translators: %s: Order ID. */ echo wp_kses_post( $before . sprintf( __( '[Order #%s]', 'woocommerce' ) . $after . ' (<time datetime="%s">%s</time>)', $order->get_order_number(), $order->get_date_created()->format( 'c' ), wc_format_datetime( $order->get_date_created() ) ) ); ?> </h2> <div style="margin-bottom: 40px;"> <table class="td" cellspacing="0" cellpadding="6" style="width: 100%; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif;" border="1"> <thead> <tr> <th class="td" scope="col" style="text-align:<?php echo esc_attr( $text_align ); ?>;"><?php esc_html_e( 'Product', 'woocommerce' ); ?></th> <th class="td" scope="col" style="text-align:<?php echo esc_attr( $text_align ); ?>;"><?php esc_html_e( 'Quantity', 'woocommerce' ); ?></th> <th class="td" scope="col" style="text-align:<?php echo esc_attr( $text_align ); ?>;"><?php esc_html_e( 'Price', 'woocommerce' ); ?></th> </tr> </thead> <tbody> <?php echo wc_get_email_order_items( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $order, array( 'show_sku' => $sent_to_admin, 'show_image' => false, 'image_size' => array( 32, 32 ), 'plain_text' => $plain_text, 'sent_to_admin' => $sent_to_admin, ) ); ?> </tbody> <tfoot> <?php $item_totals = $order->get_order_item_totals(); if ( $item_totals ) { $i = 0; foreach ( $item_totals as $total ) { $i++; ?> <tr> <th class="td" scope="row" colspan="2" style="text-align:<?php echo esc_attr( $text_align ); ?>; <?php echo ( 1 === $i ) ? 'border-top-width: 4px;' : ''; ?>"><?php echo wp_kses_post( $total['label'] ); ?></th> <td class="td" style="text-align:<?php echo esc_attr( $text_align ); ?>; <?php echo ( 1 === $i ) ? 'border-top-width: 4px;' : ''; ?>"><?php echo wp_kses_post( $total['value'] ); ?></td> </tr> <?php } } if ( $order->get_customer_note() ) { ?> <tr> <th class="td" scope="row" colspan="2" style="text-align:<?php echo esc_attr( $text_align ); ?>;"><?php esc_html_e( 'Note:', 'woocommerce' ); ?></th> <td class="td" style="text-align:<?php echo esc_attr( $text_align ); ?>;"><?php echo wp_kses_post( nl2br( wptexturize( $order->get_customer_note() ) ) ); ?></td> </tr> <?php } ?> </tfoot> </table> </div> <?php do_action( 'woocommerce_email_after_order_table', $order, $sent_to_admin, $plain_text, $email ); ?>
We need to update this so it uses the Postcards template code and add PHP so the template code uses data from WooCommerce.
There are many sections to this file.
- The view my order button.
- The order numbers and order dates.
- The addresses.
- The order items table.
- The price table.
Luckily for us, the Postcards template already has everything, we just need to know what to copy and paste and where.
The first thing we can add is the view my order button. This is one <tbody> that we can copy into the order details file.
The <tbody> that contains this button is below the last <tbody> from the previous section.
This is what my order details file looks like so far. I deleted all the content and replaced it with this <tbody>.
On lines 33, 34, and 35, there is some PHP code added.
Line 33: this is code that prints out a line depending on if the recipient of the email is an admin or not.
Line 34: In the href, this line of code dynamically creates the URL link.
Line 35: This is how to print the View my order text through PHP and WooCommerce.
Next is the order number, order date, and addresses.
This <tbody> is right below the button code.
The text that shows “Order numbers” and the order number need to be changed to PHP code. This will make the text display dynamically.
Change the text on lines 69 and 79 to:
And then for the order date:
If I save this file and refresh the preview, my template looks like:
8. Edit the email-address.php file
There is another action that needs to be added in this action. The action that will be added to order details action code is the email addresses code.
We need to add the email addresses action in the order details action because the Postcards HTML code has the addresses and order details in the same code area.
The part of the template that defines the addresses is under the code that looks like this:
We want to move all the code that defines the addresses into email-addresses.php and include that code in this file by using an action.
Copy and delete the entire <table> that is the addresses code and replace it with an action that includes the email-addresses.php file. That will look like:
Now open the email-addresses.php file. It’ll look like this file.
<?php /** * Email Addresses * * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-addresses.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.5.4 */ if ( ! defined( 'ABSPATH' ) ) { exit; } $text_align = is_rtl() ? 'right' : 'left'; $address = $order->get_formatted_billing_address(); $shipping = $order->get_formatted_shipping_address(); ?><table id="addresses" cellspacing="0" cellpadding="0" style="width: 100%; vertical-align: top; margin-bottom: 40px; padding:0;" border="0"> <tr> <td style="text-align:<?php echo esc_attr( $text_align ); ?>; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; border:0; padding:0;" valign="top" width="50%"> <h2><?php esc_html_e( 'Billing address', 'woocommerce' ); ?></h2> <address class="address"> <?php echo wp_kses_post( $address ? $address : esc_html__( 'N/A', 'woocommerce' ) ); ?> <?php if ( $order->get_billing_phone() ) : ?> <br/><?php echo esc_html( $order->get_billing_phone() ); ?> <?php endif; ?> <?php if ( $order->get_billing_email() ) : ?> <br/><?php echo esc_html( $order->get_billing_email() ); ?> <?php endif; ?> </address> </td> <?php if ( ! wc_ship_to_billing_address_only() && $order->needs_shipping_address() && $shipping ) : ?> <td style="text-align:<?php echo esc_attr( $text_align ); ?>; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; padding:0;" valign="top" width="50%"> <h2><?php esc_html_e( 'Shipping address', 'woocommerce' ); ?></h2> <address class="address"><?php echo wp_kses_post( $shipping ); ?></address> </td> <?php endif; ?> </tr> </table>
Replace the <table> that’s there with the <table> from the Postcards template. That’ll make it look like:
Now we have to update the values to use PHP code and remove the hardcoded values.
There are 3 places where PHP code was added.
Lines 31-33 is code that prints the following section if there is a shipping address.
Line 43 is code that prints the shipping address title.
Line 53 is code that prints the shipping address in WordPress with the shipping value defined in line 23.
Next is the billing address section.
Lines 65-67 are ending the if statements from before.
Line 78 is printing the billing address line.
Lines 88-94 is dynamically printing the billing address, phone number, and email address depending on if those values are available.
If you save the file and refresh your preview, you should see:
The 2nd billing address will be removed once we finish more parts of the template.
The final email-addresses.php file can be found here.
<?php /** * Email Addresses * * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-addresses.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.5.4 */ if ( ! defined( 'ABSPATH' ) ) { exit; } $address = $order->get_formatted_billing_address(); $shipping = $order->get_formatted_shipping_address(); ?> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td style="font-size: 0;" valign="top"> <!--[if (gte mso 9)|(IE)]><table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"><![endif]--> <?php if (!wc_ship_to_billing_address_only() && $order->needs_shipping_address() && $shipping) : ?> <!--[if (gte mso 9)|(IE)]><tr><td width="280" valign="top"><![endif]--> <div class="pc-sm-mw-100pc" style="display: inline-block; max-width: 280px; width: 100%; vertical-align: top;"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td style="padding: 10px 20px;" valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td class="pc-fb-font" style="border-bottom: 1px solid #E5E5E5; padding-bottom: 10px; letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: bold; color: #151515;" valign="top"> <?php esc_html_e('Shipping address', 'woocommerce'); ?> </td> </tr> <tr> <td height="10" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td class="pc-fb-font" style="letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; color: #151515; padding: 10px 0 0;" valign="top"> <?php echo wp_kses_post($shipping); ?> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </div> <!--[if (gte mso 9)|(IE)]></td><td width="280" valign="top"><![endif]--> <?php else: ?> <!--[if (gte mso 9)|(IE)]><tr><td width="100%" valign="top"><![endif]--> <?php endif; ?> <div class="pc-sm-mw-100pc" style="display: inline-block; max-width: 280px; width: 100%; vertical-align: top;"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td style="padding: 10px 20px;" valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td class="pc-fb-font" style="border-bottom: 1px solid #E5E5E5; padding-bottom: 10px; letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: bold; color: #151515;" valign="top"> <?php esc_html_e('Billing address', 'woocommerce'); ?> </td> </tr> <tr> <td height="10" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td class="pc-fb-font" style="letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; color: #151515; padding: 10px 0 0;" valign="top"> <?php echo wp_kses_post($address ? $address : esc_html__('N/A', 'woocommerce') ); ?> <?php if ($order->get_billing_phone()) : ?> <br/><?php echo wc_make_phone_clickable($order->get_billing_phone()); ?> <?php endif; ?> <?php if ($order->get_billing_email()) : ?> <br/><?php echo esc_html($order->get_billing_email()); ?> <?php endif; ?> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </div> <!--[if (gte mso 9)|(IE)]></td></tr></table><![endif]--> </td> </tr> </tbody> </table>
The updated email-order-details.php file can be found here.
<?php /** * Order details table shown in emails. * * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-order-details.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.7.0 */ defined( 'ABSPATH' ) || exit; ?> <tbody> <tr> <td style="padding: 0 10px;" valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td style="padding: 5px 10px;" valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation"> <tbody> <tr> <td style="border-radius: 8px; padding: 14px 19px; background-color: #1595E7;" bgcolor="#1595E7" valign="top" align="center"> <?php $order_url = $sent_to_admin ? $order->get_edit_order_url() : $order->get_view_order_url(); ?> <a class="pc-fb-font" href="<?php echo esc_url($order_url); ?>" style="text-decoration: none; word-break: break-word; line-height: 24px; letter-spacing: -0.2px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: 500; color: #ffffff; display: block;"> <?php esc_html_e('View my order', 'woocommerce'); ?> </a> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </td> </tr> <tr> <td height="25" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td style="font-size: 0;" valign="top"> <!--[if (gte mso 9)|(IE)]><table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"><tr><td width="280" valign="top"><![endif]--> <div class="pc-sm-mw-100pc" style="display: inline-block; max-width: 280px; width: 100%; vertical-align: top;"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td style="padding: 10px 20px;" valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td class="pc-fb-font" style="border-bottom: 1px solid #E5E5E5; padding-bottom: 10px; letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: bold; color: #151515;" valign="top"> <?php esc_html_e('Order number', 'woocommerce'); ?> </td> </tr> <tr> <td height="10" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td class="pc-fb-font" style="letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; color: #151515; padding: 10px 0 0;" valign="top"> <?php echo $order->get_order_number(); ?> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </div> <!--[if (gte mso 9)|(IE)]></td><td width="280" valign="top"><![endif]--> <div class="pc-sm-mw-100pc" style="display: inline-block; max-width: 280px; width: 100%; vertical-align: top;"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td style="padding: 10px 20px;" valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td class="pc-fb-font" style="border-bottom: 1px solid #E5E5E5; padding-bottom: 10px; letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: bold; color: #151515;" valign="top"> <?php esc_html_e('Order date', 'woocommerce'); ?> </td> </tr> <tr> <td height="10" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td class="pc-fb-font" style="letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; color: #151515; padding: 10px 0 0;" valign="top"> <?php echo $order->get_date_created()->format('F d Y'); ?> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </div> <!--[if (gte mso 9)|(IE)]></td></tr></table><![endif]--> </td> </tr> </tbody> </table> </td> </tr> <tr> <td style="height: 10px; line-height: 1px; font-size: 1px;" height="10"> </td> </tr> <tr> <td valign="top"> <?php /* * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); ?> </td> </tr> </tbody>
9. Add the order items code to email-order-details.php
Next up is the order items table. This is where the order item images, price, and quantity are defined.
We will be adding this code to the email-order-details.php file under the email addresses action.
The first thing we can do is add the entire <tbody> containing the order items and prices from the Postcards template and add it to the end of the email-order-details.php file.
The opening <tbody> for this section in the index.html file begins here:
Find the end of that tag and insert it at the bottom of the email-order-details.php file.
If you refresh your preview you should see:
Now we have to add code to print the values dynamically.
The first thing to do is add an action before the order table begins and print the table headers with PHP code.
Line 149 is an action that is needed for WooCommerce admins.
Lines 155, 158, and 161 are code for printing the titles of the table headers.
Next are the item rows. We add the item rows using a special WooCommerce function called wc_get_email_order_items.
This function gets the HTML code for the order items. It works like an action and it puts the code from email-order-items.php in where it’s called.
Instead of having every row of an order item printed using code, we add this function and update the code in email-order-items.php like we’ve been doing with actions.
That makes the email-order-details.php look like this:
The large code which printed out the order items was moved to email-order-items.php and replaced with this function.
The email-order-items.php file looks like this.
<?php /** * Email Order Items * * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-order-items.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.7.0 */ defined( 'ABSPATH' ) || exit; $text_align = is_rtl() ? 'right' : 'left'; $margin_side = is_rtl() ? 'left' : 'right'; foreach ( $items as $item_id => $item ) : $product = $item->get_product(); $sku = ''; $purchase_note = ''; $image = ''; if ( ! apply_filters( 'woocommerce_order_item_visible', true, $item ) ) { continue; } if ( is_object( $product ) ) { $sku = $product->get_sku(); $purchase_note = $product->get_purchase_note(); $image = $product->get_image( $image_size ); } ?> <tr class="<?php echo esc_attr( apply_filters( 'woocommerce_order_item_class', 'order_item', $item, $order ) ); ?>"> <td class="td" style="text-align:<?php echo esc_attr( $text_align ); ?>; vertical-align: middle; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; word-wrap:break-word;"> <?php // Show title/image etc. if ( $show_image ) { echo wp_kses_post( apply_filters( 'woocommerce_order_item_thumbnail', $image, $item ) ); } // Product name. echo wp_kses_post( apply_filters( 'woocommerce_order_item_name', $item->get_name(), $item, false ) ); // SKU. if ( $show_sku && $sku ) { echo wp_kses_post( ' (#' . $sku . ')' ); } // allow other plugins to add additional product information here. do_action( 'woocommerce_order_item_meta_start', $item_id, $item, $order, $plain_text ); wc_display_item_meta( $item, array( 'label_before' => '<strong class="wc-item-meta-label" style="float: ' . esc_attr( $text_align ) . '; margin-' . esc_attr( $margin_side ) . ': .25em; clear: both">', ) ); // allow other plugins to add additional product information here. do_action( 'woocommerce_order_item_meta_end', $item_id, $item, $order, $plain_text ); ?> </td> <td class="td" style="text-align:<?php echo esc_attr( $text_align ); ?>; vertical-align:middle; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif;"> <?php $qty = $item->get_quantity(); $refunded_qty = $order->get_qty_refunded_for_item( $item_id ); if ( $refunded_qty ) { $qty_display = '<del>' . esc_html( $qty ) . '</del> <ins>' . esc_html( $qty - ( $refunded_qty * -1 ) ) . '</ins>'; } else { $qty_display = esc_html( $qty ); } echo wp_kses_post( apply_filters( 'woocommerce_email_order_item_quantity', $qty_display, $item ) ); ?> </td> <td class="td" style="text-align:<?php echo esc_attr( $text_align ); ?>; vertical-align:middle; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif;"> <?php echo wp_kses_post( $order->get_formatted_line_subtotal( $item ) ); ?> </td> </tr> <?php if ( $show_purchase_note && $purchase_note ) { ?> <tr> <td colspan="3" style="text-align:<?php echo esc_attr( $text_align ); ?>; vertical-align:middle; font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif;"> <?php echo wp_kses_post( wpautop( do_shortcode( $purchase_note ) ) ); ?> </td> </tr> <?php } ?> <?php endforeach; ?>
The basic idea is to create a loop and iterate over each order item and print out the HTML for each one.
Change your email-order-items.php file to look like this:
This sets up the loop and prints out the Postcard template’s <tr> containing the first order item.
Now we want to change the values to update dynamically. Let’s start with the image.
Change the image code from:
To:
This is how to display images using WooCommerce code.
Next, the product name.
Before:
After:
Line 76 is the WooCommerce and WordPress way of printing the item name.
Line 79 is printing out the SKU value if there is one.
Next, we want to update the description of the item.
Before:
After:
Line 94 allows other plugins to add additional product information there.
Line 96 prints out meta information of the item.
Line 104 ends the plugin information action.
Next are the quantity and price.
Before:
After:
In between lines 121 and 133 is code to dynamically update the quantity of the item and it takes refunds into consideration.
Line 135 prints out the subtotal for the item using WooCommerce and WordPress code.
Lines 138-150 prints out more data if there are some purchase notes to display.
If you save the file and refresh the preview, you should see:
The final email-order-items.php file is here.
<?php /** * Email Order Items * * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-order-items.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.7.0 */ defined( 'ABSPATH' ) || exit; foreach ( $items as $item_id => $item ) : $product = $item->get_product(); $sku = ''; $purchase_note = ''; $image = ''; if (!apply_filters('woocommerce_order_item_visible', true, $item)) { continue; } if (is_object($product)) { $sku = $product->get_sku(); $purchase_note = $product->get_purchase_note(); $image = $product->get_image($image_size, array( 'class' => 'pc-fb-font', 'alt' => $item->get_name(), 'style' => 'border: 0; outline: 0; line-height: 100%; -ms-interpolation-mode: bicubic; display: block;', )); } ?> <tr> <td style="padding: 20px 10px 20px 0; letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; border-bottom: 1px solid #E5E5E5;" valign="top"> <table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"> <tbody> <tr> <td valign="top" style="font-size: 0;"> <!--[if (gte mso 9)|(IE)]><table role="presentation" cellspacing="0" cellpadding="0" border="0" width="400"><tr><td width="120" valign="top"><![endif]--> <div style="display: inline-block; max-width: 120px; vertical-align: top;"> <table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"> <tbody> <tr> <td style="padding: 0 20px 0 0;" valign="top"> <?php // Show title/image etc. if ($show_image) { echo wp_kses_post(apply_filters('woocommerce_order_item_thumbnail', $image, $item)); } ?> </td> </tr> </tbody> </table> </div> <!--[if (gte mso 9)|(IE)]></td><td width="280" valign="top"><![endif]--> <div style="display: inline-block; max-width: 280px; vertical-align: top;"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td style="padding: 9px 0 0;" valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td class="pc-fb-font" style="font-family: 'Fira Sans', Helvetica, Arial, sans-serif; letter-spacing: -0.3px; line-height: 28px; font-weight: 500; font-size: 18px; color: #151515;" valign="top"> <?php // Product name. echo wp_kses_post(apply_filters('woocommerce_order_item_name', $item->get_name(), $item, false)); // SKU. if ($show_sku && $sku) { echo wp_kses_post('<br />(#' . $sku . ')'); } ?> </td> </tr> <tr> <td height="4" style="font-size: 1px; line-height: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td class="pc-fb-font" style="font-family: 'Fira Sans', Helvetica, Arial, sans-serif; letter-spacing: -0.2px; line-height: 24px; font-size: 16px; color: #9B9B9B;" valign="top"> <?php // allow other plugins to add additional product information here. do_action('woocommerce_order_item_meta_start', $item_id, $item, $order, $plain_text); wc_display_item_meta( $item, array( 'before' => '<ul class="wc-item-meta" style="margin:0; padding-left:0; list-style-type:none;"><li>', ) ); // allow other plugins to add additional product information here. do_action('woocommerce_order_item_meta_end', $item_id, $item, $order, $plain_text); ?> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </div> <!--[if (gte mso 9)|(IE)]></td></tr></table><![endif]--> </td> </tr> </tbody> </table> </td> <td class="pc-fb-font" style="padding: 29px 10px 20px 0; color: #9B9B9B; letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; border-bottom: 1px solid #E5E5E5;" valign="top" align="right"> <?php $qty = $item->get_quantity(); $refunded_qty = $order->get_qty_refunded_for_item($item_id); if ($refunded_qty) { $qty_display = '<del>' . esc_html($qty) . '</del> <ins>' . esc_html($qty - ( $refunded_qty * -1 )) . '</ins>'; } else { $qty_display = esc_html($qty); } echo wp_kses_post(apply_filters('woocommerce_email_order_item_quantity', $qty_display, $item)); ?> </td> <td class="pc-fb-font" style="padding: 29px 0 20px; letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; border-bottom: 1px solid #E5E5E5; color: #151515;" valign="top" align="right"> <?php echo wp_kses_post($order->get_formatted_line_subtotal($item)); ?> </td> </tr> <?php if ($show_purchase_note && $purchase_note) { ?> <tr> <td style="padding: 20px 10px 20px 0; letter-spacing: -0.2px; line-height: 22px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 12px; border-bottom: 1px solid #E5E5E5;" valign="top" colspan="3"> <?php echo wp_kses_post(wpautop(do_shortcode($purchase_note))); ?> </td> </tr> <?php } ?> <?php endforeach; ?>
10. Add the total price table to email-order-details.php
The last part of the email-order-details.php file is the total prices.
Once again, we want to loop through each row of the totals list and print out the relevant information.
After the order items <tbody> and the <tbody> containing space for layout, add in PHP code to loop over the table rows containing the total elements.
Before:
After.
A lot changed here. The opening and closing <tbody> tags are the empty space created by the Postcards template for layout purposes. Everything in between opening and closing PHP tags is the code for printing out the total prices depending on which one needs to be printed.
If you refresh the preview, you’ll see:
Next, we can finish off this file by adding a new section that prints if there is a customer note to be printed.
After that there is an action for after the order table, and then close the <tbody> tag.
The final file for email-order-details.php can be found here.
<?php /** * Order details table shown in emails. * * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-order-details.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.7.0 */ defined('ABSPATH') || exit; ?> <tbody> <tr> <td style="padding: 0 10px;" valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td style="padding: 5px 10px;" valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation"> <tbody> <tr> <td style="border-radius: 8px; padding: 14px 19px; background-color: #1595E7;" bgcolor="#1595E7" valign="top" align="center"> <?php $order_url = $sent_to_admin ? $order->get_edit_order_url() : $order->get_view_order_url(); ?> <a class="pc-fb-font" href="<?php echo esc_url($order_url); ?>" style="text-decoration: none; word-break: break-word; line-height: 24px; letter-spacing: -0.2px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: 500; color: #ffffff; display: block;"> <?php esc_html_e('View my order', 'woocommerce'); ?> </a> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </td> </tr> <tr> <td height="25" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td style="font-size: 0;" valign="top"> <!--[if (gte mso 9)|(IE)]><table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"><tr><td width="280" valign="top"><![endif]--> <div class="pc-sm-mw-100pc" style="display: inline-block; max-width: 280px; width: 100%; vertical-align: top;"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td style="padding: 10px 20px;" valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td class="pc-fb-font" style="border-bottom: 1px solid #E5E5E5; padding-bottom: 10px; letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: bold; color: #151515;" valign="top"> <?php esc_html_e('Order number', 'woocommerce'); ?> </td> </tr> <tr> <td height="10" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td class="pc-fb-font" style="letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; color: #151515; padding: 10px 0 0;" valign="top"> <?php echo $order->get_order_number(); ?> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </div> <!--[if (gte mso 9)|(IE)]></td><td width="280" valign="top"><![endif]--> <div class="pc-sm-mw-100pc" style="display: inline-block; max-width: 280px; width: 100%; vertical-align: top;"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td style="padding: 10px 20px;" valign="top"> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <td class="pc-fb-font" style="border-bottom: 1px solid #E5E5E5; padding-bottom: 10px; letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: bold; color: #151515;" valign="top"> <?php esc_html_e('Order date', 'woocommerce'); ?> </td> </tr> <tr> <td height="10" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td class="pc-fb-font" style="letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; color: #151515; padding: 10px 0 0;" valign="top"> <?php echo $order->get_date_created()->format('F d Y'); ?> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> </div> <!--[if (gte mso 9)|(IE)]></td></tr></table><![endif]--> </td> </tr> </tbody> </table> </td> </tr> <tr> <td style="height: 10px; line-height: 1px; font-size: 1px;" height="10"> </td> </tr> <tr> <td valign="top"> <?php /* * @hooked WC_Emails::customer_details() Shows customer details * @hooked WC_Emails::email_address() Shows email address */ do_action('woocommerce_email_customer_details', $order, $sent_to_admin, $plain_text, $email); ?> </td> </tr> </tbody> <tbody> <tr> <td height="20" style="line-height: 1px; font-size: 1px;"> </td> </tr> <tr> <td style="padding: 0 20px;" valign="top"> <?php do_action('woocommerce_email_before_order_table', $order, $sent_to_admin, $plain_text, $email); ?> <table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%"> <tbody> <tr> <th class="pc-fb-font" width="400" style="letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; padding: 10px 10px 10px 0; border-bottom: 1px solid #E5E5E5; color: #151515;" align="left"> <?php esc_html_e('Item', 'woocommerce'); ?> </th> <th class="pc-fb-font" width="44" style="letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; padding: 10px 10px 10px 0; border-bottom: 1px solid #E5E5E5; color: #151515;" align="right"> <?php esc_html_e('Qty', 'woocommerce'); ?> </th> <th class="pc-fb-font" width="56" style="letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; border-bottom: 1px solid #E5E5E5; padding: 10px 0; color: #151515;" align="right"> <?php esc_html_e('Price', 'woocommerce'); ?> </th> </tr> <tr> <td colspan="3" height="0" style="font-size: 1px; line-height: 1px;"> </td> </tr> </tbody> <tbody> <?php echo wc_get_email_order_items( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $order, array( 'show_sku' => $sent_to_admin, 'show_image' => true, 'image_size' => array(100, 100), 'plain_text' => $plain_text, 'sent_to_admin' => $sent_to_admin, ) ); ?> </tbody> <tbody> <tr> <td colspan="3" height="20" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <?php $item_totals = $order->get_order_item_totals(); if ($item_totals) { ?> <tbody> <?php $i = 1; $count_totals = count($item_totals) - 1; foreach ($item_totals as $key => $total) { if ($key === 'order_total') { continue; } ?> <tr> <td colspan="2" class="pc-fb-font" style="padding: 0 10px 0 0; letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; <?php echo $key === 'discount' ? 'color: #40BE65;' : 'color: #151515;'; ?>" valign="top" align="right"> <?php echo wp_kses_post($total['label']); ?> </td> <td class="pc-fb-font" style="letter-spacing: -0.2px; line-height: 26px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 16px; <?php echo $key === 'discount' ? 'color: #40BE65;' : 'color: #151515;'; echo ($i === 1 || $i === $count_totals) ? 'padding: 0;' : ''; ?>" valign="top" align="right"> <?php echo wp_kses_post($total['value']); ?> </td> </tr> <?php $i++; } ?> </tbody> <?php if (isset($item_totals['order_total']) && is_array($item_totals['order_total'])) { ?> <tbody> <tr> <td colspan="3" height="20" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td class="pc-fb-font pc-sm-fs-20" colspan="2" style="padding: 20px 10px 0 0; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; letter-spacing: -0.4px; line-height: 34px; font-size: 24px; border-top: 2px solid #E5E5E5; font-weight: bold; color: #151515;" valign="top" align="right"> <?php echo wp_kses_post($item_totals['order_total']['label']); ?> </td> <td class="pc-fb-font pc-sm-fs-20" style="padding: 20px 0 0; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; letter-spacing: -0.4px; line-height: 34px; font-size: 24px; border-top: 2px solid #E5E5E5; font-weight: bold; color: #151515;" valign="top" align="right"> <?php echo wp_kses_post($item_totals['order_total']['value']); ?> </td> </tr> </tbody> <?php } ?> <?php } ?> <?php if ($order->get_customer_note()) { ?> <tbody> <tr> <td colspan="3" height="20" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td colspan="3" style="padding: 20px 0; letter-spacing: -0.2px; line-height: 24px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 14px; border-top: 2px solid #E5E5E5; " valign="top"> <div style="margin-bottom: 10px; font-weight: bold;"><?php esc_html_e('Note:', 'woocommerce'); ?></div> <?php echo wp_kses_post(nl2br(wptexturize($order->get_customer_note()))); ?> </td> </tr> </tbody> <?php } ?> </table> <?php do_action('woocommerce_email_after_order_table', $order, $sent_to_admin, $plain_text, $email); ?> </td> </tr> </tbody>
11. Finish off the customer-completed-order.php file
There is only the customer-completed-order.php file and email-footer.php left.
This we left off with this file looking like this:
The last thing we edited was the email order details action.
We have to wrap the email order meta action and additional content in some HTML wrapping code from the Postcards template and delete the customer details action since we already added that.
The code should now look like:
In this code, we just check if there is an order meta or additional content to display, then wrap that code in the Postcards template layout tables, and then print out the email footer action.
The final customer-completed-order.php file can be found here.
<?php /** * Customer completed order email * * This template can be overridden by copying it to yourtheme/woocommerce/emails/customer-completed-order.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.7.0 */ if (!defined('ABSPATH')) { exit; } /* * @hooked WC_Emails::email_header() Output the email header */ do_action('woocommerce_email_header', $email_heading, $email); ?> <tbody> <tr> <td class="pc-fb-font" style="line-height: 20px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 500; color: #40BE65; padding: 0 20px;" valign="top"> <?php echo sprintf(__('Order #%s', 'woocommerce'), $order->get_order_number()); ?> </td> </tr> <tr> <td height="15" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td class="pc-sm-fs-30 pc-fb-font" style="font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 36px; font-weight: 800; line-height: 46px; letter-spacing: -0.6px; color: #151515; padding: 0 20px;" valign="top"> Thank you for your purchase! </td> </tr> <tr> <td height="15" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <tbody> <tr> <td class="pc-sm-fs-18 pc-xs-fs-16 pc-fb-font" style="font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 20px; line-height: 30px; letter-spacing: -0.2px; color: #9B9B9B; padding: 0 20px;" valign="top"> Thanks for shopping with us.<br />We've received your order and we're already getting started on it. You'll get an email soon with all the details. </td> </tr> <tr> <td height="25" style="line-height: 1px; font-size: 1px;"> </td> </tr> </tbody> <?php /* * @hooked WC_Emails::order_details() Shows the order details table. * @hooked WC_Structured_Data::generate_order_data() Generates structured data. * @hooked WC_Structured_Data::output_structured_data() Outputs structured data. * @since 2.5.0 */ do_action('woocommerce_email_order_details', $order, $sent_to_admin, $plain_text, $email); /* * @hooked WC_Emails::order_meta() Shows order meta data. */ if (has_action('woocommerce_email_order_meta')) { ?> <tbody> <tr> <td height="20" style="line-height: 1px; font-size: 1px;"> </td> </tr> <tr> <td style="padding: 0 20px;" valign="top"> <?php do_action('woocommerce_email_order_meta', $order, $sent_to_admin, $plain_text, $email); ?> </td> </tr> </tbody> <?php } /** * Show user-defined additional content - this is set in each email's settings. */ if ($additional_content) { ?> <tbody> <tr> <td height="20" style="line-height: 1px; font-size: 1px;"> </td> </tr> <tr> <td style="padding: 0 20px; letter-spacing: -0.2px; line-height: 24px; font-family: 'Fira Sans', Helvetica, Arial, sans-serif; font-size: 14px; text-align: center;" valign="top"> <?php echo wp_kses_post(wpautop(wptexturize($additional_content))); ?> </td> </tr> </tbody> <?php } /* * @hooked WC_Emails::email_footer() Output the email footer */ do_action('woocommerce_email_footer', $email);
Next open the email-footer.php file. The original one looks like this.
<?php /** * Email Footer * * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-footer.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.7.0 */ defined( 'ABSPATH' ) || exit; ?> </div> </td> </tr> </table> <!-- End Content --> </td> </tr> </table> <!-- End Body --> </td> </tr> </table> </td> </tr> <tr> <td align="center" valign="top"> <!-- Footer --> <table border="0" cellpadding="10" cellspacing="0" width="600" id="template_footer"> <tr> <td valign="top"> <table border="0" cellpadding="10" cellspacing="0" width="100%"> <tr> <td colspan="2" valign="middle" id="credit"> <?php echo wp_kses_post( wpautop( wptexturize( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) ) ) ); ?> </td> </tr> </table> </td> </tr> </table> <!-- End Footer --> </td> </tr> </table> </div> </body> </html>
Change that file to look like:
If you save your file and refresh your preview, you should see:
The final email-footer.php file can be found here.
<?php /** * Email Footer * * This template can be overridden by copying it to yourtheme/woocommerce/emails/email-footer.php. * * HOWEVER, on occasion WooCommerce will need to update template files and you * (the theme developer) will need to copy the new files to your theme to * maintain compatibility. We try to do this as little as possible, but it does * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce/Templates/Emails * @version 3.7.0 */ defined('ABSPATH') || exit; ?> </table> </td> </tr> </tbody> </table> <!-- END MODULE: Transactional 7 --> <table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation"> <tbody> <tr> <td height="20" style="font-size: 1px; line-height: 1px;"> </td> </tr> </tbody> </table> </td> </tr> </tbody> </table> <!--[if (gte mso 9)|(IE)]></td></tr></table><![endif]--> </td> </tr> </tbody> </table> <!-- Fix for Gmail on iOS --> <div class="pc-gmail-fix" style="white-space: nowrap; font: 15px courier; line-height: 0;"> </div> </body> </html>
This is the finished customer completed order email template!
What we covered
- How to create and export a Postcards email template.
- How to download the WooCommerce email preview plugin.
- How to edit WooCommerce email templates.
- How to change the code in WooCommerce templates to use the HTML from Postcards.