Adding Dynamic Fields to Contact Form 7 Plugin

I have recently come across a situation where I needed to add a dropdown menu that was dynamically populated with posts from another custom post type. The form was generated by using the Contact Form 7 plugin, I searched the files for any filters or hooks that would help in my situation, but didn’t find any. Since the company that I work for has a policy about installing plugins I ended up writing up some code that would solve my problems. In all the code below just allows you to add in any custom shortcode within the CF7 editor.

So basically, when creating your form inside CF7, you just add the shortcode and pass in the custom post type slug as the property

[dynamic_select post_type="programs"]

The above code will give me a select menu that the user can now highlight a program custom post type post. In CF7 where you add your fields to the recipients email you would simply add:

[dynamic-select]

dynamic-select is the default name that is given to the select field, this is what CF7 will use to output the data. If you set the name property of the shortcode, then you will use that in your email template. ie:

[dynamic_select post_type="programs" name="program-select-field"]

you will then add the following to your email template:

[program-select-field]

Other shortcode properties you may use are, with their default values:

"name" => 'dynamic-select'
"id" => $id
"class" => $class
"initial_value" => ''
"initial_label" => 'Please select'
"input_atts" => '' // inserts attributes to the select element

Please note, if you happen to use this code then you should take note that I added in transients. So if you happen to make any changes and don’t see them on your site, check the transient and delete when needed.

(The code could be more flexible, but I am running out of time and works great for me at the moment)

/**
 * Code Group: CF7
 * Inserts a dynamic dropdown menu inside contact form 7
 *
 * @uses Contact Form 7 Plugin
 * @return Select Field populated with data from a custom post type
 * @author Morgan Newcomb
 */

// if you are editing this code, set this bool to TRUE
// setting this to true will delete the transient each
// time the form is viewed, otherwise the transient gets
// delete when the post is saved (post referring to the 'post_type' property)
// that is passed in the shortcode.
// --
// define("DELETE_CF7_TRANSIENTS_FOR_DEBUGGING", TRUE);
define("DELETE_CF7_TRANSIENTS_FOR_DEBUGGING", FALSE);

/**
 * Filter the content that outputs CF7 form to the browser. This allows
 * for us to add in our custom shortcode(s)
 */
if( ! function_exists('filter_form_content_to_allow_filters') ):
function filter_form_content_to_allow_filters($content){
	if ( ! class_exists( 'WPCF7_ContactForm' ) )
        return;
    
    return apply_filters('the_content', $content);
}
endif;
add_filter('wpcf7_form_elements', 'filter_form_content_to_allow_filters');

/**
 * Actual shortcode to process the select field
 * @properties post_type must be set
 * @implementation
 *    [dynamic_select post_type="post"]
 *    This will create a dropdown menu populated with all the posts
 */
if ( ! function_exists( 'dynamic_select' ) ):
function dynamic_select($atts){
	extract(shortcode_atts(array(
		"post_type" => false,
		"name" => 'dynamic-select',
		"id" => $id,
		"class" => $class,
		"initial_value" => '',
		"initial_label" => 'Please select',
		"input_atts" => ''
	), $atts));

	// require a post type
	if ( ! $post_type )
		return 'ERROR: Post type property not set.';

	// clean up
	$name = sanitize_text_field($name);
	$id = sanitize_title( ( ! $id ? $name : $id) );
	$class = sanitize_text_field($class);
	$initial_value = sanitize_text_field($initial_value);
	$initial_label = sanitize_text_field($initial_label);
	$input_atts = esc_attr($input_atts);
	
	// create templates
	$select_field = '';

	$option_field = '';

	// Are we debugging/updating/adding code?
	if ( DELETE_CF7_TRANSIENTS_FOR_DEBUGGING )
		delete_transient('cf7_dynamic_select_options_' . $post_type);

	// Data
	// Check for cached queries. If none, then execute get_posts and create our select field
	if ( ! ( $dynamic_field = get_transient( 'cf7_dynamic_select_options_' . $post_type ) ) ) {
		$posts = get_posts(
			array(
				'post_type'	=> $post_type,
				'posts_per_page' => -1
			));

		// Run the loop as normal
		$options = '';
		if ( $posts ) { foreach ($posts as $post) {
			$option_value = apply_filters('custom_option_value', esc_attr($post->post_title), $post->ID);
			$option_label = apply_filters('custom_option_label', $post->post_title, $post->ID);
			$options .= sprintf($option_field,
				$option_value,
				$option_label
				);	
		}}

		// Create the field
		$dynamic_field = sprintf($select_field,
			$name,
			$id,
			$class,
			$options
			);

		// Put the results in a transient. Expire after 1 year.
		set_transient( 'cf7_dynamic_select_options_' . $post_type, $dynamic_field, 365 * DAY_IN_SECONDS );
	}

	return $dynamic_field;
}
endif;
add_shortcode("dynamic_select", "dynamic_select");

