Add to cart in WooCommerce with PHP or JavaScript

Often when building your WooCommerce store you run into having to add to cart in unfamiliar ways.

This may happen when you have a custom offer page, for example. Or you might just want to avoid having a regular, long, checkout funnel. Some stores just don’t need all the options a regular store does and, usually, the easier it is for customers to purchase, the better.

Add a product to cart programmatically using JavaScript

The JavaScript approach might be more common because it’s often connected to custom pop-ups, menu items and such. There are also plugins which allow you to add your custom callbacks when certain elements are clicked.

Here’s a straightforward approach extracted from the actual WooCommerce function:

You can add this to your theme’s footer for example ( just before the closing body tag </body> ) or include it in another JavaScript file which is already loaded. Alternatively you can use the Insert Headers and Footers plugin.

One thing to keep in mind is that if you include the code inside another function it won’t be globally accessible anymore.

The cool part of this is that if you have a properly-built theme the cart will also update without having to reload the page.

Things to note:

  • The function makes an Ajax call so it’s not instant ( synchronous ). Even though it feels like that on a good connection.
  • If you want to add visual feedback you’ll need to modify the $.post call to add a callback.
  • Read the comments in the code and remove the parts which aren’t necessary for you.
  • If you want specific help, ask in the comments! I’m happy to help if I can.

Making sure the function works

If you want to quickly test this function, after adding it to your site, just open the console and call it directly. I’ve set up a quick example site using WooCommerce’s default theme Storefront here: https://wcplayground.mircian.com/

To see the function in action, go to the link above, open the Developer console, write m_wc_add_to_cart( 42 ) and press Enter. You’ll notice that the top right cart in the header is immediately updated.

You’ll also be able to use this for variations, if you know the variation id. Try using the id 66 in the example above and check out the cart.

If the function doesn’t work in your setup, make sure the function is available globally. Also check if wc_add_to_cart_params is available, otherwise the function won’t do anything.

Add to cart programmatically using PHP

It’s easier to modify the cart on the PHP side. For most setups you’ll still need to call this from the frontend using an Ajax call or submitting a form.

A couple of concepts to keep in mind for this:

  • WooCommerce interactions should be made using the WC() function which should be available anywhere. This allows you to access the WooCommerce instance while ensuring future compatibility of your code.
  • You won’t be able to use all functionality if you don’t use the right hook. A safe bet is to use woocommerce_init which ensures all components were loaded.

With that in mind, adding to the cart is just a matter of calling:

WC()->cart->add_to_cart( $product_id, $quantity );

You can even leave out the quantity as it will default to 1.

Similar to the JavaScript code, the function is flexible and allows you to use a variation id directly. This means that if you know your variation id is 45, for example, you can call directly WC()->cart->add_to_cart( 45 ); and the right variation will be added.

Photo by rawpixel on Unsplash

Bulk copy-paste post meta ( custom fields )

When dealing with highly customised WordPress websites you often end up with a lot of custom fields. Especially if using a manager like  Advanced Custom Fields, it could be tedious to manually copy all the fields.

Why not push the whole database?

That’s a very good solution if you have the staging and live websites in sync. In my experience though, especially when developing websites ( code ), you end up building pages locally or on staging and duplicating them is difficult.

Having a repeater field in ACF or a Flexible content field could mean lots of different text boxes to manage.

What’s the solution?

I wrote a small plugin which does a simple job:

  • Adds a textarea field with all your custom fields in JSON format.
  • Below that textarea you have a checkbox which when checked will use the value in the JSON to update the custom fields.

A simple usage example is:

  1. Install the plugin on both websites ( or just on one if you want to copy paste among pages/posts in the same website )
  2. Copy-paste the values and update.

That’s it!

Please note: the plugin is not really tested so use at your own risk, I will try to improve it as time allows it.

If you want to contribute, the GitHub repository is here.

You can download the current version here.

Photo by Paulius Dragunas on Unsplash

Privately test & configure a WooCommerce payment gateway

Given its importance, adding a new payment method to a live WooCommerce website can be a troublesome task for a developer. In this article I’m going to describe what a payment gateway is, how you can use it and most importantly how to enable a new gateway in production, safely. Our main concern is testing a gateway in production without exposing its potential issues to customers.

What is a payment gateway?

