WooCommerce: Add video support to product gallery

Background
I’m the marketing manager over at Horner APG, a global industrial automation control company, currently responsible for the design and development of our new website.

It’s no secret that I’m huge fan of WooCommerce. The Woo team is simply creating a wonderful product backed by a great community of support. I may have stepped out a bit from the norm by choosing it to power our corporate store but I think it’ll pay off in the long run.

While building our new site I noticed our old store included links to product videos powered by YouTube. By default, as designed by WordPress, videos are not supported by the WordPress image gallery. As a result, I started looking for a solution that would modify the default behavior to accept external links. Below is the solution I used to make that happen.

Solution
Eventually, I found some code shared by Geekee Media that adds a “custom link” input box in the WordPress “Add Media panel.”

Geekee Media has since updated the code below and it’s freely available on Github if you’d like to grab the latest version which I haven’t tested yet. Hit me up in the comments below if this process works with his latest code submission.

  • Add the first block of code below to your functions.php file.
  • Add the second block of code below to your functions.php file.
  • I’ve modified it to include a unique link ID (id=”tip4″) that’s being called in my footer with specific Fancybox settings. I know it’s not the cleanest process but I’ll leave it to you to tidy it up.
  • Add the third block of code to your footer (Fancybox settings).
  • How to include a video in the product gallery:
  • Edit your product and upload an image ( ex: video player ) that signifies the gallery link will be of a video. You’ll see from my screenshot that I’ve included a link to YouTube where my video is hosted.

Demo: Horner XLt Industrial Automation Controller


First block of code
Adds a custom field to your image gallery that you’ll use to include your video link.

/* -------------------------------
CUSTOM LINK PER GALLERY IMAGE WHICH ALLOWS US TO DISPLAY VIDEO in WP GALLERY
------------------------------- */
function rt_image_attachment_fields_to_edit($form_fields, $post) {
  $form_fields["rt-image-link"] = array(
    "label" => __("Custom Link"),
    "input" => "text", // default
    "value" => get_post_meta($post->ID, "_rt-image-link", true),
    //"helps" => __("To be used with special slider added via [rt_carousel] shortcode."),
  );
  return $form_fields;
}
add_filter("attachment_fields_to_edit", "rt_image_attachment_fields_to_edit", null, 2);

function rt_image_attachment_fields_to_save($post, $attachment) {
  // $attachment part of the form $_POST ($_POST[attachments][postID])
  // $post['post_type'] == 'attachment'
  if( isset($attachment['rt-image-link']) ){
    // update_post_meta(postID, meta_key, meta_value);
    update_post_meta($post['ID'], '_rt-image-link', $attachment['rt-image-link']);
  }
  return $post;
}
add_filter("attachment_fields_to_save", "rt_image_attachment_fields_to_save", null , 2);

/* -------------------------------
MODIFIED CORE FUNCTION
------------------------------- */
add_shortcode('gallery', 'geekee_gallery_shortcode');

/**
* The Gallery shortcode.
*
* This implements the functionality of the Gallery Shortcode for displaying
* WordPress images on a post.
*
* @since 2.5.0
*
* @param array $attr Attributes attributed to the shortcode.
* @return string HTML content to display gallery.
*/
function geekee_gallery_shortcode($attr) {
  global $post, $wp_locale;

  static $instance = 0;
  $instance++;

  // Allow plugins/themes to override the default gallery template.
  $output = apply_filters('post_gallery', '', $attr);
  if ( $output != '' )
    return $output;

  // We're trusting author input, so let's at least make sure it looks like a valid orderby statement
  if ( isset( $attr['orderby'] ) ) {
    $attr['orderby'] = sanitize_sql_orderby( $attr['orderby'] );
    if ( !$attr['orderby'] )
      unset( $attr['orderby'] );
  }

  extract(shortcode_atts(array(
    'order'      => 'ASC',
    'orderby'    => 'menu_order ID',
    'id'         => $post->ID,
    'itemtag'    => 'dl',
    'icontag'    => 'dt',
    'captiontag' => 'dd',
    'columns'    => 3,
    'size'       => 'thumbnail',
    'include'    => '',
    'exclude'    => ''
  ), $attr));

  $id = intval($id);
  if ( 'RAND' == $order )
    $orderby = 'none';

  if ( !empty($include) ) {
    $include = preg_replace( '/[^0-9,]+/', '', $include );
    $_attachments = get_posts( array('include' => $include, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby) );

    $attachments = array();
    foreach ( $_attachments as $key => $val ) {
      $attachments[$val->ID] = $_attachments[$key];
    }
  } elseif ( !empty($exclude) ) {
    $exclude = preg_replace( '/[^0-9,]+/', '', $exclude );
    $attachments = get_children( array('post_parent' => $id, 'exclude' => $exclude, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby) );
  } else {
    $attachments = get_children( array('post_parent' => $id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', 'order' => $order, 'orderby' => $orderby) );
  }

  if ( empty($attachments) )
    return '';

  if ( is_feed() ) {
    $output = "\n";
    foreach ( $attachments as $att_id => $attachment )
      $output .= wp_get_attachment_link($att_id, $size, true) . "\n";
    return $output;
  }

  $itemtag = tag_escape($itemtag);
  $captiontag = tag_escape($captiontag);
  $columns = intval($columns);
  $itemwidth = $columns > 0 ? floor(100/$columns) : 100;
  $float = is_rtl() ? 'right' : 'left';

  $selector = "gallery-{$instance}";

	$gallery_style = $gallery_div = '';
	if ( apply_filters( 'use_default_gallery_style', true ) )
		$gallery_style = "
		<style type='text/css'>
			#{$selector} {
				margin: auto;
			}
			#{$selector} .gallery-item {
				float: {$float};
				margin-top: 10px;
				text-align: center;
				width: {$itemwidth}%;
			}
			#{$selector} img {
				border: 2px solid #cfcfcf;
			}
			#{$selector} .gallery-caption {
				margin-left: 0;
			}
		</style>
		<!-- see gallery_shortcode() in wp-includes/media.php -->";
	$size_class = sanitize_html_class( $size );
	$gallery_div = "<div id='$selector' class='gallery galleryid-{$id} gallery-columns-{$columns} gallery-size-{$size_class}'>";
	$output = apply_filters( 'gallery_style', $gallery_style . "\n\t\t" . $gallery_div );

  $i = 0;
  foreach ( $attachments as $id => $attachment ) {
    $link = isset($attr['link']) && 'file' == $attr['link'] ? wp_get_attachment_link($id, $size, false, false) : wp_get_attachment_link($id, $size, true, false);

  /* -------------------------------
  MODIFICATION ################
  ------------------------------- */
  $image = wp_get_attachment_image($id, $size, false);
  $attachment_meta = get_post_meta($id, '_rt-image-link', true);
  if($attachment_meta){
    if($attr['link'] == 'custom_url'){
      $link = $attachment_meta;
    }
  }

  $output .= "<{$itemtag} class='gallery-item'>";
  $output .= "<{$icontag} class='gallery-icon'>";

    /* -------------------------------
  MODIFICATION ################
  ------------------------------- */
  if($attachment_meta){
    $output .= "<a href='$link'>$image</a>";
  } else {
    $output .= $link;
  }
 
  $output .= "</{$icontag}>";
  if ( $captiontag && trim($attachment->post_excerpt) ) {
    $output .= "<{$captiontag} class='gallery-caption'>" . wptexturize($attachment->post_excerpt) . "</{$captiontag}>";
  }
  $output .= "</{$itemtag}>";
  if ( $columns > 0 && ++$i % $columns == 0 )
    $output .= '<br style="clear: both;" />';
  }
 
  $output .= "<br style='clear: both;' /></div>\n";
 
  return $output;
}

