diff --git a/modules/calendar/calendar.php b/modules/calendar/calendar.php index 2d20b016..239fef36 100644 --- a/modules/calendar/calendar.php +++ b/modules/calendar/calendar.php @@ -5,697 +5,706 @@ * * @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'; - private int $max_weeks; - private string $create_post_cap; - - /** - * 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 customizable calendar.', '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 edit the post.", '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' => __('

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.

', 'edit-flow'), - ), - 'settings_help_sidebar' => __( '

For more information:

Calendar Documentation

Edit Flow Forum

Edit Flow on Github

', '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') +if ( ! class_exists( 'EF_Calendar' ) ) { + + class EF_Calendar extends EF_Module { + + // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase + const usermeta_key_prefix = 'ef_calendar_'; + // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase + const screen_id = 'dashboard_page_calendar'; + + public $module; + + public $start_date = ''; + public $current_week = 1; + public $total_weeks = 6; // default number of weeks to show per screen + public $hidden = 0; // counter of hidden posts per date square + public $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'; + private int $max_weeks; + private string $create_post_cap; + + /** + * Calendar published statuses are the same as other + * components but without the future + */ + public $published_statuses = array( + 'publish', + 'private', ); - 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 ); + /** + * Construct the EF_Calendar class + */ + public 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' ), + /* translators: %s: URL to the calendar page */ + 'short_description' => sprintf( __( 'View upcoming content in a customizable calendar.', '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' ), + /* translators: %s: URL to the published post */ + 'published-post-ajax' => __( "Updating the post date dynamically doesn't work for published content. Please edit the post.", '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' => __( '

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.

', 'edit-flow' ), + ), + 'settings_help_sidebar' => __( '

For more information:

Calendar Documentation

Edit Flow Forum

Edit Flow on Github

