Your IP : 216.73.217.143


Current Path : /home/ereika83/www/wp-content/plugins/edit-flow/modules/calendar/
Upload File :
Current File : /home/ereika83/www/wp-content/plugins/edit-flow/modules/calendar/calendar.php

<?php
/**
 * class EF_Calendar
 * This class displays an editorial calendar for viewing upcoming and past content at a glance
 *
 * @author danielbachhuber
 */
if ( !class_exists('EF_Calendar') ) {

class EF_Calendar extends EF_Module {
	
	const usermeta_key_prefix = 'ef_calendar_';
	const screen_id = 'dashboard_page_calendar';
	
	var $module;
	
	var $start_date = '';
	var $current_week = 1;
	var $total_weeks = 6; // default number of weeks to show per screen
	var $hidden = 0; // counter of hidden posts per date square
	var $max_visible_posts_per_date = 4; // total number of posts to be shown per square before 'more' link

	private $post_date_cache = array();
	private static $post_li_html_cache_key = 'ef_calendar_post_li_html';

	/**
	 * Calendar published statuses are the same as other 
	 * components but without the future
	 */
	public $published_statuses = array(
		'publish',
		'private',
	);

	/**
	 * Construct the EF_Calendar class
	 */
	function __construct() {
		$this->max_weeks = 12;
	
		$this->module_url = $this->get_module_url( __FILE__ );
		// Register the module with Edit Flow
		$args = array(
			'title' => __( 'Calendar', 'edit-flow' ),
			'short_description' => sprintf( __( 'View upcoming content in a <a href="%s">customizable calendar</a>.', 'edit-flow' ), admin_url( 'index.php?page=calendar' ) ),
			'extended_description' => __( 'Edit Flow’s calendar lets you see your posts over a customizable date range. Filter by status or click on the post title to see its details. Drag and drop posts between days to change their publication date.', 'edit-flow' ),
			'module_url' => $this->module_url,
			'img_url' => $this->module_url . 'lib/calendar_s128.png',
			'slug' => 'calendar',
			'post_type_support' => 'ef_calendar',
			'default_options' => array(
				'enabled' => 'on',
				'post_types' => array(
					'post' => 'on',
					'page' => 'off',
				),
				'quick_create_post_type' => 'post',
				'ics_subscription' => 'off',
				'ics_secret_key' => '',
			),
			'messages' => array(
				'post-date-updated' => __( "Post date updated.", 'edit-flow' ),
				'update-error' => __( 'There was an error updating the post. Please try again.', 'edit-flow' ),
				'published-post-ajax' => __( "Updating the post date dynamically doesn't work for published content. Please <a href='%s'>edit the post</a>.", 'edit-flow' ),
				'key-regenerated' => __( 'iCal secret key regenerated. Please inform all users they will need to resubscribe.', 'edit-flow' ),
			),
			'configure_page_cb' => 'print_configure_view',
			'configure_link_text' => __( 'Calendar Options', 'edit-flow' ),
			'settings_help_tab' => array(
				'id' => 'ef-calendar-overview',
				'title' => __('Overview', 'edit-flow'),
				'content' => __('<p>The calendar is a convenient week-by-week or month-by-month view into your content. Quickly see which stories are on track to being published on time, and which will need extra effort.</p>', 'edit-flow'),
				),
			'settings_help_sidebar' => __( '<p><strong>For more information:</strong></p><p><a href="http://editflow.org/features/calendar/">Calendar Documentation</a></p><p><a href="http://wordpress.org/tags/edit-flow?forum_id=10">Edit Flow Forum</a></p><p><a href="https://github.com/danielbachhuber/Edit-Flow">Edit Flow on Github</a></p>', 'edit-flow' ),
		);
		$this->module = EditFlow()->register_module( 'calendar', $args );		
		
	}
	
	/**
	 * Initialize all of our methods and such. Only runs if the module is active
	 *
	 * @uses add_action()
	 */
	function init() {
		
		// .ics calendar subscriptions
		add_action( 'wp_ajax_ef_calendar_ics_subscription', array( $this, 'handle_ics_subscription' ) );
		add_action( 'wp_ajax_nopriv_ef_calendar_ics_subscription', array( $this, 'handle_ics_subscription' ) );

		// Check whether the user should have the ability to view the calendar
		$view_calendar_cap = 'ef_view_calendar';
		$view_calendar_cap = apply_filters( 'ef_view_calendar_cap', $view_calendar_cap );
		if ( !current_user_can( $view_calendar_cap ) ) return false;

		// Define the create-post capability
		$this->create_post_cap = apply_filters( 'ef_calendar_create_post_cap', 'edit_posts' );

		add_action( 'admin_init', array( $this, 'add_screen_options_panel' ) );
		add_action( 'admin_init', array( $this, 'handle_save_screen_options' ) );
		
		add_action( 'admin_init', array( $this, 'register_settings' ) );
		add_action( 'admin_menu', array( $this, 'action_admin_menu' ) );
		add_action( 'admin_print_styles', array( $this, 'add_admin_styles' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
		
		// Ajax manipulation for the calendar
		add_action( 'wp_ajax_ef_calendar_drag_and_drop', array( $this, 'handle_ajax_drag_and_drop' ) );
		
		// Ajax insert post placeholder for a specific date
		add_action( 'wp_ajax_ef_insert_post', array( $this, 'handle_ajax_insert_post' ) );

		//Update metadata
		add_action( 'wp_ajax_ef_calendar_update_metadata', array( $this, 'handle_ajax_update_metadata' ) );

		// Clear li cache for a post when post cache is cleared
		add_action( 'clean_post_cache', array( $this, 'action_clean_li_html_cache' ) );

		// Action to regenerate the calendar feed sekret
		add_action( 'admin_init', array( $this, 'handle_regenerate_calendar_feed_secret' ) );

		// Hacks to fix deficiencies in core
		add_action( 'pre_post_update', array( $this, 'fix_post_date_on_update_part_one' ), 10, 2 );
		add_action( 'post_updated', array( $this, 'fix_post_date_on_update_part_two' ), 10, 3 );
	}
	
	/**
	 * Load the capabilities onto users the first time the module is run
	 *
	 * @since 0.7
	 */
	function install() {

		// Add necessary capabilities to allow management of calendar
		// view_calendar - administrator --> contributor
		$calendar_roles = array(
			'administrator' => array('ef_view_calendar'),
			'editor' =>        array('ef_view_calendar'),
			'author' =>        array('ef_view_calendar'),
			'contributor' =>   array('ef_view_calendar')
		);

		foreach ( $calendar_roles as $role => $caps ) {
			$this->add_caps_to_role( $role, $caps );
		}
	}

	/**
	 * Upgrade our data in case we need to
	 *
	 * @since 0.7
	 */
	function upgrade( $previous_version ) {
		global $edit_flow;

		// Upgrade path to v0.7
		if ( version_compare( $previous_version, '0.7' , '<' ) ) {
			// Migrate whether the calendar was enabled or not and clean up old option
			if ( $enabled = get_option( 'edit_flow_calendar_enabled' ) )
				$enabled = 'on';
			else
				$enabled = 'off';
			$edit_flow->update_module_option( $this->module->name, 'enabled', $enabled );
			delete_option( 'edit_flow_calendar_enabled' );

			// Technically we've run this code before so we don't want to auto-install new data
			$edit_flow->update_module_option( $this->module->name, 'loaded_once', true );
		}
		
	}
	
	/**
	 * Add the calendar link underneath the "Dashboard"
	 *
	 * @uses add_submenu_page
	 */
	function action_admin_menu() {	
		add_submenu_page('index.php', __('Calendar', 'edit-flow'), __('Calendar', 'edit-flow'), apply_filters( 'ef_view_calendar_cap', 'ef_view_calendar' ), $this->module->slug, array( $this, 'view_calendar' ) );
	}
	
	/**
	 * Add any necessary CSS to the WordPress admin
	 *
	 * @uses wp_enqueue_style()
	 */
	function add_admin_styles() {
		global $pagenow;
		// Only load calendar styles on the calendar page
		if ( 'index.php' === $pagenow && isset( $_GET['page'] ) && 'calendar' === $_GET['page'] ) {
			wp_enqueue_style( 'edit-flow-calendar-css', $this->module_url . 'lib/calendar.css', false, EDIT_FLOW_VERSION );
			wp_enqueue_style( 'edit-flow-calendar-react-css', $this->module_url . 'lib/dist/calendar.react.style.build.css', array( 'wp-components' ), EDIT_FLOW_VERSION );
		}
	}
	
	/**
	 * Add any necessary JS to the WordPress admin
	 *
	 * @since 0.7
	 * @uses wp_enqueue_script()
	 */
	function enqueue_admin_scripts() {
		global $pagenow;
		
		if ( 'index.php' === $pagenow && isset( $_GET['page'] ) && 'calendar' === $_GET['page'] ) {
			$this->enqueue_datepicker_resources();

			$js_libraries = array(
				'jquery',
				'jquery-ui-core',
				'jquery-ui-sortable',
				'jquery-ui-draggable',
				'jquery-ui-droppable',
				'wp-data'
			);
			foreach( $js_libraries as $js_library ) {
				wp_enqueue_script( $js_library );
			}
			wp_enqueue_script( 'edit-flow-calendar-js', $this->module_url . 'lib/calendar.js', $js_libraries, EDIT_FLOW_VERSION, true );
			
			$ef_cal_js_params = array( 'can_add_posts' => current_user_can( $this->create_post_cap ) ? 'true' : 'false' );
			wp_localize_script( 'edit-flow-calendar-js', 'ef_calendar_params', $ef_cal_js_params );

			/**
			 * Powering the new React interface
			 */
			wp_enqueue_script( 'edit-flow-calendar-react-js', $this->module_url . 'lib/dist/calendar.react.build.js', array( 'react', 'react-dom', 'wp-components', 'wp-url', 'wp-data', 'moment' ), EDIT_FLOW_VERSION, true );
			
			wp_add_inline_script(
				'edit-flow-calendar-react-js',
				'var EF_CALENDAR = ' . wp_json_encode( $this->get_calendar_frontend_config() ),
				'before'
			);
		}
		
	}
	
	/**
	 * Prepare the options that need to appear in Screen Options
	 *
	 * @since 0.7
	 */ 
	function generate_screen_options() {
		
		$output = '';
		
		$args = array(
				'action'       => 'ef_calendar_ics_subscription',
				'user'         => wp_get_current_user()->user_login,
				'user_key'     => md5( wp_get_current_user()->user_login . $this->module->options->ics_secret_key ),
			);
		$subscription_link = add_query_arg( $args, admin_url( 'admin-ajax.php' ) );
		$output .= '<br />';
		$output .= __( 'Subscribe in iCal or Google Calendar', 'edit-flow' );
		$output .= ':<br /><input type="text" size="100" value="' . esc_attr( $subscription_link ) . '" />';
		
		return $output;	
	}
	
	/**
	 * Add module options to the screen panel
	 *
	 * @since 0.8.3
	 */
	function add_screen_options_panel() {
		require_once( EDIT_FLOW_ROOT . '/common/php/' . 'screen-options.php' );
		if ( 'on' == $this->module->options->ics_subscription && $this->module->options->ics_secret_key ) {
			add_screen_options_panel( self::usermeta_key_prefix . 'screen_options', __( 'Calendar Options', 'edit-flow' ), array( $this, 'generate_screen_options' ), self::screen_id, false, true );		
		}
	}
	
	/**
	 * Handle the request to save the screen options
	 *
	 * @since 0.7
	 */
	function handle_save_screen_options() {
		
		// Only handle screen options submissions from the current screen
		if ( ! isset( $_POST['screen-options-apply'] ) )
			return;
		
		// Nonce check
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		if ( !isset( $_POST['_wpnonce-' . self::usermeta_key_prefix . 'screen_options'] ) || !wp_verify_nonce ( $_POST['_wpnonce-' . self::usermeta_key_prefix . 'screen_options'], 'save_settings-' . self::usermeta_key_prefix . 'screen_options' ) ) {
			wp_die( esc_html( $this->module->messages['nonce-failed'] ) );
		}
		
		// Get the current screen options
		$screen_options = $this->get_screen_options();
		
		// Save the screen options
		$current_user = wp_get_current_user();
		$this->update_user_meta( $current_user->ID, self::usermeta_key_prefix . 'screen_options', $screen_options );
		
		// Redirect after we're complete
		$redirect_to = menu_page_url( $this->module->slug, false );
		wp_redirect( $redirect_to );
		exit;
	}
	
	/**
	 * Handle an AJAX request from the calendar to update a post's timestamp.
	 * Notes:
	 * - For Post Time, if the post is unpublished, the change sets the publication timestamp
	 * - If the post was published or scheduled for the future, the change will change the timestamp. 'publish' posts
	 * will become scheduled if moved past today and 'future' posts will be published if moved before today
	 * - Need to respect user permissions. Editors can move all, authors can move their own, and contributors can't move at all
	 *
	 * @since 0.7
	 */
	function handle_ajax_drag_and_drop() {
		global $wpdb;
		
		// Nonce check!
		if ( !isset( $_POST['nonce'] ) || !wp_verify_nonce( $_POST['nonce'], 'ef-calendar-modify' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] );
		}

		if ( !isset( $_POST['post_id'] ) ) {
			$this->print_ajax_response( 'error', $this->module->messages['missing-post'] );
		}	

		// Check that we got a proper post
		$post_id = (int) $_POST['post_id'];
		$post = get_post( $post_id );
		if ( !$post ) {
			$this->print_ajax_response( 'error', $this->module->messages['missing-post'] );
		}
			
		// Check that the user can modify the post
		if ( !$this->current_user_can_modify_post( $post ) ) {
			$this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );
		}
			
		// Check that it's not yet published
		if ( in_array( $post->post_status, $this->published_statuses ) ) {
			$this->print_ajax_response( 'error', sprintf( $this->module->messages['published-post-ajax'], get_edit_post_link( $post_id ) ) );
		}

		if ( !isset( $_POST['next_date'] ) ) {
			$this->print_ajax_response( 'error', __( 'Missing new date.', 'edit-flow' ) );
		}
		
		// Check that the new date passed is a valid one
		$next_date_full = strtotime( $_POST['next_date'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		if ( !$next_date_full ) {
			$this->print_ajax_response( 'error', __( 'Something is wrong with the format for the new date.', 'edit-flow' ) );
		}
		
		// Persist the old hourstamp because we can't manipulate the exact time on the calendar
		// Bump the last modified timestamps too
		$existing_time = date( 'H:i:s', strtotime( $post->post_date ) );
		$existing_time_gmt = date( 'H:i:s', strtotime( $post->post_date_gmt ) );
		$new_values = array(
			'post_date' => date( 'Y-m-d', $next_date_full ) . ' ' . $existing_time,
			'post_modified' => current_time( 'mysql' ),
			'post_modified_gmt' => current_time( 'mysql', 1 ),
		);
		
		// By default, changing a post on the calendar won't set the timestamp.
		// If the user desires that to be the behaviour, they can set the result of this filter to 'true'
		// With how WordPress works internally, setting 'post_date_gmt' will set the timestamp
		if ( apply_filters( 'ef_calendar_allow_ajax_to_set_timestamp', false ) ) {
			$new_values['post_date_gmt'] = date( 'Y-m-d', $next_date_full ) . ' ' . $existing_time_gmt;
		}
		
		// We have to do SQL unfortunately because of core bugginess
		// Note to those reading this: bug Nacin to allow us to finish the custom status API
		// See http://core.trac.wordpress.org/ticket/18362
		$response = $wpdb->update( $wpdb->posts, $new_values, array( 'ID' => $post->ID ) );
		clean_post_cache( $post->ID );
		
		if ( !$response ) {
			$this->print_ajax_response( 'error', $this->module->messages['update-error'] );
		}
		
		$this->print_ajax_response( 'success', $this->module->messages['post-date-updated'] );
		exit;
	}

	/**
	 * After checking that the request is valid, do an .ics file
	 *
	 * @since 0.8
	 */
	function handle_ics_subscription() {

		// Only do .ics subscriptions when the option is active
		if ( 'on' != $this->module->options->ics_subscription )
			die(); // @todo return accepted response value.

		// Confirm all of the arguments are present
		if ( ! isset( $_GET['user'], $_GET['user_key'] ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			die(); // @todo return an error response

		// Confirm this is a valid request
		$user = sanitize_user( $_GET['user'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$user_key = sanitize_user( $_GET['user_key'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$ics_secret_key = $this->module->options->ics_secret_key;
		if ( ! $ics_secret_key || md5( $user . $ics_secret_key ) !== $user_key ) {
			die( esc_html( $this->module->messages['nonce-failed'] ) );
		}

		// Set up the post data to be printed
		$post_query_args = array();
		$calendar_filters = $this->calendar_filters();
		foreach( $calendar_filters as $filter ) {
			if ( isset( $_GET[$filter] ) && false !== ( $value = $this->sanitize_filter( $filter, $_GET[$filter] ) ) ) //phpcs:ignore WordPress.Security.NonceVerification.Recommended
				$post_query_args[$filter] = $value;
		}

		// Set the start date for the posts_where filter
		$this->start_date = apply_filters( 'ef_calendar_ics_subscription_start_date', $this->get_beginning_of_week( date( 'Y-m-d', current_time( 'timestamp' ) ) ) );

		$this->total_weeks = apply_filters( 'ef_calendar_total_weeks', $this->total_weeks, 'ics_subscription' );

		$formatted_posts = array();
		for( $current_week = 1; $current_week <= $this->total_weeks; $current_week++ ) {
			// We need to set the object variable for our posts_where filter
			$this->current_week = $current_week;
			$week_posts = $this->get_calendar_posts_for_week( $post_query_args, 'ics_subscription' );
			foreach( $week_posts as $date => $day_posts ) {
				foreach( $day_posts as $num => $post ) {

					$start_date    = self::ics_format_time( $post->post_date );
					$end_date      = self::ics_format_time( $post->post_date, 5 * MINUTE_IN_SECONDS );
					$last_modified = self::ics_format_time( $post->post_modified );
					$post_status_obj = get_post_status_object( get_post_status( $post->ID ) );
					// Remove the convert chars and wptexturize filters from the title
					remove_filter( 'the_title', 'convert_chars' );
					remove_filter( 'the_title', 'wptexturize' );

					$formatted_post = array(
						'BEGIN'           => 'VEVENT',
						'UID'             => $post->guid,
						'SUMMARY'         => $this->do_ics_escaping( apply_filters( 'the_title', $post->post_title ) ) . ' - ' . $post_status_obj->label,
						'DTSTART'         => $start_date,
						'DTEND'           => $end_date,
						'LAST-MODIFIED'   => $last_modified,
						'URL'             => get_post_permalink( $post->ID ),
					);

					// Description should include everything visible in the calendar popup
					$information_fields = $this->get_post_information_fields( $post );
					$formatted_post['DESCRIPTION'] = '';
					if ( ! empty( $information_fields ) ) {
						foreach( $information_fields as $key => $values ) {
							$formatted_post['DESCRIPTION'] .= $values['label'] . ': ' . $values['value'] . '\n';
						}
						$formatted_post['DESCRIPTION'] = rtrim( $formatted_post['DESCRIPTION'] );
					}

					$formatted_post['END'] = 'VEVENT';

					// @todo auto format any field longer than 75 bytes

					$formatted_posts[] = $formatted_post;
				}
			}
		}

		// Other template data
		$header = array(
				'BEGIN'             => 'VCALENDAR',
				'VERSION'           => '2.0',
				'PRODID'            => '-//Edit Flow//Edit Flow ' . EDIT_FLOW_VERSION . '//EN',
			);

		$footer = array(
				'END'               => 'VCALENDAR',
			);

		// Render the .ics template and set the content type
		header( 'Content-type: text/calendar' );
		foreach( array( $header, $formatted_posts, $footer ) as $section ) {
			foreach( $section as $key => $value ) {
				/**
				 * This is output to text/calendar content-type
				 */
				if ( is_string( $value ) )
					echo $this->do_ics_line_folding( $key . ':' . $value ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				else
					foreach( $value as $k => $v ) {
						echo $this->do_ics_line_folding( $k . ':' . $v ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
					}
			}
		}
		die();

	}

	/**
	 * Perform line folding according to RFC 5545.
	 *
	 * @param string $line The line without trailing CRLF
	 * @return string The line after line-folding with all necessary CRLF.
	 */
	function do_ics_line_folding( $line ) {
		$len = mb_strlen( $line );
		if ( $len <= 75) {
			return $line . "\r\n";
		}

		$chunks = array();
		$start = 0;
		while( true ) {
			$chunk = mb_substr( $line, $start, 75 );
			$chunkLen = mb_strlen( $chunk );
			$start += $chunkLen;
			if ( $start < $len ) {
				$chunks[] = $chunk . "\r\n ";
			}
			else {
				$chunks[] = $chunk ."\r\n";
				return implode( "", $chunks );
			}
		}
	}

	/**
	 * Perform the encoding necessary for ICS feed text.
	 *
	 * @param string $text The string that needs to be escaped
	 * @return string The string after escaping for ICS.
	 * @since 0.8
	 * */

	function do_ics_escaping( $text ) {
		$text = str_replace( ",", "\,", $text );
		$text = str_replace( ";", "\:", $text );
		$text = str_replace( "\\", "\\\\", $text );
		return $text;
	}

	/**
	 * Convert a time string into a `.ics` formatted time string with the proper GMT offset
	 *
	 * @param     $time_string       - Any time string that `strtotime()` can understand
	 * @param int $offset_in_seconds - Allows to offset the timestamp generated from $time_string
	 *
	 * @return string|false
	 */
	public static function ics_format_time( $time_string, $offset_in_seconds = 0) {

		// Timestamp it
		$timestamp = strtotime( $time_string );

		if( ! $timestamp ) {
			return false;
		}

		// Subtract GMT Offset to return to UTC+0
		$timestamp -= get_option('gmt_offset') * HOUR_IN_SECONDS;

		// Add manual offset
		$timestamp += $offset_in_seconds;

		// \T and \Z are escaped for literal T and Z characters
		return date( 'Ymd\THis\Z', $timestamp );

	}

	/**
	 * Handle a request to regenerate the calendar feed secret
	 *
	 * @since 0.8
	 */
	public function handle_regenerate_calendar_feed_secret() {

		if ( ! isset( $_GET['action'] ) || 'ef_calendar_regenerate_calendar_feed_secret' != $_GET['action'] )
			return;

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html( $this->module->messages['invalid-permissions'] ) );
		}

		if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'ef-regenerate-ics-key' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			wp_die( esc_html( $this->module->messages['nonce-failed'] ) );
		}

		EditFlow()->update_module_option( $this->module->name, 'ics_secret_key', wp_generate_password() );

		wp_safe_redirect( add_query_arg( 'message', 'key-regenerated', menu_page_url( $this->module->settings_slug, false ) ) );
		exit;
	}
	
	/**
	 * Get a user's screen options
	 *
	 * @since 0.7
	 * @uses get_user_meta()
	 *
	 * @return array $screen_options The screen options values
	 */
	function get_screen_options() {
		
		/**
		 * `num_weeks` has been moved to a filter and out of screen options, it's maintained here for legacy purposes
		 * @deprecated `num_weeks`
		 */
		$defaults = array(
			'num_weeks' => (int)$this->total_weeks,
		);
		$current_user = wp_get_current_user();
		$screen_options = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'screen_options', true );
		$screen_options = array_merge( (array)$defaults, (array)$screen_options );
		
		return $screen_options;
	}
	
	/**
	 * Get the user's filters for calendar, either with $_GET or from saved
	 *
	 * @uses get_user_meta()
	 * @return array $filters All of the set or saved calendar filters
	 */
	function get_filters() {				
		$current_user 	= wp_get_current_user();
		$filters 		= array();
		$old_filters 	= $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', true );
		
		/**
		 * To support legacy screen option for num_weeks
		 */
		$screen_options = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'screen_options', true );

		$default_filters = array(
				'post_status' => '',
				'cpt' => '',
				'cat' => '',
				'author' => '',
				'num_weeks' => $this->total_weeks,
				'start_date' => date( 'Y-m-d', current_time( 'timestamp' ) ),
			);
		$old_filters = array_merge( $default_filters, isset( $screen_options['num_weeks'] ) ? array( 'num_weeks' => $screen_options['num_weeks'] ) : array(), (array)$old_filters );
		
		// Sanitize and validate any newly added filters
		foreach( $old_filters as $key => $old_value ) {
			if ( isset( $_GET[$key] ) && false !== ( $new_value = $this->sanitize_filter( $key, $_GET[$key] ) ) )
				$filters[$key] = $new_value;
			else
				$filters[$key] = $old_value;
		}

		// Set the start date as the beginning of the week, according to blog settings
		$filters['start_date'] = $this->get_beginning_of_week( $filters['start_date'] );

		$filters = apply_filters( 'ef_calendar_filter_values', $filters, $old_filters );
		
		$this->update_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', $filters );
		
		return $filters;
	}
	
	/**
	 * Build all of the HTML for the calendar view
	 */
	function view_calendar() {

		$this->dropdown_taxonomies = array();
		
		$supported_post_types = $this->get_post_types_for_module( $this->module );

		// Get filters either from $_GET or from user settings
		$filters = $this->get_filters();

		// Total number of weeks to display on the calendar. Run it through a filter in case we want to override the
		// user's standard
		$this->total_weeks = apply_filters( 'ef_calendar_total_weeks', $filters['num_weeks'], 'dashboard' );
		
		$dotw = array(
			'Sat',
			'Sun',
		);
		$dotw = apply_filters( 'ef_calendar_weekend_days', $dotw );
		
		// For generating the WP Query objects later on
		$post_query_args = array(
			'post_status' => $filters['post_status'],
			'post_type'   => $filters['cpt'],
			'cat'         => $filters['cat'],
			'author'      => $filters['author']
		);
		$this->start_date = $filters['start_date'];
		
		// We use this later to label posts if they need labeling
		if ( count( $supported_post_types ) > 1 ) {
			$all_post_types = get_post_types( null, 'objects' );
		}
		$dates = array();
		$heading_date = $filters['start_date'];
		for ( $i=0; $i<7; $i++ ) {
			$dates[$i] = $heading_date;
			$heading_date = date( 'Y-m-d', strtotime( "+1 day", strtotime( $heading_date ) ) );
		}
		
		// we sort by post statuses....... eventually
		$post_statuses = $this->get_calendar_post_stati();
		?>
		<div class="wrap">
			<div id="ef-calendar-title"><!-- Calendar Title -->
				<?php echo '<img src="' . esc_url( $this->module->img_url ) . '" class="module-icon icon32" />'; ?>
				<h2><?php esc_html_e( 'Calendar', 'edit-flow' ); ?>&nbsp;<span class="time-range"><?php $this->calendar_time_range(); ?></span></h2>
			</div><!-- /Calendar Title -->

			<?php
				// Handle posts that have been trashed or untrashed
				if ( isset( $_GET['trashed'] ) || isset( $_GET['untrashed'] ) ) {

					echo '<div id="trashed-message" class="updated"><p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
					if ( isset( $_GET['trashed'] ) && (int) $_GET['trashed'] ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
						echo esc_html( sprintf( _n( 'Post moved to the trash.', '%d posts moved to the trash.', $_GET['trashed'] ), number_format_i18n( $_GET['trashed'] ) ) );
						$ids = isset($_GET['ids']) ? $_GET['ids'] : 0;
						$pid = explode( ',', $ids );
						$post_type = get_post_type( $pid[0] );
						echo ' <a href="' . esc_url( wp_nonce_url( "edit.php?post_type=$post_type&doaction=undo&action=untrash&ids=$ids", "bulk-posts" ) ) . '">' . esc_html__( 'Undo', 'edit-flow' ) . '</a><br />';
						unset( $_GET['trashed'] );
					}
					if ( isset($_GET['untrashed'] ) && (int) $_GET['untrashed'] ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
						echo esc_html( sprintf( _n( 'Post restored from the Trash.', '%d posts restored from the Trash.', $_GET['untrashed'] ), number_format_i18n( $_GET['untrashed'] ) ) );
						unset( $_GET['undeleted'] );
					}
					echo '</p></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
				}
			?>

			<div id="ef-calendar-navigation-mount"></div> <!-- Mount point for React -->

			<div id="ef-calendar-wrap"><!-- Calendar Wrapper -->

			<?php
				$table_classes = array();
				// CSS don't like our classes to start with numbers
				if ( $this->total_weeks == 1 )
					$table_classes[] = 'one-week-showing';
				elseif ( $this->total_weeks == 2 )
					$table_classes[] = 'two-weeks-showing';
				elseif ( $this->total_weeks == 3 )
					$table_classes[] = 'three-weeks-showing';
					
				$table_classes = apply_filters( 'ef_calendar_table_classes', $table_classes );
			?>
			<table id="ef-calendar-view" class="<?php echo esc_attr( implode( ' ', $table_classes ) ); ?>">
				<thead>
				<tr class="calendar-heading">
					<?php echo $this->get_time_period_header( $dates ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> 
				</tr>
				</thead>
				<tbody>
				
				<?php
				$current_month = date_i18n( 'F', strtotime( $filters['start_date'] ) );
				for( $current_week = 1; $current_week <= $this->total_weeks; $current_week++ ):
					// We need to set the object variable for our posts_where filter
					$this->current_week = $current_week;
					$week_posts = $this->get_calendar_posts_for_week( $post_query_args );
					$date_format = 'Y-m-d';
					$week_single_date = $this->get_beginning_of_week( $filters['start_date'], $date_format, $current_week );
					$week_dates = array();
					$split_month = false;
					for ( $i = 0 ; $i < 7; $i++ ) {
						$week_dates[$i] = $week_single_date;
						$single_date_month = date_i18n( 'F', strtotime( $week_single_date ) );
						if ( $single_date_month != $current_month ) {
							$split_month = $single_date_month;
							$current_month = $single_date_month;
						}
						$week_single_date = date( 'Y-m-d', strtotime( "+1 day", strtotime( $week_single_date ) ) );
					}			
				?>
				<?php if ( $split_month ): ?>
				<tr class="month-marker">
					<?php foreach( $week_dates as $key => $week_single_date ) {
						if ( date_i18n( 'F', strtotime( $week_single_date ) ) != $split_month && date_i18n( 'F', strtotime( "+1 day", strtotime( $week_single_date ) ) ) == $split_month ) {
							$previous_month = date_i18n( 'F', strtotime( $week_single_date ) );
							echo '<td class="month-marker-previous">' . esc_html( $previous_month ) . '</td>';
						} else if ( date_i18n( 'F', strtotime( $week_single_date ) ) == $split_month && date_i18n( 'F', strtotime( "-1 day", strtotime( $week_single_date ) ) ) != $split_month ) {
							echo '<td class="month-marker-current">' . esc_html( $split_month ) . '</td>';
						} else {
							echo '<td class="month-marker-empty"></td>';
						}
					} ?>
				</tr>
				<?php endif; ?>

				<tr class="week-unit">
				<?php foreach( $week_dates as $day_num => $week_single_date ): ?>
				<?php
					// Somewhat ghetto way of sorting all of the day's posts by post status order
					if ( !empty( $week_posts[$week_single_date] ) ) {
						$week_posts_by_status = array();
						foreach( $post_statuses as $post_status ) {
							$week_posts_by_status[$post_status->name] = array();
						}
						// These statuses aren't handled by custom statuses or post statuses
						$week_posts_by_status['private'] = array();
						$week_posts_by_status['publish'] = array();
						$week_posts_by_status['future'] = array();
						foreach( $week_posts[$week_single_date] as $num => $post ) {
							$week_posts_by_status[$post->post_status][$num] = $post;
						}
						unset( $week_posts[$week_single_date] );
						foreach( $week_posts_by_status as $status ) {
							foreach( $status as $num => $post ) {
								$week_posts[$week_single_date][] = $post; 
							}
						}
					}
				
					$td_classes = array(
						'day-unit',
					);
					$day_name = date( 'D', strtotime( $week_single_date ) );
					
					if ( in_array( $day_name, $dotw ) )
						$td_classes[] = 'weekend-day';
					
					if ( $week_single_date == date( 'Y-m-d', current_time( 'timestamp' ) ) )
						$td_classes[] = 'today';
						
					// Last day of the week
					if ( $day_num == 6 )
						$td_classes[] = 'last-day';
						
					$td_classes = apply_filters( 'ef_calendar_table_td_classes', $td_classes, $week_single_date );
				?>
				<td class="<?php echo esc_attr( implode( ' ', $td_classes ) ); ?>" id="<?php echo esc_attr( $week_single_date ); ?>">
					<button class='schedule-new-post-button'>+</button>
					<?php if ( $week_single_date == date( 'Y-m-d', current_time( 'timestamp' ) ) ): ?>
						<div class="day-unit-today"><?php esc_html_e( 'Today', 'edit-flow' ); ?></div>
					<?php endif; ?>
					<div class="day-unit-label"><?php echo esc_html( date( 'j', strtotime( $week_single_date ) ) ); ?></div>
					<ul class="post-list">
						<?php
						$this->hidden = 0;
						if ( !empty( $week_posts[$week_single_date] ) ) {

							$week_posts[$week_single_date] = apply_filters( 'ef_calendar_posts_for_week', $week_posts[$week_single_date], $week_single_date );

							foreach ( $week_posts[$week_single_date] as $num => $post ) {
								$output = apply_filters( 'ef_pre_calendar_single_date_item_html', '', $this, $num, $post, $week_single_date );
								if ( ! $output ) {
									$output = $this->generate_post_li_html( $post, $week_single_date, $num );
								}
								echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
							} 

						 } 
						 ?>
					</ul>
					<?php if ( $this->hidden ): ?>
						<a class="show-more" href="#"><?php esc_html_e( sprintf( __( 'Show %d more', 'edit-flow' ), $this->hidden ) ); ?></a>
					<?php endif; ?>

					<?php if( current_user_can( $this->create_post_cap ) ) :
						$date_formatted = date( 'D, M jS, Y', strtotime( $week_single_date ) );
					?>

						<form method="POST" class="post-insert-dialog">
							<?php /* translators: %1$s = post type name, %2$s = date */ ?>
							<h1><?php esc_html_e( sprintf( __( 'Schedule a %1$s for %2$s', 'edit-flow' ), $this->get_quick_create_post_type_name(), $date_formatted ) ); ?></h1>	
							<?php /* translators: %s = post type name */ ?>
							<input type="text" class="post-insert-dialog-post-title" name="post-insert-dialog-post-title" placeholder="<?php echo esc_attr( sprintf( _x( '%s Title', 'post type name', 'edit-flow' ), $this->get_quick_create_post_type_name() ) ); ?>">
							<input type="hidden" class="post-insert-dialog-post-date" name="post-insert-dialog-post-title" value="<?php echo esc_attr( $week_single_date ); ?>">
							<div class="post-insert-dialog-controls">		
								<input type="submit" class="button left" value="<?php echo esc_attr( sprintf( _x( 'Create %s', 'post type name', 'edit-flow' ), $this->get_quick_create_post_type_name() ) ); ?>">
								<a class="post-insert-dialog-edit-post-link" href="#"><?php echo esc_attr( sprintf( _x( 'Edit %s', 'post type name', 'edit-flow' ), $this->get_quick_create_post_type_name() ) ); ?>&nbsp;&raquo;</a>
							</div>	
							<div class="spinner">&nbsp;</div>
						</form>
					<?php endif; ?>

					</td>
					<?php endforeach; ?>
					</tr>
					
					<?php endfor; ?>
					
					</tbody>		
					</table><!-- /Week Wrapper -->
					<?php
					// Nonce field for AJAX actions
					wp_nonce_field( 'ef-calendar-modify', 'ef-calendar-modify' ); ?>
					
					<div class="clear"></div>
				</div><!-- /Calendar Wrapper -->

			  </div>

		<?php 
		
	}

	/**
	 * Generates the HTML for a single post item in the calendar
	 * @param  obj $post The WordPress post in question
	 * @param  str $post_date The date of the post
	 * @param  int $num The index of the post
	 *
	 * @return str HTML for a single post item
	 */
	function generate_post_li_html( $post, $post_date, $num = 0 ){

		$can_modify = ( $this->current_user_can_modify_post( $post ) ) ? 'can_modify' : 'read_only';
		$cache_key = $post->ID . $can_modify . '_' . get_current_user_id();
		$cache_val = wp_cache_get( $cache_key, self::$post_li_html_cache_key );
		// Because $num is pertinent to the display of the post LI, need to make sure that's what's in cache
		if ( is_array( $cache_val ) && $cache_val['num'] == $num ) {
			$this->hidden = $cache_val['hidden'];
			return $cache_val['post_li_html'];
		}

		ob_start();
		$post_id = $post->ID;
		$edit_post_link = get_edit_post_link( $post_id );
		$status_object = get_post_status_object( get_post_status( $post_id ) );
		
		$post_classes = array(
			'day-item',
			'custom-status-' . $post->post_status,
		);
		// Only allow the user to drag the post if they have permissions to
		// or if it's in an approved post status
		// This is checked on the ajax request too.
		if ( $this->current_user_can_modify_post( $post ) && !in_array( $post->post_status, $this->published_statuses ) )
			$post_classes[] = 'sortable';
		
		if ( in_array( $post->post_status, $this->published_statuses ) )
			$post_classes[] = 'is-published';
		
		// Hide posts over a certain number to prevent clutter, unless user is only viewing 1 or 2 weeks
		$max_visible_posts = apply_filters( 'ef_calendar_max_visible_posts_per_date', $this->max_visible_posts_per_date);

		if ( $num >= $max_visible_posts && $this->total_weeks > 2 ) {
			$post_classes[] = 'hidden';
			$this->hidden++;
		}
		$post_classes = apply_filters( 'ef_calendar_table_td_li_classes', $post_classes, $post_date, $post->ID );
		
		?>
		<li class="<?php echo esc_attr( implode( ' ', $post_classes ) ); ?>" id="post-<?php echo esc_attr( $post->ID ); ?>">
			<div style="clear:right;"></div>
			<div class="item-static">
				<div class="item-default-visible">
					<div class="item-status"><span class="status-text"><?php echo esc_html( $status_object->label ); ?></span></div>
					<div class="inner">
						<span class="item-headline post-title"><strong><?php echo esc_html( _draft_or_post_title( $post->ID ) ); ?></strong></span>
					</div>
					<?php do_action( 'ef_calendar_item_html', $post->ID ); ?>
				</div>
				<div class="item-inner">
					<?php $this->get_inner_information( $this->get_post_information_fields( $post ), $post ); ?>
				</div>
			</div>
		</li>
		<?php

		$post_li_html = ob_get_contents();
		ob_end_clean();

		$post_li_cache = array(
			'num' => $num,
			'post_li_html' => $post_li_html,
			'hidden' => $this->hidden,
			);
		wp_cache_set( $cache_key, $post_li_cache, self::$post_li_html_cache_key );

		return $post_li_html;

	} // generate_post_li_html()

	/**
	 * get_inner_information description
	 * Functionality for generating the inner html elements on the calendar
	 * has been separated out so various ajax functions can reload certain
	 * parts of an inner html element.
	 * @param  array $ef_calendar_item_information_fields
	 * @param  WP_Post $post                               
	 * @param  array $published_statuses                 
	 * 
	 * @since 0.8
	 */
	function get_inner_information( $ef_calendar_item_information_fields, $post ) {
		?>
			<table class="item-information">
				<?php foreach( $this->get_post_information_fields( $post ) as $field => $values ): ?>
					<tr class="item-field item-information-<?php echo esc_attr( $field ); ?>">
						<th class="label"><?php echo esc_html( $values['label'] ); ?>:</th>
						<?php if ( $values['value'] && isset($values['type']) ): ?>
							<?php if( isset( $values['editable'] ) && $this->current_user_can_modify_post( $post ) ) : ?>
								<td class="value<?php if( $values['editable'] ) { ?> editable-value<?php } ?>"><?php echo esc_html( $values['value'] ); ?></td>
								<?php if( $values['editable'] ): ?>
									<td class="editable-html hidden" data-type="<?php echo esc_attr( $values['type'] ); ?>" data-metadataterm="<?php echo esc_attr( str_replace( 'editorial-metadata-', '', str_replace( 'tax_', '', $field ) ) ); ?>"><?php echo $this->get_editable_html( $values['type'], $values['value'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></td>
								<?php endif; ?>
							<?php else: ?>
								<td class="value"><?php echo esc_html( $values['value'] ); ?></td>
							<?php endif; ?>
						<?php elseif( $values['value'] ): ?>
							<td class="value"><?php echo esc_html( $values['value'] ); ?></td>
						<?php else: ?>
						<td class="value"><em class="none"><?php esc_html_e( 'None', 'edit-flow' ); ?></em></td>
						<?php endif; ?>
					</tr>
				<?php endforeach; ?>
				<?php do_action( 'ef_calendar_item_additional_html', $post->ID ); ?>
			</table>
			<?php
				$post_type_object = get_post_type_object( $post->post_type );
				$item_actions = array();
				if ( $this->current_user_can_modify_post( $post ) ) {
					// Edit this post
					$item_actions['edit'] = '<a href="' . get_edit_post_link( $post->ID, true ) . '" title="' . esc_attr( __( 'Edit this item', 'edit-flow' ) ) . '">' . __( 'Edit', 'edit-flow' ) . '</a>';
					// Trash this post
					$item_actions['trash'] = '<a href="'. get_delete_post_link( $post->ID) . '" title="' . esc_attr( __( 'Trash this item' ), 'edit-flow' ) . '">' . __( 'Trash', 'edit-flow' ) . '</a>';
					// Preview/view this post
					if ( !in_array( $post->post_status, $this->published_statuses ) ) {
						$item_actions['view'] = '<a href="' . esc_url( apply_filters( 'preview_post_link', add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ), $post ) ) . '" title="' . esc_attr( sprintf( __( 'Preview &#8220;%s&#8221;', 'edit-flow' ), $post->post_title ) ) . '" rel="permalink">' . __( 'Preview', 'edit-flow' ) . '</a>';
					} elseif ( 'trash' != $post->post_status ) {
						$item_actions['view'] = '<a href="' . get_permalink( $post->ID ) . '" title="' . esc_attr( sprintf( __( 'View &#8220;%s&#8221;', 'edit-flow' ), $post->post_title ) ) . '" rel="permalink">' . __( 'View', 'edit-flow' ) . '</a>';
					}
					//Save metadata
					$item_actions['save hidden'] = '<a href="#savemetadata" id="save-editorial-metadata" class="post-'. esc_attr( $post->ID ) .'" title="'. esc_attr( sprintf( __( 'Save &#8220;%s&#8221;', 'edit-flow' ), $post->post_title ) ) . '" >' . __( 'Save', 'edit-flow') . '</a>';
				}
				// Allow other plugins to add actions
				$item_actions = apply_filters( 'ef_calendar_item_actions', $item_actions, $post->ID );
				if ( count( $item_actions ) ) {
					echo '<div class="item-actions">';
					$html = '';
					foreach ( $item_actions as $class => $item_action ) {
						$html .= '<span class="' . esc_attr( $class ) . '">' . $item_action . ' | </span> '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
					}
					echo rtrim( $html, '| ' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
					echo '</div>';
				}
			?>
			<div style="clear:right;"></div>
		<?php

	} // generate_post_li_html()

	function get_editable_html( $type, $value ) {

		switch( $type ) {
			case 'text':
			case 'location':
			case 'number':
				return '<input type="text" class="metadata-edit-' . esc_attr( $type ) . '" value="' . esc_attr( $value ) . '"/>';
			break;
			case 'paragraph':
				return '<textarea type="text" class="metadata-edit-' . esc_attr( $type ) . '">' . esc_html( $value ) . '</textarea>';
			break;
			case 'date':
				return '<input type="text" value="' . esc_attr( $value ) . '" class="date-time-pick metadata-edit-' . esc_attr( $type ) . '"/>';
			break;
			case 'checkbox':
				$output = '<select class="metadata-edit">';
				
				if( $value == 'No' )
					$output .= '<option value="0">No</option><option value="1">Yes</option>';
				else
					$output .= '<option value="1">Yes</option><option value="0">No</option>';
				
				$output .= '</select>';

				return $output;
			break;
			case 'user':
				return wp_dropdown_users( array( 'echo' => false ) );
			break;
			case 'taxonomy':
				return '<input type="text" class="metadata-edit-' . esc_attr( $type ) . '" value="' . esc_attr( $value ) . '" />';
			break;
			case 'taxonomy hierarchical':
				return wp_dropdown_categories( array( 'echo' => 0, 'hide_empty' => 0 ) );
			break;
		}
	}

	/**
	 * Get the information fields to be presented with each post popup
	 *
	 * @since 0.8
	 *
	 * @param obj $post Post to gather information fields for
	 * @return array $information_fields All of the information fields to be presented
	 */
	function get_post_information_fields( $post ) {

		$information_fields = array();
		// Post author
		$information_fields['author'] = array(
			'label'        => __( 'Author', 'edit-flow' ),
			'value'        => get_the_author_meta( 'display_name', $post->post_author ),
			'type'         => 'author',
		);

		// If the calendar supports more than one post type, show the post type label
		if ( count( $this->get_post_types_for_module( $this->module ) ) > 1 ) {
			$information_fields['post_type'] = array(
				'label' => __( 'Post Type', 'edit-flow' ),
				'value' => get_post_type_object( $post->post_type )->labels->singular_name,
			);
		}
		// Publication time for published statuses
		$published_statuses = array(
			'publish',
			'future',
			'private',
		);
		if ( in_array( $post->post_status, $published_statuses ) ) {
			if ( $post->post_status == 'future' ) {
				$information_fields['post_date'] = array(
					'label' => __( 'Scheduled', 'edit-flow' ),
					'value' => get_the_time( null, $post->ID ),
				);
			} else {
				$information_fields['post_date'] = array(
					'label' => __( 'Published', 'edit-flow' ),
					'value' => get_the_time( null, $post->ID ),
				);
			}
		}
		// Taxonomies and their values
		$args = array(
			'post_type' => $post->post_type,
		);
		$taxonomies = get_object_taxonomies( $args, 'object' );
		foreach( (array)$taxonomies as $taxonomy ) {
			// Sometimes taxonomies skip by, so let's make sure it has a label too
			if ( !$taxonomy->public || !$taxonomy->label )
				continue;

			$terms = get_the_terms( $post->ID, $taxonomy->name );
			if ( ! $terms || is_wp_error( $terms ) )
				continue;

			$key = 'tax_' . $taxonomy->name;
			if ( count( $terms ) ) {
				$value = '';
				foreach( (array)$terms as $term ) {
					$value .= $term->name . ', ';
				}
				$value = rtrim( $value, ', ' );
			} else {
				$value = '';
			}
			 //Used when editing editorial metadata and post meta
			if ( is_taxonomy_hierarchical( $taxonomy->name ) )
				$type = 'taxonomy hierarchical';
			else
				$type = 'taxonomy';

			$information_fields[$key] = array(
				'label' => $taxonomy->label,
				'value' => $value,
				'type' => $type,
			);

			if( $post->post_type == 'page' )
				$ed_cap = 'edit_page';
			else
				$ed_cap = 'edit_post';

			if( current_user_can( $ed_cap, $post->ID ) )
				$information_fields[$key]['editable'] = true;
		}
		
		$information_fields = apply_filters( 'ef_calendar_item_information_fields', $information_fields, $post->ID );
		foreach( $information_fields as $field => $values ) {
			// Allow filters to hide empty fields or to hide any given individual field. Hide empty fields by default.
			if ( ( apply_filters( 'ef_calendar_hide_empty_item_information_fields', true, $post->ID ) && empty( $values['value'] ) )
					|| apply_filters( "ef_calendar_hide_{$field}_item_information_field", false, $post->ID ) )
				unset( $information_fields[$field] );
		}
		return $information_fields;
	}
	
	/**
	 * Generate the calendar header for a given range of dates
	 *
	 * @param array $dates Date range for the header
	 * @return string $html Generated HTML for the header
	 */
	function get_time_period_header( $dates ) {
		
		$html = '';
		foreach( $dates as $date ) {
			$html .= '<th class="column-heading" >';
			$html .= esc_html( date_i18n('l', strtotime( $date ) ) );
			$html .= '</th>';
		}
		
		return $html;
		
	}
		
	/**
	 * Query to get all of the calendar posts for a given day
	 *
	 * @param array $args Any filter arguments we want to pass
	 * @param string $request_context Where the query is coming from, to distinguish dashboard and subscriptions
	 * @return array $posts All of the posts as an array sorted by date
	 */
	function get_calendar_posts_for_week( $args = array(), $context = 'dashboard' ) {
		
		$supported_post_types = $this->get_post_types_for_module( $this->module );
		$defaults = array(
			'post_status'      => null,
			'cat'              => null,
			'author'           => null,
			'post_type'        => $supported_post_types,
			'posts_per_page'   => 200,
		);
						 
		$args = array_merge( $defaults, $args );
		
		// Unpublished as a status is just an array of everything but 'publish'.
		if ( 'unpublish' == $args['post_status'] ) {
			$args['post_status'] = '';
			$post_stati = wp_filter_object_list( $this->get_calendar_post_stati(), array( 'name' => 'publish' ), 'not' );

			if ( ! apply_filters( 'ef_show_scheduled_as_unpublished', false ) ) {
				$post_stati = wp_filter_object_list( $post_stati, array( 'name' => 'future' ), 'not' );
			}

			$args['post_status'] .= implode( ',', wp_list_pluck( $post_stati, 'name' ) );
		}
		// The WP functions for printing the category and author assign a value of 0 to the default
		// options, but passing this to the query is bad (trashed and auto-draft posts appear!), so
		// unset those arguments.
		if ( $args['cat'] === '0' ) {
			unset( $args['cat'] );
		}
		if ( $args['author'] === '0' ) {
			unset( $args['author'] );
		}

		if ( empty( $args['post_type'] ) || ! in_array( $args['post_type'], $supported_post_types ) ) {
			$args['post_type'] = $supported_post_types;
		}

		$beginning_date = $this->get_beginning_of_week( $this->start_date, 'Y-m-d', $this->current_week );
		$ending_date    = date( "Y-m-d", strtotime( $beginning_date ) + WEEK_IN_SECONDS );

		$args['date_query'] = array(
			'after'     => $beginning_date,
			'before'    => $ending_date,
			'inclusive' => true,
		);

		// Filter for an end user to implement any of their own query args
		$args = apply_filters( 'ef_calendar_posts_query_args', $args, $context );
		$post_results = new WP_Query( $args );

		$posts = array();
		while ( $post_results->have_posts() ) {
			$post_results->the_post();
			global $post;
			$key_date = date( 'Y-m-d', strtotime( $post->post_date ) );
			$posts[$key_date][] = $post;
		}
		
		return $posts;
		
	}

	/**
	 * Gets the link for the next time period
	 *
	 * @param string $direction 'previous' or 'next', direction to go in time
	 * @param array $filters Any filters that need to be applied
	 * @param int $weeks_offset Number of weeks we're offsetting the range	
	 * @return string $url The URL for the next page
	 */
	function get_pagination_link( $direction = 'next', $filters = array(), $weeks_offset = null ) {

		$supported_post_types = $this->get_post_types_for_module( $this->module );
		
		if ( !isset( $weeks_offset ) )
			$weeks_offset = $this->total_weeks;
		else if ( $weeks_offset == 0 )
			$filters['start_date'] = $this->get_beginning_of_week( date( 'Y-m-d', current_time( 'timestamp' ) ) );
			
		if ( $direction == 'previous' )
			$weeks_offset = '-' . $weeks_offset;
		
		$filters['start_date'] = date( 'Y-m-d', strtotime( $weeks_offset . " weeks", strtotime( $filters['start_date'] ) ) );
		$url = add_query_arg( $filters, menu_page_url( $this->module->slug, false ) );

		if ( count( $supported_post_types ) > 1 )
			$url = add_query_arg( 'cpt', $filters['cpt'] , $url );
		
		return $url;
		
	}
	
	/**
	 * Given a day in string format, returns the day at the beginning of that week, which can be the given date.
	 * The beginning of the week is determined by the blog option, 'start_of_week'.
	 *
	 * @see http://www.php.net/manual/en/datetime.formats.date.php for valid date formats
	 *
	 * @param string $date String representing a date
	 * @param string $format Date format in which the beginning of the week should be returned
	 * @param int $week Number of weeks we're offsetting the range	
	 * @return string $formatted_start_of_week Beginning of the week
	 */
	function get_beginning_of_week( $date, $format = 'Y-m-d', $week = 1 ) {

		$date = strtotime( $date );
		$start_of_week = get_option( 'start_of_week' );
		$day_of_week = date( 'w', $date );
		$date += (( $start_of_week - $day_of_week - 7 ) % 7) * 60 * 60 * 24 ;
		$date = strtotime ( '+' . ( $week - 1 ) . ' week', $date ) ;
                $formatted_start_of_week = date( $format, $date );
		return $formatted_start_of_week;
		
	}
	
	/**
	 * Given a day in string format, returns the day at the end of that week, which can be the given date.
	 * The end of the week is determined by the blog option, 'start_of_week'.
	 *
	 * @see http://www.php.net/manual/en/datetime.formats.date.php for valid date formats	
	 *
	 * @param string $date String representing a date
	 * @param string $format Date format in which the end of the week should be returned
	 * @param int $week Number of weeks we're offsetting the range		
	 * @return string $formatted_end_of_week End of the week
	 */
	function get_ending_of_week( $date, $format = 'Y-m-d', $week = 1  ) {
		
		$date = strtotime( $date );
		$end_of_week = get_option( 'start_of_week' ) - 1;
		$day_of_week = date( 'w', $date );
		$date += (( $end_of_week - $day_of_week + 7 ) % 7) * 60 * 60 * 24;
		$date = strtotime ( '+' . ( $week - 1 ) . ' week', $date ) ;
		$formatted_end_of_week = date( $format, $date );
		return $formatted_end_of_week;
		
	}
	
	/**
	 * Human-readable time range for the calendar
	 * Shows something like "for October 30th through November 26th" for a four-week period
	 *
	 * @since 0.7
	 */
	function calendar_time_range() {
		
		$first_datetime = strtotime( $this->start_date );
		$first_date = date_i18n( get_option( 'date_format' ), $first_datetime );
		$total_days = ( $this->total_weeks * 7 ) - 1;
		$last_datetime = strtotime( "+" . $total_days . " days", date( 'U', strtotime( $this->start_date ) ) );
		$last_date = date_i18n( get_option( 'date_format' ), $last_datetime );
		echo esc_html( sprintf( __( 'for %1$s through %2$s', 'edit-flow' ), $first_date, $last_date ) );
	}
	
	/**
	 * Check whether the current user should have the ability to modify the post
	 *
	 * @since 0.7
	 *
	 * @param object $post The post object we're checking
	 * @return bool $can Whether or not the current user can modify the post
	 */
	function current_user_can_modify_post( $post ) {
		
		if ( !$post )
			return false;
			
		$post_type_object = get_post_type_object( $post->post_type );

		// Editors and admins are fine
		if ( current_user_can( $post_type_object->cap->edit_others_posts, $post->ID ) )
			return true;
		// Authors and contributors can move their own stuff if it's not published
		if ( current_user_can( $post_type_object->cap->edit_post, $post->ID ) && wp_get_current_user()->ID == $post->post_author && !in_array( $post->post_status, $this->published_statuses ) )
			return true;
		// Those who can publish posts can move any of their own stuff
		if ( current_user_can( $post_type_object->cap->publish_posts, $post->ID ) && wp_get_current_user()->ID == $post->post_author )
			return true;
		
		return false;
	}
	
	/**
	 * Register settings for notifications so we can partially use the Settings API
	 * We use the Settings API for form generation, but not saving because we have our
	 * own way of handling the data.
	 * 
	 * @since 0.7
	 */
	function register_settings() {
		
			add_settings_section( $this->module->options_group_name . '_general', false, '__return_false', $this->module->options_group_name );
			add_settings_field( 'post_types', __( 'Post types to show', 'edit-flow' ), array( $this, 'settings_post_types_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' );
			add_settings_field( 'quick_create_post_type', __( 'Post type to create directly from calendar', 'edit-flow' ), array( $this, 'settings_quick_create_post_type_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' );
			add_settings_field( 'ics_subscription', __( 'Subscription in iCal or Google Calendar', 'edit-flow' ), array( $this, 'settings_ics_subscription_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' );

	}
	
	/**
	 * Choose the post types that should be displayed on the calendar
	 *
	 * @since 0.7
	 */
	function settings_post_types_option() {
		global $edit_flow;
		$edit_flow->settings->helper_option_custom_post_type( $this->module );
	}

	/**
	 * Choose the post type that should be created on the calendar
	 *
	 * @since 0.8
	 */
	function settings_quick_create_post_type_option() {
		
		$allowed_post_types = $this->get_all_post_types();

		echo "<select name='" . esc_attr( $this->module->options_group_name ) . "[quick_create_post_type]'>";
		foreach( $allowed_post_types as $post_type => $title ) 
			echo "<option value='" . esc_attr( $post_type ) . "' " . selected( $post_type, $this->module->options->quick_create_post_type, false ) . ">".esc_html( $title )."</option>";
		echo "</select>";

	}

	/**
	 * Enable calendar subscriptions via .ics in iCal or Google Calendar
	 *
	 * @since 0.8
	 */
	function settings_ics_subscription_option() {
		$options = array(
			'off'       => __( 'Disabled', 'edit-flow' ),
			'on'        => __( 'Enabled', 'edit-flow' ),
		);
		echo '<select id="ics_subscription" name="' . esc_attr( $this->module->options_group_name ) . '[ics_subscription]">';
		foreach ( $options as $value => $label ) {
			echo '<option value="' . esc_attr( $value ) . '"';
			echo selected( $this->module->options->ics_subscription, $value );
			echo '>' . esc_html( $label ) . '</option>';
		}
		echo '</select>';


		$regenerate_url = add_query_arg( 'action', 'ef_calendar_regenerate_calendar_feed_secret', admin_url( 'index.php' ) );
		$regenerate_url = wp_nonce_url( $regenerate_url, 'ef-regenerate-ics-key' );
		echo '&nbsp;&nbsp;&nbsp;<a href="' . esc_url( $regenerate_url ) . '">' . esc_html_e( 'Regenerate calendar feed secret', 'edit-flow' ) . '</a>';

		// If our secret key doesn't exist, create a new one
		if ( empty( $this->module->options->ics_secret_key ) )
			EditFlow()->update_module_option( $this->module->name, 'ics_secret_key', wp_generate_password() );
	}

	/**
	 * Validate the data submitted by the user in calendar settings
	 *
	 * @since 0.7
	 */
	function settings_validate( $new_options ) {

		$options = (array)$this->module->options;

		$options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support );

		if ( in_array( $new_options['quick_create_post_type'], array_keys( $this->get_all_post_types() ) ) )
			$options['quick_create_post_type'] = $new_options['quick_create_post_type'];

		if ( 'on' != $new_options['ics_subscription'] )
			$options['ics_subscription'] = 'off';
		else
			$options['ics_subscription'] = 'on';

		return $options;
	}
	
	/**
	 * Settings page for calendar
	 */
	function print_configure_view() {
		global $edit_flow;
		?>
		<form class="basic-settings" action="<?php echo esc_url( menu_page_url( $this->module->settings_slug, false ) ); ?>" method="post">
			<?php settings_fields( $this->module->options_group_name ); ?>
			<?php do_settings_sections( $this->module->options_group_name ); ?>	
			<?php				
				echo '<input id="edit_flow_module_name" name="edit_flow_module_name" type="hidden" value="' . esc_attr( $this->module->name ) . '" />';
			?>
			<p class="submit"><?php submit_button( null, 'primary', 'submit', false ); ?><a class="cancel-settings-link" href="<?php echo esc_url( EDIT_FLOW_SETTINGS_PAGE ); ?>"><?php esc_html_e( 'Back to Edit Flow', 'edit-flow' ); ?></a></p>
		</form>
		<?php
	}

	/**
	 * Ajax callback to insert a post placeholder for a particular date
	 *
	 * @since 0.8
	 */
	function handle_ajax_insert_post() {

		// Nonce check!
		if ( !isset( $_POST['nonce'] ) || !wp_verify_nonce( $_POST['nonce'], 'ef-calendar-modify' ) ) {
			$this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] );
		}

		// Check that the user has the right capabilities to add posts to the calendar (defaults to 'edit_posts')
		if ( !current_user_can( $this->create_post_cap ) )
			$this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );

		if ( empty( $_POST['ef_insert_date'] ) ) {
			$this->print_ajax_response( 'error', __( 'No date supplied.', 'edit-flow' ) );
		}

		// Post type has to be visible on the calendar to create a placeholder
		if ( ! in_array( $this->module->options->quick_create_post_type, $this->get_post_types_for_module( $this->module ) ) )
			$this->print_ajax_response( 'error', __( 'Please change Quick Create to use a post type viewable on the calendar.', 'edit-flow' ) );

		// Sanitize post values
		$post_title = isset( $_POST['ef_insert_title'] ) ? sanitize_text_field( $_POST['ef_insert_title'] ) : null;

		if( ! $post_title ) {
			$post_title = esc_html__( 'Untitled', 'edit-flow' );
		}

		$post_date = sanitize_text_field( $_POST['ef_insert_date'] );

		$post_status = $this->get_default_post_status();
		
		// Set new post parameters
		$post_placeholder = array(
			'post_title' => $post_title,
			'post_status' => $post_status,
			'post_date' => date( 'Y-m-d H:i:s', strtotime( $post_date ) ),
			'post_type' => $this->module->options->quick_create_post_type,
		);

		// By default, adding a post to the calendar won't set the timestamp.
		// If the user desires that to be the behavior, they can set the result of this filter to 'true'
		// With how WordPress works internally, setting 'post_date_gmt' will set the timestamp
		if ( apply_filters( 'ef_calendar_allow_ajax_to_set_timestamp', false ) )
			$post_placeholder['post_date_gmt'] = date( 'Y-m-d H:i:s', strtotime( $post_date ) );

		// Create the post
		$post_id = wp_insert_post( $post_placeholder );

		if( $post_id ) { // success!

			$post = get_post( $post_id );

			// Generate the HTML for the post item so it can be injected
			$post_li_html = $this->generate_post_li_html( $post, $post_date );

			// announce success and send back the html to inject
			$this->print_ajax_response( 'success', $post_li_html );

		} else {
			$this->print_ajax_response( 'error', __( 'Post could not be created', 'edit-flow' ) );
		}
	}

	/**
	 * Returns the singular label for the posts that are
	 * quick-created on the calendar
	 * 
	 * @return str Singular label for a post-type
	 */
	function get_quick_create_post_type_name(){

		$post_type_slug = $this->module->options->quick_create_post_type;
		$post_type_obj = get_post_type_object( $post_type_slug );

		return $post_type_obj->labels->singular_name ? $post_type_obj->labels->singular_name : $post_type_slug;
	}

	/**
	 * ajax_ef_calendar_update_metadata
	 * Update the metadata from the calendar.
	 * @return string representing the overlay
	 *
	 * @since 0.8
	 */
	function handle_ajax_update_metadata() {
		global $wpdb;

		if ( !isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'ef-calendar-modify' ) ) {
			$this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] );
		}

		if ( !isset( $_POST['post_id'] ) ) {
			$this->print_ajax_response( 'error', $this->module->messages['missing-post'] );
		}
		
		// Check that we got a proper post
		$post_id = ( int )$_POST['post_id'];
		$post = get_post( $post_id );

		if ( ! $post )
			$this->print_ajax_response( 'error', $this->module->messages['missing-post'] );


		if( $post->post_type == 'page' )
			$edit_check = 'edit_page';
		else
			$edit_check = 'edit_post';

		if ( !current_user_can( $edit_check, $post->ID ) )
			$this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );	
			
		// Check that the user can modify the post
		if ( ! $this->current_user_can_modify_post( $post ) )
			$this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );

		$default_types = array(
			'author',
			'taxonomy',
		);

		$metadata_types = array();

		if ( !$this->module_enabled( 'editorial_metadata' ) )
			$this->print_ajax_response( 'error', $this->module->messages['update-error'] );	
		
		$metadata_types = array_keys( EditFlow()->editorial_metadata->get_supported_metadata_types() );

		// Update an editorial metadata field
		$metadata_term = isset( $_POST['metadata_term'] ) ? $_POST['metadata_term'] : '';
		$metadata_type = isset( $_POST['metadata_type'] ) ? $_POST['metadata_type'] : '';
		$incoming_metadata_value = isset( $_POST['metadata_value'] ) ? $_POST['metadata_value'] : '';

		if ( isset( $_POST['metadata_type'] ) && in_array( $_POST['metadata_type'], $metadata_types ) ) {
			$post_meta_key = sanitize_text_field( '_ef_editorial_meta_' . $_POST['metadata_type'] . '_' . $metadata_term );

			//Javascript date parsing is terrible, so use strtotime in php
			if ( $metadata_type == 'date' )
				$metadata_value = strtotime( sanitize_text_field( $incoming_metadata_value ) );
			else
				$metadata_value = sanitize_text_field( $incoming_metadata_value );

			update_post_meta( $post->ID, $post_meta_key, $metadata_value );
			$response = 'success';
		} else {
			switch( $_POST['metadata_type'] ) {
				case 'taxonomy':
				case 'taxonomy hierarchical':
					$response = wp_set_post_terms( $post->ID, $incoming_metadata_value, $metadata_term );
				break;
				default:
					$response = new WP_Error( 'invalid-type', __( 'Invalid metadata type', 'edit-flow' ) );
				break;
			}
		}

		//Assuming we've got to this point, just regurgitate the value
		if ( ! is_wp_error( $response ) )
			$this->print_ajax_response( 'success', $incoming_metadata_value );
		else
			$this->print_ajax_response( 'error', __( 'Metadata could not be updated.', 'edit-flow' ) );
	}

	function calendar_filters() {		
		$select_filter_names = array();

		$select_filter_names['post_status'] = 'post_status';
		$select_filter_names['cat'] = 'cat';
		$select_filter_names['author'] = 'author';
		$select_filter_names['type'] = 'cpt';
		$select_filter_name['num_weeks'] = 'num_weeks';

		return apply_filters( 'ef_calendar_filter_names', $select_filter_names );
	}

	/**
	 * Sanitize a $_GET or similar filter being used on the calendar
	 *
	 * @since 0.8
	 *
	 * @param string $key Filter being sanitized
	 * @param string $dirty_value Value to be sanitized
	 * @return string $sanitized_value Safe to use value 
	 */
	function sanitize_filter( $key, $dirty_value ) {

		switch( $key ) {
			case 'post_status':
				// Whitelist-based validation for this parameter
				$valid_statuses = wp_list_pluck( $this->get_calendar_post_stati(), 'name' );
				$valid_statuses[] = 'unpublish';

				if ( in_array( $dirty_value, $valid_statuses ) )
					return $dirty_value;
				else
					return '';
				break;
			case 'cpt':
				$cpt = sanitize_key( $dirty_value );
				$supported_post_types = $this->get_post_types_for_module( $this->module );
				if ( $cpt && in_array( $cpt, $supported_post_types ) )
					return $cpt;
				else
					return '';
				break;
			case 'start_date':
				return date( 'Y-m-d', strtotime( $dirty_value ) );
				break;
			case 'cat':
			case 'author':
				return intval( $dirty_value );
				break;
			case 'num_weeks':
				$num_weeks = intval( $dirty_value );
				if ( $num_weeks <= 0 ) {
					return $this->total_weeks;
				} else if ( $num_weeks > $this->max_weeks ) {
					return $this->max_weeks;
				} else {
					return $num_weeks;
				}
			default:
				return false;
				break;
		}
	}

	/**
	 * When a post is updated, clean the <li> html post cache for it
	 */
	public function action_clean_li_html_cache( $post_id ) {

		wp_cache_delete( $post_id . 'can_modify', self::$post_li_html_cache_key );
		wp_cache_delete( $post_id . 'read_only', self::$post_li_html_cache_key );
	}

	/**
	 * This is a hack! hack! hack! until core is fixed
	 * 
	 * The calendar uses 'post_date' field to store the position on the calendar
	 * If a post has a core post status assigned (e.g. 'draft' or 'pending'), the `post_date`
	 * field will be reset when `wp_update_post()`
	 * is used: http://core.trac.wordpress.org/browser/tags/3.7.1/src/wp-includes/post.php#L2998
	 * 
	 * This method temporarily caches the `post_date` field if it needs to be restored.
	 * 
	 * @uses fix_post_date_on_update_part_two()
	 */
	public function fix_post_date_on_update_part_one( $post_ID, $data ) {

		$post = get_post( $post_ID );

		// `post_date` is only nooped for these three statuses,
		// but don't try to persist if `post_date_gmt` is set
		if ( ! in_array( $post->post_status, array( 'draft', 'pending', 'auto-draft' ) )
			|| '0000-00-00 00:00:00' !== $post->post_date_gmt
			|| '0000-00-00 00:00:00' !== $data['post_date_gmt'] )
			return;

		$this->post_date_cache[ $post_ID ] = $post->post_date;

	}

	/**
	 * This is a hack! hack! hack! until core is fixed
	 * 
	 * The calendar uses 'post_date' field to store the position on the calendar
	 * If a post has a core post status assigned (e.g. 'draft' or 'pending'), the `post_date`
	 * field will be reset when `wp_update_post()`
	 * is used: http://core.trac.wordpress.org/browser/tags/3.7.1/src/wp-includes/post.php#L2998
	 * 
	 * This method restores the `post_date` field if it needs to be restored.
	 * 
	 * @uses fix_post_date_on_update_part_one()
	 */
	public function fix_post_date_on_update_part_two( $post_ID, $post_after, $post_before ) {
		global $wpdb;

		if ( empty( $this->post_date_cache[ $post_ID ] ) )
			return;

		$post_date = $this->post_date_cache[ $post_ID ];
		unset( $this->post_date_cache[ $post_ID ] );
		$wpdb->update( $wpdb->posts, array( 'post_date' => $post_date ), array( 'ID' => $post_ID ) );
		clean_post_cache( $post_ID );
	}

	/**
	 * Returns a list of custom status objects used by the calendar
	 * 
	 * @return array An array of StdClass objects representing statuses
	 */
	public function get_calendar_post_stati() {
		$post_stati = get_post_stati( array(), 'object' );
		$custom_status_slugs = wp_list_pluck( $this->get_post_statuses(), 'slug' );
		$custom_status_slugs[] = 'future';	
		$custom_status_slugs[] = 'publish';

		$custom_status_slug_keys = array_flip( $custom_status_slugs );

		$final_statuses = [];

		foreach( $post_stati as $status ) {
			if ( !empty( $custom_status_slug_keys[ $status->name ] ) ) {
				$final_statuses[] = $status;
			}
		}

		return apply_filters( 'ef_calendar_post_stati', $final_statuses );
	}

	public function get_calendar_users() {
		$users_args = array(
			'orderby'                 => 'display_name',
			'order'                   => 'ASC',
			'blog_id'                 => get_current_blog_id()
		);

		$users_args = apply_filters( 'ef_calendar_dropdown_users_args', $users_args );

		return get_users( $users_args );
	}

	public function get_calendar_categories() {
		$categories_args = array(
			'orderby'           => 'id',
			'order'             => 'ASC',
			'hide_empty'        => 0,
			'hierarchical'      => 0,
			'taxonomy'          => 'category'
		);

		return get_terms( $categories_args );
	}

	public function get_calendar_frontend_config() {
		global $wp_version;

		$all_post_types = get_post_types( null, 'objects' );

		$config = array(
			'POST_STATI' => $this->get_calendar_post_stati(),
			'USERS' => array_map( 
				function( $item ) {
					return array( 
						'id' => $item->ID, 
						'display_name' => $item->display_name,
					);
				}, 
				$this->get_calendar_users() 
			),
			'CATEGORIES' => $this->get_calendar_categories(),
			'POST_TYPES' => array_map( function ( $item ) use ( $all_post_types ) {	
				return $all_post_types[ $item ];	
			}, $this->get_post_types_for_module( $this->module ) ),
			'NUM_WEEKS' => array(
				'MAX' => $this->max_weeks,
				'DEFAULT' => $this->total_weeks,
			),
			'BEGINNING_OF_WEEK' => $this->get_beginning_of_week( date( 'Y-m-d', current_time( 'timestamp' ) ) ),
			'FILTERS' => $this->get_filters(),
			'PAGE_URL' => menu_page_url( $this->module->slug, false ),
			'WP_VERSION' => $wp_version
		);

		return apply_filters( 'ef_calendar_frontend_config', $config );
	}
	
} // EF_Calendar
	
} // class_exists('EF_Calendar')