/*
 * Deletes transient set by this cf7 code when the post
 * is saved.
 */
if ( ! function_exists( 'delete_cf7_transients' ) ):
function delete_cf7_transients( $post_id ) {
	// If this is just a revision, don't delete the transient.
	if ( wp_is_post_revision( $post_id ) )
		return;

	delete_transient('cf7_dynamic_select_options_' . get_post_type($post_id));
}
endif;
add_action( 'save_post', 'delete_cf7_transients' );

/**
 * END: Code Group: CF7
 */


/** 
 * Looking to customize the select form option values or label?
 * Implement these filters and have fun.
 *
 * IMPORTANT:
 * if you add these functions and don't see your changes then set the
 * constant variable: DELETE_CF7_TRANSIENTS_FOR_DEBUGGING to true to
 * delete the transient
 */

// option label:
function custom_option_label_filtered($value, $post_id){
	return $value; // or return something else.
}
add_filter('custom_option_label','custom_option_label_filtered',1,2); // 2 parameters

// option value:
function custom_option_value_filtered($value, $post_id){
	return $post_id; // or return something else.
}
add_filter('custom_option_value','custom_option_value_filtered',1,2); // 2 parameters

Centering Images within a Grid

I have clients that love to upload random sized images and I can’t always rely on the CMS or the user to crop an image to size to work with the design, so I wrote this plugin awhile ago, but want to ‘store’ it here so I can access it for future projects. I understand that WordPress will allow for new image variations with hard and soft cropping applied, but I don’t always want to apply a new variation to every single image that gets uploaded, in this instance though I was working with on a Shopify site.

image-grid

Here is the plugin in nutshell.

$.fn.centerImage = function(options){
	// set default options
	var defaults = {
		wrapperEl: ".jqCenterImage"
	};

	// set options
	var options = $.extend(defaults, options);

	$ct=0;
	$(this).find('img').each(function(i){
		// set this image off the screen
		$(this).css('position','absolute');
		$(this).css('left','-999px');
		$(this).css({
			'position':'absolute',
			'left':'-999px'
		})

		// wrap the image with an element for positioning
		if ( ! $(this).parents(options.wrapperEl).length )
			$(this).wrap('<div class="'+options.wrapperEl.replace('.','')+' preloader" />');

		var $wrapping_el = $(this).parents(options.wrapperEl);

		// add some css to our wrapper element
		$($wrapping_el).css({
			'position':'relative',
			'overflow':'hidden'
		});

		// create an image object so we can run actions 
		// on our image once it is loaded to screen
		var img = $(this);
		var img_load = new Image();
		$(img_load)
		    // once the image has loaded, execute this code
		    .load(function () {
				$(img).fadeOut(0,function(){
					// rid of preloader
					$wrapping_el.removeClass('preloader');

					// get dimensions of wrapper
					wrap_width = $wrapping_el.width();
					wrap_height = $wrapping_el.height();

					// get dimensions of image and set the position
					img_width = $(this).width();
					img_height = $(this).height();

					left_pos = (wrap_width - img_width) / 2;
					top_pos = (wrap_height - img_height) / 2;

					// position and show the image
					$(img).css({
						'position':'absolute',
						'top': top_pos + 'px',
						'left': left_pos + 'px'
					}).fadeIn('fast');
				});				
			})
			// if there was an error loading the image, react accordingly
			.error(function () {
				// notify the user that the image could not be loaded
			})
			// *finally*, set the src attribute of the new image to our image
			.attr('src', $(this).attr('src') );
	});
};

Last I checked the minified file weighed less thank 1kb.

To implement, just include jQuery (I used this recent version on v1.10.2) and you may use this demo code:

<script type="text/javascript">

		/**
		 * Implementation
		 */

		 /**
		  * Function to call our plugin
		  * Using function here because I want to center the image on DOM load and Window Resize
		  */
		function centerImages(){
			if ( $('.product-grid').length ) { 
				$('.product-grid').centerImage({
					wrapperEl : '.myOtherSelector'
				});

				/*
				Note:
				The plugin wraps the images with a div containing a class
				.jqCenterImage. You will need to apply the height and width
				to this element within your CSS. You can use a different
				selector by simply passing it in as an option. ie:

				centerImage({
					wrapperEl : '.myOtherSelector'
				});

				Also note that the selector element applies a position relative and overflow hidden style.
				*/
			}
		}

		// DOM Load
		$(function(){
			// call our function
			centerImages();
		});

		// Window Resize
		$(window).resize(function() {
    		clearTimeout($.data(this, 'resizeTimer'));
    		$.data(this, 'resizeTimer', setTimeout(function() {
		        centerImages();
    		}, 200)); // check resize ever 200ms
		});
	</script>