', 'edit-flow' ), + ); + $this->module = EditFlow()->register_module( 'calendar', $args ); } - } - - /** - * 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 ); + /** + * Initialize all of our methods and such. Only runs if the module is active + * + * @uses add_action() + */ + public 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 ); } - } - - /** - * 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' + + /** + * Load the capabilities onto users the first time the module is run + * + * @since 0.7 + */ + public 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( $js_libraries as $js_library ) { - wp_enqueue_script( $js_library ); + + foreach ( $calendar_roles as $role => $caps ) { + $this->add_caps_to_role( $role, $caps ); } - 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 ); + /** + * Upgrade our data in case we need to + * + * @since 0.7 + */ + public 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 + $enabled = get_option( 'edit_flow_calendar_enabled' ); + if ( $enabled ) { + $enabled = 'on'; + } else { + $enabled = 'off'; + } + $edit_flow->update_module_option( $this->module->name, 'enabled', $enabled ); + delete_option( 'edit_flow_calendar_enabled' ); - /** - * 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 ); + // 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 ); + } + } - wp_add_inline_script( - 'edit-flow-calendar-react-js', - 'var EF_CALENDAR = ' . wp_json_encode( $this->get_calendar_frontend_config() ), - 'before' - ); + /** + * Add the calendar link underneath the "Dashboard" + * + * @uses add_submenu_page + */ + public 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() + */ + public 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() + */ + public 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 ); - /** - * Prepare the options that need to appear in Screen Options - * - * @since 0.7 - */ - function generate_screen_options() { + /** + * 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 ); - $output = ''; + wp_add_inline_script( + 'edit-flow-calendar-react-js', + 'var EF_CALENDAR = ' . wp_json_encode( $this->get_calendar_frontend_config() ), + 'before' + ); + } + } - $args = array( + /** + * Prepare the options that need to appear in Screen Options + * + * @since 0.7 + */ + public 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 .= '
'; - $output .= __( 'Subscribe in iCal or Google Calendar', 'edit-flow' ); - $output .= ':
'; - - 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'] ) ); - } + $subscription_link = add_query_arg( $args, admin_url( 'admin-ajax.php' ) ); + $output .= '
'; + $output .= __( 'Subscribe in iCal or Google Calendar', 'edit-flow' ); + $output .= ':
'; - // 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'] ); + return $output; } - if ( !isset( $_POST['post_id'] ) ) { - $this->print_ajax_response( 'error', $this->module->messages['missing-post'] ); + /** + * Add module options to the screen panel + * + * @since 0.8.3 + */ + public 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 ); + } } - // 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'] ); - } + /** + * Handle the request to save the screen options + * + * @since 0.7 + */ + public function handle_save_screen_options() { - // 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'] ); - } + // Only handle screen options submissions from the current screen + if ( ! isset( $_POST['screen-options-apply'] ) ) { + return; + } - // 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 ) ) ); - } + // 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'] ) ); + } - if ( !isset( $_POST['next_date'] ) ) { - $this->print_ajax_response( 'error', __( 'Missing new date.', 'edit-flow' ) ); - } + // 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 ); - // 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' ) ); + // Redirect after we're complete + $redirect_to = menu_page_url( $this->module->slug, false ); + wp_redirect( $redirect_to ); + exit; } - // 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 ), - ); + /** + * 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 + */ + public function handle_ajax_drag_and_drop() { + global $wpdb; - // 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; - } + // 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'] ); + } - // 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 ( ! isset( $_POST['post_id'] ) ) { + $this->print_ajax_response( 'error', $this->module->messages['missing-post'] ); + } - if ( !$response ) { - $this->print_ajax_response( 'error', $this->module->messages['update-error'] ); - } + // 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'] ); + } - $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'] ) ); - } + // 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 ) ) ); + } - // 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; + 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 = gmdate( 'H:i:s', strtotime( $post->post_date ) ); + $existing_time_gmt = gmdate( 'H:i:s', strtotime( $post->post_date_gmt ) ); + $new_values = array( + 'post_date' => gmdate( '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'] = gmdate( '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; } - // 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 ), - ); + /** + * After checking that the request is valid, do an .ics file + * + * @since 0.8 + */ + public function handle_ics_subscription() { - // 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'; + // 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( gmdate( '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['DESCRIPTION'] = rtrim( $formatted_post['DESCRIPTION'] ); - } - $formatted_post['END'] = 'VEVENT'; + $formatted_post['END'] = 'VEVENT'; - // @todo auto format any field longer than 75 bytes + // @todo auto format any field longer than 75 bytes - $formatted_posts[] = $formatted_post; + $formatted_posts[] = $formatted_post; + } } } - } - // Other template data - $header = array( + // Other template data + $header = array( 'BEGIN' => 'VCALENDAR', 'VERSION' => '2.0', 'PRODID' => '-//Edit Flow//Edit Flow ' . EDIT_FLOW_VERSION . '//EN', ); - $footer = array( + $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 + // 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"; + die(); } - $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 "; + /** + * 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. + */ + public function do_ics_line_folding( $line ) { + $len = mb_strlen( $line ); + if ( $len <= 75 ) { + return $line . "\r\n"; } - else { - $chunks[] = $chunk ."\r\n"; - return implode( "", $chunks ); + + $chunks = array(); + $start = 0; + while ( true ) { + $chunk = mb_substr( $line, $start, 75 ); + $chunk_len = mb_strlen( $chunk ); + $start += $chunk_len; + 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; + /** + * 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 + * */ + + public function do_ics_escaping( $text ) { + $text = str_replace( ',', '\,', $text ); + $text = str_replace( ';', '\:', $text ); + $text = str_replace( '\\', '\\\\', $text ); + return $text; + } - // Add manual offset - $timestamp += $offset_in_seconds; + /** + * 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 ) { - // \T and \Z are escaped for literal T and Z characters - return date( 'Ymd\THis\Z', $timestamp ); + // Timestamp it + $timestamp = strtotime( $time_string ); - } + if ( ! $timestamp ) { + return false; + } - /** - * Handle a request to regenerate the calendar feed secret - * - * @since 0.8 - */ - public function handle_regenerate_calendar_feed_secret() { + // Subtract GMT Offset to return to UTC+0 + $timestamp -= get_option( 'gmt_offset' ) * HOUR_IN_SECONDS; - if ( ! isset( $_GET['action'] ) || 'ef_calendar_regenerate_calendar_feed_secret' != $_GET['action'] ) - return; + // Add manual offset + $timestamp += $offset_in_seconds; - if ( ! current_user_can( 'manage_options' ) ) { - wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); + // \T and \Z are escaped for literal T and Z characters + return gmdate( 'Ymd\THis\Z', $timestamp ); } - 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'] ) ); - } + /** + * Handle a request to regenerate the calendar feed secret + * + * @since 0.8 + */ + public function handle_regenerate_calendar_feed_secret() { - EditFlow()->update_module_option( $this->module->name, 'ics_secret_key', wp_generate_password() ); + if ( ! isset( $_GET['action'] ) || 'ef_calendar_regenerate_calendar_feed_secret' != $_GET['action'] ) { + return; + } - wp_safe_redirect( add_query_arg( 'message', 'key-regenerated', menu_page_url( $this->module->settings_slug, false ) ) ); - exit; - } + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html( $this->module->messages['invalid-permissions'] ) ); + } - /** - * 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() { + 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; + } /** - * `num_weeks` has been moved to a filter and out of screen options, it's maintained here for legacy purposes - * @deprecated `num_weeks` + * Get a user's screen options + * + * @since 0.7 + * @uses get_user_meta() + * + * @return array $screen_options The screen options values */ - $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 ); + public 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; + } /** - * To support legacy screen option for num_weeks + * 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 */ - $screen_options = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'screen_options', true ); + public 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 ); - $default_filters = array( + /** + * 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' ) ), + 'start_date' => gmdate( '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; - } + $old_filters = array_merge( $default_filters, isset( $screen_options['num_weeks'] ) ? array( 'num_weeks' => $screen_options['num_weeks'] ) : array(), (array) $old_filters ); - // 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'] ); + // 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; + } + } - $filters = apply_filters( 'ef_calendar_filter_values', $filters, $old_filters ); + // 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'] ); - $this->update_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', $filters ); + $filters = apply_filters( 'ef_calendar_filter_values', $filters, $old_filters ); - return $filters; - } + $this->update_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', $filters ); - /** - * Build all of the HTML for the calendar view - */ - function view_calendar() { - $supported_post_types = $this->get_post_types_for_module( $this->module ); + return $filters; + } - // Get filters either from $_GET or from user settings - $filters = $this->get_filters(); + /** + * Build all of the HTML for the calendar view + */ + public function view_calendar() { + $supported_post_types = $this->get_post_types_for_module( $this->module ); - // 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' ); + // Get filters either from $_GET or from user settings + $filters = $this->get_filters(); - $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']; + // 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' ); - // 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 ) ) ); - } + $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 sort by post statuses....... eventually - $post_statuses = $this->get_calendar_post_stati(); - ?> + // 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 = gmdate( 'Y-m-d', strtotime( '+1 day', strtotime( $heading_date ) ) ); + } + + // we sort by post statuses....... eventually + $post_statuses = $this->get_calendar_post_stati(); + ?>
module->img_url ) . '" class="module-icon icon32" />'; ?> @@ -704,23 +713,25 @@ function view_calendar() {

'; // 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 ' ' . esc_html__( 'Undo', 'edit-flow' ) . '
'; - 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 '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + if ( isset( $_GET['trashed'] ) || isset( $_GET['untrashed'] ) ) { + + echo '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + if ( isset( $_GET['trashed'] ) && (int) $_GET['trashed'] ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + /* translators: %d: number of posts trashed */ + 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 ' ' . esc_html__( 'Undo', 'edit-flow' ) . '
'; + unset( $_GET['trashed'] ); + } + if ( isset( $_GET['untrashed'] ) && (int) $_GET['untrashed'] ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + /* translators: %d: number of posts restored */ + 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 '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } ?>
@@ -730,12 +741,13 @@ function view_calendar() { 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'; + if ( 1 == $this->total_weeks ) { + $table_classes[] = 'one-week-showing'; + } elseif ( 2 == $this->total_weeks ) { + $table_classes[] = 'two-weeks-showing'; + } elseif ( 3 == $this->total_weeks ) { + $table_classes[] = 'three-weeks-showing'; + } $table_classes = apply_filters( 'ef_calendar_table_classes', $table_classes ); ?> @@ -749,7 +761,7 @@ function view_calendar() { total_weeks; $current_week++ ): + 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 ); @@ -757,103 +769,108 @@ function view_calendar() { $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; + 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 ) ) ); + $week_single_date = gmdate( 'Y-m-d', strtotime( '+1 day', strtotime( $week_single_date ) ) ); } - ?> - + ?> + - $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 '' . esc_html( $previous_month ) . ''; - } else if ( date_i18n( 'F', strtotime( $week_single_date ) ) == $split_month && date_i18n( 'F', strtotime( "-1 day", strtotime( $week_single_date ) ) ) != $split_month ) { - echo '' . esc_html( $split_month ) . ''; - } else { - echo ''; + $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 '' . esc_html( $previous_month ) . ''; + } else if ( date_i18n( 'F', strtotime( $week_single_date ) ) == $split_month && date_i18n( 'F', strtotime( '-1 day', strtotime( $week_single_date ) ) ) != $split_month ) { + echo '' . esc_html( $split_month ) . ''; + } else { + echo ''; + } } - } ?> + ?> - $week_single_date ): ?> - 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; + $week_single_date ) : ?> + 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 ) ); + $td_classes = array( + 'day-unit', + ); + $day_name = gmdate( 'D', strtotime( $week_single_date ) ); - if ( in_array( $day_name, $dotw ) ) - $td_classes[] = 'weekend-day'; + if ( in_array( $day_name, $dotw ) ) { + $td_classes[] = 'weekend-day'; + } - if ( $week_single_date == date( 'Y-m-d', current_time( 'timestamp' ) ) ) - $td_classes[] = 'today'; + if ( gmdate( 'Y-m-d', current_time( 'timestamp' ) ) == $week_single_date ) { + $td_classes[] = 'today'; + } - // Last day of the week - if ( $day_num == 6 ) - $td_classes[] = 'last-day'; + // Last day of the week + if ( 6 == $day_num ) { + $td_classes[] = 'last-day'; + } - $td_classes = apply_filters( 'ef_calendar_table_td_classes', $td_classes, $week_single_date ); - ?> + $td_classes = apply_filters( 'ef_calendar_table_td_classes', $td_classes, $week_single_date ); + ?> - +
-
+
- hidden ): ?> - hidden ) ); ?> + hidden ) : ?> + hidden ) ); ?> - create_post_cap ) ) : - $date_formatted = date( 'D, M jS, Y', strtotime( $week_single_date ) ); - ?> + create_post_cap ) ) : + $date_formatted = gmdate( 'D, M jS, Y', strtotime( $week_single_date ) ); + ?>
@@ -862,12 +879,12 @@ function view_calendar() {
- - get_quick_create_post_type_name() ) ); ?> » + + get_quick_create_post_type_name() ) ); ?> »
 
