Gravity Forms entries quick view

Gravity Forms is a great plugin many WordPress websites use. I’ve used it many client projects in the past and I’ve always missed a feature!

If you are a OS X user you are surely familiar with the “quick view” action – pressing space after selecting an item in Finder. I find this almost instinctive for me and miss it in many situations.

This feature is something which I miss in the list of GF entries. So I set out to build a plugin that does exactly that. This article will also include a step-by-step guide on how I built the plugin.

You can browse the code here or download the plugin here. If you also want to see how it’s built, read on.

Getting started

You should be comfortable coding WordPress plugins and I recommend using and IDE ( like PhpStorm ). This will make navigating a large plugin like Gravity Forms much simpler.

Finding a hook ?

First thing I did was to search for a hook to add the preview button to the list of entries. Easiest way, in my opinion, inspect the table and find something specific:


See that id there? “gf_entry_list” – next step is I went in the plugin folder and searched all the files:


Which is where I found the hook we are going to use: gform_entries_first_column_actions. This allows us to add an extra custom button next to the “edit”, “delete”, etc buttons.

Maybe going through the documentation is faster for some people but I find this method to be the most efficient for me.

Adding the button

The next step is going to be to add a button as the preview trigger. Because we are not in a OS interface I think it makes sense to have a click trigger.

Using the hook found above we add the button to the list of actions:

All the code is available at the end of this article.

This will add the button ad the end of the list of actions:

<<
xt step is to add the id of the entry to the button and move on to the JS part. In this case, the $lead array contains the entry data ( as reflected by the PHPDoc comments ).

The actual functionality

Next we’ll need to add some JS into that page which will handle the loading of the preview. In these cases I usually start writing the code in a separate JS file, but if it turns out to be small I just use the proper hooks to load it only in that page. I’m not a fan of making an extra request although it is easier to maintain.

Adding scripts

Using the hook “admin_enqueue_scripts” the following function came to life:

Make sure you use the proper hook to avoid loading these scripts on other pages.

The JavaScript

As WP-admin already has it loaded, I used jQuery. Make sure to use the “promise” structure for the Ajax call as the old one is deprecated. Here’s the basic structure which we’ll use to go and define the Ajax handler in PHP.

Next up: the PHP which will handle the Ajax call and retrieve the GF entry data.

The PHP part of the Ajax call

For Ajax calls, WordPress has a very nice system in place which allows you to hook using the prefix “wp_ajax_” – if you you’re not familiar with it read more here.

Getting the entry data is not a big deal as GF has a proper API and we can use this function:

GFAPI::get_entry( $entry_id );

The only problem here is that the data has no human-readable labels, so it makes no sense.

In order to get over this, we’ll also need the form fields. In the end I decided to load the markup for the pop-up directly with the form table structure ( to avoid loading the form structure each time ).

You can find the form structure easily using:

GFAPI::get_form( $form_id );

In the array retrieved there there will be one entry for the “fields”. Just looping through them and through the entry data is enough. Luckily, GF takes care of ordering things properly for the API.

The markup

A good hook to load the markup of the quick-view panel is “gform_post_entry_list“. This is called at the end of the list and we can pre-load the table there and hide it with CSS.

I’ve loaded a simple markup there to display the info. Using the function mentioned above to retrieve the form structure we end up with something like this:

Linking it all together

Now that all components are in place we just need to send the entry data using the Ajax function and populate the html with it.

First we send the data using wp_send_json to make sure it goes out ok. Next, we loop through the list of labels and populate them with the data in the JSON.Now we can take care of the styles and using a CSS class we can toggle the visibility of the table. The final touch is to also allow users to close that table.

If you want to see the plugin in action you can browse the code here or download it here.

Copy & Print quick setup for websites with examples

When building web directories in particular or websites in general you often need copy & print functionalities.

For example you could allow users to easily copy an address. Or maybe you want to allow users to print certain elements from a list – like a search result.

Having tried & used multiple solutions in the past I recently found a combination which I think works great.

As a result, I suggest using 2 simple plugins.

Copy / Clipboard

For the copy/functionality, the best cross-browser solution, in my opinion, at this moment is clipboard.js.

A quick usage example ( besides the examples on their website ) would be:

The plugin gets its value from the data attribute “data-clipboard-text” in the example above. Also the button’s text is changed, after clicking the link, so your users have a nice feedback.

Printing