Add a little style:

.product-grid { list-style: none;margin: 0; padding: 0; }
.product-grid li { float: left; }

/* 
This class is provide out of the box with this plugin,
If you prefer a different select, you can just pass in
a new selector as a plugin option
*/
.jqCenterImage {
	width: 350px;
	height: 150px;
}

Now a little markup:

<ul class="product-grid">
	<li><img src="/path/to/image.ext" alt="" /></li>
	<li><img src="/path/to/image.ext" alt="" /></li>
	<li><img src="/path/to/image.ext" alt="" /></li>
	<li><img src="/path/to/image.ext" alt="" /></li>
</ul><!-- /.product-grid -->

The plugin wraps the images with a div containing a class .jqCenterImage. You will need to apply the height and width to this element within your CSS. You can use a different selector by simply passing it in as an option. ie:

centerImage({
	wrapperEl : '.myOtherSelector'
});

Also note that the selector element applies a position relative and overflow hidden style.

Saving Multiple Values in One Meta Key using SuperCPT Plugin

One plugin that you should have in your WordPress installation is SuperCPT written by Matt Boynes. It makes registering custom post types and creating custom meta boxes incredibly fast and easy.
non-unique-meta-key-valuesI recently ran in to a situation where I wanted to empower the site owner to add in any number of dates to their programs. My current version of SuperCPT is only allowing for a meta field to be unique, so by leveraging filters and hooks I came up with this solution.

I will have to say that this isn’t ideal, but it works. Since the flow to adding an entry is that you have save the page, it would be best if this was done with AJAX, for now here is code.

First, let’s instantiate, register, and create our custom post type:

if  ( ! function_exists('generate_custom_post_types') ) :
function generate_custom_post_types() {
    if ( ! class_exists( 'Super_Custom_Post_Type' ) )
        return; // plugin not found, installed, activated.. get out

	$cpt_programs = new UsmCustomPostType('program', 'Program', 'Programs');
	$cpt_programs->add_meta_box( array(
		'id' => 'program-dates-editor-meta-box',
		'title' => 'Add Start and End Date',
		'context' => 'side',
		'fields' => array(
			 'start-date' => array("type" => "date"), // this field works with customposttype-program.inc.php
			 'end-date'	=> array("type" => "date"), // this field works with customposttype-program.inc.php
		)
	));

	$cpt_programs->add_meta_box( array(
		'id' => 'program-dates-meta-box',
		'title' => 'Start and End Dates',
		'callback' => 'render_start_and_end_dates', // this is where the data will get added
		'context' => 'side',
		'fields' => array(
			// leaving empty	 
		)
	));
}
endif;
add_action( 'after_setup_theme', 'generate_custom_post_types' );

My thinking was:

1. the user saves the post it will take the fields, start-date and end-date, generated by the SuperCPT plugin
2. create a new meta field, called _program-start-end-dates, and store the start-date and end-date fields as an array item
3. save the new field _program-start-end-dates

/*
 * Function Name: save_program_cpt_dates 
 * Arguments: 
 * Returns: 
 * 
 * Author: 
 * Comments: 
 * File: 
 */

if  ( ! function_exists('save_program_cpt_dates') ) :
	function save_program_cpt_dates ($post_id){
		$slug = 'program';

		/* check whether anything should be done */
	    $_POST += array("{$slug}_edit_nonce" => '');
	    if ( $slug != $_POST['post_type'] ) {
	        return;
	    }
	    if ( !current_user_can( 'edit_post', $post_id ) ) {
	        return;
	    }

		if ( get_post_type($post_id) == $slug ){
			// get values
			$program_date = array(
				'start-date' => wp_kses(get_post_meta($post_id,'start-date',true)),
				'end-date' => wp_kses(get_post_meta($post_id,'end-date',true))
				);

			if ( $program_date['start-date'] != '' || $program_date['end-date'] != '' ){
				// add the new dates
				if ( add_post_meta ( $post_id, '_program-start-end-dates', $program_date, false ) ){
					// delete the fields
					delete_post_meta($post_id,'start-date');
					delete_post_meta($post_id,'end-date');
				}
			}

			// are we deleting any trips
			$program_date_remove = $_POST['program_date_remove'];

			if(is_array($program_date_remove) && !empty($program_date_remove)){
    			remove_program_date($program_date_remove);
  			}
		}

		return;
	}
endif; // ending function save_program_cpt_dates  check
add_action( 'save_post', 'save_program_cpt_dates', 11 ); // setting priority here is important

In the admin area we will want to view all our values for start and end dates. This method is called as the callback within our add_meta_box method property.

/*
 * Function Name: render_start_and_end_dates
 * Arguments: 
 * Returns: 
 * 
 * Author: 
 * Comments: 
 * File: 
 */

if  ( ! function_exists('render_start_and_end_dates') ) :
	function render_start_and_end_dates(){
		global $post;

		$post_program_dates = get_post_meta($post->ID,'_program-start-end-dates');

		if ( empty($post_program_dates) ){
			echo 'This program does not have any start and end dates assigned.';
		} else {
			echo '<table class="widefat fixed">
				<thead>
					<tr>
						<th>Start</th>
						<th>End</th>
						<th>Delete?</th>
					</tr>
				</thead>
				<tbody>';
			foreach ( $post_program_dates as $post_program_date ){
				$start_date = sanitize_text_field($post_program_date['start-date']);
				$end_date = sanitize_text_field($post_program_date['end-date']);

				echo '<tr>
					<td>'.$start_date.'</td>
					<td>'.$end_date.'</td>
					<td><input type="checkbox" name="program_date_remove[]" value="'.$post->ID.':'.$start_date.':'.$end_date.'" /></td>
					</tr>';
			}

			echo '</tbody></table>';
		}

	}
endif; // ending function render_start_and_end_dates check

Since we allow the user to delete the field from the checkbox, user may not be able to edit a field (not really an issue here since we are dealing with a small piece of data). NOTE: this will delete multiple entries of the same value. ie: if there are 2 sets of dates both with begin date of 08-12-2013 and the same end date of 09-01-2013, then if one field is checked off then both of these entries will be deleted.

/*
 * Function Name: remove_program_date
 * Arguments: 
 * Returns: 
 * 
 * Author: 
 * Comments: NOTE: this will delete multiple entries of the same value. ie: if there are 2 sets of dates both with begin date of 08-12-2013 and the same end date of 09-01-2013, then if one field is checked off then both of these entries will be deleted.
 * File: 
 */

if  ( ! function_exists('remove_program_date') ) :
	function remove_program_date($fields){
		foreach ( $fields as $field ){
			if ( is_array($data) ) unset($data);

			$data = explode(':',$field); // format: post id, start date, end date

			$program_date_value = array(
				'start-date' => $data[1],
				'end-date' => $data[2]
			);

			if ( $program_date_value['start-date'] != '' || $program_date_value['end-date'] != '' ){
				delete_post_meta($data[0], '_program-start-end-dates',$program_date_value);	
			}
		}	

		return;
	}
endif; // ending function remove_program_date check

Get Post Meta Keys and Values Function

A function to retrieve all meta keys and values, excluding private.

/**
 * Function to return all post meta variables as an array
 */
if ( ! function_exists( 'get_meta_keys_and_values' ) ):
	function get_meta_keys_and_values($post_id=false){
		if ( ! $post_id ){
			global $post;
			$post_id = $post->ID;	
		}

		$the_metas = array_map( 'stripslashes_deep', get_post_meta( $post_id ) );

		foreach ( $the_metas as $key => $value ):
			if ( $key[0] != '_' ):
				$meta[$key] = $value[0];
			endif;
		endforeach;

		return $meta;
	}
endif;

Simple301 Redirects Bulk Import

Plugin: Simple301 Redirects

Author: Scott Nelle

WordPress shortcode to import 301 redirects. This plugin currently stores the redirects in the option ’301_redirects’. Simply populate the $redirect array and call the shortcode.

Code:
(without warranty, backup your data!)

/* Shortcode for saving the 301 redirects */
if ( ! function_exists( 'redirects_shortcode' ) ):
function redirects_shortcode(){
	$redirect = array();
	$redirect["/OLD/PATH/"] = "http://DOMAIN.TLD/NEW/PATH/";

	// If existing option exists, merge them with new redirect array from above
	if ( $redirect_options == get_option('301_redirects') ) {
		// let's make sure the shortcode isn't being called multiple times
		// merge the array and store in a temporary variable for comparison
		$redirect_temp = array_merge($redirect_options, $redirect);
		// if no difference, reassign the redirect variable to be stored in the redirect option
		if ( array_diff($redirect_options, $redirect_temp) ) $redirect = $redirect_temp;		
	}

	// Update our option
	update_option('301_redirects', $redirect);

	// Return the number of redirects
	return count($redirect) . " Total Redirects";
}
endif;
add_shortcode('301_redirects_shortcode', 'redirects_shortcode');