- + @@ -879,64 +896,66 @@ function view_calendar() { + wp_nonce_field( 'ef-calendar-modify', 'ef-calendar-modify' ); + ?>
- 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']; + ID; - $edit_post_link = get_edit_post_link( $post_id ); - $status_object = get_post_status_object( get_post_status( $post_id ) ); + /** + * 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 + */ + public 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']; + } - $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'; + 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 ) ); - if ( in_array( $post->post_status, $this->published_statuses ) ) - $post_classes[] = 'is-published'; + $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'; + } - // 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 ( in_array( $post->post_status, $this->published_statuses ) ) { + $post_classes[] = 'is-published'; + } - 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 ); + // 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 ); - ?> + ?>
  • @@ -952,51 +971,54 @@ function generate_post_li_html( $post, $post_date, $num = 0 ){
  • - $num, - 'post_li_html' => $post_li_html, - 'hidden' => $this->hidden, + $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 ) { - ?> + 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 + */ + public function get_inner_information( $ef_calendar_item_information_fields, $post ) { + ?> - get_post_information_fields( $post ) as $field => $values ): ?> + get_post_information_fields( $post ) as $field => $values ) : ?> - - current_user_can_modify_post( $post ) ) : ?> - - + + current_user_can_modify_post( $post ) ) : ?> + + - + - + - + @@ -1006,481 +1028,498 @@ function get_inner_information( $ef_calendar_item_information_fields, $post ) { post_type ); $item_actions = array(); - if ( $this->current_user_can_modify_post( $post ) ) { - // Edit this post - $item_actions['edit'] = '' . __( 'Edit', 'edit-flow' ) . ''; - // Trash this post - $item_actions['trash'] = '' . __( 'Trash', 'edit-flow' ) . ''; - // Preview/view this post - if ( !in_array( $post->post_status, $this->published_statuses ) ) { - $item_actions['view'] = '' . __( 'Preview', 'edit-flow' ) . ''; - } elseif ( 'trash' != $post->post_status ) { - $item_actions['view'] = '' . __( 'View', 'edit-flow' ) . ''; - } - //Save metadata - $item_actions['save hidden'] = '' . __( 'Save', 'edit-flow') . ''; + if ( $this->current_user_can_modify_post( $post ) ) { + // Edit this post + $item_actions['edit'] = '' . __( 'Edit', 'edit-flow' ) . ''; + // Trash this post + $item_actions['trash'] = '' . __( 'Trash', 'edit-flow' ) . ''; + // Preview/view this post + if ( ! in_array( $post->post_status, $this->published_statuses ) ) { + /* translators: %s: post title */ + $item_actions['view'] = '' . __( 'Preview', 'edit-flow' ) . ''; + } elseif ( 'trash' != $post->post_status ) { + /* translators: %s: post title */ + $item_actions['view'] = '' . __( 'View', 'edit-flow' ) . ''; } + //Save metadata + /* translators: %s: post title */ + $item_actions['save hidden'] = '' . __( 'Save', 'edit-flow' ) . ''; + } // Allow other plugins to add actions $item_actions = apply_filters( 'ef_calendar_item_actions', $item_actions, $post->ID ); - if ( count( $item_actions ) ) { - echo '
    '; - $html = ''; - foreach ( $item_actions as $class => $item_action ) { - $html .= '' . $item_action . ' | '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } - echo rtrim( $html, '| ' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - echo '
    '; + if ( count( $item_actions ) ) { + echo '
    '; + $html = ''; + foreach ( $item_actions as $class => $item_action ) { + $html .= '' . $item_action . ' | '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } + echo rtrim( $html, '| ' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo '
    '; + } ?>
    - '; - break; - case 'paragraph': - return ''; - break; - case 'date': - return ''; - break; - case 'checkbox': - $output = ''; - - return $output; - break; - case 'user': - return wp_dropdown_users( array( 'echo' => false ) ); - break; - case 'taxonomy': - return ''; - break; - case 'taxonomy hierarchical': - return wp_dropdown_categories( array( 'echo' => 0, 'hide_empty' => 0 ) ); - break; + '; + break; + case 'paragraph': + return ''; + break; + case 'date': + return ''; + break; + case 'checkbox': + $output = ''; + + return $output; + break; + case 'user': + return wp_dropdown_users( array( 'echo' => false ) ); + break; + case 'taxonomy': + return ''; + 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, + /** + * 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 + */ + public 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', ); - } - // 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 ), + + // 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, ); } - } - // 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 . ', '; + // Publication time for published statuses + $published_statuses = array( + 'publish', + 'future', + 'private', + ); + if ( in_array( $post->post_status, $published_statuses ) ) { + if ( 'future' == $post->post_status ) { + $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 ), + ); } - $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, + } + // 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; + } - if( $post->post_type == 'page' ) - $ed_cap = 'edit_page'; - else - $ed_cap = 'edit_post'; + $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 ( 'page' == $post->post_type ) { + $ed_cap = 'edit_page'; + } else { + $ed_cap = 'edit_post'; + } + + if ( current_user_can( $ed_cap, $post->ID ) ) { + $information_fields[ $key ]['editable'] = true; + } + } - 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; } - $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] ); + /** + * 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 + */ + public function get_time_period_header( $dates ) { + + $html = ''; + foreach ( $dates as $date ) { + $html .= ''; + } + + return $html; } - 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 .= ''; + + /** + * 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 + */ + public 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 ( '0' === $args['cat'] ) { + unset( $args['cat'] ); + } + if ( '0' === $args['author'] ) { + 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 = gmdate( '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 = gmdate( 'Y-m-d', strtotime( $post->post_date ) ); + $posts[ $key_date ][] = $post; + } + + return $posts; } - 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, - ); + /** + * 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 + */ + public function get_pagination_link( $direction = 'next', $filters = array(), $weeks_offset = null ) { - $args = array_merge( $defaults, $args ); + $supported_post_types = $this->get_post_types_for_module( $this->module ); - // 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 ( ! isset( $weeks_offset ) ) { + $weeks_offset = $this->total_weeks; + } else if ( 0 == $weeks_offset ) { + $filters['start_date'] = $this->get_beginning_of_week( gmdate( 'Y-m-d', current_time( 'timestamp' ) ) ); + } - if ( ! apply_filters( 'ef_show_scheduled_as_unpublished', false ) ) { - $post_stati = wp_filter_object_list( $post_stati, array( 'name' => 'future' ), 'not' ); + if ( 'previous' == $direction ) { + $weeks_offset = '-' . $weeks_offset; } - $args['post_status'] .= implode( ',', wp_list_pluck( $post_stati, 'name' ) ); + $filters['start_date'] = gmdate( '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; } - // 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'] ); + + /** + * 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 + */ + public 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 = gmdate( 'w', $date ); + $date += ( ( $start_of_week - $day_of_week - 7 ) % 7 ) * 60 * 60 * 24; + $date = strtotime( '+' . ( $week - 1 ) . ' week', $date ); + $formatted_start_of_week = gmdate( $format, $date ); + return $formatted_start_of_week; } - if ( $args['author'] === '0' ) { - unset( $args['author'] ); + + /** + * 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 + */ + public 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 = gmdate( 'w', $date ); + $date += ( ( $end_of_week - $day_of_week + 7 ) % 7 ) * 60 * 60 * 24; + $date = strtotime( '+' . ( $week - 1 ) . ' week', $date ); + $formatted_end_of_week = gmdate( $format, $date ); + return $formatted_end_of_week; } - if ( empty( $args['post_type'] ) || ! in_array( $args['post_type'], $supported_post_types ) ) { - $args['post_type'] = $supported_post_types; + /** + * Human-readable time range for the calendar + * Shows something like "for October 30th through November 26th" for a four-week period + * + * @since 0.7 + */ + public 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', gmdate( 'U', strtotime( $this->start_date ) ) ); + $last_date = date_i18n( get_option( 'date_format' ), $last_datetime ); + // translators: %1$s = first date, %2$s = last date + echo esc_html( sprintf( __( 'for %1$s through %2$s', 'edit-flow' ), $first_date, $last_date ) ); } - $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 ); + /** + * 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 + */ + public function current_user_can_modify_post( $post ) { - $args['date_query'] = array( - 'after' => $beginning_date, - 'before' => $ending_date, - 'inclusive' => true, - ); + if ( ! $post ) { + return false; + } - // 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 ); + $post_type_object = get_post_type_object( $post->post_type ); - $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; - } + // 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 $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() { + /** + * 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 + */ + public 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 ""; - - } - - /** - * 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 ''; + /** + * Choose the post type that should be created on the calendar + * + * @since 0.8 + */ + public function settings_quick_create_post_type_option() { - $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 '   ' . esc_html_e( 'Regenerate calendar feed secret', 'edit-flow' ) . ''; + $allowed_post_types = $this->get_all_post_types(); - // 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() ); - } + echo "'; + } + + /** + * Enable calendar subscriptions via .ics in iCal or Google Calendar + * + * @since 0.8 + */ + public function settings_ics_subscription_option() { + $options = array( + 'off' => __( 'Disabled', 'edit-flow' ), + 'on' => __( 'Enabled', 'edit-flow' ), + ); + echo ''; + + + $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 '   ' . esc_html_e( 'Regenerate calendar feed secret', 'edit-flow' ) . ''; + + // 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 ) { + /** + * Validate the data submitted by the user in calendar settings + * + * @since 0.7 + */ + public function settings_validate( $new_options ) { - $options = (array)$this->module->options; + $options = (array) $this->module->options; - $options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support ); + $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 ( 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'; + if ( 'on' != $new_options['ics_subscription'] ) { + $options['ics_subscription'] = 'off'; + } else { + $options['ics_subscription'] = 'on'; + } - return $options; - } + return $options; + } - /** - * Settings page for calendar - */ - function print_configure_view() { - global $edit_flow; - ?> + /** + * Settings page for calendar + */ + public function print_configure_view() { + global $edit_flow; + ?> module->options_group_name ); ?> module->options_group_name ); ?> @@ -1489,380 +1528,392 @@ function print_configure_view() { ?>

    - print_ajax_response( 'error', $this->module->messages['nonce-failed'] ); + create_post_cap ) ) - $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] ); + /** + * Ajax callback to insert a post placeholder for a particular date + * + * @since 0.8 + */ + public function handle_ajax_insert_post() { - if ( empty( $_POST['ef_insert_date'] ) ) { - $this->print_ajax_response( 'error', __( 'No date supplied.', 'edit-flow' ) ); - } + // Nonce check! + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'ef-calendar-modify' ) ) { + $this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] ); + } - // 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' ) ); + // 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'] ); + } - // Sanitize post values - $post_title = isset( $_POST['ef_insert_title'] ) ? sanitize_text_field( $_POST['ef_insert_title'] ) : null; + if ( empty( $_POST['ef_insert_date'] ) ) { + $this->print_ajax_response( 'error', __( 'No date supplied.', 'edit-flow' ) ); + } - if( ! $post_title ) { - $post_title = esc_html__( 'Untitled', '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' ) ); + } - $post_date = sanitize_text_field( $_POST['ef_insert_date'] ); + // Sanitize post values + $post_title = isset( $_POST['ef_insert_title'] ) ? sanitize_text_field( $_POST['ef_insert_title'] ) : null; - $post_status = $this->get_default_post_status(); + if ( ! $post_title ) { + $post_title = esc_html__( 'Untitled', 'edit-flow' ); + } - // 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, - ); + $post_date = sanitize_text_field( $_POST['ef_insert_date'] ); - // 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 ) ); + $post_status = $this->get_default_post_status(); - // Create the post - $post_id = wp_insert_post( $post_placeholder ); + // Set new post parameters + $post_placeholder = array( + 'post_title' => $post_title, + 'post_status' => $post_status, + 'post_date' => gmdate( 'Y-m-d H:i:s', strtotime( $post_date ) ), + 'post_type' => $this->module->options->quick_create_post_type, + ); - if( $post_id ) { // success! + // 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'] = gmdate( 'Y-m-d H:i:s', strtotime( $post_date ) ); + } - $post = get_post( $post_id ); + // Create the post + $post_id = wp_insert_post( $post_placeholder ); - // Generate the HTML for the post item so it can be injected - $post_li_html = $this->generate_post_li_html( $post, $post_date ); + if ( $post_id ) { // success! - // announce success and send back the html to inject - $this->print_ajax_response( 'success', $post_li_html ); + $post = get_post( $post_id ); - } 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'] ); + // 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' ) ); + } } - if ( !isset( $_POST['post_id'] ) ) { - $this->print_ajax_response( 'error', $this->module->messages['missing-post'] ); + /** + * Returns the singular label for the posts that are + * quick-created on the calendar + * + * @return str Singular label for a post-type + */ + public 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; } - // Check that we got a proper post - $post_id = ( int )$_POST['post_id']; - $post = get_post( $post_id ); + /** + * ajax_ef_calendar_update_metadata + * Update the metadata from the calendar. + * @return string representing the overlay + * + * @since 0.8 + */ + public function handle_ajax_update_metadata() { + global $wpdb; - if ( ! $post ) - $this->print_ajax_response( 'error', $this->module->messages['missing-post'] ); + 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'] ); + } - if( $post->post_type == 'page' ) - $edit_check = 'edit_page'; - else - $edit_check = 'edit_post'; + // Check that we got a proper post + $post_id = (int) $_POST['post_id']; + $post = get_post( $post_id ); - if ( !current_user_can( $edit_check, $post->ID ) ) - $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] ); + 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'] ); - $default_types = array( - 'author', - 'taxonomy', - ); + if ( 'page' == $post->post_type ) { + $edit_check = 'edit_page'; + } else { + $edit_check = 'edit_post'; + } - $metadata_types = array(); + if ( ! current_user_can( $edit_check, $post->ID ) ) { + $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] ); + } - if ( !$this->module_enabled( 'editorial_metadata' ) ) - $this->print_ajax_response( 'error', $this->module->messages['update-error'] ); + // 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'] ); + } - $metadata_types = array_keys( EditFlow()->editorial_metadata->get_supported_metadata_types() ); + $default_types = array( + 'author', + 'taxonomy', + ); - // 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'] : ''; + $metadata_types = array(); - 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 ); + if ( ! $this->module_enabled( 'editorial_metadata' ) ) { + $this->print_ajax_response( 'error', $this->module->messages['update-error'] ); + } - //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 ); + $metadata_types = array_keys( EditFlow()->editorial_metadata->get_supported_metadata_types() ); - 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; + // 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 ( 'date' == $metadata_type ) { + $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' ) ); } } - //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 ) ); + public 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 + */ + public 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 gmdate( 'Y-m-d', strtotime( $dirty_value ) ); break; - case 'cat': - case 'author': - return intval( $dirty_value ); + 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; + 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
  • 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 ); } - } - - /** - * When a post is updated, clean the
  • 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' ) ) + + /** + * 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; + || '0000-00-00 00:00:00' !== $data['post_date_gmt'] ) { + return; } + + $this->post_date_cache[ $post_ID ] = $post->post_date; } - return apply_filters( 'ef_calendar_post_stati', $final_statuses ); - } + /** + * 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; - public function get_calendar_users() { - $users_args = array( - 'orderby' => 'display_name', - 'order' => 'ASC', - 'blog_id' => get_current_blog_id() - ); + if ( empty( $this->post_date_cache[ $post_ID ] ) ) { + return; + } - $users_args = apply_filters( 'ef_calendar_dropdown_users_args', $users_args ); + $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 ); + } - return get_users( $users_args ); - } + /** + * 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'; - public function get_calendar_categories() { - $categories_args = array( - 'orderby' => 'id', - 'order' => 'ASC', - 'hide_empty' => 0, - 'hierarchical' => 0, - 'taxonomy' => 'category' - ); + $custom_status_slug_keys = array_flip( $custom_status_slugs ); - return get_terms( $categories_args ); - } + $final_statuses = []; - public function get_calendar_frontend_config() { - global $wp_version; + foreach ( $post_stati as $status ) { + if ( ! empty( $custom_status_slug_keys[ $status->name ] ) ) { + $final_statuses[] = $status; + } + } - $all_post_types = get_post_types( null, 'objects' ); + return apply_filters( 'ef_calendar_post_stati', $final_statuses ); + } - $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 - ); + public function get_calendar_users() { + $users_args = array( + 'orderby' => 'display_name', + 'order' => 'ASC', + 'blog_id' => get_current_blog_id(), + ); - return apply_filters( 'ef_calendar_frontend_config', $config ); - } + $users_args = apply_filters( 'ef_calendar_dropdown_users_args', $users_args ); -} // EF_Calendar + 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( gmdate( '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') diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 8f88a940..318e2134 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -24,6 +24,10 @@ + + + + .
  • :
    '; + $html .= esc_html( date_i18n( 'l', strtotime( $date ) ) ); + $html .= ''; - $html .= esc_html( date_i18n('l', strtotime( $date ) ) ); - $html .= '