WooCommerce uses payment gateways to capture different types of payment from your customers. Some involve manual interaction, like check payments or bank transfer, which need to be confirmed by a shop manager/administrator. Others, use an external provider which handles credit card payments, which is what I’ll be covering here in this article.

So you might say that a WooCommerce payment gateway is your shop’s connection to a safe payment process handled externally.

There are many options and each one has its specific options, so adding a new option might prove problematic.

Picking a payment gateway

When looking for a payment gateway I think there are 3 main features which you have to consider:

  • Commission rate. Payment processors, who handle the actual transaction and make sure the data is safe, need to make money also. That’s why they will take a small commission from each transaction although some processors also ask for a monthly fee or might have different plans depending on the size of your business.
  • Trust. You want your customers to feel safe when they make a purchase on your website. There’s no point to having a low commission rate if people don’t place the orders. That’s why sometimes with a local audience it’s ok to use a local payment processor. But, with a global audience you might want to use something recognisable around the globe.
  • Refund & dispute policy. I’m not saying you should be trying to trick your customers but sometimes you might have complaints from customers who were not paying attention. Look out for increased fees in case of refunds. Also, make sure to check which actions might block your account as that means you won’t be receiving any payments.

Adding a new payment gateway

In WooCommerce, installing a new gateway is as easy as installing a WordPress plugin & configuring that plugin to use the credentials from the payment gateway you’ve chosen.

For most gateways you’ll find the configuration panel in WordPress admin under WooCommerce > Settings > Checkout. After setting up the desired credentials make sure to also check the option at the top to activate the gateway.

Setting up these credentials properly is essential as you’ll want to avoid:

  • Customers who took time to find the right product and filled in all their info but aren’t able to pay you. That’s one customer which will be hard to get back on your website.
  • Issues with redirects and warnings about a SSL certificate not being set up properly.Customers not seeing the payment confirmation screen which would leave them wondering if the payment got through successfully.

How to privately test your new payment gateway

Part of the issues described above can be avoided with a gateway properly setup and configured. Most gateways offer ways to test them. A test account is a copy of the actual payment processing but using “ dummy” card data to capture payments which don’t involve real money. This way you can go through the process of checkout without having to spend money.

The process is great for setting up a gateway & making sure all your pages work as intended.

For most gateways the “live” or “production” implementation requires an SSL certificate which might not be available on a staging website.

Code

Payment gateway IDs: how to find them and how to hide them (when you don’t need them)

As you see in the code you can have one or multiple gateways hidden by id. You can find a gateway’s id by going to WooCommerce > Settings in your WordPress admin. In the Settings screen choose the Gateways tab and at the bottom you will see a table with all the available gateways and their id.

When you are done with the testing simply remove or comment out the code and you’ll be good to go having a safely configured & tested new gateway for WooCommerce.

Removing a gateway

If, for any reason, you want to remove a payment gateway – either temporarily or because you’re switching to a new one, the first thing you want to do is disable it.

You can easily do that by navigating to WooCommerce > Settings > Checkout, choosing the gateway from the links at the top. On that screen uncheck the option to have the gateway activated and save your settings.

Now you are safe to also remove the gateway’s WordPress plugin if you want. You can do so by going to the Plugins menu in the WordPress administration area, deactivate & delete the plugin.

Final thoughts

Providing your users a safe and clear payment method is very important because it can be a real deal-breaker. That’s why I think you should always test all the potential pitfalls outline here in a controlled environment before going live with your new payment gateway.

Proper testing of a gateway and really any tiny thing that might affect it, makes it easier for you to relax without getting complaints from clients or customers that money ( an important part of their business! ) isn’t getting through.

Photo by Aidan Bartos on Unsplash

Extending WooCommerce admin product search to use custom fields

This post comes as a follow-up to the wp-admin posts search article. In that article, I was mentioning altering that code to be used for WooCommerce admin product search would be simple!

Turns out, from the comments, that it’s not so straight forward with the way WooCommerce handles search in the latest versions so I found an alternate way to do that.

The objective here is to enable users to search by a meta value, set for products or product variations.

Why the previous version doesn’t work

That’s pretty simple to spot once you look at the way things run. WooCommerce uses a Data Store for its post types ( products, orders, etc ) and the search is also done using a custom function. In this case it’s called ‘search_products’ and it does a custom query which basically returns an array of ids to be used for the results. The query looks like this:

$search_results = $wpdb->get_results(
   // phpcs:disable
   $wpdb->prepare(
      "SELECT DISTINCT posts.ID as product_id, posts.post_parent as parent_id FROM {$wpdb->posts} posts
      LEFT JOIN {$wpdb->postmeta} postmeta ON posts.ID = postmeta.post_id
      $type_join
      WHERE (
         posts.post_title LIKE %s
         OR posts.post_excerpt LIKE %s
         OR posts.post_content LIKE %s
         OR (
            postmeta.meta_key = '_sku' AND postmeta.meta_value LIKE %s
         )
      )
      AND posts.post_type IN ('" . implode( "','", $post_types ) . "')
      $status_where
      $type_where
      ORDER BY posts.post_parent ASC, posts.post_title ASC",
      $like_term,
      $like_term,
      $like_term,
      $like_term
   )
   // phpcs:enable
);

In this query, WooCommerce searches for the search term in the product title, description and the SKU.

The SKU search is interesting as it’s also stored as a meta. We can copy that to use the same structure.

The solution

A quick solution to this is to alter the query later and append the results to the “post__in” variable of the query args. Place this code in your theme’s functions.php or in a custom plugin.

Update: there was a mistake in the initial version, no new array was needed on line 25.

Update #2: Ray asked in the comments and I added a second example which includes the option to search for multiple, comma separated, SKUs at the same time.

This will work and add the results to the admin.

There’s one thing I would look further into. And that’s an attempt to replace the first query done by WooCommerce with a query which also includes the custom meta key we are looking for. This way, you’d end up with one query instead of 2 before the main query.

I don’t expect performance issues though, and this definitely is more update-proof as it doesn’t remove any WooCommerce filters which might change in time.

 

WordPress media uploader custom filename

In the past months, I’ve been working on a plugin which creates a custom screen in the WordPress admin. It’s a custom functionality entirely but I’ve tried using as much WordPress functions to keep the right feel in the interface.

One of the forms in this page has an image uploader which uses wp.media. I was faced with this challenge: change the filename of the images uploaded on that page.

I looked around the code and inspected the upload requests and this is the solution I came up with.

1. Mark in the media uploader

The first step was to make sure this won’t affect other media uploaders in the website. For that I used the hook current_screen in which I checked on what page we are.

Because this hook is called early on in the admin I added a filter inside of it for plupload_default_params. Using this filter I added a custom parameter which will be sent each time an image is uploaded on that page.

2. Change the filename

The next part was changing the filename. In my case I just wanted to make sure the filename is completely random ( for which I have a separate function ).

I found the most straight-forward way of doing this is using the sanitize_file_name filter. This also ensures that the filename is unique as it is called inside the wp_unique_filename function.

So inside the filter I added a check for the custom parameter added in step one and replaced the name.

The code

In order to make things easier to understand I stripped down the functions mentioned in the article and created a custom class.

Pretty easy in the end but hopefully will save someone time looking into a way of doing this. I am aware this might not be the best solution but it does a good job for what I wanted.

Photo by Samuel Zeller on Unsplash

 

Adding a custom element to the Enfold Avia Builder

In the very popular Enfold theme, the author created an easy to use layout builder called Avia Advanced Layout Builder. Because it’s so popular, I am constantly encounter it in the projects I work on for clients.

Most times I work on adding a new feature I prefer extending the builder. From a plugin or a child theme, this allows users to keep the theme up to date.

The Enfold custom element

In order to keep things simple we’re going to create a custom text element. And of course, I’m using the existing “Text block” element as reference & starting point.