A good stylesheet is the key to printing pages properly. The problem is that sometimes it’s hard to manage this for dynamic elements. One solution which I used successfully is the jQuery plugin printThis.

This plugin gives you the option to directly print independent elements. Let’s say you want to allow users to print only the search result relevant to them.

Here’s a code sample which I think is self-explanatory, the plugin is very easy to use.

The 2 examples above will come in handy for many websites. I think these plugins will allow you to easily integrate functionality which will make the website more interactive.

WordPress TinyMCE button for shortcode

For those of you who didn’t know, TinyMCE has a straight-forward way to include a form in a modal window which you can use to generate shortcodes.

This is great news because it will make WordPress editors much less prone to error not having to write shortcodes by hand.

How do you do it?

So the approach in the WordPress way to do this is:

  1. Add the button to the WordPress TinyMCE toolbar using the “mce_buttons” ( or others ) filter.
  2. Ask TinyMCE to load a new JavaScript plugin for the editor which will handle the button.
  3. In the TinyMCE JavaScript plugin add the button and handle its events.
  4. Optionally if you have a custom shortcode, define it with its arguments.

In my example I have created a small plugin, which can be found on Github.

The plugin will add a button on the first row of the editor which when clicked will open a modal. The modal is handled by the WindowManager class included in TinyMCE. Another great part of using this approach is that you already have what is needed to generate a form.

After that, getting the parameters from the form and generating a shortcode to be inserted in the content is pretty easy.

You can get the whole plugin here.

I think the code is self-explanatory for the most part.

Things to watch out for

The code can be easily included in another plugin and extended to generate multiple buttons.

One issue I found not very intuitive at first is that if you add another button you will need to define another plugin for it to be taken into consideration, even if it’s in the same file.

So in the function where you add the js plugins you’ll end up with:

function add_embed_button_js( $plugin_array ) {
   $plugin_array['msb_button'] = plugin_dir_url( MSB_PLUGIN_FILE ) . 'assets/js/embed_button.js';
   $plugin_array['msb_button_2'] = plugin_dir_url( MSB_PLUGIN_FILE ) . 'assets/js/embed_button.js';
   return $plugin_array;
}

I trust that TinyMCE won’t attempt to load the same file 2 times. Also I am aware it might not be the right approach but it works!

Also, as mentioned in the plugin README – by default the plugin will add a button with no icon so you might miss it.

Extend wp-admin search to use custom fields

UPDATE: For the WooCommerce-friendly version of this, see this article.

I think the title is self-explanatory but let’s detail the situation a bit:

Let’s presume you have a custom post type for Cars and for each car you have the VIN number in a custom field. You want to allow users to search from the admin directly by that field. Also, you need them to use the default search input:

Wp-admin search

The code

First we have to find the right hook to extend the search with. In our case I found the best results with the posts_search filter.

Next, the logic! Keep in mind this filter is being called in the get_posts function of the WP_Query class, so everywhere. That results in adding a check to see if we are on the right page.

Also, keep in mind that we are altering the “where” part of the actual SQL query.

Further usage

Like I mentioned before – this snippet checks if the search is performed in the admin, but that also means you can use it in the frontend. So if you’d like to extend the default WordPress search to enable custom field searches you can change the logic at the top. This can be easily switched to enable WooCommerce products search by SKU or similar.

Youtube embed preview image

Let’s take the following situation: you have lots of YouTube embeds on a page and the page is loading extremely slow ( sometimes not loading at all! ) – what can be done?

Fortunately I had to fix this exact problem for a client recently and here’s the approach I used. Instead of adding the actual YouTube iframe by default I thought a good approach would be to use the video’s thumbnail which on click would be replaced by the iframe.

Getting the YouTube video’s thumbnail image

First you need the YouTube video id, let’s take for example: https://www.youtube.com/watch?v=VdphvuyaV_I – in this case the id is: “VdphvuyaV_I”.

Next you have to decide on the image size, there are 4 size available ( more info here ): default, medium quality, standard quality and high quality. I usually use the high quality size which is 480px x 360px.

So in our case the thumbnail url is: https://img.youtube.com/vi/VdphvuyaV_I/hqdefault.jpg – notice the video id in the url.

Making the thumbnail clickable

Next up is adding a JavaScript action that will switch the image to the iframe on click. I chose to include the full embed url – in case you want to use this with Vimeo ( it’s possible ). So the markup is:

<div class="m_load_here">
<img src="https://img.youtube.com/vi/VdphvuyaV_I/hqdefault.jpg" alt="YouTube Video" />
<a class="m_overlay" href="#" data-src="//www.youtube.com/embed/VdphvuyaV_I?wmode=transparent&autoplay=1">
</div>

I recommend placing this inside a div with the styling of your choice, for example 3 columns per row. And the js needed to do the switch:

<script type="text/javascript">
   jQuery( function ($) {
      $('.m_overlay').click(function (e) {
         e.preventDefault();
         var th = $(this);
         var embed_url = th.data('src');
         var width = th.width();
         var height = th.height();
         var iframe = '<iframe src="'+embed_url+'" width="'+width+'" height="'+height+'"></iframe>';
         th.parent().html(iframe);
      });
   });
</script>

Styling it to look like a video

Because we are replacing a video we should make it look similar, so what we’ll also need is the video title. The easiest way to get that without using the annoying YouTube API that needs authentication is using the oEmbed feature.

In a WordPress context I would store the video info in a transient. Use this code to only pull the video title once a month:

$id = 'VdphvuyaV_I';
$video_title_transient = get_transient('m_yt_title_'.$id);
if ( !$video_title_transient ) {
   $video_info = file_get_contents('https://www.youtube.com/oembed?url=http://www.youtube.com/watch?v='.$id.'&format=json');
   $video_info = json_decode($video_info);
   if ( $video_info && isset($video_info->title) ) {
      $video_title_transient = $video_info->title;
      set_transient('m_yt_title_'.$id, $video_title_transient, MONTH_IN_SECONDS);
   }
}

Next step is to place these over the thumbnail so they look similar to the embed. The html I used is:

<div class="m_load_here">
<img src="https://img.youtube.com/vi/VdphvuyaV_I/hqdefault.jpg" alt="YouTube Video" />
<a class="m_overlay" href="#" data-src="//www.youtube.com/embed/VdphvuyaV_I?wmode=transparent&autoplay=1">
<span class="m_yt_title"><?php echo $video_title_transient; ?></span>
<span class="m_yt_button"><svg height="100%" version="1.1" viewBox="0 0 68 48" width="100%"><path class="ytp-large-play-button-bg" d="m .66,37.62 c 0,0 .66,4.70 2.70,6.77 2.58,2.71 5.98,2.63 7.49,2.91 5.43,.52 23.10,.68 23.12,.68 .00,-1.3e-5 14.29,-0.02 23.81,-0.71 1.32,-0.15 4.22,-0.17 6.81,-2.89 2.03,-2.07 2.70,-6.77 2.70,-6.77 0,0 .67,-5.52 .67,-11.04 l 0,-5.17 c 0,-5.52 -0.67,-11.04 -0.67,-11.04 0,0 -0.66,-4.70 -2.70,-6.77 C 62.03,.86 59.13,.84 57.80,.69 48.28,0 34.00,0 34.00,0 33.97,0 19.69,0 10.18,.69 8.85,.84 5.95,.86 3.36,3.58 1.32,5.65 .66,10.35 .66,10.35 c 0,0 -0.55,4.50 -0.66,9.45 l 0,8.36 c .10,4.94 .66,9.45 .66,9.45 z" fill="#1f1f1e" fill-opacity="0.81"/><path d="m 26.96,13.67 18.37,9.62 -18.37,9.55 -0.00,-19.17 z" fill="#fff"/><path d="M 45.02,23.46 45.32,23.28 26.96,13.67 43.32,24.34 45.02,23.46 z" fill="#ccc"/></svg></span>
</div>