Second block of code
Then I assigned a specific ID to the video link. This class triggered the Fancybox plugin that currently powers the WooCommerce product image gallery.

if (!function_exists('woocommerce_show_product_thumbnails')) {
	function woocommerce_show_product_thumbnails() {
		
		global $_product, $post;
		
		echo '<div class="thumbnails">';
		
		$thumb_id = get_post_thumbnail_id();
		$small_thumbnail_size = apply_filters('single_product_small_thumbnail_size', 'shop_thumbnail');
		$args = array( 
			'post_type' 	=> 'attachment', 
			'order' 		=> 'ASC',
			'orderby' 		=> 'menu_order ID',
			'numberposts' 	=> -1, 
			'post_status' 	=> null, 
			'post_parent' 	=> $post->ID,
			'post__not_in'	=> array($thumb_id),
			'post_mime_type' => 'image',
			'meta_query' 	=> array(
				array(
					'key' 		=> '_woocommerce_exclude_image',
					'value'		=> '1',
					'compare' 	=> '!='
				)
			)
		); 
		$attachments = get_posts($args);
		if ($attachments) :
			$loop = 0;
			$columns = apply_filters('woocommerce_product_thumbnails_columns', 3);
			foreach ( $attachments as $attachment ) : 
				
				$loop++;
				
				$_post = & get_post( $attachment->ID );
				$url = wp_get_attachment_url($_post->ID);
				$post_title = esc_attr($_post->post_title);
				$image = wp_get_attachment_image($attachment->ID, $small_thumbnail_size);
				$attachment_meta = get_post_meta($_post->ID, '_rt-image-link', true);
				$link = $attachment_meta;
				if (isset($attachment_meta['true'])) {
				echo '<a href="'.$link.'" title="'.$post_title.'" rel="thumbnails" id="tip4" class=" ';
				if ($loop==1 || ($loop-1)%$columns==0) echo 'first';
				if ($loop%$columns==0) echo 'last';
				echo '">'.$image.'</a>';
				}
				else  {
				echo '<a href="'.$url.'" title="'.$post_title.'" rel="thumbnails" class="zoom ';
				if ($loop==1 || ($loop-1)%$columns==0) echo 'first';
				if ($loop%$columns==0) echo 'last';
				echo '">'.$image.'</a>';
				}

				
			endforeach;
		endif;
		wp_reset_query();
		
		echo '</div>';
		
	}
}

Third block of code.
This snippet of code is to trigger the Fancybox plugin. Edit the settings for your purposes.

<script type="text/javascript">
$("#tip4").click(function() {
	$.fancybox({
			'padding'		: 0,
			'autoScale'		: false,
			'transitionIn'	: 'none',
			'transitionOut'	: 'none',
			'title'			: this.title,
			'width'		: 680,
			'height'		: 495,
			'href'			: this.href.replace(new RegExp("watch\\?v=", "i"), 'v/'),
			'type'			: 'swf',
			'swf'			: {
			   	 'wmode'		: 'transparent',
				'allowfullscreen'	: 'true'
			}
		});

	return false;
});
</script>

Final Thoughts
I’m by no means the strongest coder. So if you cook up a better solution please share it with the rest of us. Maybe I don’t need the full second block of code? I know I was having issues a few months ago with product image order so part of the second block of code probably includes my fix. It might not be needed any longer or an issue specific to my project.