Adding the new element is an easy 3-step process:

  1. Copy the existing class file to your child theme ( or plugin ), in this case /wp-content/themes/enfold/config-templatebuilder/avia-shortcodes/textblock.php. Don’t forget to include it.
  2. Change the name of the class if you don’t want to override the existing element.
    Before:

    class avia_sc_text extends aviaShortcodeTemplate {

    After:

    class custom_text_element extends aviaShortcodeTemplate {
  3. Customize by changing the values in the function “shortcode_insert_button”. You’ll notice a few properties:
    $this->config['name']         = __('Text Block', 'avia_framework' );
    $this->config['tab']         = __('Content Elements', 'avia_framework' );
    $this->config['icon']        = AviaBuilder::$path['imagesURL']."sc-text_block.png";
    $this->config['order']       = 100;
    $this->config['target']          = 'avia-target-insert';
    $this->config['shortcode']        = 'av_textblock';
    $this->config['tinyMCE']       = array('disable' => true);
    $this->config['tooltip']       = __('Creates a simple text block', 'avia_framework' );
    $this->config['preview']      = "large";

Customizing your new component

From the properties above I think you should focus on a few:

  • “name” – I think it’s obvious, this is the name of the block which is displayed in the builder
  • “tab” – this is the tab of elements, by default you have 3 tabs in the builder to group similar elements. You can even create a new tab.
  • “icon” – obvious, you can see they also have a nice helper function so you can check out the path at /wp-content/themes/enfold/config-templatebuilder/avia-template-builder/images for other icons available, or use custom one from your child theme/plugin.
  • “order” – a number used to order the elements, you can look at the other elements to figure out where to place it. It works similarly to the WordPress custom menu item order.
  • “shortcode” – the most important option which I highly encourage you to change if you don’t want to override an existing element. Use a prefix to make sure you don’t override an existing shortcode.
  • “tooltip” – this is the text displayed when you hover over the element in the builder.

Adding new options

The reason you created a new element is to add custom options, right? This can be done from the “popup_elements” function which contains an array of arrays defining each field.

Listing all the available fields and their options would require a separate, long, article. I usually just go through other components and look at what fields are available and copy them over to my custom element.

Using the new options

The good news is that once you define a new element in this array it will automatically be added to the shortcode. This means that if you add, for example, a new text input for a caption, or a subtitle, you can use it directly. Take this code as example:

array(
   'name'            => esc_html__( 'Subtitle', 'm_text' ),
   'id'              => 'subtitle',
   'container_class' => 'avia-element-fullwidth',
   'std'             => esc_html__( 'What are you thinking?', 'm_text' ),
   'type'            => 'input',
)

This will create a new property, “subtitle”, in the generated shortcode, which means you can go ahead in the “shortcode_handler” function.

You can use it by calling:

$atts['subtitle'];

Use that in the generated html. That’s all!

A few tips

There are some special elements used for layouts – you’ll notice this:

array(
      "type"     => "tab_container", 'nodescription' => true
   ),
   
array(
      "type"     => "tab",
      "name"  => __("Content" , 'avia_framework'),
      'nodescription' => true
   ),

Very useful to have the elements clearly separated.

Another one I wanted to point out is the “linkpicker”. If you’re familiar with ACF, it works similarly to the “relationship” field. The tabs are also used similarly to ACF as you just list them and when a new one is added it creates a new tab.

Photo by Ant Rozetsky on Unsplash

WooCommerce variation data in SearchWP results

SearchWP is, in my opinion, the best solution when it comes to search in your WordPress website. Besides providing a great user experience by actually returning good results, it’s well written and allows some nice extensions.

The situation which I’m going to provide a solution for is this: you have a WooCommerce website with variable products. Those products have some custom characteristics which you want to allow your users to search by.

Let’s go with an even more specific example. Let’s say the products are books, and each book has an ISBN. They are variable maybe in edition.

And you want to allow your customers to search by ISBN.

Indexing the data with SearchWP

If you’re not aware, SearchWP’s system is based on an index. The index goes through all the items/posts in the background and creates a structure which makes searching more efficient. But at the same time it allows you to set priorities for each info.

The first step to index the data properly is to make SearchWP aware of it. In order to do that we’ll need a bit of code added to either functions.php in your current theme or small custom plugin. I always recommend a custom plugin because in case you make a typo the plugin will be deactivated and you won’t kill the whole website.

In order to index the data, I used the hook “searchwp_extra_metadata“. This hook allows you to add extra data to be indexed for each post.

Now for the actual code:

As you can see, this is pretty straight-forward and give you great power on what data to associate to each post. You could also use this for example to associate relevant posts to one another or even data from taxonomies.

Resetting the index

Just adding the code above won’t do anything. You’ll need to go to the WordPress admin, navigate to Settings > Search WP. Select the post type you wish to change ( in our case Products ) and at the bottom click on “Add Custom Field”.

If the code is working properly, you will see the newly added key in that list ( in our case “m_product_isbns” ). Select that field, set its priority and save the settings.

Next, go to the “Advanced” tab and click on Reset Index. When the index finishes re-indexing the data, you’ll have the data properly attached to the products.

Sync 2 WordPress websites using REST API custom endpoints

Luckily, I recently had to find a solution to sync data between 2 WordPress websites. I say luckily because it gave me the chance to use WP REST API’s custom endpoints.

The data to be synced was not very complex fortunately, mostly basic post data. In this particular case I had to sync 2 custom post types, actually some custom fields, the post status and the permalink.

Adding custom endpoints

I must admit, for me, this as clear as it can be. Especially in a WordPress environment, the structure for adding a custom endpoint is very straight-forward.

This is something similar to what I used:

register_rest_route( 'm_api/v1', '/custom', array(
   'methods'  => 'POST',
   'callback' => array( $this, 'handle_api_request' ), // In a class
) );

What this does is:

  1. It adds a new route in the form of: http://your-website.com/wp-json/m_api/v1/custom – of course, you can use whichever structure you wish.
  2. It only passes through POST requests unless otherwise specified.
  3. And also has support for custom variables in the url ( ids and such ) – more on this in the documentation.

In this case – being a “closed circuit” situation, I used a secret key to sign all requests and make sure nobody gets in the middle. Along with HTTPS and considering no secret data was being sent, it was enough. You might want to use different types of authentication.

I’m not going to share the rest of the plugin because this is the only relevant part to this subject. What I am going to share is how I handled things from the other plugin ( on the other website ).

Connecting to the your new endpoint

In the “client” plugin, which I used to initiate the updates, I used a very simple structure.

First, I hooked on the save_post action to listen for a change in the post. After I had the data collected and validated from the post, I used this function to make the call:

public static function make_request( $data ) {
   if ( ! empty( $data ) ) {
      $data['token'] = self::get_token( $data );
      wp_remote_post( self::API_URL, array(
         'body' => $data,
      ) );
   }
}

As mentioned above, I use a secret key to sign the data and use the very nice function wp_remote_post to send the data to the other website.

On the other website, the endpoint takes over and passes the data to the assigned function to process it.

I find this structure very easy to set up with 2 small plugins: safe & simple.

Save form state with localStorage

If you haven’t used JavaScript’s “localStorage” before – now’s the time to at least look at it.

It allows for some really simple data storage – in a way similar to cookies but much easier ( in my opinion ) to work with.

So adding an item to it is as simple as:

localStorage.setItem('checkbox-name', 'checked');

Retrieving the value, let’s say after a page refresh, is as simple as:

var checkbox_state = localStorage.getItem('checkbox-name');

Which brings us to the subject of this article.

Saving a form state

You can use the simple syntax above to store the state of a checkbox or any other input to avoid frustrating your website visitors.

I’ve found this very helpful in a cart-like page where users could select some options and further navigate the website before submitting.

Instead of having to create a back-end storage for a form state that would get discarded after submission if found it much more straight-forward to use localStorage.

Here some sample code:

m_form.on('change', '.checkbox', function(e) {
   var name = $(this).attr('name');
   localStorage.setItem(name, $(this).is(':checked'));
   update_notice_message(); // An example function which you can call
});

This in turn allows you to retrieve the data on page load and keep the form state. Something among the lines of:

checkboxes.each( function () {
   var name = $(this).attr('name');
   var checked = JSON.parse(localStorage.getItem(name));
   if (checked === true) {
      $(this).prop('checked', true);
      $(this).parent().addClass('selected');
   }
});

See how the flexibility of these methods allows us to make a form more enjoyable in just a few lines of code?

Column-count might mess up Google Maps

I’ve recently struggled with what seems like a stupid issue, but I thought sharing it couldn’t hurt.

I was working on a website with 2 columns created from CSS – you know, the “column-count” property.

So I was using an element which inherited the style mentioned above which I overwrote using

column-count: 1;

But for some reason, when I was loading a Google Maps iframe it wasn’t displaying properly.

This almost drove me nuts until I realised the way to avoid this is to use:

column-count: initial;

That’s all. Hope it helps.