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
1 Comment
Add Yours →For anyone still looking for a soluion, i have created a calendar plugin that works with custom post type and uses ACF custom_date to display the articles. If you need it, email me at : vivi@spraystudio.ro, and i can give it to you.
Right now i am trying to clean it a bit, and will be uploading it to wordpress repository asap.