You will notice this is the actual SVG for the YouTube button, and below is the CSS:

   .ytp-large-play-button-bg {
      fill: #1f1f1f;
      fill-opacity: 0.81;
      transition: fill 0.1s cubic-bezier(0.4, 0, 1, 1) 0s, fill-opacity 0.1s cubic-bezier(0.4, 0, 1, 1) 0s;
   }
   .m_overlay:hover .ytp-large-play-button-bg {
      fill: #cc181e;
      fill-opacity: 1;
      transition: fill 0.1s cubic-bezier(0, 0, 0.2, 1) 0s, fill-opacity 0.1s cubic-bezier(0, 0, 0.2, 1) 0s;
   }
   .m_yt_button {
      background: rgba(0, 0, 0, 0) none repeat scroll 0 0;
      border-radius: 10px;
      height: 30px;
      left: 50%;
      position: absolute;
      top: 50%;
      transform: translate(-50%, -50%);
      width: 42px;
      z-index: 5;
      display: block;
   }
   .m_overlay {
      left: 0;
      top:0;
      right: 0;
      bottom :0;
      position: absolute;
   }
   .m_yt_title {
      color: #FFF;
      outline: 0 none;
      text-decoration: none;
      transition: color 0.1s cubic-bezier(0, 0, 0.2, 1) 0s;
      float: left;
      max-width: 100%;
      overflow: hidden;
      overflow-wrap: normal;
      text-overflow: ellipsis;
      white-space: nowrap;
      font-size: 18px;
      padding: 14px 16px 0;
   }

 

Transform Gravity Forms sections into an accordion with jQuery

On a project I was working last week I was asked to create an accordion structure, for ease of use, from an existing Gravity Forms form. The form had a structure in which price fields were separated by section fields, which is a great structure to start with.

Prerequisites

First thing – the form has to contain sections which separate groups of fields.

The second thing is to identify what kind of fields are contained between the sections ( maybe they are not all the same ). Make a note of the specific css classes of those fields. For example: price fields have the class “gfield_price”.

The code

First you have an array for the section which you will check to identify what to do. Next you create an accordion container which will contain the fields whose visibility you can toggle. After that you append the fields to the container and add the actions which allow opening/closing accordions. Bonus: you can also add a small text to make the section more intuitive to click.

This is the final code with explanations for each operation:

EDIT: after a comment making a good observation I updated the code to include a toggle of the EXPAND/COLLAPSE text.

Attempting to speed up WooCommerce Subscriptions admin

If you use WooCommerce along with their WooCommerce Subscriptions plugin you will probably notice the site becomes slow after a number of subscribers. As I wrote in a previous article, there are some tricks that may make it faster.

In this article I will present some code which might help with speeding up some pages which use lots of queries.

User’s active subscriptions count

If you ever used the Query Monitor plugin you probably noticed that on the Users list, even when loading 20 users WordPress does many queries ( hundreds ). With WooCommerce Subscriptions enabled there is one query which is especially resource expensive. That’s the query used to populate the “Active subscriber” column. You can go ahead and remove that if you don’t use it because you can also filter the users by the “subscriber” role. So here’s the code to do that:

// remove the subscription column in users table
remove_filter( 'manage_users_columns', 'WC_Subscriptions_Admin::add_user_columns', 11 );
remove_filter( 'manage_users_custom_column', 'WC_Subscriptions_Admin::user_column_values', 11 );

Comments count

Another very expensive query which I found might slow your website down if you have lots of WooCommerce orders ( which when using subscriptions is highly probable ) is the comments count. As you probably know the WooCommerce order notes are stored as comments, on average you have at least 2 notes for each order which results in a lot of comments.

You can prevent that count from taking place in the admin using the code below:

if ( is_admin() ) {
    add_filter( 'wp_count_comments', function( $counts, $post_id ) {
        if ( $post_id )
            return $counts;
        return (object) array(
            'approved'        => 0,
            'spam'            => 0,
            'trash'           => 0,
            'total_comments'  => 0,
            'moderated'       => 0,
            'post-trashed'    => 0,
        );
    }, 10, 2 );
}
// If running WooCommerce, remove their filter so that nothing funky goes down
remove_filter( 'wp_count_comments', array( 'WC_Comments', 'wp_count_comments' ), 10 );

Another quick improvement if you have lots of “processing” orders is to remove the count that shows up in the admin menu ( which is being called on every screen obviously ).

// Remove order count from admin menu
add_filter( 'woocommerce_include_processing_order_count_in_menu', '__return_false' );

Changing pseudo-elements style with JavaScript

Recently I had to add a color-picker that changed the background-color of a div and I was faced with the problem of changing a pseudo-element’s style. The div I was manipulating with JS had a :before styled to give a tilted effect, so the top of the element looked like this:

pseudo-element

If you didn’t know, styling a pseudo-element ( like :after or :before ) is quite complicated in JavaScript. You have to insert css rules directly in the page’s stylesheets and that doesn’t work ok in all browsers.

I found that the easiest way to change the background color of pseudo-elements is something very simple, instead of using:

.tilted-div:before {
    background-color: #008000;
}

You can use:

.tilted-div {
    background-color: #008000;
}
.tilted-div:before {
    background-color: inherit;
}

So now when you change the background-color using style attributes it will reflect on the :before div.

This might seem a bit obvious – but it reminds you that the inherit property can be used in a variety of situations.

Other uses

Another example in which I found this useful was with a custom bullet icon in a list. Let’s say you have this code:

.bullet-list-element {
    color: #33A880;
}
.bullet-list-element:before {
    content: "◎";
    color: inherit;
}

Now if you change the color of the text in the “bullet-list-element” it will reflect on the custom bullet added before it, which is useful especially when previewing color-changing feature something like a premium theme.

Gravity Forms disabled inputs that submit correctly

If you looked for how to make a Gravity Forms field disabled/read-only you probably found the official article from their documentation. They suggest using a piece of JavaScript added to the “gform_pre_render” action, it looks like this:

add_filter('gform_pre_render_1', 'add_readonly_script');
function add_readonly_script($form){
    ?>
    <script type="text/javascript">
        jQuery(document).ready(function(){
            jQuery("li.gf_readonly input").attr("readonly","readonly");
        });
    </script>
    <?php
    return $form;
}

That suggestion is good if you work with simple text inputs ( or swapping that input to textarea ) but for select/drop-down it’s a bit more complicated, especially the ones that use the Chosen JavaScript library.

Including all form elements

I wanted to include all form elements so I used the “:input” jQuery selector which includes all form elements.

Next I wanted to also disable select elements that use the Chosen library – so I added trigger, so the call looks like this:

jQuery(function($){
    jQuery('.mgf_disabled :input').prop('disabled', true).trigger("chosen:updated");
});

The looks

You probably noticed I made the fields disabled instead of “read-only” and that’s because by default the browsers don’t display the fields differently when they are marked as “read-only”, and I didn’t want to add any custom css. You can just skip this part and use read-only if you add some styles to that using the :read-only selector:

input:-moz-read-only { /* For Firefox */
    background-color: yellow;
}
input:read-only {
    background-color: yellow;
}

If you decide to use disabled – you probably know that those fields get ignored when submitting the form so the solution is to remove that attribute on the submit action. The final code looks like this:

jQuery(function($){
    jQuery('.mgf_disabled :input').prop('disabled', true).trigger("chosen:updated");
    jQuery('.gform_wrapper form').submit(function() {
        jQuery('.mgf_disabled :input').prop('disabled', false);
    });
});

 

AWS S3 preview private and encrypted files with PHP

Recently I had to develop a WordPress plugin that is used to manage files stored on Amazon AWS S3. Due to the nature of the files which contain sensible information they are stored as private and using the AES256 ServerSideEncryption.

In order to serve the files to authorized users an AJAX call is used to generate a temporary link from the AWS PHP SDK. My problem is that for these links the “content-disposition” header is set to “attachment” which forces a download. As the client wanted to offer the option to also view directly in the browser these ( PDF ) files, I was faced with a problem.

Previewing the files without forcing a client download

The Amazon AWS documentation specifies an optional content-disposition parameter. So, for example, in a “getObjectUrl” call you would use:

$args = array(
    'ResponseContentDisposition' => 'attachment; filename=testing.pdf'
);
$request = $s3Client->getObjectUrl( $bucket, 'example/test.pdf', '+2 minutes', $args );

Those arguments get passed to the “getObject” function as explained here. The problem is that for some reason this parameter is ignored – probably because of the encryption.

I found a quick solution to this problem by downloading the file and serving directly from PHP on my server instead of trying to change the content-disposition on Amazon’s requests.

So in the WordPress plugin I added a simple function which takes a file and serves it with content-disposition inline:

add_action( 'wp_ajax_m_view_link', array( $this, 'view_pdf' ) );
function view_pdf() {
   if ( isset($_GET['url']) && is_user_logged_in() ) {
      header('Content-disposition: inline; filename='.basename($_GET['url']));
      header("Content-type:application/pdf");
      echo file_get_contents($_GET['url']);
   }
   die();
}

So now when calling /wp-admin/admin-ajax.php?action=m_view_link&url=url_of_amazon_file the users can preview the files directly in their browsers.

Notes:

To prevent potential security threats you should add some checks to the “view_pdf” function above.

Obviously you can use this for other purposes than Amazon S3 files it’s a quick way to change the content-disposition of certain external files.