Last January, Tim Owens did a masterful job of assembling the Daily Create site out of a premium theme (Salutation), a mix of plugins (including Awesome Flickr Gallery, Tubepress, and Gravity Forms), etc– and no custom code.

When I arrived at UMW in February, I offered to take over the creation of the challenges. It is a mix of ones submitted by our site users, borrowed ones from the Daily Shoot, b3ta Challenges, the Vancouver Draw Down, and the 30 Day Drawing Challenge, plus about a good 30% we just dreamed up.

Creating them turned out to be a bit tedious as there was at least 7 different things to set on the interface. We had to have them, scheduled in advance, each to be published at 10:00 am EST.

All thee steps for editing a Daily Create

(click for full size image)

(1) The title would be present if it was submitted via a form; for one from scratch it is entered/pasted.
(2) The post name was manually entered, based on the tag for the day
(3) Depending on the type of Daily Create, we had a format for the post that would use a set of links to flickr, you tube tags to set up the instructions, and for the embedding of media, a Tubepress shortcode, flickr gallery ID, or a SoundCLoud embed code. For these, the tag had to be manually entered in at least two spots. For the flickr ones, we would have to create an Awesome flickr gallery (another admin page), and manually copy/paste the shortcode. I started by copying from old posts (and managing a few times to edit the wrong one) and later kept a generalized version in a text doc for cutting/pasting
(4) The future date for publishing was manually set (10:00am EST)
(5) Two categories assigned for the type, and an overall category we use to group all assignments.
(6) The tag is entered here
(7) The theme required a selection menu to match it to the type of assignment (each type has its own graphic which is displayed in the single post view.

I did this for about two months before rolling up my elbows to see if this could be streamlined.

Each of the four types (Audio, Video, Photography, Drawing) have a different content structure which is what embeds the creative works in a post. When I looked it at, once the type was set and the tag as known (the tag tdc123 is also the slug for the post), it was potentially template-able.

I looked to see if there were any plugins for setting up at least the templates for the content, but did not find any that allowed for multiple templates.

But ti was looking at the admin interface for this very theme (the theme options down the right sidebar) that it dawned on me that I could possibly create my own box with a drop down menu to select a type, and when saved, the post would automagically fill in the right content and check the right boxes, etc.

So after completing the Daily Creates for 6 months in advance, doing this seriously paid off. I was typically able to knock off a months worth in about 90 minutes, and more of that was having to look around or make up assignments.

The way is works now is for a new post or if I edit something that is a draft, the TDC Settings meta box is activated.

(click for full size image)

(Gulp, note in this fake example, it should be a Drawing type, not photography, this assignment is not reallly in the mix… yet)

The only thing it needs is (1) the title, (the code automatically creates the next tag it should use based on the last one created) and (2) I just select the type from the drop down menu, and click Save Draft.

When saved, everything else is done for me!

(click for full size image)

(1) Tag is used for the url
(2) The basic content is created according to the kind of assignment, and the tag is inserted where it is needed. The * is because the automation is not 100% complete; I still have to generate the flickr gallery shortcode for Photography and Drawing types (I did experiment with calling the code directly; it was close but not quite correct). Also, the embed codes for SoundCloud are random IDs, not related to the group name, so that code is also manual.
(3) The future publication date is automatically set! This saves a ton of time. It simply looks for the last one created, and then sets the publication date for 24 hours later.
(4) Categories automatically checked
(5) (6) The tags and the header setting for the template are auto selected.

This is all done by some custom code to intercept the hook when a draft is saved, and then updating the post data (and some post metadata).

Below is the semi hairy code… broken into parts for explanation. This all lives in the functions.php part of the theme template (which truly should be done as a child theme). If I were more clever, I would have made it a plugin, but the code would be the same (One day I want to be 0.01% as code clever as Boone Gorges).

Here we go… code below the fold…

The first thing we need to put in is a hook to add the meta box when the admin interface is built, tis simply says when the admin page is loaded and it is time to add meta boxes (those things on the right), call my function tdc_meta_box_add() to do set up the work of building the form interface you see (it passes all the variables the box needs, including the ‘high’ option to place it at the top right)

// hook to add the box to admin interface
add_action( 'add_meta_boxes', 'tdc_meta_box_add' );  

function tdc_meta_box_add()  
// meta box insertion to right side of post editor, at top
{ 
	add_meta_box( 'tdc-presets', 'TDC Settings', 'tdc_meta_box_cb', 'post', 'side', 'high' );
}

A few utility scripts were needed… tdc_get_next_tag() looks in the database to find the most recent tag used (via a custom query), and then returns an incremented on; so if the most recent one created was tdc233, we return tdc234.

function tdc_get_next_tag()
// get the next tag available from the database, these are in the form tdc123
{
	global $wpdb;
		
	// query to get the last tag used in the database
	$tag_query = "SELECT $wpdb->terms.name AS tag_name
	FROM $wpdb->terms
		INNER JOIN $wpdb->term_taxonomy ON ($wpdb->terms.term_id =     $wpdb->term_taxonomy.term_id)
		INNER JOIN $wpdb->term_relationships ON ($wpdb->terms.term_id = $wpdb->term_relationships.term_taxonomy_id)
		INNER JOIN $wpdb->posts ON ($wpdb->term_relationships.object_id = $wpdb->posts.ID)
	WHERE $wpdb->term_taxonomy.taxonomy = 'post_tag' AND $wpdb->terms.name LIKE 'tdc%%' 
	ORDER BY $wpdb->posts.post_date DESC
	LIMIT 1";
		
	$tags = $wpdb->get_results($tag_query); 
	
	// we loop although its only one time!
	if ($tags) {
		foreach ($tags as $tag) {
			$last_tag = substr($tag->tag_name,3); // get the numeric part of tag, after 'tdc'
		}
		
		$last_tag++; // increment tag
		return('tdc' . $last_tag);
	} else {
		// first time through
		return('tdc1');
	} 
}

Likewise, we need to find the date of the post of the last one created (it is not the edited ddate but the date it is set to be published in the future)

function tdc_get_last_date() {
	// Used to get the date of the last schedule TDC
	
	// we will query to get all future dated posts
	$args = array( 'numberposts' => 1, 'post_status'=> 'future' );
	$lastposts = get_posts( $args );
	
	if ($lastposts) {
		// simple loop to get the post_date for the last scheduled post
		foreach($lastposts as $post) {
			setup_postdata($post);
			return ($post->post_date);
		}
	} else {
		// we have no scheduled posts, set for today at 10:00am 
		
		return (mktime(10,0,0));
	}
}

And now we have the callback function to do the work. A couple of things going on here, we need to make sure it only gets called for a new or a draft post (so it does not override an existing one).

If a draft had been made before, ti stores the tdc tag as a post_meta data, we check this first to see if it has been assigned, if not we give it a flag value of just ‘tdc’- this signals later that we need to find the next tag available via our custom function created above.

We have to find the tag we should use for the new post and insert it in the text field of the form (allowed for it to be edited). We put the date for the new post in a hidden form field, along with a tag to indicate if it is a brand new post. The checkl box to confirm the over-write allows us to choose whether we change the post content or not (we do not want to change it if the post is still a draft).

function tdc_meta_box_cb($post) {

	// escape for published or scheduled posts, once set we do not present the box
	if ($post->post_status == 'publish' OR $post->post_status == 'future') return; 
    
	// we need access to post meta
	$values = get_post_custom( $post->ID ); 
	
	// the category of TDC
	$tdc_type = isset( $values['tdc_type'] ) ? esc_attr( $values['tdc_type'][0] ) : ''; 
	
	// the tdc tag 
	$last_tag = isset( $values['tdc_tag'] ) ? esc_attr( $values['tdc_tag'][0] ) : 'tdc'; 

	if ($last_tag == 'tdc') {
		// this is a new post and has not been assigned a tag, get the next one available
		$last_tag = tdc_get_next_tag();
	
		// flag for new tdc or not
		$tdc_is_new = 'on';
	} else {
		$tdc_is_new = 'off';
	}
	
	// the tag to use
	$tdc_tag = isset( $values['tdc_tag'] ) ? esc_attr( $values['tdc_tag'][0] ) : $last_tag; 
	
	// add a day to the last tdc
	$next_tdc_date = strtotime(tdc_get_last_date()) + 3600*24;
	
	
	// Output form, including nonce field   
    wp_nonce_field( 'tdc_meta_box_setting', 'tdc_settings_nonce' );  
	?>
		

Preset and Save Draft!

/>

Whew, that was just to create the form in the box of our interface. Now we have to make it do something. To do this we need to set up a hook to catch when a post is saved:

add_action( 'save_post', 'tdc_settings_save' );

This could happen whether the Save as Draft, Post, or Update buttons are pressed by the user.

Basically this does a few checks to make sure we are doing it only for the kinds of saves we want to be doing. If the type selection is not set we want to bail. We do not want to change anything if the user unselects te check box. And we do not want to be doing this on a WordPress autosave...

The code to remoce the hook was a subtle trick I found was needed to make it possible for me to edit the post date. That was obscure! I think that was the reason, I cannot even find the reference, it was likely at Stack Overflow.

The whole point is to pass the tag, date, type, and post id to our function that will do the updating.

function tdc_settings_save( $post_id )
{

	if (!isset($_POST['tdc_type'])) return; // not using TDC settings, abort
	
	if ($_POST['tdc_is_new'] != 'on') return; // skip saving if the box not checked, saves over writing
	
	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
	
	if( !isset( $_POST['tdc_settings_nonce'] ) || !wp_verify_nonce( $_POST['tdc_settings_nonce'], 'tdc_meta_box_setting' ) ) return;
	if( !current_user_can( 'save_post' ) ) return;

	// unhook this function so it doesn't loop infinitely
	remove_action('save_post', 'tdc_settings_save');
	
	if( $_POST['tdc_type'] != '' ) 
	{
		// send type and tag to function to update post
		 tdc_update_post($post_id, esc_attr( $_POST['tdc_type']), esc_attr( $_POST['tdc_tag'] ), esc_attr( $_POST['tdc_date'] ));
    }

  	// re-hook this function
	add_action('save_post', 'tdc_settings_save');

}

And her we do the work of updating the post via tdc_update_post() It is basically setting up an array of all the new values for the post (and adding/editing a few postmeta fields for info tracking). One of the nitty things is that in the type of assignment (which is what gets set as the theme's layer heading" is not stored as values like "Photography", "vide" but cryptic keys like 'y0krz2ep75f' -- these are actually buried in an options array in the blog meta table, and rather than parsing through that, I got lazy and hadn coded it. This should be updated one day...

function tdc_update_post($post_id, $tdc_type, $tdc_tag, $tdc_date)
// update a post using the provided type and tag
{
		//set the TDC type as meta data
		update_post_meta( $post_id, 'tdc_type', $tdc_type  );
		
		// THIS IS UGLY HAND CODED TIL I CAN FIGURE OUT HOW TO PARSE THEME SETTINGS
		// It embeds these codes inside an ugly array.
		
		switch ($_POST['tdc_type']) {
			case 'Audio':	
				$tdc_layout_header = 'y0krz2ep75f';
				break;
			
			case 'Photography':
				$tdc_layout_header = '1dew1kfr1ygi';
				break;
				
			case 'Drawing':
				$tdc_layout_header = '2eti4jw9hn46';
				break;
			
			case 'Video':
				$tdc_layout_header = '7bq94vtlkxhn';
				break;
		}
			
		// set the theme layout header options
		update_post_meta( $post_id, 'tdc_layout_header', $tdc_layout_header);

	if( isset( $_POST['tdc_tag'] ) )
		update_post_meta( $post_id, 'tdc_tag', $tdc_tag ); 
	
	// set the TDC categories; all  get the base category	
	$tdc_categories = array(get_cat_ID('TDC Assignments'));
	
	// add the category for this type
	$tdc_categories[] = get_cat_ID($tdc_type);
	
	// set the categories for the post
	wp_set_post_categories( $post_id, $tdc_categories);
	
	// set the tag	
	wp_set_post_tags( $post_id, $tdc_tag);
	
	// Update post content with templates for each TDC type
  	$tdc_post = array();
  	$tdc_post['ID'] = $post_id;
  	$tdc_post['post_name'] = $tdc_tag;
  	$tdc_post['edit_date'] = true; 

  	$tdc_post['post_date'] = date('Y-m-d', $tdc_date) . ' 10:00:00';
  	$tdc_post['post_date_gmt'] = gmdate('Y-m-d', $tdc_date) . ' 10:00:00';
  	$tdc_post['post_status'] = 'future';
  	
  	
  	// now set up default post content
  	switch ($_POST['tdc_type']) 
  	{
    	case 'Audio':  	
			$tdc_post['post_content'] = 'Upload your recording to SoundCloud and add it to the ' . $tdc_tag . " group.\n\n[insert soundcloud embed code]";
			break;
			
    	case 'Drawing':
			$tdc_post['post_content'] = 'Upload your drawing as a photo to flickr and tag it ' . $tdc_tag . "\n\n(insert AFG gallery)";
			break;
			
    	case 'Photography':
			$tdc_post['post_content'] = 'Upload your photo to flickr and tag it ' . $tdc_tag . "\n\n(insert AFG gallery)";
			break;
			
		case 'Video':
			$tdc_post['post_content'] = 'Upload your video to YouTube with the tag ' . $tdc_tag . ".\n\n" . '[tubepress mode="tag" tagValue="' . $tdc_tag . '" playerLocation="shadowbox" orderBy="published" showRelated="false" ajaxPagination="false" resultsPerPage="28" author="true" videoBlacklist=""]';
			break;
	}
  	
	// Update the post into the database
  	wp_update_post( $tdc_post );
}

And that is how its done! For reference, below you will find all of this custom code in one block.

It was fun to work through, and I am pretty darned proud of it, but like all code, it could likely use some improvements. I'm just the kind who bangs at it til it works, sand then tweaks the corners forever.

#-----------------------------------------------------------------
# Custom for ds106 Daily Create
# Metabox for triggering editor settings
# Hand hacked by Alan Levine @cogdog
#-----------------------------------------------------------------

// hook to add the box to admin interface
add_action( 'add_meta_boxes', 'tdc_meta_box_add' );  



function tdc_get_next_tag()
// get the next tag available from the database, these are in the form tdc123
{
	global $wpdb;
		
	// query to get the last tag used in the database
	$tag_query = "SELECT $wpdb->terms.name AS tag_name
	FROM $wpdb->terms
		INNER JOIN $wpdb->term_taxonomy ON ($wpdb->terms.term_id =     $wpdb->term_taxonomy.term_id)
		INNER JOIN $wpdb->term_relationships ON ($wpdb->terms.term_id = $wpdb->term_relationships.term_taxonomy_id)
		INNER JOIN $wpdb->posts ON ($wpdb->term_relationships.object_id = $wpdb->posts.ID)
	WHERE $wpdb->term_taxonomy.taxonomy = 'post_tag' AND $wpdb->terms.name LIKE 'tdc%%' 
	ORDER BY $wpdb->posts.post_date DESC
	LIMIT 1";
		
	$tags = $wpdb->get_results($tag_query); 
	
	// we loop although its only one time!
	if ($tags) {
		foreach ($tags as $tag) {
			$last_tag = substr($tag->tag_name,3); // get the numeric part of tag, after 'tdc'
		}
		
		$last_tag++; // increment tag
		return('tdc' . $last_tag);
	} else {
		// first time through
		return('tdc1');
	} 


}


function tdc_get_last_date() {
	// Used to get the date of the last schedule TDC
	
	// we will query to get all future dated posts
	$args = array( 'numberposts' => 1, 'post_status'=> 'future' );
	$lastposts = get_posts( $args );
	
	if ($lastposts) {
		// simple loop to get the post_date for the last scheduled post
		foreach($lastposts as $post) {
			setup_postdata($post);
			return ($post->post_date);
		}
	} else {
		// we have no scheduled posts, set for today at 10:00am 
		
		return (mktime(10,0,0));
	}
}
	
	
function tdc_meta_box_add()  
// meta box insertion to right side of post editor, at top
{ 
	add_meta_box( 'tdc-presets', 'TDC Settings', 'tdc_meta_box_cb', 'post', 'side', 'high' );
}

function tdc_meta_box_cb($post) {

	// escape for published or scheduled posts, once set we do not present the box
	if ($post->post_status == 'publish' OR $post->post_status == 'future') return; 
    
	// we need access to post meta
	$values = get_post_custom( $post->ID ); 
	
	// the category of TDC
	$tdc_type = isset( $values['tdc_type'] ) ? esc_attr( $values['tdc_type'][0] ) : ''; 
	
	// the tdc tag 
	$last_tag = isset( $values['tdc_tag'] ) ? esc_attr( $values['tdc_tag'][0] ) : 'tdc'; 

	
	if ($last_tag == 'tdc') {
		// this is a new post and has not been assigned a tag, get the next one available
		
		$last_tag = tdc_get_next_tag();
		
		// flag for new tdc or not
		$tdc_is_new = 'on';
	} else {
		$tdc_is_new = 'off';
	}
	
	// the tag to use
	$tdc_tag = isset( $values['tdc_tag'] ) ? esc_attr( $values['tdc_tag'][0] ) : $last_tag; 
	
	// add a day to the last tdc
	$next_tdc_date = strtotime(tdc_get_last_date()) + 3600*24;
	
	
	// Output form, including nonce field   
    wp_nonce_field( 'tdc_meta_box_setting', 'tdc_settings_nonce' );  
	?>
		

Preset and Save Draft!

/>

SoundCloud and add it to the ' . $tdc_tag . " group.\n\n[insert soundcloud embed code]"; break; case 'Drawing': $tdc_post['post_content'] = 'Upload your drawing as a photo to flickr and tag it ' . $tdc_tag . "\n\n(insert AFG gallery)"; break; case 'Photography': $tdc_post['post_content'] = 'Upload your photo to flickr and tag it ' . $tdc_tag . "\n\n(insert AFG gallery)"; break; case 'Video': $tdc_post['post_content'] = 'Upload your video to YouTube with the tag ' . $tdc_tag . ".\n\n" . '[tubepress mode="tag" tagValue="' . $tdc_tag . '" playerLocation="shadowbox" orderBy="published" showRelated="false" ajaxPagination="false" resultsPerPage="28" author="true" videoBlacklist=""]'; break; } // Update the post into the database wp_update_post( $tdc_post ); } add_action( 'save_post', 'tdc_settings_save' ); function tdc_settings_save( $post_id ) { if (!isset($_POST['tdc_type'])) return; // not using TDC settings, abort if ($_POST['tdc_is_new'] != 'on') return; // skip saving if the box not checked, saves over writing if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return; if( !isset( $_POST['tdc_settings_nonce'] ) || !wp_verify_nonce( $_POST['tdc_settings_nonce'], 'tdc_meta_box_setting' ) ) return; if( !current_user_can( 'save_post' ) ) return; // unhook this function so it doesn't loop infinitely remove_action('save_post', 'tdc_settings_save'); if( $_POST['tdc_type'] != '' ) { // send type and tag to function to update post tdc_update_post($post_id, esc_attr( $_POST['tdc_type']), esc_attr( $_POST['tdc_tag'] ), esc_attr( $_POST['tdc_date'] )); } // re-hook this function add_action('save_post', 'tdc_settings_save'); }

If this kind of stuff has value, please support me by tossing a one time PayPal kibble or monthly on Patreon
Become a patron at Patreon!
Profile Picture for CogDog The Blog
An early 90s builder of web stuff and blogging Alan Levine barks at CogDogBlog.com on web storytelling (#ds106 #4life), photography, bending WordPress, and serendipity in the infinite internet river. He thinks it's weird to write about himself in the third person. And he is 100% into the Fediverse (or tells himself so) Tooting as @cogdog@cosocial.ca

Comments

  1. I love that you share this stuff. I don’t always follow all the details but I file it away for future reference if I ever want to do something similar (which has happened more than once over the years). What I most get out of these posts is some insight into how you think & problem solve; and that’s awesome!

  2. Thanks for sharing this! My school district tends to block most tools that involve any kind of sharing (flckr, YouTube, SoundClould, and even ds106.us and magicmacguffin.info) so I’m constantly figuring out how to build my own systems for students to at least be able to share with each other inside our system. Is there a post anywhere that gives similar insight into how you guys built the Assignments area of ds106?

  3. This is great stuff Alan – love how you folded your own workflow for the TDC. A great example of working smarter, not harder.

Leave a Reply

Your email address will not be published. Required fields are marked *