How To Encrypt (and Decrypt) Fields With Formidable Forms

Introduction

One of the common concerns I kept bumping up against was how to collect sensitive information from clients securely. For me, more often than not, it was for one of two main reasons. One was getting credit card information (if they weren't comfortable providing that info over the phone). The other was when taking on new Care Plan clients whose site I hadn't built (requiring me to request things like host and site login credentials for an audit).

In both of those cases, I don't want the client to email such details to me, simply because there are so many points on that email's journey where that data could be compromised - especially because everything is in plain clear text. So that was a big no-no.

In the past, I have used services like NoteShred which allowed me to pass on sensitive information knowing it gets auto destroyed after being read once. The issue for me there is, whilst the "note" is password-protected, communicating that password (to allow the recipient to open the note) requires being communicated to them in some other way (email, chat, text, phone etc). Overall, whilst a lot more secure than just email, it still made me a little uncomfortable.

Most agencies like mine face this challenge. Yes, I know there are numerous paid-for services/plugins that can address this. However, for me, I couldn't shake off knowing that there must be a better way of doing this using my own WordPress site to handle this.

Now, I use Formidable Forms (FF) as my form builder of choice, and forms built with this are the main way I get any information (sensitive or otherwise) from visitors and clients. So surely I could create forms for those two use cases?

The answer is of course - easily. The challenge is that FF saves the collected information to the WordPress database in plain text (as do all form builders, I believe). Sure, this is more secure than sending sensitive data around in emails. However, if my site should get hacked/compromised then all that golden information is easily readable. So that's not good.

So I went on a Google hunt; surely someone else must have done this. It took quite a while (lots of blind alleys and rabbit holes) but I stumbled on a great post by Victor Font addressing this very issue. All the key points I needed were there (so, a very big thank you, Victor, for writing that post).

This post here is very much built on the foundations of that post. So why write another post on this? Well, whilst Victor's post pointed me in all the right places, it still took me a while to implement it and get it all working. So I wanted to go a little further and fill in some of the blanks I had to figure out for myself. If you want to check out Victor's post you can find it here.

So, the purpose of this post is to show you everything you need to do so you can collect that sensitive information through a form, and then encrypt it before writing it to the WordPress database (and, of course, decrypt it after). So if you did, unfortunately, get hacked - at least your clients' sensitive data is not compromised.

Whilst this post specifically focuses on Formidable Forms, the theory and principals should also work for the other big form builders out there like Gravity Forms, NinjaForms etc. Naturally, any equivalent hooks will differ and the suggested code would need to be re-jigged.

I don't believe (at the time of writing) that any of these form builders offer the ability to encrypt field data natively. That said, and not sure how true this is, but I gather this is on FF's roadmap at some future time (no idea when), but until then...

A Few Caveats

Before we get started:

  1. I mentioned earlier using these methods to possibly collect credit card details. You absolutely can, but it is recommended you don't (unless you want to go through all the hoops to be PCI compliant and/or understand the risks of holding customer/client credit card details). I use payment gateways wherever possible because they are PCI compliant and all the risk is with them.
  2. This really isn’t that hard to do, but you do need to be comfortable editing WordPress config files like functions.php and wp-config.php. If not, then stop and get someone who is to follow this.
  3. This is offered as-is with no support, implied or otherwise. That said, I will endeavour to respond to any comments/questions in the comments section, time permitting.
  4. If you follow this and it messes up your site I take no responsibility. That said if you take the usual precautions before starting this, like making sure your site and database is FULLY backed up. If you do run into problems you can restore your site back to its former glory. Or better yet try it out on a staging/cloned site.

Pre-requisites

In order for this to be set up and working, there are few dependencies that need to be in place:

  • A WordPress site with at least PHP 5.3 (this version has OpenSSL baked in, which is what we'll use to do the actual encryption/decryption)
  • Formidable Forms plugin

Getting Started

The first thing we need to do is make a change to wp-config.php.

Before that, a couple of points regarding wp-config.php. Usually, by default, this key file is in the root folder of your site. The downside to this is if your site is hacked, then any information in there is compromised, such as database user name and password, plus other stuff like that. To mitigate this you should consider moving that file up a level so it's not in the root folder of your site (i.e. it's no longer a publicly accessible file). This means someone would have to hack your host to get to that file. Whilst not impossible, it's a much harder proposition for a hacker. I use Gridpane to host many of my sites, and by default, they already have it set that wp-config.php is outside the site's root folder.

Now, you cannot simply move the file; WordPress needs to know where it resides. Remember, you don't have to do this, but if you wish to, this article describes how to do just that. If you do move it, remember to update any backup plugin so it knows where that file is now.

Wherever your wp-config.php file resides, the first thing is to define some constants in there. Calls to OpenSSL, later on, will use these. The following sample lines should be added somewhere in your wp-config.php file.

define('FORMIDABLE_SALT', 'bzD?Vi&+s!i{PjUCN2N_+gUQ:`|wt::O/+~!es[pRi%-<JPS<8*Eu1%}R(nmdqY2');
define('FORMIDABLE_METHOD', 'AES-256-CBC');
define('FORMIDABLE_OPTIONS', 0);
define('FORMIDABLE_IV', '!MJekYW9G^6juRBH');Code language: PHP (php)

These lines basically define 4 "constants". The last one, for example, defines a constant with the name of FORMIDABLE_IV to have a value of "!MJekYW9G^6juRBH". Essentially, this definition acts like a global variable, except the value cannot be changed, hence why it is known as a "constant". This will save us having to state these values in multiple places. We define them in wp-config.php once, and then reference those constants whenever they're required in OpenSSL function calls.

NOTE: You will need to replace the values for FORMIDABLE_SALT and FORMIDABLE_IV with your own values. This is quite easy to do - and I'll show you how shortly, but first, let's just look at OpenSSL and what these constants are actually going to be used for.

OpenSSL

As mentioned earlier we will be using OpenSSL to do the actual encryption/decryption. This is done by calling the openssl_encrypt() and openssl_decrypt() functions respectively.

A call to either looks like:

openssl_encrypt( $data, $method, $key, $options, $iv );    // to ENCRYPT something

openssl_decrypt( $data, $method, $key, $options, $iv );    // to DECRYPT somethingCode language: PHP (php)

As you can see, both calls require the same parameters. When we call these functions later on, we will be using our constants for these parameters (except for $data). So what do they mean?

  • $data - this is the data to be encrypted/decrypted
  • $method - this defines what encryption method to be used. By all means, look up the function here to see what all the options are. The one we're using (AES-256-CBC) is very strong and should suitable for pretty much all your needs.
  • $key - The encryption/decryption key.
  • $options - You can leave this as the default value of 0.
  • $iv - A non-NULL Initialization Vector string, which is required for the chosen $method. NOTE: The length of this string depends on the $method used. For AES-256-CBC this needs to be 16 characters. If you are going to use use a different method you can find out the required length by calling openssl_cipher_iv_length() yourself.

As mentioned earlier, the list of 4 constant defines earlier is just an example. Lines 2 and 3 are fine as is, however you will need to replace the values for FORMIDABLE_SALT and FORMIDABLE_IV with your own values.

For FORMIDABLE_SALT, the easiest way is to visit https://api.wordpress.org/secret-key/1.1/salt/ and copy any one of the generated result values.

For FORMIDABLE_IV, use your favourite password generator to generate a 16-character string.

Once your constants have been added to your wp-config.php file ensure you make a secure copy of those details somewhere else where you'll never lose them. They are required for both encrypting and decrypting. If you lose them you will NOT be able to decrypt any data that was encrypted that used those keys. You have been warned!

The Functions

The final piece of the puzzle is made up of 5 small functions. These should all be added to your functions.php file (preferably within a child theme).

General Encrypt/Decrypt Functions

The first 2 functions are the basic encrypt/decrypt functions. These will be called by the main functions (further down) as required to do the actual encrypting or decrypting, and as you can see they make use of the constants we defined earlier. Essentially, the first takes in data and returns an encrypted version of that data. The second takes in encrypted data and returns a decrypted version of that text.

/* Functions allowing Formidable functions to encrypt/decrypt field data in the database
 * - constants/salts are set in wp-config.php
 */

function adz_encrypt( $data ) {
    $key =	FORMIDABLE_SALT;
    $method = 	FORMIDABLE_METHOD;
    $options = 	FORMIDABLE_OPTIONS;
    $iv = 	FORMIDABLE_IV;
    return openssl_encrypt( $data, $method, $key, $options, $iv );
}

function adz_decrypt( $encrypted ) {
    $key =	FORMIDABLE_SALT;
    $method = 	FORMIDABLE_METHOD;
    $options = 	FORMIDABLE_OPTIONS;
    $iv = 	FORMIDABLE_IV;
    return openssl_decrypt( $encrypted, $method, $key, $options, $iv );
}Code language: PHP (php)

Formidable Forms ENCRYPT Function

With the constants defined and the basic encrypt/decrypt functions in place, how do we integrate that with a Formidable Forms form?

Below is an example Formidable Form highlighting the fields I specifically want encrypted (probably overkill, but hey).

As you know, each form you have built with Formidable Forms has an ID, and each field on that form has its own ID too. You will need to know these and note them down.

Using the form above as an example, let's say the form's ID is 22, and the field IDs I want to be encrypted are 300 to 306 inclusive. The function would look like this:

// Encrypts specific fields on forms 22 and 30

add_filter('frm_pre_create_entry', 'adz_encrypt_my_field');
add_filter('frm_pre_update_entry', 'adz_encrypt_my_field');

function adz_encrypt_my_field($values) {
    if ( $values['form_id'] == 22 ) { 	// change num to your form id
        $values['item_meta'][300] = adz_encrypt( $values['item_meta'][300] );  	// field 
        $values['item_meta'][301] = adz_encrypt( $values['item_meta'][301] );   // field
	$values['item_meta'][302] = adz_encrypt( $values['item_meta'][302] );  	// ...
	$values['item_meta'][303] = adz_encrypt( $values['item_meta'][303] );   
	$values['item_meta'][304] = adz_encrypt( $values['item_meta'][304] );  	
	$values['item_meta'][305] = adz_encrypt( $values['item_meta'][305] );   
	$values['item_meta'][306] = adz_encrypt( $values['item_meta'][306] );  	
    }

    if ( $values['form_id'] == 30 ) { 	// change num to your form id
	$values['item_meta'][400] = adz_encrypt( $values['item_meta'][400] );  	
	$values['item_meta'][401] = adz_encrypt( $values['item_meta'][401] );   
    }

    return $values;
}Code language: PHP (php)

You'll also see a second "if" block for a second fictional form (ID 30) to illustrate how to manage multiple forms (that one just supposes 2 fields need encrypting). Just delete or comment out that entire "if" block if not required.

So what's it actually doing? Well, in order to encrypt form data, we need to intercept the data from the form after it has been submitted, but before the resultant entry is written to the database. To do this we can use two Formidable Forms hooks (frm_pre_create_entry and frm_pre_update_entry).

These are both called after a form is submitted and an entry is to be either created or updated - but they're called before any data is written to the database. This allows us to then take that data, encrypt it, and then pass it back to Formidable Forms to then be written to the database.

Formidable Forms DECRYPT Function - Backend

That's the encryption of fields sorted out. Now to decrypt them.

For this, you only need to know the field IDs. As these are all unique, the form ID is not required. In which case you can just list all the fields, regardless of what form they are associated with.

// Click "Edit" on Card Details or Audit Details form entry to see decrypted values
// (NOTE: just "viewing" an entry won't decrypt it - entry needs to be "edited"),
// Only field IDs needed, not form IDs

add_filter('frm_setup_edit_fields_vars', 'adz_decrypt_my_field', 20, 3);

function adz_decrypt_my_field($values, $field, $args) {
    if ( $field->id == 300 or		// field
	  $field->id == 301 or		// field
	  $field->id == 302 or		// ...
	  $field->id == 303 or		
	  $field->id == 304 or	  	
	  $field->id == 305 or		
	  $field->id == 306 or		
	  $field->id == 400 or		
	  $field->id == 401 ) {		
	$values['value'] = adz_decrypt( $values['value'] ); 					
    }
    return $values;
}Code language: PHP (php)

This function decrypts the fields listed as and when an entry is EDITED (i.e. decryption does not occur if simply viewed). It does this by using the frm_setup_edit_fields_vars hook to intercept an EDIT of any of the listed fields from the database. When that happens it decrypts the passed value before passing the new decrypted value on to be displayed.

You can (if you wish) set permissions in the Formidable plugin for who can EDIT entries. This is useful if you only want to give that functionality to specific users - meaning those that don't have that permission will just see the "view" version of the entry, which will be the hashed gibberish.

Formidable Forms DECRYPT Function - Frontend "View" / Email etc

What if you need to decrypt fields, but not through the backend? For example, if you have a front-end Formidable Forms View designed for logged in users, say. Or perhaps to decrypt fields for an email (the latter is not recommended, but again, hey).

This uses frmpro_fields_replace_shortcodes hook which intercepts the data, and if a field's shortcode has the appropriate "decrypt" flag set it will then decrypt that field's before displaying the decrypted information.

// For views or other places that can use Formidable shortcodes, e.g. emailing out an entry etc.
// Just need to add "decrypt=1" to the shortcode

add_filter('frmpro_fields_replace_shortcodes', 'decrypt_my_field_for_view', 10, 4);

function decrypt_my_field_for_view($replace_with, $tag, $atts, $field){
    if(isset($atts['decrypt'])){
        $encrypted = $replace_with;
        $replace_with = adz_decrypt($encrypted);
    }
    return $replace_with;
}Code language: PHP (php)

This will work for wherever you can put a Formidable shortcode. You just need to add “decrypt=1” as a parameter of the shortcode. For example, in a view, to show the decrypted values of 2 fields you’d specify:

<tr>
<td>[177]</td>            <!-- This field doesn't require decryption -->
<td>[178 decrypt=1]</td>  <!-- This field does -->
<td>[181 decrypt=1]</td>  <!-- This field does -->
</tr>Code language: HTML, XML (xml)

Armed with the constants and all of those functions you should now be good to go.

A Warning About Password Fields

One of the rabbit holes I ran down was with password type fields. And given, more often than not, it will likely be passwords you want to encrypt, you'll need to bear this in mind.

A Formidable Forms password field is like most password fields; it hides/masks the text that is typed into it. Fair enough, right? The issue comes when you need to see the decrypted password when editing the resultant entry that has such a field.

When you edit such an entry containing a password field, that field (even after being decrypted) still shows in the backend as masked so you can't read it! This stumped me for a little while. In that scenario, the only way to see the password was to use a tool like phpMyAdmin to find it in the metadata tables in the database, and then hack around decrypting it manually. Well, that sucked; and I certainly didn't want to have to do that every time.

Now, there may be a better way than this, but what I ended up doing to work around that was this.

For every password field in a form, I also created a "paired" hidden text field (usually with a label matching the password field's but with the word "(copy)" at the end) and then set that hidden field's Default Value to equal the field ID of the password field - see below for an example.

Then just ensure you include both fields in all the functions, and then the hidden field's decrypted value will be properly visible/available to you.

Conclusion

This was a lifesaver for me, and I hope this helps you out too. If you have any comments/questions, please do comment below.

Introduction

This is a HOW TO "article" I wanted to share on what I did to customise Woocommerce to display additional useful event / ticket information on the shop page under each event when using The Events Calendar and Event Tickets Plus plugins. I thought some people may find it useful.

Before I start:

  1. This isn't that hard to do, but you do need to be comfortable editing WordPress config files like functions.php, and using a database tool like PHPMyAdmin. If not, then stop and get someone who is to follow this.
  2. This is offered as-is with no support. If you follow this and it messes up your site I take no responsibility. That said if you take the usual precautions before starting this, like using a child theme and making sure your site is FULLY backed up before starting, then if you do run into problems you can restore your site back to its former glory.
  3. This is specifically for the Woocommerce e-commerce plugin. The principles are "likely" to be valid for whatever e-commerce plugin you use (if it uses posts to store product information), BUT you will likely need to adjust accordingly.
  4. "Tickets" created by Event Tickets Plus are instantiated as Woocommerce "Products" as far as Woocommerce is concerned. So, whenever I talk about Woocommerce products, think Event Ticket Plus tickets - and vice versa.
  5. This assumes you're using Woocommerce's default templates and / or widgets. If you have customised templates this should still work, but bear in mind you may need to adjust your customised templates.
  6. Finally, I’m not saying this is the best, only, or most efficient way to do this (I'm sure using something like Pods or ACF to enhance the various post types is one reasonable way to go too). If people have suggestions for improvements, please feel free to comment below.

Got all that? Okay then, let's begin.

Why did I do this?

I have The Events Calendar (Free) and Event Tickets Plus (Paid), and use Woocommerce (Free) as the e-commerce platform. All work well together and do pretty much all I need on sites that manage and sell tickets for events.

However, one of the things I really wanted was better, fuller information on the ticket products when they’re listed on Woocommerce shop pages.

One option was to add to / update the relevant post types (across both Woocommerce and Events), or create custom fields in both. But that would mean having to remember to manually update those fields if things changed for an event. Also, the data was already there in one form or another, and I didn't want to have duplicated information.

Knowing the data was already there somewhere, I thought it couldn't be that hard getting the data I wanted displayed where I wanted it. I didn't find much on any of the plugins' forums / knowledgebases specifically dealing with this so I thought I'd have a go and then share what I did, as I'm sure I'm not the only one who wants to do something like this.

The default product display in Woocommerce (whether it's tickets, T-shirts or kettles) is a grid of products showing a product image (if there is one), the title of the Product, maybe a short description, the product's price, and a button or link allowing you buy it or add it to the shopping cart.

Now, that's all well and good. But I wanted to show more information under the product image than that. Plus, I wanted the button under each ticket product to say "Event Details", and for it to take the customer to the detailed event page.

This is mainly because, for me, the main details are in the single event page, plus that’s where I have the “add tickets” and quantity selector bar. In other words, adding a ticket to the cart from the list makes no real sense. If you’re happy with the default button's text and behaviour then just ignore the section at the end dealing with that.

So, what did I want to be displayed under the product's image, apart from just the event title and price? Well, I thought it really needed to show the event's date and start time, a summary of the venue information, and ticket availability. Re the latter, I don't mean how many tickets are available. I personally don't like to show that. What I do mean is displaying one of four coloured status messages indicating purchase-ability (is that a word?).

The picture below illustrates the result using mock events showing all those possible stock information values in use.

event tickets plus extra meta data in woocommerce shop

Of course, those are my choices. You can use the methods described here to display any existing meta data values, e.g. the number of tickets sold / remaining etc.

Understanding the Data and Where it Lives

The purpose of this section is to explain how I figured out what was there and where to find it. Ultimately my research identified what and where the right data could be found, so that I could build a function for extracting it automatically (using a Woocommerce hook).

The reason for illustrating that process here is so that if you want different things displayed you can use this to help you find what you need. And armed with that you can easily modify the function to use the specific data you want to see. That said, you can skip straight to the function below if you just want to implement it as I have set it up.

Before we get to the nitty gritty, know that all Woocommerce ticket products (created from Event Tickets Plus) and all Events (created from The Event Calendar) are stored as posts.

Once you have figured out what you want to show, probably the hardest bit is determining what and where that data is held. Essentially, it will be in one or both of two places. Either within Event and/or Woocommerce product post metadata.

Data is stored as key/value pairs – this just means that there is a key (basically a label) that has a value associated with it. For example, there is a Woocommerce meta key called _price and the value stored with it (in this case) is a number representing the product price, e.g. 25.

To get to this data you need to examine the WordPress database itself. I started with the Woocommerce metadata. You can skip to the sample output below to save a little time but bear in mind that future updates to Woocommerce and/or Event Tickets Plus and/or The Event Calendar may add/remove/modify keys listed.

So, to do this yourself you can use whatever tool you’re comfortable with for that but I used PHPMyAdmin (the following assumes you know how to use a tool like PHPMyAdmin to view database tables / run queries etc - if not, skip ahead to the sample output).

If you view the database, you'll see many tables listed. The one we're interested in is has a name of xxxxx_postmeta (where xxxxx will be whatever prefix was generated when WordPress was originally installed). If you view that table you will see all the post metadata for the whole site. We really want to see what meta keys exist for tickets (we’ll look at Events metadata further down).

To do that we need to run a simple query specifically against a ticket post. So first we'll need to get the post ID of a ticket (any will do). To do that, from the WordPress Dashboard, under Woocommerce, click on Products. All the products will be listed on the right. Click on Edit on any that is a ticket. The post ID is in the URL in the browser address bar, for example:

.../wp-admin/post.php?post=315&action=edit

Entering the simple query below will list all the meta keys for that ticket (post ID of 315 in this case). Replace the two "xxxx"s with your own database prefix, and "PPP" with the post ID you found.

SELECT `meta_key` , `meta_value`
FROM `xxxx_postmeta`
WHERE `post_id` = PPP
ORDER BY `xxxx_postmeta`.`meta_key` ASCCode language: SQL (Structured Query Language) (sql)

For me, using a post ID of 315 yielded the following:

Sample Woocommerce Ticket Meta Keys Output

meta_keymeta_value
_backordersno
_crosssell_idsa:0:{}
_downloadableno
_edit_last2
_edit_lock1490437846:2
_featuredno
_global_stock_cap0
_global_stock_modeown
_height
_length
_manage_stockyes
_price25
_product_attributesa:0:{}
_product_image_gallery
_product_version2.6.14
_purchase_note0
_regular_price25
_sale_price
_sale_price_dates_from
_sale_price_dates_to
_skuJSE5
_sold_individually
_stock20
_stock_statusinstock
_tax_class
_tax_statustaxable
_thumbnail_id58
_ticket_end_date15/08/2017 17:00:00
_ticket_purchase_limit0
_ticket_start_date08/02/2017 08:00:00
_tribe_tickets_meta_enabled0
_tribe_wooticket_for_event313
_upsell_idsa:0:{}
_virtualyes
_visibilityvisible
_wc_average_rating0
_wc_rating_counta:0:{}
_weight
_width
_total_sales0

These are all the meta keys and their associated values for that specific ticket - the majority are Woocommerce meta keys, the others are custom fields added by Event Tickets Plus. For the purposes of what I wanted I needed these:

  • _ticket_start_date
  • _ticket_end_date
  • _stock_status
  • _tribe_wooticket_for_event

The first two are the dates between when this ticket is available for purchase. Note, one or both can be empty. When you set up the ticket with Event Tickets Plus you have an option of adding either of these dates. If the start date is left blank it is treated as available "now". If the end date is left blank it is treated as the date and time the event itself ends (we will cater for that later on in the function).

The _stock_status key indicates whether the ticket is in or stock or not.

Those are the optional ones I needed for this exercise, but if you wanted to display tickets remaining, for example, you can see in the sample output you can use the _stock key.

Regardless of what data keys you choose to go with, you will always need the _tribe_wooticket_for_event key because we also need the Event post metadata associated with that ticket too (the function below needs to pull data from the ticket post as well as its corresponding Event post). I used a similar approach as above to get the Event metadata (see below). Note, there is a knowledge base article here that lists all the event post metadata keys. It includes PRO fields too, so bear that in mind if you don't have Events Calendar PRO (the fields I'm using didn’t require PRO).

So, using the process from before, here's how to get the Event meta data yourself...

View the database and find the xxxxx_postmeta file (if you're not already in there from earlier). This time we want to see what meta keys exist for Events.

So this time we'll need to get the post ID of an Event (any one will do). To do that, from the WordPress Dashboard, click Events. All the events will be listed on the right. Click on Edit on any one. The post ID is in the URL in the browser address bar, for example:

.../wp-admin/post.php?post=313&action=edit

Run exactly the same query as before, but change the post ID to be what you just found. This is what I got, using 313:

Sample Event Post Output:

meta_key meta_value
_edit_last2
_edit_lock1490453623:2
_EventCost25
_EventDuration0
_EventEndDate02/07/2017 20:00:00
_EventEndDateUTC02/07/2017 19:00:00
_EventOriginevents-calendar
_EventOriginevents-calendar
_EventOriginevents-calendar
_EventOriginevents-calendar
_EventShowMap1
_EventShowMapLink1
_EventStartDate02/07/2017 20:00:00
_EventStartDateUTC02/07/2017 19:00:00
_EventTimezoneEurope/London
_EventTimezoneAbbrBST
_EventURL
_EventVenueID190
_thumbnail_id58
_transient_timeout_tribe_attendees1490200300
_transient_tribe_attendeesa:0:{}
_tribe_deleted_attendees_count4
_tribe_hide_attendees_ list1
_tribe_modified_fieldsa:13:{s:15:"_EventStar tDate";d:1490293076;s:13: "_E...
_tribe_post_rootJSE2-
_tribe_progressive_ticket_current_number5
_tribe_ticket_global_stock_level148
_tribe_ticket_header58
_tribe_ticket_use_global_stock
_wp_old_slugjse-1
_wp_old_slugjse-4

These are all the meta keys and their associated values for that Event. For the purposes of what I wanted I'll need:

  • _EventStartDate
  • _EventVenueID

The first is the date and time of the event will take place. The second is the post ID of a post that contains venue information. I need this because I want some venue information displayed too. In this example, the post ID for the relevant venue is "190"). So, as you may have gathered, Venues are stored under their own posts. To get those keys we run the same query yet again but this time with the venue’s post ID. In my case I used “190”, which yielded:

Sample Venue Post Output

meta_key meta_value
_edit_last2
_edit_lock1489854105:2
_EventShowMap
_EventShowMapLink
_VenueAddressThe Stables Market, Chalk Farm Road
_VenueCityCamden
_VenueCountryUnited Kingdom
_VenueOriginevents-calendar
_VenuePhone0207 428 4922
_VenueProvinceLondon
_VenueShowMapTRUE
_VenueShowMapLinkTRUE
_VenueState
_VenueStateProvinceLondon
_VenueURLwww.gilgameshbar.com
_VenueZipNW1 8AH

From these I need:

  • _VenueCity
  • _VenueProvince

But I also want the Venue name which isn't in that bunch of keys! Hmmm. After a bit of rooting about, I discovered that the folks at Modern Tribe have a function reference (found here) that list all the functions available for us to use (if we wish).

One is tribe_get_venue which returns the Venue name using that post ID - we'll use that later in the function below.

We now know all the keys for the various bits and pieces we want to display. So armed with that, how do we display it? Well, we use the Woocommerce hook woocommerce_after_shop_loop_item_title. That's the entry point that allows us to create our own function to automatically interrogate these keys and display them on each Woocommerce ticket product on the shop page.

The Main Function

Below, then, is the function I wrote to process all those keys we discovered and display them. Comments are at the main sections explaining what's going on. Add the code snippet into your theme's functions.php file (preferably, for safety, your CHILD theme's functions.php).

NOTE 1: I had said earlier that if a date hasn’t been supplied (is blank) for when ticket sales should end, then the event plugin defaults the "end of sales date" to be the date and time the event itself is due to finish. In my case I want the default to be 24 hours before that. So I added a line to kind of do that. In reality, this will ONLY affect the message we’re adding under the product. If a customer goes to the single event page in that final 24 hours they could still purchase a ticket. So, if you really want ticket sales to stop at a particular date/time, then you need to add that date/time in the ticket itself when you set it up. Then the message under the product will be wholly accurate.

At some point, I may make it so that the calculation will then optionally update the actual meta key with the new calculated date/time. That is a little drastic and may not be what you want as it means any event, as soon as it is displayed on the shop page, will then get its ticket end of sales date updated from blank to 24 hours earlier! Hence me not taking it that far. If there seems to be a lot of demand then I will consider it (time permitting). Although it would be better if Modern Tribe could maybe add a Global setting that you could set for that.

Likewise, at some point, I may update/add a function that removes/disables the “Event Details” button (see the bottom section) once that time has passed.

However, on both fronts, I make no promises (it all comes down to need and time!). If I do though, I will post an update here.

However, if you’re happy with those shortcomings, but you want the end sale date to be a different time period, or to be the actual plugin default when blank, then you have 2 choices (respectively):

  1. Change the formula I use in the function of (24*60*60) to be what you need (it returns the number of seconds to be subtracted from the event end time), or;
  2. Remove (or comment out) that line respectively.

The line in question is the one with this comment at its end (highlighted below in the main code):

// Subtract 24 hoursCode language: PHP (php)

To reiterate, this is only potentially an issue if you have not set the end of sales date/time for event tickets and left it blank.

NOTE 2: In the code, I've manually coloured the stock status messages using an inline style. Other than that, all the elements’ output provide their own CSS class so you can customise them through CSS further if you wish.

/*
 * Adds some post fields from a ticket's post meta data and corresponding Event's meta data so
 * they appear below Event titles in the Woo shop page.
 */
add_action( 'woocommerce_after_shop_loop_item_title', 'add_shop_event_details', 9 );
function add_shop_event_details() {
    // 1. Get the current Ticket post ID
    $ticket_post_id = get_the_id();

    // 2. Get correlating Event ID for this ticket product
    $evt_id = get_post_meta( $ticket_post_id , "_tribe_wooticket_for_event", true );

    // 3. Pull event time info
    $evt_datetime = get_post_meta( $evt_id, "_EventStartDate", true );
    $evt_datetime = date("D jS M Y @ g:ia",strtotime($evt_datetime));
    echo '<div class="product-meta-time" style="color: grey;">' . $evt_datetime . '</div>';

    // 4.Pull out some Venue info 
    $venue_id = get_post_meta( $evt_id, "_EventVenueID", true );
    $venue_name = tribe_get_venue($venue_id);
    $venue_city = get_post_meta( $venue_id, "_VenueCity", true );
    $venue_prov = get_post_meta( $venue_id, "_VenueProvince", true );

    if ( trim($venue_city) != '' ) { $venue_prov = ', ' . $venue_prov; }

    echo '<div class="product-meta-venue" style="color: grey;">At ' . $venue_name . ' (' . 
        $venue_city . $venue_prov . ')</div>';

    // 5.Pull stock status and ticket sale start / end date
    $today        = strtotime( date('Y-m-d H:i:s') );    // The date and time now.
    $stock_status    = get_post_meta( $ticket_post_id , "_stock_status", true );

    // 6. Determine when ticket sales can start
    $ticket_start_sale_date = get_post_meta( $ticket_post_id , "_ticket_start_date", true );
    $ticket_start_sale_date = trim( strtotime($ticket_start_sale_date) );

    // 7. If no sale start date supplied in the ticket, make sure the start date will be just 
    //    before "now".
    if ($ticket_start_sale_date == '') {        
        $ticket_start_sale_date = $today - 1;     
    }

    // 8. Determine when ticket sales stop
    $ticket_end_sale_date   = get_post_meta( $ticket_post_id , "_ticket_end_date", true );
    $ticket_end_sale_date   = trim( strtotime($ticket_end_sale_date) );

    // 9. If no sale end date supplied in the ticket, default the close of sales 24 hours before 
    //    Event finishing time
    if ($ticket_end_sale_date == '') {            
        $ticket_end_sale_date = get_post_meta( $evt_id , "_EventEndDate", true );
        $ticket_end_sale_date = strtotime($ticket_end_sale_date);
        $ticket_end_sale_date = $ticket_end_sale_date - (24*60*60);    // Subtract 24 hours    
    }

    // 10. Build the stock status message, depending on its availability...
    if ( $stock_status == "outofstock" ) {
        $stock_msg = '<div class="product-meta-stock" style="color: red;"><b>' .
            'Sorry, these tickets are<br />SOLD OUT</b></div>';

    } else if ( ( $ticket_start_sale_date <= $today ) && ( $today <= $ticket_end_sale_date ) ) {
        $stock_msg = '<div class="product-meta-stock" style="color: green;"><b>' .
            'Tickets AVAILABLE<br />to purchase</b></div>';
    } else if ( $ticket_end_sale_date <= $today ) {
        $stock_msg = '<div class="product-meta-stock" style="color: red;"><b>' .
            'Ticket sales for this Event<br />are now CLOSED</b></div>';
    } else {
        $stock_msg = '<div class="product-meta-stock" style="color: orange;"><b>' .
            'Tickets go ON SALE<br />' . date( "D jS M Y @ g:ia", $ticket_start_sale_date ) . 
            '</b></div>';
    }
    echo $stock_msg;
}
Code language: PHP (php)

Replacement of default "Add to Cart" button

Below is the other snippet you can optionally add (made up of two small functions). This changes the text on the button under an Event on the shop page from "Add to Cart" to "Event Details", and also changes the button's link to point to the appropriate Single Event page.

/*
 * Replace Add to Cart button with a button that goes to the product details 
 * 
 * First, remove default add to cart button on shop page 
 */
add_action('init','remove_loop_button');
function remove_loop_button(){
    remove_action( 'woocommerce_after_shop_loop_item', 'woocommerce_template_loop_add_to_cart', 10 );
}
/*
 * Now add new button that links to appropriate single event page 
 */
add_action('woocommerce_after_shop_loop_item','replace_add_to_cart');
function replace_add_to_cart() {
    global $product;
    $link = $product->get_permalink();
    echo do_shortcode('Event Details');
}Code language: PHP (php)

And that's pretty much it.

If this is useful to you, please feel free to use it. I would also love to hear your comments/suggestions.

Elixir Web Studio is a trading name of YourScope Consulting Ltd.
Copyright © 2024 YourScope Consulting Ltd.
linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram