Calendar of CPT Using ACF Date/Time Field
This is a work in progress. Displays a small calendar with dates specified using a date/time field in Advanced Custom Fields on a Custom Post Type. Currently assumes a posts to posts connection between two CPTs projects and activities. 
The PHP
jlv-cpt-calendar.php
<?php
/*
Plugin Name: JLV CPT Calendar
Plugin URI: http://dev.abuyasmeen.com/
Description: CPT Calendar with ACF
Version: 0.3
Author: Jeremy Varnham
Author URI: https://abuyasmeen.com/
*/
define( 'JLV_CPT_CAL_VERSION', '3' );
define( 'JLV_CPT_CAL_URL', plugin_dir_url( __FILE__ ) );
define( 'JLV_CPT_CAL_DIR', dirname( __FILE__ ) . '/' );
require JLV_CPT_CAL_DIR . 'jlv-cpt-calendar-scriptinit.php';
require JLV_CPT_CAL_DIR . 'jlv-cpt-calendar-loader.php';
require JLV_CPT_CAL_DIR . 'jlv-cpt-calendar-widget.php';
?>
jlv-cpt-calendar-loader.php
<?php
/*
*/
class jlv_cpt_calendar {
/**
* Define static values for testing purposes.
* Will be replaced with Wordpress options and user input.
*/
public $strPostType = 'project';
public $strStartTimeACF = 'start_time';
public $intSelectedYear = 0;
public $intSelectedMonth = 0;
public $intFirstDayOfWeek = 0;
/**
* Define blank arays to be built up with functions below.
*/
private $_WeekDays = array();
private $_Structure = array();
private $_TheDays = array();
/**
* Defines the days of the week starting with Sunday in English and Arabic.
*
* @since 0.2
*/
private function define_week_days() {
$this->_WeekDays['en-US'] = array('Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat');
$this->_WeekDays['ar'] = array('الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت');
}
/**
* Extracts various numbers and strings from the given date.
*
* @since 0.1
*/
private function define_day_codes() {
$firstDayOfMonthTimeCode = (int) mktime(0, 0, 0, $this->intSelectedMonth, 1, $this->intSelectedYear);
$this->_TheDays['Total Days In Month'] = (int) date('t', $firstDayOfMonthTimeCode);
$this->_TheDays['Weekday For First Day Of Month'] = (int) date('w', $firstDayOfMonthTimeCode);
$this->_TheDays['The Month Name'] = (string) date('F', $firstDayOfMonthTimeCode);
$this->_TheDays['Current Date Int'] = (int) date("Ymd",mktime(0,0,0,date("m"),date("d"),date("Y")));
}
/**
* Defines the HTML parts in one place for more readable output function.
*
* @since 0.2
*/
private function define_html_structure() {
$this->_Structure['Start Cal Div'] = '<div class="cal">';
$this->_Structure['End Cal Div'] = '</div>';
$this->_Structure['Start Table'] = '<table class="cal-table" style="background: none;">';
$this->_Structure['End Table'] = '</table>';
$this->_Structure['Start Table Header'] = '<thead><tr>';
$this->_Structure['End Table Header'] = '</tr></thead>';
$this->_Structure['Start Table Caption'] = '<caption class="cal-caption">';
$this->_Structure['End Table Caption'] = '</caption>';
$this->_Structure['Start Table Body'] = '<tbody class="cal-body"><tr>';
$this->_Structure['End Table Body'] = '</tbody>';
$this->_Structure['Start Table Column'] = '<td>';
$this->_Structure['End Table Column'] = '</td>';
$this->_Structure['End Table Row'] = '</tr>';
}
/**
* Converts slashes to dashes for compatibility with already entered dates
*
* @since 0.2
*
* @param string $the_date the date saved by ACF date/time picker
* @return int $the_date_int the date as Ymd
*/
private function convert_the_date($the_date) {
if (strpos($the_date,'/') !== false) { // if it's the old format (dd/mm/yy)
$the_date_bits = explode('/',$the_date);
$the_date = $the_date_bits[0].'-'.$the_date_bits[1].'-'.$the_date_bits[2];
$the_date_int = (int) date('Ymd', strtotime($the_date)); // Strip time, strip dashes, make integer
}
else
{
$the_date_int = (int) date('Ymd', strtotime($the_date)); // Strip time, strip dashes, make integer
}
return $the_date_int;
}
/**
* Performs the Wordpress query.
*
* @since 0.1
*
* @return object
*/
private function do_the_query() {
wp_reset_postdata();
global $post;
$args = array(
'post_type' => $this->strPostType,
'meta_key' => $this->strStartTimeACF,
'orderby' => 'meta_value',
'order' => 'ASC',
'connected_type' => 'activity_to_project',
'connected_items'=>$post->ID
);
$loop = new WP_Query( $args );
return $loop;
}
/**
* Loops through days of week starting with defined starting day.
* Wraps each day name in a TH
*
* @since 0.1
*
* @return string $html
*/
private function get_the_table_header() {
$html = '';
for ($counter = 0, $i = $this->intFirstDayOfWeek; 7 > $counter; $counter++, $i++) // Loop 7x to output weekday names
{
$html .= '<th>' . $this->_WeekDays[get_bloginfo('language')][$i] . '</th>';
if (6 == $i) { $i = -1; } // If counter reached to 6, set it to -1
}
return $html;
}
/**
* Builds the table caption with pagination / month name and year.
*
* @since 0.1
*
* @return string $html
*/
private function get_the_table_caption() {
$html = '';
//$html .= '<a href="#" class="prev">«</a>';
//$html .= '<a href="#" class="next">»</a>';
$html .= $this->_TheDays['The Month Name'] . ' ' . $this->intSelectedYear;
return $html;
}
/**
* If new week has started then close current table row and start new one
*
* @since 0.1
*
* @return string $html
*/
private function start_new_row($day) {
$html = '';
$current_day_of_week = date('w', mktime(0, 0, 0, $this->intSelectedMonth, $day, $this->intSelectedYear)); if (1 < $day && $this->intFirstDayOfWeek == $current_day_of_week )
{
$html .= '</tr><tr>';
}
return $html;
}
/**
* Checks if 'first day of week' is not equal to weekday for first day of month
* then outputs empty TDs
*
* @since 0.1
*
* @return string $html
*/
private function get_the_empty_days_before() {
$html = '';
if ($this->intFirstDayOfWeek != $this->_TheDays['Weekday For First Day Of Month'])
{
$totalEmptyDays = ($this->_TheDays['Weekday For First Day Of Month'] - $this->intFirstDayOfWeek); // Calculate total empty days
if ($this->intFirstDayOfWeek > $this->_TheDays['Weekday For First Day Of Month']) // If 1st day of week > weekday for 1st day of month, +7 days to empty days
{
$totalEmptyDays += 7;
}
for ($i = 0; $i < $totalEmptyDays; $i++) // Empty TDs if 1st day of month doesn't start on '1st day of week'
{
$html .= '<td class="cal-off"> </td>';
}
}
return $html;
}
/**
* Checks if 'last day of week' is not equal to weekday for last day of month
* then outputs empty TDs
*
* @since 0.1
*
* @return string $html
*/
private function get_the_empty_days_after() {
$weekdayForLastDayOfMonth = date('w', mktime(0, 0, 0, $this->intSelectedMonth, $this->_TheDays['Total Days In Month'], $this->intSelectedYear));
$totalEmptyDays = ($this->intFirstDayOfWeek - $weekdayForLastDayOfMonth - 1); // Calculate total empty days
if ($this->intFirstDayOfWeek <= $weekdayForLastDayOfMonth)
{
$totalEmptyDays += 7;
}
$html = '';
for ($i = 0; $i < $totalEmptyDays; $i++)
{
$html .= '<td class="cal-off"> </td>';
}
return $html;
}
/**
* Prints the day numbers into each table cell
*
* @since 0.1
*
* @return string $html
*/
private function write_day_numbers($day) {
$html = '';
$html .= '<a class="day">' . $day . '</a>';
return $html;
}
/**
* If any post(s) for current day in current month/year then display them
*
* @since 0.1
*
* @return string $html
*/
private function get_day_activities($loop, $day) {
$html = '';
$numberofcolumns = 1; $count = $numberofcolumns;
while ($loop->have_posts()) : $loop->the_post();
$start_date = get_field($this->strStartTimeACF);
$start_date_int = $this->convert_the_date($start_date);
$current_date_int = $this->_TheDays['Current Date Int'];
$cal_date = $this->intSelectedYear.'-'.$this->intSelectedMonth.'-'.$day;
$cal_date_int = $this->convert_the_date($cal_date);
if ($cal_date_int == $start_date_int)
{
$html .= '<a href="' . get_permalink() . '" class="hasevent">';
$html .= '<p style="padding: 0; margin: 0; ">x</p>';
$html .= '</a>';
}
elseif ($cal_date_int == $current_date_int) {
$html .= '<a name="istoday" class="istoday"></a>';
}
endwhile;
return $html;
}
/**
* Outputs HTML before the table.
*/
private function do_before_the_table() {
$html = $this->_Structure['Start Cal Div'];
$html .= $this->_Structure['Start Table'];
return $html;
}
/**
* Outputs the Table Header.
*/
private function do_the_table_header() {
$html = $this->_Structure['Start Table Header'];
$html .= $this->get_the_table_header();
$html .= $this->_Structure['End Table Header'];
return $html;
}
/**
* Outputs the Table Caption.
*/
private function do_the_table_caption() {
$html = $this->_Structure['Start Table Caption'];
$html .= $this->get_the_table_caption();
$html .= $this->_Structure['End Table Caption'];
return $html;
}
/**
* Outputs the Table Body.
*/
private function do_the_table_body($loop) {
$html = $this->_Structure['Start Table Body'];
$html .= $this->get_the_empty_days_before();
for ($day = 1; $day <= $this->_TheDays['Total Days In Month']; $day++)
{
$html .= $this->start_new_row($day);
$html .= $this->_Structure['Start Table Column'];
$html .= $this->write_day_numbers($day);
$html .= $this->get_day_activities($loop,$day);
$html .= $this->_Structure['End Table Column'];
}
$html .= $this->get_the_empty_days_after();
return $html;
}
/**
* Outputs HTML after the table.
*/
private function do_after_the_table() {
$html = $this->_Structure['End Table Row'];
$html .= $this->_Structure['End Table Body'];
$html .= $this->_Structure['End Table'];
$html .= $this->_Structure['End Cal Div'];
return $html;
}
/**
* Outputs the HTML.
*/
private function make_the_output() {
$this->define_week_days();
$this->define_day_codes();
$this->define_html_structure();
$loop = $this->do_the_query();
if ($loop->have_posts()) {
$html = $this->do_before_the_table();
$html .= $this->do_the_table_header();
$html .= $this->do_the_table_caption();
$html .= $this->do_the_table_body($loop);
$html .= $this->do_after_the_table();
}
return $html;
}
public function set_the_date($month,$year) {
$this->intSelectedYear = (int) $year;
$this->intSelectedMonth = (int) $month;
}
public function printcalendar($month,$year) {
$this->intSelectedYear = (int) $year;
$this->intSelectedMonth = (int) $month;
echo $this->make_the_output();
}
} // end class
?>
jlv-cpt-calendar-scriptinit.php
<?php
/*
*/
class styleloader {
/**
* Register and enqueue style sheet.
*
* @since 0.1
*/
public function register_plugin_styles() {
wp_register_style( 'jlv_cpt_calendar_style', JLV_CPT_CAL_URL . 'jlv-cpt-calendar.css' );
wp_enqueue_style( 'jlv_cpt_calendar_style' );
}
/**
* Register and enqueue scripts.
*
* @since 0.1
*/
public function register_plugin_scripts() {
wp_register_script( 'jlv_cpt_calendar_script', JLV_CPT_CAL_URL . 'jlv-cpt-calendar.js', array( 'jquery' ) );
wp_enqueue_script( 'jlv_cpt_calendar_script' );
}
/**
* Add style and script actions.
*
* @since 0.1
*/
private function add_plugin_actions() {
add_action( 'wp_enqueue_scripts', array($this, 'register_plugin_styles') );
add_action( 'wp_enqueue_scripts', array($this, 'register_plugin_scripts') );
}
}
$loadstyles = new styleloader();
$loadstyles->register_plugin_styles();
$loadstyles->register_plugin_scripts();
?>
jlv-cpt-calendar-widget.php
<?php
/*
* @since 0.3
*/
class jlv_cpt_acf_cal_widget extends WP_Widget {
// constructor
function jlv_cpt_acf_cal_widget() {
parent::WP_Widget(false, $name = __('Activity Calendar', 'jlv_cpt_acf_widget') );
}
// widget form creation
function form($instance) {
// Check values
if ( $instance) {
$year = (int) esc_attr($instance['year']);
} else {
$year = 2013;
}
if ( $instance) {
$month = (int) esc_attr($instance['month']);
} else {
$month = 1;
}
?>
<p>
<label for="<?php echo $this->get_field_id('year'); ?>"><?php _e('Year:', 'jlv_cpt_acf_widget'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('year'); ?>" name="<?php echo $this->get_field_name('year'); ?>" type="text" value="<?php echo $year; ?>" />
<label for="<?php echo $this->get_field_id('month'); ?>"><?php _e('Month', 'jlv_cpt_acf_widget'); ?></label>
<select name="<?php echo $this->get_field_name('month'); ?>" id="<?php echo $this->get_field_id('month'); ?>" class="widefat">
<?php
$options = array('Jan' => 1, 'Feb'=> 2, 'Mar' => 3, 'Apr' => 4, 'May' => 5, 'Jun' => 6, 'Jul' => 7, 'Aug' => 8, 'Sep' => 9, 'Oct' => 10, 'Nov' => 11, 'Dec' => 12);
foreach ($options as $key => $value) {
echo '<option value="' . $value . '" id="' . $key . '"', $month == $value ? ' selected="selected"' : '', '>', $key, '</option>';
}
?>
</select>
</p>
<?php
}
// update widget
function update($new_instance, $old_instance) {
$instance = $old_instance;
// Fields
$instance['year'] = strip_tags($new_instance['year']);
$instance['month'] = strip_tags($new_instance['month']);
return $instance;
}
// display widget
function widget($args, $instance) {
//include_once('google-apps-link.php');
extract( $args );
// these are the widget options
$year = $instance['year'];
$month = $instance['month'];
echo $before_widget;
// Display the widget
$jlv_cpt_calendar = new jlv_cpt_calendar();
$jlv_cpt_calendar->printcalendar($month,$year);
echo $after_widget;
}
}
// register widget
add_action('widgets_init', create_function('', 'return register_widget("jlv_cpt_acf_cal_widget");'));
?>
The JS
jQuery(document).ready(function(){
jQuery("a.hasevent").parent().addClass("cal-check");
jQuery("a.hasevent").parent().children(':first-child').addClass("haschildevent");
jQuery("a.istoday").parent().addClass("cal-today");
jQuery('td.cal-check').click(function(){
var thisHref = jQuery(this).find('a.hasevent').attr('href');
location.href = thisHref;
});
});
The CSS
.cal div, .cal span, .cal p, .cal a, .cal table, .cal tbody, .cal tfoot, .cal thead, .cal tr, .cal th, .cal td
{
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
line-height: 1;
font: 13px/20px 'Lucida Grande', Tahoma, Verdana, sans-serif;
}
.cal table {
border-collapse: collapse;
border-spacing: 0;
}
.cal {
position: relative;
padding: 4px;
font-weight: bold;
background: #bebfc0;
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
display: inline-block;
vertical-align: baseline;
zoom: 1;
*display: inline;
*vertical-align: auto;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2), 0 1px rgba(255, 255, 255, 0.4);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2), 0 1px rgba(255, 255, 255, 0.4);
}
.cal:before {
content: '';
position: absolute;
bottom: 3px;
left: 4px;
right: 4px;
height: 6px;
background: #d9d9d9;
border: 1px solid #909090;
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.cal a {
text-decoration: none;
}
.cal tr:first-child td {
border-top: 0;
}
.cal td:first-child {
border-left: 0;
}
.cal tr:first-child a {
border-top: 0;
margin-top: 0;
}
.cal tr:last-child a {
border-bottom: 0;
margin-bottom: 0;
}
.cal td:first-child a {
border-left: 0;
margin-left: 0;
}
.cal td:last-child a {
border-right: 0;
margin-right: 0;
}
.cal tr:last-child td:first-child a {
border-radius: 0 0 0 3px;
}
.cal tr:last-child td:last-child a {
border-radius: 0 0 3px 0;
}
.cal-table {
position: relative;
margin: 0 0 1px;
border-collapse: separate;
border-left: 1px solid #979797;
border-right: 1px solid #979797;
border-bottom: 1px solid #bbb;
border-radius: 0 0 3px 3px;
-webkit-box-shadow: 1px 0 rgba(0, 0, 0, 0.1), -1px 0 rgba(0, 0, 0, 0.1);
box-shadow: 1px 0 rgba(0, 0, 0, 0.1), -1px 0 rgba(0, 0, 0, 0.1);
}
.cal-caption {
width: 100%;
padding-bottom: 1px;
line-height: 32px;
color: white;
text-align: center;
text-shadow: 0 -1px rgba(0, 0, 0, 0.3);
background: #155382;
border-radius: 3px 3px 0 0;
background-image: -webkit-linear-gradient(top, #358ab4, #155382 75%, #0e4273);
background-image: -moz-linear-gradient(top, #358ab4, #155382 75%, #0e4273);
background-image: -o-linear-gradient(top, #358ab4, #155382 75%, #0e4273);
background-image: linear-gradient(to bottom, #358ab4, #155382 75%, #0e4273);
-webkit-box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2), inset 0 1px rgba(0, 0, 0, 0.1), inset 0 2px rgba(255, 255, 255, 0.25), 0 1px 3px rgba(0, 0, 0, 0.4);
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2), inset 0 1px rgba(0, 0, 0, 0.1), inset 0 2px rgba(255, 255, 255, 0.25), 0 1px 3px rgba(0, 0, 0, 0.4);
}
.cal-caption a {
line-height: 30px;
padding: 0 10px;
font-size: 20px;
font-weight: normal;
color: white;
}
.cal-caption .prev {
float: left;
}
.cal-caption .next {
float: right;
}
.cal-body td {
width: 45px;
font-size: 11px;
border-top: 1px solid #eaeaea;
border-left: 1px solid #eaeaea;
}
.cal-body a.day {
display: block;
position: relative;
line-height: 28px;
color: #555;
text-align: center;
background: white; cursor: default;
}
.cal-body a:hover {
background: #fafafa;
}
.cal-body a.hasevent {
float: left;
position: relative;
display: none;
}
.cal-off {
background-color: #eee;
}
.cal-off a {
color: #ccc;
font-weight: normal;
}
.cal-today a.day {
color: black;
background: #f5f5f5;
background-image: -webkit-linear-gradient(top, whitesmoke, white 70%);
background-image: -moz-linear-gradient(top, whitesmoke, white 70%);
background-image: -o-linear-gradient(top, whitesmoke, white 70%);
background-image: linear-gradient(to bottom, whitesmoke, white 70%);
}
.cal-selected a, .cal-body a:active {
margin: -1px;
color: #b2494d;
background: #fff5f6;
border: 1px solid #e7d4d4;
}
.cal-check a.event, .haschildevent {
color: #f79901;
overflow: hidden; cursor: pointer!important; cursor: hand!important;
}
.cal-check a.event:before, .haschildevent:before {
content: '';
position: absolute;
top: -6px;
right: -6px;
width: 12px;
height: 12px;
background: #ffb83b;
background-image: -webkit-linear-gradient(top, #ffb83b, #ff6c00);
background-image: -moz-linear-gradient(top, #ffb83b, #ff6c00);
background-image: -o-linear-gradient(top, #ffb83b, #ff6c00);
background-image: linear-gradient(to bottom, #ffb83b, #ff6c00);
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.lt-ie8 .cal-table {
*border-collapse: collapse;
}
.lt-ie8 .cal-body a {
zoom: 1;
}
The CSS is adapted from Mini Calendar
Display the calendar by placing the widget in a sidebar, where you can select the month and year, or directly in a template using:
<?php $jlv_cpt_calendar->printcalendar(12,2013); ?>
where 12 is the month and 2013 is the year