PluginProbe
Edit Flow / 0.9
Edit Flow v0.9
0.11.1 0.11.0 0.7.2 0.7.3 0.7.4 0.7.5 0.7.6 0.8 0.8.1 0.8.2 0.9 0.9.1 0.9.2 0.9.3 0.9.4 0.9.5 0.9.6 0.9.7 0.9.8 0.9.9 trunk 0.1.5 0.10.0 0.10.1 0.10.2 All 44 releases
edit-flow / modules / calendar / calendar.php

calendar.php in Edit Flow 0.9, at modules/calendar/calendar.php

1,858 lines 70.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * class EF_Calendar
4 * This class displays an editorial calendar for viewing upcoming and past content at a glance
5 *
6 * @author danielbachhuber
7 */
8 if ( !class_exists('EF_Calendar') ) {
9
10 class EF_Calendar extends EF_Module {
11
12 const usermeta_key_prefix = 'ef_calendar_';
13 const screen_id = 'dashboard_page_calendar';
14
15 var $module;
16
17 var $start_date = '';
18 var $current_week = 1;
19 var $total_weeks = 6; // default number of weeks to show per screen
20 var $hidden = 0; // counter of hidden posts per date square
21 var $max_visible_posts_per_date = 4; // total number of posts to be shown per square before 'more' link
22
23 private $post_date_cache = array();
24 private static $post_li_html_cache_key = 'ef_calendar_post_li_html';
25
26 /**
27 * Construct the EF_Calendar class
28 */
29 function __construct() {
30
31 $this->module_url = $this->get_module_url( __FILE__ );
32 // Register the module with Edit Flow
33 $args = array(
34 'title' => __( 'Calendar', 'edit-flow' ),
35 'short_description' => sprintf( __( 'View upcoming content in a <a href="%s">customizable calendar</a>.', 'edit-flow' ), admin_url( 'index.php?page=calendar' ) ),
36 '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' ),
37 'module_url' => $this->module_url,
38 'img_url' => $this->module_url . 'lib/calendar_s128.png',
39 'slug' => 'calendar',
40 'post_type_support' => 'ef_calendar',
41 'default_options' => array(
42 'enabled' => 'on',
43 'post_types' => array(
44 'post' => 'on',
45 'page' => 'off',
46 ),
47 'quick_create_post_type' => 'post',
48 'ics_subscription' => 'off',
49 'ics_secret_key' => '',
50 ),
51 'messages' => array(
52 'post-date-updated' => __( "Post date updated.", 'edit-flow' ),
53 'update-error' => __( 'There was an error updating the post. Please try again.', 'edit-flow' ),
54 'published-post-ajax' => __( "Updating the post date dynamically doesn't work for published content. Please <a href='%s'>edit the post</a>.", 'edit-flow' ),
55 'key-regenerated' => __( 'iCal secret key regenerated. Please inform all users they will need to resubscribe.', 'edit-flow' ),
56 ),
57 'configure_page_cb' => 'print_configure_view',
58 'configure_link_text' => __( 'Calendar Options', 'edit-flow' ),
59 'settings_help_tab' => array(
60 'id' => 'ef-calendar-overview',
61 'title' => __('Overview', 'edit-flow'),
62 'content' => __('<p>The calendar is a convenient week-by-week or month-by-month view into your content. Quickly see which stories are on track to being published on time, and which will need extra effort.</p>', 'edit-flow'),
63 ),
64 'settings_help_sidebar' => __( '<p><strong>For more information:</strong></p><p><a href="http://editflow.org/features/calendar/">Calendar Documentation</a></p><p><a href="http://wordpress.org/tags/edit-flow?forum_id=10">Edit Flow Forum</a></p><p><a href="https://github.com/danielbachhuber/Edit-Flow">Edit Flow on Github</a></p>', 'edit-flow' ),
65 );
66 $this->module = EditFlow()->register_module( 'calendar', $args );
67
68 }
69
70 /**
71 * Initialize all of our methods and such. Only runs if the module is active
72 *
73 * @uses add_action()
74 */
75 function init() {
76
77 // .ics calendar subscriptions
78 add_action( 'wp_ajax_ef_calendar_ics_subscription', array( $this, 'handle_ics_subscription' ) );
79 add_action( 'wp_ajax_nopriv_ef_calendar_ics_subscription', array( $this, 'handle_ics_subscription' ) );
80
81 // Check whether the user should have the ability to view the calendar
82 $view_calendar_cap = 'ef_view_calendar';
83 $view_calendar_cap = apply_filters( 'ef_view_calendar_cap', $view_calendar_cap );
84 if ( !current_user_can( $view_calendar_cap ) ) return false;
85
86 // Define the create-post capability
87 $this->create_post_cap = apply_filters( 'ef_calendar_create_post_cap', 'edit_posts' );
88
89 add_action( 'admin_init', array( $this, 'add_screen_options_panel' ) );
90 add_action( 'admin_init', array( $this, 'handle_save_screen_options' ) );
91
92 add_action( 'admin_init', array( $this, 'register_settings' ) );
93 add_action( 'admin_menu', array( $this, 'action_admin_menu' ) );
94 add_action( 'admin_print_styles', array( $this, 'add_admin_styles' ) );
95 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
96
97 // Ajax manipulation for the calendar
98 add_action( 'wp_ajax_ef_calendar_drag_and_drop', array( $this, 'handle_ajax_drag_and_drop' ) );
99
100 // Ajax insert post placeholder for a specific date
101 add_action( 'wp_ajax_ef_insert_post', array( $this, 'handle_ajax_insert_post' ) );
102
103 //Update metadata
104 add_action( 'wp_ajax_ef_calendar_update_metadata', array( $this, 'handle_ajax_update_metadata' ) );
105
106 // Clear li cache for a post when post cache is cleared
107 add_action( 'clean_post_cache', array( $this, 'action_clean_li_html_cache' ) );
108
109 // Action to regenerate the calendar feed sekret
110 add_action( 'admin_init', array( $this, 'handle_regenerate_calendar_feed_secret' ) );
111
112 // Hacks to fix deficiencies in core
113 add_action( 'pre_post_update', array( $this, 'fix_post_date_on_update_part_one' ), 10, 2 );
114 add_action( 'post_updated', array( $this, 'fix_post_date_on_update_part_two' ), 10, 3 );
115 }
116
117 /**
118 * Load the capabilities onto users the first time the module is run
119 *
120 * @since 0.7
121 */
122 function install() {
123
124 // Add necessary capabilities to allow management of calendar
125 // view_calendar - administrator --> contributor
126 $calendar_roles = array(
127 'administrator' => array('ef_view_calendar'),
128 'editor' => array('ef_view_calendar'),
129 'author' => array('ef_view_calendar'),
130 'contributor' => array('ef_view_calendar')
131 );
132
133 foreach ( $calendar_roles as $role => $caps ) {
134 $this->add_caps_to_role( $role, $caps );
135 }
136 }
137
138 /**
139 * Upgrade our data in case we need to
140 *
141 * @since 0.7
142 */
143 function upgrade( $previous_version ) {
144 global $edit_flow;
145
146 // Upgrade path to v0.7
147 if ( version_compare( $previous_version, '0.7' , '<' ) ) {
148 // Migrate whether the calendar was enabled or not and clean up old option
149 if ( $enabled = get_option( 'edit_flow_calendar_enabled' ) )
150 $enabled = 'on';
151 else
152 $enabled = 'off';
153 $edit_flow->update_module_option( $this->module->name, 'enabled', $enabled );
154 delete_option( 'edit_flow_calendar_enabled' );
155
156 // Technically we've run this code before so we don't want to auto-install new data
157 $edit_flow->update_module_option( $this->module->name, 'loaded_once', true );
158 }
159
160 }
161
162 /**
163 * Add the calendar link underneath the "Dashboard"
164 *
165 * @uses add_submenu_page
166 */
167 function action_admin_menu() {
168 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' ) );
169 }
170
171 /**
172 * Add any necessary CSS to the WordPress admin
173 *
174 * @uses wp_enqueue_style()
175 */
176 function add_admin_styles() {
177 global $pagenow;
178 // Only load calendar styles on the calendar page
179 if ( $pagenow == 'index.php' && isset( $_GET['page'] ) && $_GET['page'] == 'calendar' )
180 wp_enqueue_style( 'edit-flow-calendar-css', $this->module_url . 'lib/calendar.css', false, EDIT_FLOW_VERSION );
181 }
182
183 /**
184 * Add any necessary JS to the WordPress admin
185 *
186 * @since 0.7
187 * @uses wp_enqueue_script()
188 */
189 function enqueue_admin_scripts() {
190
191 $this->enqueue_datepicker_resources();
192
193 if ( $this->is_whitelisted_functional_view() ) {
194 $js_libraries = array(
195 'jquery',
196 'jquery-ui-core',
197 'jquery-ui-sortable',
198 'jquery-ui-draggable',
199 'jquery-ui-droppable',
200 );
201 foreach( $js_libraries as $js_library ) {
202 wp_enqueue_script( $js_library );
203 }
204 wp_enqueue_script( 'edit-flow-calendar-js', $this->module_url . 'lib/calendar.js', $js_libraries, EDIT_FLOW_VERSION, true );
205
206 $ef_cal_js_params = array( 'can_add_posts' => current_user_can( $this->create_post_cap ) ? 'true' : 'false' );
207 wp_localize_script( 'edit-flow-calendar-js', 'ef_calendar_params', $ef_cal_js_params );
208 }
209
210 }
211
212 /**
213 * Prepare the options that need to appear in Screen Options
214 *
215 * @since 0.7
216 */
217 function generate_screen_options() {
218
219 $output = '';
220 $screen_options = $this->get_screen_options();
221
222 $output .= __( 'Number of Weeks: ', 'edit-flow' );
223 $output .= '<select id="' . self::usermeta_key_prefix . 'num_weeks" name="' . self::usermeta_key_prefix . 'num_weeks">';
224 for( $i = 1; $i <= 12; $i++ ) {
225 $output .= '<option value="' . esc_attr( $i ) . '" ' . selected( $i, $screen_options['num_weeks'], false ) . '>' . esc_attr( $i ) . '</option>';
226 }
227 $output .= '</select>';
228
229 $output .= '&nbsp;&nbsp;&nbsp;<input id="screen-options-apply" name="screen-options-apply" type="submit" value="' . __( 'Apply' ) . '" class="button-secondary" />';
230
231 if ( 'on' == $this->module->options->ics_subscription && $this->module->options->ics_secret_key ) {
232 $args = array(
233 'action' => 'ef_calendar_ics_subscription',
234 'user' => wp_get_current_user()->user_login,
235 'user_key' => md5( wp_get_current_user()->user_login . $this->module->options->ics_secret_key ),
236 );
237 $subscription_link = add_query_arg( $args, admin_url( 'admin-ajax.php' ) );
238 $output .= '<br />';
239 $output .= __( 'Subscribe in iCal or Google Calendar', 'edit-flow' );
240 $output .= ':<br /><input type="text" size="100" value="' . esc_attr( $subscription_link ) . '" />';
241 }
242
243 return $output;
244 }
245
246 /**
247 * Add module options to the screen panel
248 *
249 * @since 0.8.3
250 */
251 function add_screen_options_panel() {
252 require_once( EDIT_FLOW_ROOT . '/common/php/' . 'screen-options.php' );
253 add_screen_options_panel( self::usermeta_key_prefix . 'screen_options', __( 'Calendar Options', 'edit-flow' ), array( $this, 'generate_screen_options' ), self::screen_id, false, true );
254 }
255
256 /**
257 * Handle the request to save the screen options
258 *
259 * @since 0.7
260 */
261 function handle_save_screen_options() {
262
263 // Only handle screen options submissions from the current screen
264 if ( !isset( $_POST['screen-options-apply'], $_POST['ef_calendar_num_weeks'] ) )
265 return;
266
267 // Nonce check
268 if ( !wp_verify_nonce( $_POST['_wpnonce-' . self::usermeta_key_prefix . 'screen_options'], 'save_settings-' . self::usermeta_key_prefix . 'screen_options' ) )
269 wp_die( $this->module->messages['nonce-failed'] );
270
271 // Get the current screen options
272 $screen_options = $this->get_screen_options();
273
274 // Save the number of weeks to show
275 $screen_options['num_weeks'] = (int)$_POST['ef_calendar_num_weeks'];
276
277 // Save the screen options
278 $current_user = wp_get_current_user();
279 $this->update_user_meta( $current_user->ID, self::usermeta_key_prefix . 'screen_options', $screen_options );
280
281 // Redirect after we're complete
282 $redirect_to = menu_page_url( $this->module->slug, false );
283 wp_redirect( $redirect_to );
284 exit;
285 }
286
287 /**
288 * Handle an AJAX request from the calendar to update a post's timestamp.
289 * Notes:
290 * - For Post Time, if the post is unpublished, the change sets the publication timestamp
291 * - If the post was published or scheduled for the future, the change will change the timestamp. 'publish' posts
292 * will become scheduled if moved past today and 'future' posts will be published if moved before today
293 * - Need to respect user permissions. Editors can move all, authors can move their own, and contributors can't move at all
294 *
295 * @since 0.7
296 */
297 function handle_ajax_drag_and_drop() {
298 global $wpdb;
299
300 // Nonce check!
301 if ( !wp_verify_nonce( $_POST['nonce'], 'ef-calendar-modify' ) )
302 $this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] );
303
304 // Check that we got a proper post
305 $post_id = (int)$_POST['post_id'];
306 $post = get_post( $post_id );
307 if ( !$post )
308 $this->print_ajax_response( 'error', $this->module->messages['missing-post'] );
309
310 // Check that the user can modify the post
311 if ( !$this->current_user_can_modify_post( $post ) )
312 $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );
313
314 // Check that it's not yet published
315 if ( in_array( $post->post_status, $this->published_statuses ) )
316 $this->print_ajax_response( 'error', sprintf( $this->module->messages['published-post-ajax'], get_edit_post_link( $post_id ) ) );
317
318 // Check that the new date passed is a valid one
319 $next_date_full = strtotime( $_POST['next_date'] );
320 if ( !$next_date_full )
321 $this->print_ajax_response( 'error', __( 'Something is wrong with the format for the new date.', 'edit-flow' ) );
322
323 // Persist the old hourstamp because we can't manipulate the exact time on the calendar
324 // Bump the last modified timestamps too
325 $existing_time = date( 'H:i:s', strtotime( $post->post_date ) );
326 $existing_time_gmt = date( 'H:i:s', strtotime( $post->post_date_gmt ) );
327 $new_values = array(
328 'post_date' => date( 'Y-m-d', $next_date_full ) . ' ' . $existing_time,
329 'post_modified' => current_time( 'mysql' ),
330 'post_modified_gmt' => current_time( 'mysql', 1 ),
331 );
332
333 // By default, changing a post on the calendar won't set the timestamp.
334 // If the user desires that to be the behaviour, they can set the result of this filter to 'true'
335 // With how WordPress works internally, setting 'post_date_gmt' will set the timestamp
336 if ( apply_filters( 'ef_calendar_allow_ajax_to_set_timestamp', false ) )
337 $new_values['post_date_gmt'] = date( 'Y-m-d', $next_date_full ) . ' ' . $existing_time_gmt;
338
339 // We have to do SQL unfortunately because of core bugginess
340 // Note to those reading this: bug Nacin to allow us to finish the custom status API
341 // See http://core.trac.wordpress.org/ticket/18362
342 $response = $wpdb->update( $wpdb->posts, $new_values, array( 'ID' => $post->ID ) );
343 clean_post_cache( $post->ID );
344 if ( !$response )
345 $this->print_ajax_response( 'error', $this->module->messages['update-error'] );
346
347 $this->print_ajax_response( 'success', $this->module->messages['post-date-updated'] );
348 exit;
349 }
350
351 /**
352 * After checking that the request is valid, do an .ics file
353 *
354 * @since 0.8
355 */
356 function handle_ics_subscription() {
357
358 // Only do .ics subscriptions when the option is active
359 if ( 'on' != $this->module->options->ics_subscription )
360 die(); // @todo return accepted response value.
361
362 // Confirm all of the arguments are present
363 if ( ! isset( $_GET['user'], $_GET['user_key'] ) )
364 die(); // @todo return an error response
365
366 // Confirm this is a valid request
367 $user = sanitize_user( $_GET['user'] );
368 $user_key = sanitize_user( $_GET['user_key'] );
369 $ics_secret_key = $this->module->options->ics_secret_key;
370 if ( ! $ics_secret_key || md5( $user . $ics_secret_key ) !== $user_key )
371 die( $this->module->messages['nonce-failed'] );
372
373 // Set up the post data to be printed
374 $post_query_args = array();
375 $calendar_filters = $this->calendar_filters();
376 foreach( $calendar_filters as $filter ) {
377 if ( isset( $_GET[$filter] ) && false !== ( $value = $this->sanitize_filter( $filter, $_GET[$filter] ) ) )
378 $post_query_args[$filter] = $value;
379 }
380
381 // Set the start date for the posts_where filter
382 $this->start_date = apply_filters( 'ef_calendar_ics_subscription_start_date', $this->get_beginning_of_week( date( 'Y-m-d', current_time( 'timestamp' ) ) ) );
383
384 $this->total_weeks = apply_filters( 'ef_calendar_total_weeks', $this->total_weeks, 'ics_subscription' );
385
386 $formatted_posts = array();
387 for( $current_week = 1; $current_week <= $this->total_weeks; $current_week++ ) {
388 // We need to set the object variable for our posts_where filter
389 $this->current_week = $current_week;
390 $week_posts = $this->get_calendar_posts_for_week( $post_query_args, 'ics_subscription' );
391 foreach( $week_posts as $date => $day_posts ) {
392 foreach( $day_posts as $num => $post ) {
393
394 $start_date = self::ics_format_time( $post->post_date );
395 $end_date = self::ics_format_time( $post->post_date, 5 * MINUTE_IN_SECONDS );
396 $last_modified = self::ics_format_time( $post->post_modified );
397 $post_status_obj = get_post_status_object( get_post_status( $post->ID ) );
398 // Remove the convert chars and wptexturize filters from the title
399 remove_filter( 'the_title', 'convert_chars' );
400 remove_filter( 'the_title', 'wptexturize' );
401
402 $formatted_post = array(
403 'BEGIN' => 'VEVENT',
404 'UID' => $post->guid,
405 'SUMMARY' => $this->do_ics_escaping( apply_filters( 'the_title', $post->post_title ) ) . ' - ' . $post_status_obj->label,
406 'DTSTART' => $start_date,
407 'DTEND' => $end_date,
408 'LAST-MODIFIED' => $last_modified,
409 'URL' => get_post_permalink( $post->ID ),
410 );
411
412 // Description should include everything visible in the calendar popup
413 $information_fields = $this->get_post_information_fields( $post );
414 $formatted_post['DESCRIPTION'] = '';
415 if ( ! empty( $information_fields ) ) {
416 foreach( $information_fields as $key => $values ) {
417 $formatted_post['DESCRIPTION'] .= $values['label'] . ': ' . $values['value'] . '\n';
418 }
419 $formatted_post['DESCRIPTION'] = rtrim( $formatted_post['DESCRIPTION'] );
420 }
421
422 $formatted_post['END'] = 'VEVENT';
423
424 // @todo auto format any field longer than 75 bytes
425
426 $formatted_posts[] = $formatted_post;
427 }
428 }
429 }
430
431 // Other template data
432 $header = array(
433 'BEGIN' => 'VCALENDAR',
434 'VERSION' => '2.0',
435 'PRODID' => '-//Edit Flow//Edit Flow ' . EDIT_FLOW_VERSION . '//EN',
436 );
437
438 $footer = array(
439 'END' => 'VCALENDAR',
440 );
441
442 // Render the .ics template and set the content type
443 header( 'Content-type: text/calendar' );
444 foreach( array( $header, $formatted_posts, $footer ) as $section ) {
445 foreach( $section as $key => $value ) {
446 if ( is_string( $value ) )
447 echo $this->do_ics_line_folding( $key . ':' . $value );
448 else
449 foreach( $value as $k => $v ) {
450 echo $this->do_ics_line_folding( $k . ':' . $v );
451 }
452 }
453 }
454 die();
455
456 }
457
458 /**
459 * Perform line folding according to RFC 5545.
460 *
461 * @param string $line The line without trailing CRLF
462 * @return string The line after line-folding with all necessary CRLF.
463 */
464 function do_ics_line_folding( $line ) {
465 $len = mb_strlen( $line );
466 if ( $len <= 75) {
467 return $line . "\r\n";
468 }
469
470 $chunks = array();
471 $start = 0;
472 while( true ) {
473 $chunk = mb_substr( $line, $start, 75 );
474 $chunkLen = mb_strlen( $chunk );
475 $start += $chunkLen;
476 if ( $start < $len ) {
477 $chunks[] = $chunk . "\r\n ";
478 }
479 else {
480 $chunks[] = $chunk ."\r\n";
481 return implode( "", $chunks );
482 }
483 }
484 }
485
486 /**
487 * Perform the encoding necessary for ICS feed text.
488 *
489 * @param string $text The string that needs to be escaped
490 * @return string The string after escaping for ICS.
491 * @since 0.8
492 * */
493
494 function do_ics_escaping( $text ) {
495 $text = str_replace( ",", "\,", $text );
496 $text = str_replace( ";", "\:", $text );
497 $text = str_replace( "\\", "\\\\", $text );
498 return $text;
499 }
500
501 /**
502 * Convert a time string into a `.ics` formatted time string with the proper GMT offset
503 *
504 * @param $time_string - Any time string that `strtotime()` can understand
505 * @param int $offset_in_seconds - Allows to offset the timestamp generated from $time_string
506 *
507 * @return string|false
508 */
509 public static function ics_format_time( $time_string, $offset_in_seconds = 0) {
510
511 // Timestamp it
512 $timestamp = strtotime( $time_string );
513
514 if( ! $timestamp ) {
515 return false;
516 }
517
518 // Subtract GMT Offset to return to UTC+0
519 $timestamp -= get_option('gmt_offset') * HOUR_IN_SECONDS;
520
521 // Add manual offset
522 $timestamp += $offset_in_seconds;
523
524 // \T and \Z are escaped for literal T and Z characters
525 return date( 'Ymd\THis\Z', $timestamp );
526
527 }
528
529 /**
530 * Handle a request to regenerate the calendar feed secret
531 *
532 * @since 0.8
533 */
534 public function handle_regenerate_calendar_feed_secret() {
535
536 if ( ! isset( $_GET['action'] ) || 'ef_calendar_regenerate_calendar_feed_secret' != $_GET['action'] )
537 return;
538
539 if ( ! current_user_can( 'manage_options' ) )
540 wp_die( $this->module->messages['invalid-permissions'] );
541
542 if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'ef-regenerate-ics-key' ) )
543 wp_die( $this->module->messages['nonce-failed'] );
544
545 EditFlow()->update_module_option( $this->module->name, 'ics_secret_key', wp_generate_password() );
546
547 wp_safe_redirect( add_query_arg( 'message', 'key-regenerated', menu_page_url( $this->module->settings_slug, false ) ) );
548 exit;
549 }
550
551 /**
552 * Get a user's screen options
553 *
554 * @since 0.7
555 * @uses get_user_meta()
556 *
557 * @return array $screen_options The screen options values
558 */
559 function get_screen_options() {
560
561 $defaults = array(
562 'num_weeks' => (int)$this->total_weeks,
563 );
564 $current_user = wp_get_current_user();
565 $screen_options = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'screen_options', true );
566 $screen_options = array_merge( (array)$defaults, (array)$screen_options );
567
568 return $screen_options;
569 }
570
571 /**
572 * Get the user's filters for calendar, either with $_GET or from saved
573 *
574 * @uses get_user_meta()
575 * @return array $filters All of the set or saved calendar filters
576 */
577 function get_filters() {
578
579 $current_user = wp_get_current_user();
580 $filters = array();
581 $old_filters = $this->get_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', true );
582
583 $default_filters = array(
584 'post_status' => '',
585 'cpt' => '',
586 'cat' => '',
587 'author' => '',
588 'start_date' => date( 'Y-m-d', current_time( 'timestamp' ) ),
589 );
590 $old_filters = array_merge( $default_filters, (array)$old_filters );
591
592 // Sanitize and validate any newly added filters
593 foreach( $old_filters as $key => $old_value ) {
594 if ( isset( $_GET[$key] ) && false !== ( $new_value = $this->sanitize_filter( $key, $_GET[$key] ) ) )
595 $filters[$key] = $new_value;
596 else
597 $filters[$key] = $old_value;
598 }
599
600 // Set the start date as the beginning of the week, according to blog settings
601 $filters['start_date'] = $this->get_beginning_of_week( $filters['start_date'] );
602
603 $filters = apply_filters( 'ef_calendar_filter_values', $filters, $old_filters );
604
605 $this->update_user_meta( $current_user->ID, self::usermeta_key_prefix . 'filters', $filters );
606
607 return $filters;
608 }
609
610 /**
611 * Build all of the HTML for the calendar view
612 */
613 function view_calendar() {
614
615 $this->dropdown_taxonomies = array();
616
617 $supported_post_types = $this->get_post_types_for_module( $this->module );
618
619 // Get the user's screen options for displaying the data
620 $screen_options = $this->get_screen_options();
621 // Total number of weeks to display on the calendar. Run it through a filter in case we want to override the
622 // user's standard
623 $this->total_weeks = apply_filters( 'ef_calendar_total_weeks', $screen_options['num_weeks'], 'dashboard' );
624
625 $dotw = array(
626 'Sat',
627 'Sun',
628 );
629 $dotw = apply_filters( 'ef_calendar_weekend_days', $dotw );
630
631 // Get filters either from $_GET or from user settings
632 $filters = $this->get_filters();
633 // For generating the WP Query objects later on
634 $post_query_args = array(
635 'post_status' => $filters['post_status'],
636 'post_type' => $filters['cpt'],
637 'cat' => $filters['cat'],
638 'author' => $filters['author']
639 );
640 $this->start_date = $filters['start_date'];
641
642 // We use this later to label posts if they need labeling
643 if ( count( $supported_post_types ) > 1 ) {
644 $all_post_types = get_post_types( null, 'objects' );
645 }
646 $dates = array();
647 $heading_date = $filters['start_date'];
648 for ( $i=0; $i<7; $i++ ) {
649 $dates[$i] = $heading_date;
650 $heading_date = date( 'Y-m-d', strtotime( "+1 day", strtotime( $heading_date ) ) );
651 }
652
653 // we sort by post statuses....... eventually
654 $post_statuses = $this->get_post_statuses();
655 ?>
656 <div class="wrap">
657 <div id="ef-calendar-title"><!-- Calendar Title -->
658 <?php echo '<img src="' . esc_url( $this->module->img_url ) . '" class="module-icon icon32" />'; ?>
659 <h2><?php _e( 'Calendar', 'edit-flow' ); ?>&nbsp;<span class="time-range"><?php $this->calendar_time_range(); ?></span></h2>
660 </div><!-- /Calendar Title -->
661
662 <?php
663 // Handle posts that have been trashed or untrashed
664 if ( isset( $_GET['trashed'] ) || isset( $_GET['untrashed'] ) ) {
665
666 echo '<div id="trashed-message" class="updated"><p>';
667 if ( isset( $_GET['trashed'] ) && (int) $_GET['trashed'] ) {
668 printf( _n( 'Post moved to the trash.', '%d posts moved to the trash.', $_GET['trashed'] ), number_format_i18n( $_GET['trashed'] ) );
669 $ids = isset($_GET['ids']) ? $_GET['ids'] : 0;
670 $pid = explode( ',', $ids );
671 $post_type = get_post_type( $pid[0] );
672 echo ' <a href="' . esc_url( wp_nonce_url( "edit.php?post_type=$post_type&doaction=undo&action=untrash&ids=$ids", "bulk-posts" ) ) . '">' . __( 'Undo', 'edit-flow' ) . '</a><br />';
673 unset( $_GET['trashed'] );
674 }
675 if ( isset($_GET['untrashed'] ) && (int) $_GET['untrashed'] ) {
676 printf( _n( 'Post restored from the Trash.', '%d posts restored from the Trash.', $_GET['untrashed'] ), number_format_i18n( $_GET['untrashed'] ) );
677 unset( $_GET['undeleted'] );
678 }
679 echo '</p></div>';
680 }
681 ?>
682
683 <div id="ef-calendar-wrap"><!-- Calendar Wrapper -->
684
685 <?php $this->print_top_navigation( $filters, $dates ); ?>
686
687 <?php
688 $table_classes = array();
689 // CSS don't like our classes to start with numbers
690 if ( $this->total_weeks == 1 )
691 $table_classes[] = 'one-week-showing';
692 elseif ( $this->total_weeks == 2 )
693 $table_classes[] = 'two-weeks-showing';
694 elseif ( $this->total_weeks == 3 )
695 $table_classes[] = 'three-weeks-showing';
696
697 $table_classes = apply_filters( 'ef_calendar_table_classes', $table_classes );
698 ?>
699 <table id="ef-calendar-view" class="<?php echo esc_attr( implode( ' ', $table_classes ) ); ?>">
700 <thead>
701 <tr class="calendar-heading">
702 <?php echo $this->get_time_period_header( $dates ); ?>
703 </tr>
704 </thead>
705 <tbody>
706
707 <?php
708 $current_month = date_i18n( 'F', strtotime( $filters['start_date'] ) );
709 for( $current_week = 1; $current_week <= $this->total_weeks; $current_week++ ):
710 // We need to set the object variable for our posts_where filter
711 $this->current_week = $current_week;
712 $week_posts = $this->get_calendar_posts_for_week( $post_query_args );
713 $date_format = 'Y-m-d';
714 $week_single_date = $this->get_beginning_of_week( $filters['start_date'], $date_format, $current_week );
715 $week_dates = array();
716 $split_month = false;
717 for ( $i = 0 ; $i < 7; $i++ ) {
718 $week_dates[$i] = $week_single_date;
719 $single_date_month = date_i18n( 'F', strtotime( $week_single_date ) );
720 if ( $single_date_month != $current_month ) {
721 $split_month = $single_date_month;
722 $current_month = $single_date_month;
723 }
724 $week_single_date = date( 'Y-m-d', strtotime( "+1 day", strtotime( $week_single_date ) ) );
725 }
726 ?>
727 <?php if ( $split_month ): ?>
728 <tr class="month-marker">
729 <?php foreach( $week_dates as $key => $week_single_date ) {
730 if ( date_i18n( 'F', strtotime( $week_single_date ) ) != $split_month && date_i18n( 'F', strtotime( "+1 day", strtotime( $week_single_date ) ) ) == $split_month ) {
731 $previous_month = date_i18n( 'F', strtotime( $week_single_date ) );
732 echo '<td class="month-marker-previous">' . esc_html( $previous_month ) . '</td>';
733 } else if ( date_i18n( 'F', strtotime( $week_single_date ) ) == $split_month && date_i18n( 'F', strtotime( "-1 day", strtotime( $week_single_date ) ) ) != $split_month ) {
734 echo '<td class="month-marker-current">' . esc_html( $split_month ) . '</td>';
735 } else {
736 echo '<td class="month-marker-empty"></td>';
737 }
738 } ?>
739 </tr>
740 <?php endif; ?>
741
742 <tr class="week-unit">
743 <?php foreach( $week_dates as $day_num => $week_single_date ): ?>
744 <?php
745 // Somewhat ghetto way of sorting all of the day's posts by post status order
746 if ( !empty( $week_posts[$week_single_date] ) ) {
747 $week_posts_by_status = array();
748 foreach( $post_statuses as $post_status ) {
749 $week_posts_by_status[$post_status->slug] = array();
750 }
751 // These statuses aren't handled by custom statuses or post statuses
752 $week_posts_by_status['private'] = array();
753 $week_posts_by_status['publish'] = array();
754 $week_posts_by_status['future'] = array();
755 foreach( $week_posts[$week_single_date] as $num => $post ) {
756 $week_posts_by_status[$post->post_status][$num] = $post;
757 }
758 unset( $week_posts[$week_single_date] );
759 foreach( $week_posts_by_status as $status ) {
760 foreach( $status as $num => $post ) {
761 $week_posts[$week_single_date][] = $post;
762 }
763 }
764 }
765
766 $td_classes = array(
767 'day-unit',
768 );
769 $day_name = date( 'D', strtotime( $week_single_date ) );
770
771 if ( in_array( $day_name, $dotw ) )
772 $td_classes[] = 'weekend-day';
773
774 if ( $week_single_date == date( 'Y-m-d', current_time( 'timestamp' ) ) )
775 $td_classes[] = 'today';
776
777 // Last day of the week
778 if ( $day_num == 6 )
779 $td_classes[] = 'last-day';
780
781 $td_classes = apply_filters( 'ef_calendar_table_td_classes', $td_classes, $week_single_date );
782 ?>
783 <td class="<?php echo esc_attr( implode( ' ', $td_classes ) ); ?>" id="<?php echo esc_attr( $week_single_date ); ?>">
784 <button class='schedule-new-post-button'>+</button>
785 <?php if ( $week_single_date == date( 'Y-m-d', current_time( 'timestamp' ) ) ): ?>
786 <div class="day-unit-today"><?php _e( 'Today', 'edit-flow' ); ?></div>
787 <?php endif; ?>
788 <div class="day-unit-label"><?php echo esc_html( date( 'j', strtotime( $week_single_date ) ) ); ?></div>
789 <ul class="post-list">
790 <?php
791 $this->hidden = 0;
792 if ( !empty( $week_posts[$week_single_date] ) ) {
793
794 $week_posts[$week_single_date] = apply_filters( 'ef_calendar_posts_for_week', $week_posts[$week_single_date], $week_single_date );
795
796 foreach ( $week_posts[$week_single_date] as $num => $post ) {
797 $output = apply_filters( 'ef_pre_calendar_single_date_item_html', '', $this, $num, $post, $week_single_date );
798 if ( ! $output ) {
799 $output = $this->generate_post_li_html( $post, $week_single_date, $num );
800 }
801 echo $output;
802 }
803
804 }
805 ?>
806 </ul>
807 <?php if ( $this->hidden ): ?>
808 <a class="show-more" href="#"><?php printf( __( 'Show %d more', 'edit-flow' ), $this->hidden ); ?></a>
809 <?php endif; ?>
810
811 <?php if( current_user_can( $this->create_post_cap ) ) :
812 $date_formatted = date( 'D, M jS, Y', strtotime( $week_single_date ) );
813 ?>
814
815 <form method="POST" class="post-insert-dialog">
816 <?php /* translators: %1$s = post type name, %2$s = date */ ?>
817 <h1><?php echo sprintf( __( 'Schedule a %1$s for %2$s', 'edit-flow' ), $this->get_quick_create_post_type_name(), $date_formatted ); ?></h1>
818 <?php /* translators: %s = post type name */ ?>
819 <input type="text" class="post-insert-dialog-post-title" name="post-insert-dialog-post-title" placeholder="<?php echo esc_attr( sprintf( _x( '%s Title', 'post type name', 'edit-flow' ), $this->get_quick_create_post_type_name() ) ); ?>">
820 <input type="hidden" class="post-insert-dialog-post-date" name="post-insert-dialog-post-title" value="<?php echo esc_attr( $week_single_date ); ?>">
821 <div class="post-insert-dialog-controls">
822 <input type="submit" class="button left" value="<?php echo esc_html( sprintf( _x( 'Create %s', 'post type name', 'edit-flow' ), $this->get_quick_create_post_type_name() ) ); ?>">
823 <a class="post-insert-dialog-edit-post-link" href="#"><?php echo esc_html( sprintf( _x( 'Edit %s', 'post type name', 'edit-flow' ), $this->get_quick_create_post_type_name() ) ); ?>&nbsp;&raquo;</a>
824 </div>
825 <div class="spinner">&nbsp;</div>
826 </form>
827 <?php endif; ?>
828
829 </td>
830 <?php endforeach; ?>
831 </tr>
832
833 <?php endfor; ?>
834
835 </tbody>
836 </table><!-- /Week Wrapper -->
837 <?php
838 // Nonce field for AJAX actions
839 wp_nonce_field( 'ef-calendar-modify', 'ef-calendar-modify' ); ?>
840
841 <div class="clear"></div>
842 </div><!-- /Calendar Wrapper -->
843
844 </div>
845
846 <?php
847
848 }
849
850 /**
851 * Generates the HTML for a single post item in the calendar
852 * @param obj $post The WordPress post in question
853 * @param str $post_date The date of the post
854 * @param int $num The index of the post
855 *
856 * @return str HTML for a single post item
857 */
858 function generate_post_li_html( $post, $post_date, $num = 0 ){
859
860 $can_modify = ( $this->current_user_can_modify_post( $post ) ) ? 'can_modify' : 'read_only';
861 $cache_key = $post->ID . $can_modify . '_' . get_current_user_id();
862 $cache_val = wp_cache_get( $cache_key, self::$post_li_html_cache_key );
863 // Because $num is pertinent to the display of the post LI, need to make sure that's what's in cache
864 if ( is_array( $cache_val ) && $cache_val['num'] == $num ) {
865 $this->hidden = $cache_val['hidden'];
866 return $cache_val['post_li_html'];
867 }
868
869 ob_start();
870 $post_id = $post->ID;
871 $edit_post_link = get_edit_post_link( $post_id );
872 $status_object = get_post_status_object( get_post_status( $post_id ) );
873
874 $post_classes = array(
875 'day-item',
876 'custom-status-' . $post->post_status,
877 );
878 // Only allow the user to drag the post if they have permissions to
879 // or if it's in an approved post status
880 // This is checked on the ajax request too.
881 if ( $this->current_user_can_modify_post( $post ) && !in_array( $post->post_status, $this->published_statuses ) )
882 $post_classes[] = 'sortable';
883
884 if ( in_array( $post->post_status, $this->published_statuses ) )
885 $post_classes[] = 'is-published';
886
887 // Hide posts over a certain number to prevent clutter, unless user is only viewing 1 or 2 weeks
888 $max_visible_posts = apply_filters( 'ef_calendar_max_visible_posts_per_date', $this->max_visible_posts_per_date);
889
890 if ( $num >= $max_visible_posts && $this->total_weeks > 2 ) {
891 $post_classes[] = 'hidden';
892 $this->hidden++;
893 }
894 $post_classes = apply_filters( 'ef_calendar_table_td_li_classes', $post_classes, $post_date, $post->ID );
895
896 ?>
897 <li class="<?php echo esc_attr( implode( ' ', $post_classes ) ); ?>" id="post-<?php echo esc_attr( $post->ID ); ?>">
898 <div style="clear:right;"></div>
899 <div class="item-static">
900 <div class="item-default-visible">
901 <div class="item-status"><span class="status-text"><?php echo esc_html( $status_object->label ); ?></span></div>
902 <div class="inner">
903 <span class="item-headline post-title"><strong><?php echo esc_html( _draft_or_post_title( $post->ID ) ); ?></strong></span>
904 </div>
905 <?php do_action( 'ef_calendar_item_html', $post->ID ); ?>
906 </div>
907 <div class="item-inner">
908 <?php $this->get_inner_information( $this->get_post_information_fields( $post ), $post ); ?>
909 </div>
910 </div>
911 </li>
912 <?php
913
914 $post_li_html = ob_get_contents();
915 ob_end_clean();
916
917 $post_li_cache = array(
918 'num' => $num,
919 'post_li_html' => $post_li_html,
920 'hidden' => $this->hidden,
921 );
922 wp_cache_set( $cache_key, $post_li_cache, self::$post_li_html_cache_key );
923
924 return $post_li_html;
925
926 } // generate_post_li_html()
927
928 /**
929 * get_inner_information description
930 * Functionality for generating the inner html elements on the calendar
931 * has been separated out so various ajax functions can reload certain
932 * parts of an inner html element.
933 * @param array $ef_calendar_item_information_fields
934 * @param WP_Post $post
935 * @param array $published_statuses
936 *
937 * @since 0.8
938 */
939 function get_inner_information( $ef_calendar_item_information_fields, $post ) {
940 ?>
941 <table class="item-information">
942 <?php foreach( $this->get_post_information_fields( $post ) as $field => $values ): ?>
943 <tr class="item-field item-information-<?php echo esc_attr( $field ); ?>">
944 <th class="label"><?php echo esc_html( $values['label'] ); ?>:</th>
945 <?php if ( $values['value'] && isset($values['type']) ): ?>
946 <?php if( isset( $values['editable'] ) && $this->current_user_can_modify_post( $post ) ) : ?>
947 <td class="value<?php if( $values['editable'] ) { ?> editable-value<?php } ?>"><?php echo esc_html( $values['value'] ); ?></td>
948 <?php if( $values['editable'] ): ?>
949 <td class="editable-html hidden" data-type="<?php echo $values['type']; ?>" data-metadataterm="<?php echo str_replace( 'editorial-metadata-', '', str_replace( 'tax_', '', $field ) ); ?>"><?php echo $this->get_editable_html( $values['type'], $values['value'] ); ?></td>
950 <?php endif; ?>
951 <?php else: ?>
952 <td class="value"><?php echo esc_html( $values['value'] ); ?></td>
953 <?php endif; ?>
954 <?php elseif( $values['value'] ): ?>
955 <td class="value"><?php echo esc_html( $values['value'] ); ?></td>
956 <?php else: ?>
957 <td class="value"><em class="none"><?php echo _e( 'None', 'edit-flow' ); ?></em></td>
958 <?php endif; ?>
959 </tr>
960 <?php endforeach; ?>
961 <?php do_action( 'ef_calendar_item_additional_html', $post->ID ); ?>
962 </table>
963 <?php
964 $post_type_object = get_post_type_object( $post->post_type );
965 $item_actions = array();
966 if ( $this->current_user_can_modify_post( $post ) ) {
967 // Edit this post
968 $item_actions['edit'] = '<a href="' . get_edit_post_link( $post->ID, true ) . '" title="' . esc_attr( __( 'Edit this item', 'edit-flow' ) ) . '">' . __( 'Edit', 'edit-flow' ) . '</a>';
969 // Trash this post
970 $item_actions['trash'] = '<a href="'. get_delete_post_link( $post->ID) . '" title="' . esc_attr( __( 'Trash this item' ), 'edit-flow' ) . '">' . __( 'Trash', 'edit-flow' ) . '</a>';
971 // Preview/view this post
972 if ( !in_array( $post->post_status, $this->published_statuses ) ) {
973 $item_actions['view'] = '<a href="' . esc_url( apply_filters( 'preview_post_link', add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ), $post ) ) . '" title="' . esc_attr( sprintf( __( 'Preview &#8220;%s&#8221;', 'edit-flow' ), $post->post_title ) ) . '" rel="permalink">' . __( 'Preview', 'edit-flow' ) . '</a>';
974 } elseif ( 'trash' != $post->post_status ) {
975 $item_actions['view'] = '<a href="' . get_permalink( $post->ID ) . '" title="' . esc_attr( sprintf( __( 'View &#8220;%s&#8221;', 'edit-flow' ), $post->post_title ) ) . '" rel="permalink">' . __( 'View', 'edit-flow' ) . '</a>';
976 }
977 //Save metadata
978 $item_actions['save hidden'] = '<a href="#savemetadata" id="save-editorial-metadata" class="post-'. $post->ID .'" title="'. esc_attr( sprintf( __( 'Save &#8220;%s&#8221;', 'edit-flow' ), $post->post_title ) ) . '" >' . __( 'Save', 'edit-flow') . '</a>';
979 }
980 // Allow other plugins to add actions
981 $item_actions = apply_filters( 'ef_calendar_item_actions', $item_actions, $post->ID );
982 if ( count( $item_actions ) ) {
983 echo '<div class="item-actions">';
984 $html = '';
985 foreach ( $item_actions as $class => $item_action ) {
986 $html .= '<span class="' . esc_attr( $class ) . '">' . $item_action . ' | </span> ';
987 }
988 echo rtrim( $html, '| ' );
989 echo '</div>';
990 }
991 ?>
992 <div style="clear:right;"></div>
993 <?php
994
995 } // generate_post_li_html()
996
997 function get_editable_html( $type, $value ) {
998
999 switch( $type ) {
1000 case 'text':
1001 case 'location':
1002 case 'number':
1003 return '<input type="text" class="metadata-edit-' . $type . '" value="' . $value . '"/>';
1004 break;
1005 case 'paragraph':
1006 return '<textarea type="text" class="metadata-edit-' . $type . '">' . $value . '</textarea>';
1007 break;
1008 case 'date':
1009 return '<input type="text" value="' . $value . '" class="date-time-pick metadata-edit-' . $type . '"/>';
1010 break;
1011 case 'checkbox':
1012 $output = '<select class="metadata-edit">';
1013
1014 if( $value == 'No' )
1015 $output .= '<option value="0">No</option><option value="1">Yes</option>';
1016 else
1017 $output .= '<option value="1">Yes</option><option value="0">No</option>';
1018
1019 $output .= '</select>';
1020
1021 return $output;
1022 break;
1023 case 'user':
1024 return wp_dropdown_users( array( 'echo' => false ) );
1025 break;
1026 case 'taxonomy':
1027 return '<input type="text" class="metadata-edit-' . $type . '" value="' . $value . '" />';
1028 break;
1029 case 'taxonomy hierarchical':
1030 return wp_dropdown_categories( array( 'echo' => 0, 'hide_empty' => 0 ) );
1031 break;
1032 }
1033 }
1034
1035 /**
1036 * Get the information fields to be presented with each post popup
1037 *
1038 * @since 0.8
1039 *
1040 * @param obj $post Post to gather information fields for
1041 * @return array $information_fields All of the information fields to be presented
1042 */
1043 function get_post_information_fields( $post ) {
1044
1045 $information_fields = array();
1046 // Post author
1047 $information_fields['author'] = array(
1048 'label' => __( 'Author', 'edit-flow' ),
1049 'value' => get_the_author_meta( 'display_name', $post->post_author ),
1050 'type' => 'author',
1051 );
1052
1053 // If the calendar supports more than one post type, show the post type label
1054 if ( count( $this->get_post_types_for_module( $this->module ) ) > 1 ) {
1055 $information_fields['post_type'] = array(
1056 'label' => __( 'Post Type', 'edit-flow' ),
1057 'value' => get_post_type_object( $post->post_type )->labels->singular_name,
1058 );
1059 }
1060 // Publication time for published statuses
1061 $published_statuses = array(
1062 'publish',
1063 'future',
1064 'private',
1065 );
1066 if ( in_array( $post->post_status, $published_statuses ) ) {
1067 if ( $post->post_status == 'future' ) {
1068 $information_fields['post_date'] = array(
1069 'label' => __( 'Scheduled', 'edit-flow' ),
1070 'value' => get_the_time( null, $post->ID ),
1071 );
1072 } else {
1073 $information_fields['post_date'] = array(
1074 'label' => __( 'Published', 'edit-flow' ),
1075 'value' => get_the_time( null, $post->ID ),
1076 );
1077 }
1078 }
1079 // Taxonomies and their values
1080 $args = array(
1081 'post_type' => $post->post_type,
1082 );
1083 $taxonomies = get_object_taxonomies( $args, 'object' );
1084 foreach( (array)$taxonomies as $taxonomy ) {
1085 // Sometimes taxonomies skip by, so let's make sure it has a label too
1086 if ( !$taxonomy->public || !$taxonomy->label )
1087 continue;
1088
1089 $terms = get_the_terms( $post->ID, $taxonomy->name );
1090 if ( ! $terms || is_wp_error( $terms ) )
1091 continue;
1092
1093 $key = 'tax_' . $taxonomy->name;
1094 if ( count( $terms ) ) {
1095 $value = '';
1096 foreach( (array)$terms as $term ) {
1097 $value .= $term->name . ', ';
1098 }
1099 $value = rtrim( $value, ', ' );
1100 } else {
1101 $value = '';
1102 }
1103 //Used when editing editorial metadata and post meta
1104 if ( is_taxonomy_hierarchical( $taxonomy->name ) )
1105 $type = 'taxonomy hierarchical';
1106 else
1107 $type = 'taxonomy';
1108
1109 $information_fields[$key] = array(
1110 'label' => $taxonomy->label,
1111 'value' => $value,
1112 'type' => $type,
1113 );
1114
1115 if( $post->post_type == 'page' )
1116 $ed_cap = 'edit_page';
1117 else
1118 $ed_cap = 'edit_post';
1119
1120 if( current_user_can( $ed_cap, $post->ID ) )
1121 $information_fields[$key]['editable'] = true;
1122 }
1123
1124 $information_fields = apply_filters( 'ef_calendar_item_information_fields', $information_fields, $post->ID );
1125 foreach( $information_fields as $field => $values ) {
1126 // Allow filters to hide empty fields or to hide any given individual field. Hide empty fields by default.
1127 if ( ( apply_filters( 'ef_calendar_hide_empty_item_information_fields', true, $post->ID ) && empty( $values['value'] ) )
1128 || apply_filters( "ef_calendar_hide_{$field}_item_information_field", false, $post->ID ) )
1129 unset( $information_fields[$field] );
1130 }
1131 return $information_fields;
1132 }
1133
1134 /**
1135 * Generates the filtering and navigation options for the top of the calendar
1136 *
1137 * @param array $filters Any set filters
1138 * @param array $dates All of the days of the week. Used for generating navigation links
1139 */
1140 function print_top_navigation( $filters, $dates ) {
1141 ?>
1142 <ul class="ef-calendar-navigation">
1143 <li id="calendar-filter">
1144 <form method="GET">
1145 <input type="hidden" name="page" value="calendar" />
1146 <input type="hidden" name="start_date" value="<?php echo esc_attr( $filters['start_date'] ); ?>"/>
1147 <!-- Filter by status -->
1148 <?php
1149 foreach( $this->calendar_filters() as $select_id => $select_name ) {
1150 echo $this->calendar_filter_options( $select_id, $select_name, $filters );
1151 }
1152 ?>
1153 <input type="submit" id="post-query-submit" class="button-primary button" value="<?php _e( 'Filter', 'edit-flow' ); ?>"/>
1154 </form>
1155 </li>
1156 <!-- Clear filters functionality (all of the fields, but empty) -->
1157 <li>
1158 <form method="GET">
1159 <input type="hidden" name="page" value="calendar" />
1160 <input type="hidden" name="start_date" value="<?php echo esc_attr( $filters['start_date'] ); ?>"/>
1161 <?php
1162 foreach( $this->calendar_filters() as $select_id => $select_name )
1163 echo '<input type="hidden" name="'.$select_name.'" value="" />';
1164 ?>
1165 <input type="submit" id="post-query-clear" class="button-secondary button" value="<?php _e( 'Reset', 'edit-flow' ); ?>"/>
1166 </form>
1167 </li>
1168
1169 <?php /** Previous and next navigation items (translatable so they can be increased if needed )**/ ?>
1170 <li class="date-change next-week">
1171 <a title="<?php printf( __( 'Forward 1 week', 'edit-flow' ) ); ?>" href="<?php echo esc_url( $this->get_pagination_link( 'next', $filters, 1 ) ); ?>"><?php _e( '&rsaquo;', 'edit-flow' ); ?></a>
1172 <?php if ( $this->total_weeks > 1): ?>
1173 <a title="<?php printf( __( 'Forward %d weeks', 'edit-flow' ), $this->total_weeks ); ?>" href="<?php echo esc_url( $this->get_pagination_link( 'next', $filters ) ); ?>"><?php _e( '&raquo;', 'edit-flow' ); ?></a>
1174 <?php endif; ?>
1175 </li>
1176 <li class="date-change today">
1177 <a title="<?php printf( __( 'Today is %s', 'edit-flow' ), date( get_option( 'date_format' ), current_time( 'timestamp' ) ) ); ?>" href="<?php echo esc_url( $this->get_pagination_link( 'next', $filters, 0 ) ); ?>"><?php _e( 'Today', 'edit-flow' ); ?></a>
1178 </li>
1179 <li class="date-change previous-week">
1180 <?php if ( $this->total_weeks > 1): ?>
1181 <a title="<?php printf( __( 'Back %d weeks', 'edit-flow' ), $this->total_weeks ); ?>" href="<?php echo esc_url( $this->get_pagination_link( 'previous', $filters ) ); ?>"><?php _e( '&laquo;', 'edit-flow' ); ?></a>
1182 <?php endif; ?>
1183 <a title="<?php printf( __( 'Back 1 week', 'edit-flow' ) ); ?>" href="<?php echo esc_url( $this->get_pagination_link( 'previous', $filters, 1 ) ); ?>"><?php _e( '&lsaquo;', 'edit-flow' ); ?></a>
1184 </li>
1185 <li class="ajax-actions">
1186 <img class="waiting" style="display:none;" src="<?php echo esc_url( admin_url( 'images/wpspin_light.gif' ) ); ?>" alt="" />
1187 </li>
1188 </ul>
1189 <?php
1190 }
1191
1192 /**
1193 * Generate the calendar header for a given range of dates
1194 *
1195 * @param array $dates Date range for the header
1196 * @return string $html Generated HTML for the header
1197 */
1198 function get_time_period_header( $dates ) {
1199
1200 $html = '';
1201 foreach( $dates as $date ) {
1202 $html .= '<th class="column-heading" >';
1203 $html .= esc_html( date_i18n('l', strtotime( $date ) ) );
1204 $html .= '</th>';
1205 }
1206
1207 return $html;
1208
1209 }
1210
1211 /**
1212 * Query to get all of the calendar posts for a given day
1213 *
1214 * @param array $args Any filter arguments we want to pass
1215 * @param string $request_context Where the query is coming from, to distinguish dashboard and subscriptions
1216 * @return array $posts All of the posts as an array sorted by date
1217 */
1218 function get_calendar_posts_for_week( $args = array(), $context = 'dashboard' ) {
1219
1220 $supported_post_types = $this->get_post_types_for_module( $this->module );
1221 $defaults = array(
1222 'post_status' => null,
1223 'cat' => null,
1224 'author' => null,
1225 'post_type' => $supported_post_types,
1226 'posts_per_page' => 200,
1227 );
1228
1229 $args = array_merge( $defaults, $args );
1230
1231 // Unpublished as a status is just an array of everything but 'publish'.
1232 if ( 'unpublish' == $args['post_status'] ) {
1233 $args['post_status'] = '';
1234 $post_stati = get_post_stati();
1235 unset($post_stati['inherit'], $post_stati['auto-draft'], $post_stati['trash'], $post_stati['publish'] );
1236 if ( ! apply_filters( 'ef_show_scheduled_as_unpublished', false ) ) {
1237 unset( $post_stati['future'] );
1238 }
1239 foreach ( $post_stati as $post_status ) {
1240 $args['post_status'] .= $post_status . ', ';
1241 }
1242 }
1243 // The WP functions for printing the category and author assign a value of 0 to the default
1244 // options, but passing this to the query is bad (trashed and auto-draft posts appear!), so
1245 // unset those arguments.
1246 if ( $args['cat'] === '0' ) {
1247 unset( $args['cat'] );
1248 }
1249 if ( $args['author'] === '0' ) {
1250 unset( $args['author'] );
1251 }
1252
1253 if ( empty( $args['post_type'] ) || ! in_array( $args['post_type'], $supported_post_types ) ) {
1254 $args['post_type'] = $supported_post_types;
1255 }
1256
1257 $beginning_date = $this->get_beginning_of_week( $this->start_date, 'Y-m-d', $this->current_week );
1258 $ending_date = date( "Y-m-d", strtotime( $beginning_date ) + WEEK_IN_SECONDS );
1259
1260 $args['date_query'] = array(
1261 'after' => $beginning_date,
1262 'before' => $ending_date,
1263 'inclusive' => true,
1264 );
1265
1266 // Filter for an end user to implement any of their own query args
1267 $args = apply_filters( 'ef_calendar_posts_query_args', $args, $context );
1268 $post_results = new WP_Query( $args );
1269
1270 $posts = array();
1271 while ( $post_results->have_posts() ) {
1272 $post_results->the_post();
1273 global $post;
1274 $key_date = date( 'Y-m-d', strtotime( $post->post_date ) );
1275 $posts[$key_date][] = $post;
1276 }
1277
1278 return $posts;
1279
1280 }
1281
1282 /**
1283 * Gets the link for the next time period
1284 *
1285 * @param string $direction 'previous' or 'next', direction to go in time
1286 * @param array $filters Any filters that need to be applied
1287 * @param int $weeks_offset Number of weeks we're offsetting the range
1288 * @return string $url The URL for the next page
1289 */
1290 function get_pagination_link( $direction = 'next', $filters = array(), $weeks_offset = null ) {
1291
1292 $supported_post_types = $this->get_post_types_for_module( $this->module );
1293
1294 if ( !isset( $weeks_offset ) )
1295 $weeks_offset = $this->total_weeks;
1296 else if ( $weeks_offset == 0 )
1297 $filters['start_date'] = $this->get_beginning_of_week( date( 'Y-m-d', current_time( 'timestamp' ) ) );
1298
1299 if ( $direction == 'previous' )
1300 $weeks_offset = '-' . $weeks_offset;
1301
1302 $filters['start_date'] = date( 'Y-m-d', strtotime( $weeks_offset . " weeks", strtotime( $filters['start_date'] ) ) );
1303 $url = add_query_arg( $filters, menu_page_url( $this->module->slug, false ) );
1304
1305 if ( count( $supported_post_types ) > 1 )
1306 $url = add_query_arg( 'cpt', $filters['cpt'] , $url );
1307
1308 return $url;
1309
1310 }
1311
1312 /**
1313 * Given a day in string format, returns the day at the beginning of that week, which can be the given date.
1314 * The beginning of the week is determined by the blog option, 'start_of_week'.
1315 *
1316 * @see http://www.php.net/manual/en/datetime.formats.date.php for valid date formats
1317 *
1318 * @param string $date String representing a date
1319 * @param string $format Date format in which the beginning of the week should be returned
1320 * @param int $week Number of weeks we're offsetting the range
1321 * @return string $formatted_start_of_week Beginning of the week
1322 */
1323 function get_beginning_of_week( $date, $format = 'Y-m-d', $week = 1 ) {
1324
1325 $date = strtotime( $date );
1326 $start_of_week = get_option( 'start_of_week' );
1327 $day_of_week = date( 'w', $date );
1328 $date += (( $start_of_week - $day_of_week - 7 ) % 7) * 60 * 60 * 24 ;
1329 $date = strtotime ( '+' . ( $week - 1 ) . ' week', $date ) ;
1330 $formatted_start_of_week = date( $format, $date );
1331 return $formatted_start_of_week;
1332
1333 }
1334
1335 /**
1336 * Given a day in string format, returns the day at the end of that week, which can be the given date.
1337 * The end of the week is determined by the blog option, 'start_of_week'.
1338 *
1339 * @see http://www.php.net/manual/en/datetime.formats.date.php for valid date formats
1340 *
1341 * @param string $date String representing a date
1342 * @param string $format Date format in which the end of the week should be returned
1343 * @param int $week Number of weeks we're offsetting the range
1344 * @return string $formatted_end_of_week End of the week
1345 */
1346 function get_ending_of_week( $date, $format = 'Y-m-d', $week = 1 ) {
1347
1348 $date = strtotime( $date );
1349 $end_of_week = get_option( 'start_of_week' ) - 1;
1350 $day_of_week = date( 'w', $date );
1351 $date += (( $end_of_week - $day_of_week + 7 ) % 7) * 60 * 60 * 24;
1352 $date = strtotime ( '+' . ( $week - 1 ) . ' week', $date ) ;
1353 $formatted_end_of_week = date( $format, $date );
1354 return $formatted_end_of_week;
1355
1356 }
1357
1358 /**
1359 * Human-readable time range for the calendar
1360 * Shows something like "for October 30th through November 26th" for a four-week period
1361 *
1362 * @since 0.7
1363 */
1364 function calendar_time_range() {
1365
1366 $first_datetime = strtotime( $this->start_date );
1367 $first_date = date_i18n( get_option( 'date_format' ), $first_datetime );
1368 $total_days = ( $this->total_weeks * 7 ) - 1;
1369 $last_datetime = strtotime( "+" . $total_days . " days", date( 'U', strtotime( $this->start_date ) ) );
1370 $last_date = date_i18n( get_option( 'date_format' ), $last_datetime );
1371 echo sprintf( __( 'for %1$s through %2$s', 'edit-flow' ), $first_date, $last_date );
1372 }
1373
1374 /**
1375 * Check whether the current user should have the ability to modify the post
1376 *
1377 * @since 0.7
1378 *
1379 * @param object $post The post object we're checking
1380 * @return bool $can Whether or not the current user can modify the post
1381 */
1382 function current_user_can_modify_post( $post ) {
1383
1384 if ( !$post )
1385 return false;
1386
1387 $post_type_object = get_post_type_object( $post->post_type );
1388
1389 // Editors and admins are fine
1390 if ( current_user_can( $post_type_object->cap->edit_others_posts, $post->ID ) )
1391 return true;
1392 // Authors and contributors can move their own stuff if it's not published
1393 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 ) )
1394 return true;
1395 // Those who can publish posts can move any of their own stuff
1396 if ( current_user_can( $post_type_object->cap->publish_posts, $post->ID ) && wp_get_current_user()->ID == $post->post_author )
1397 return true;
1398
1399 return false;
1400 }
1401
1402 /**
1403 * Register settings for notifications so we can partially use the Settings API
1404 * We use the Settings API for form generation, but not saving because we have our
1405 * own way of handling the data.
1406 *
1407 * @since 0.7
1408 */
1409 function register_settings() {
1410
1411 add_settings_section( $this->module->options_group_name . '_general', false, '__return_false', $this->module->options_group_name );
1412 add_settings_field( 'number_of_weeks', __( 'Number of weeks to show', 'edit-flow' ), array( $this, 'settings_number_weeks_option' ), $this->module->options_group_name, $this->module->options_group_name . '_general' );
1413 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' );
1414 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' );
1415 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' );
1416
1417 }
1418
1419 /**
1420 * Choose the post types that should be displayed on the calendar
1421 *
1422 * @since 0.7
1423 */
1424 function settings_post_types_option() {
1425 global $edit_flow;
1426 $edit_flow->settings->helper_option_custom_post_type( $this->module );
1427 }
1428
1429 /**
1430 * Choose the post type that should be created on the calendar
1431 *
1432 * @since 0.8
1433 */
1434 function settings_quick_create_post_type_option() {
1435
1436 $allowed_post_types = $this->get_all_post_types();
1437
1438 echo "<select name='" . $this->module->options_group_name . "[quick_create_post_type]'>";
1439 foreach( $allowed_post_types as $post_type => $title )
1440 echo "<option value='" . esc_attr( $post_type ) . "' " . selected( $post_type, $this->module->options->quick_create_post_type, false ) . ">".esc_html( $title )."</option>";
1441 echo "</select>";
1442
1443 }
1444
1445 /**
1446 * Give a bit of helper text to indicate the user can change
1447 * number of weeks in the screen options
1448 *
1449 * @since 0.7
1450 */
1451 function settings_number_weeks_option() {
1452 echo '<span class="description">' . __( 'The number of weeks shown on the calendar can be changed on a user-by-user basis using the calendar\'s screen options.', 'edit-flow' ) . '</span>';
1453 }
1454
1455 /**
1456 * Enable calendar subscriptions via .ics in iCal or Google Calendar
1457 *
1458 * @since 0.8
1459 */
1460 function settings_ics_subscription_option() {
1461 $options = array(
1462 'off' => __( 'Disabled', 'edit-flow' ),
1463 'on' => __( 'Enabled', 'edit-flow' ),
1464 );
1465 echo '<select id="ics_subscription" name="' . $this->module->options_group_name . '[ics_subscription]">';
1466 foreach ( $options as $value => $label ) {
1467 echo '<option value="' . esc_attr( $value ) . '"';
1468 echo selected( $this->module->options->ics_subscription, $value );
1469 echo '>' . esc_html( $label ) . '</option>';
1470 }
1471 echo '</select>';
1472
1473
1474 $regenerate_url = add_query_arg( 'action', 'ef_calendar_regenerate_calendar_feed_secret', admin_url( 'index.php' ) );
1475 $regenerate_url = wp_nonce_url( $regenerate_url, 'ef-regenerate-ics-key' );
1476 echo '&nbsp;&nbsp;&nbsp;<a href="' . esc_url( $regenerate_url ) . '">' . __( 'Regenerate calendar feed secret', 'edit-flow' ) . '</a>';
1477
1478 // If our secret key doesn't exist, create a new one
1479 if ( empty( $this->module->options->ics_secret_key ) )
1480 EditFlow()->update_module_option( $this->module->name, 'ics_secret_key', wp_generate_password() );
1481 }
1482
1483 /**
1484 * Validate the data submitted by the user in calendar settings
1485 *
1486 * @since 0.7
1487 */
1488 function settings_validate( $new_options ) {
1489
1490 $options = (array)$this->module->options;
1491
1492 $options['post_types'] = $this->clean_post_type_options( $new_options['post_types'], $this->module->post_type_support );
1493
1494 if ( in_array( $new_options['quick_create_post_type'], array_keys( $this->get_all_post_types() ) ) )
1495 $options['quick_create_post_type'] = $new_options['quick_create_post_type'];
1496
1497 if ( 'on' != $new_options['ics_subscription'] )
1498 $options['ics_subscription'] = 'off';
1499 else
1500 $options['ics_subscription'] = 'on';
1501
1502 return $options;
1503 }
1504
1505 /**
1506 * Settings page for calendar
1507 */
1508 function print_configure_view() {
1509 global $edit_flow;
1510 ?>
1511 <form class="basic-settings" action="<?php echo esc_url( menu_page_url( $this->module->settings_slug, false ) ); ?>" method="post">
1512 <?php settings_fields( $this->module->options_group_name ); ?>
1513 <?php do_settings_sections( $this->module->options_group_name ); ?>
1514 <?php
1515 echo '<input id="edit_flow_module_name" name="edit_flow_module_name" type="hidden" value="' . esc_attr( $this->module->name ) . '" />';
1516 ?>
1517 <p class="submit"><?php submit_button( null, 'primary', 'submit', false ); ?><a class="cancel-settings-link" href="<?php echo esc_url( EDIT_FLOW_SETTINGS_PAGE ); ?>"><?php _e( 'Back to Edit Flow', 'edit-flow' ); ?></a></p>
1518 </form>
1519 <?php
1520 }
1521
1522 /**
1523 * Ajax callback to insert a post placeholder for a particular date
1524 *
1525 * @since 0.8
1526 */
1527 function handle_ajax_insert_post() {
1528
1529 // Nonce check!
1530 if ( !wp_verify_nonce( $_POST['nonce'], 'ef-calendar-modify' ) )
1531 $this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] );
1532
1533 // Check that the user has the right capabilities to add posts to the calendar (defaults to 'edit_posts')
1534 if ( !current_user_can( $this->create_post_cap ) )
1535 $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );
1536
1537 if ( empty( $_POST['ef_insert_date'] ) )
1538 $this->print_ajax_response( 'error', __( 'No date supplied.', 'edit-flow' ) );
1539
1540 // Post type has to be visible on the calendar to create a placeholder
1541 if ( ! in_array( $this->module->options->quick_create_post_type, $this->get_post_types_for_module( $this->module ) ) )
1542 $this->print_ajax_response( 'error', __( 'Please change Quick Create to use a post type viewable on the calendar.', 'edit-flow' ) );
1543
1544 // Sanitize post values
1545 $post_title = sanitize_text_field( $_POST['ef_insert_title'] );
1546
1547 if( ! $post_title )
1548 $post_title = __( 'Untitled', 'edit-flow' );
1549
1550 $post_date = sanitize_text_field( $_POST['ef_insert_date'] );
1551
1552 $post_status = $this->get_default_post_status();
1553
1554 // Set new post parameters
1555 $post_placeholder = array(
1556 'post_title' => $post_title,
1557 'post_status' => $post_status,
1558 'post_date' => date( 'Y-m-d H:i:s', strtotime( $post_date ) ),
1559 'post_type' => $this->module->options->quick_create_post_type,
1560 );
1561
1562 // By default, adding a post to the calendar won't set the timestamp.
1563 // If the user desires that to be the behavior, they can set the result of this filter to 'true'
1564 // With how WordPress works internally, setting 'post_date_gmt' will set the timestamp
1565 if ( apply_filters( 'ef_calendar_allow_ajax_to_set_timestamp', false ) )
1566 $post_placeholder['post_date_gmt'] = date( 'Y-m-d H:i:s', strtotime( $post_date ) );
1567
1568 // Create the post
1569 $post_id = wp_insert_post( $post_placeholder );
1570
1571 if( $post_id ) { // success!
1572
1573 $post = get_post( $post_id );
1574
1575 // Generate the HTML for the post item so it can be injected
1576 $post_li_html = $this->generate_post_li_html( $post, $post_date );
1577
1578 // announce success and send back the html to inject
1579 $this->print_ajax_response( 'success', $post_li_html );
1580
1581 } else {
1582 $this->print_ajax_response( 'error', __( 'Post could not be created', 'edit-flow' ) );
1583 }
1584 }
1585
1586 /**
1587 * Returns the singular label for the posts that are
1588 * quick-created on the calendar
1589 *
1590 * @return str Singular label for a post-type
1591 */
1592 function get_quick_create_post_type_name(){
1593
1594 $post_type_slug = $this->module->options->quick_create_post_type;
1595 $post_type_obj = get_post_type_object( $post_type_slug );
1596
1597 return $post_type_obj->labels->singular_name ? $post_type_obj->labels->singular_name : $post_type_slug;
1598 }
1599
1600 /**
1601 * ajax_ef_calendar_update_metadata
1602 * Update the metadata from the calendar.
1603 * @return string representing the overlay
1604 *
1605 * @since 0.8
1606 */
1607 function handle_ajax_update_metadata() {
1608 global $wpdb;
1609
1610 if ( ! wp_verify_nonce( $_POST['nonce'], 'ef-calendar-modify' ) )
1611 $this->print_ajax_response( 'error', $this->module->messages['nonce-failed'] );
1612
1613 // Check that we got a proper post
1614 $post_id = ( int )$_POST['post_id'];
1615 $post = get_post( $post_id );
1616
1617 if ( ! $post )
1618 $this->print_ajax_response( 'error', $this->module->messages['missing-post'] );
1619
1620
1621 if( $post->post_type == 'page' )
1622 $edit_check = 'edit_page';
1623 else
1624 $edit_check = 'edit_post';
1625
1626 if ( !current_user_can( $edit_check, $post->ID ) )
1627 $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );
1628
1629 // Check that the user can modify the post
1630 if ( ! $this->current_user_can_modify_post( $post ) )
1631 $this->print_ajax_response( 'error', $this->module->messages['invalid-permissions'] );
1632
1633 $default_types = array(
1634 'author',
1635 'taxonomy',
1636 );
1637
1638 $metadata_types = array();
1639
1640 if ( !$this->module_enabled( 'editorial_metadata' ) )
1641 $this->print_ajax_response( 'error', $this->module->messages['update-error'] );
1642
1643 $metadata_types = array_keys( EditFlow()->editorial_metadata->get_supported_metadata_types() );
1644
1645 // Update an editorial metadata field
1646 if ( isset( $_POST['metadata_type'] ) && in_array( $_POST['metadata_type'], $metadata_types ) ) {
1647 $post_meta_key = sanitize_text_field( '_ef_editorial_meta_' . $_POST['metadata_type'] . '_' . $_POST['metadata_term'] );
1648
1649 //Javascript date parsing is terrible, so use strtotime in php
1650 if ( $_POST['metadata_type'] == 'date' )
1651 $metadata_value = strtotime( sanitize_text_field( $_POST['metadata_value'] ) );
1652 else
1653 $metadata_value = sanitize_text_field( $_POST['metadata_value'] );
1654
1655 update_post_meta( $post->ID, $post_meta_key, $metadata_value );
1656 $response = 'success';
1657 } else {
1658 switch( $_POST['metadata_type'] ) {
1659 case 'taxonomy':
1660 case 'taxonomy hierarchical':
1661 $response = wp_set_post_terms( $post->ID, $_POST['metadata_value'], $_POST['metadata_term'] );
1662 break;
1663 default:
1664 $response = new WP_Error( 'invalid-type', __( 'Invalid metadata type', 'edit-flow' ) );
1665 break;
1666 }
1667 }
1668
1669 //Assuming we've got to this point, just regurgitate the value
1670 if ( ! is_wp_error( $response ) )
1671 $this->print_ajax_response( 'success', $_POST['metadata_value'] );
1672 else
1673 $this->print_ajax_response( 'error', __( 'Metadata could not be updated.', 'edit-flow' ) );
1674 }
1675
1676 function calendar_filters() {
1677 $select_filter_names = array();
1678
1679 $select_filter_names['post_status'] = 'post_status';
1680 $select_filter_names['cat'] = 'cat';
1681 $select_filter_names['author'] = 'author';
1682 $select_filter_names['type'] = 'cpt';
1683
1684 return apply_filters( 'ef_calendar_filter_names', $select_filter_names );
1685 }
1686
1687 /**
1688 * Sanitize a $_GET or similar filter being used on the calendar
1689 *
1690 * @since 0.8
1691 *
1692 * @param string $key Filter being sanitized
1693 * @param string $dirty_value Value to be sanitized
1694 * @return string $sanitized_value Safe to use value
1695 */
1696 function sanitize_filter( $key, $dirty_value ) {
1697
1698 switch( $key ) {
1699 case 'post_status':
1700 // Whitelist-based validation for this parameter
1701 $valid_statuses = get_post_stati();
1702 $valid_statuses[] = 'unpublish';
1703 unset( $valid_statuses['inherit'], $valid_statuses['auto-draft'], $valid_statuses['trash'] );
1704 if ( in_array( $dirty_value, $valid_statuses ) )
1705 return $dirty_value;
1706 else
1707 return '';
1708 break;
1709 case 'cpt':
1710 $cpt = sanitize_key( $dirty_value );
1711 $supported_post_types = $this->get_post_types_for_module( $this->module );
1712 if ( $cpt && in_array( $cpt, $supported_post_types ) )
1713 return $cpt;
1714 else
1715 return '';
1716 break;
1717 case 'start_date':
1718 return date( 'Y-m-d', strtotime( $dirty_value ) );
1719 break;
1720 case 'cat':
1721 case 'author':
1722 return intval( $dirty_value );
1723 break;
1724 default:
1725 return false;
1726 break;
1727 }
1728 }
1729
1730 function calendar_filter_options( $select_id, $select_name, $filters ) {
1731 switch( $select_id ){
1732 case 'post_status':
1733 $post_stati = get_post_stati();
1734 unset( $post_stati['inherit'], $post_stati['auto-draft'], $post_stati['trash'] );
1735 ?>
1736 <select id="<?php echo $select_id; ?>" name="<?php echo $select_name; ?>" >
1737 <option value=""><?php _e( 'View all statuses', 'edit-flow' ); ?></option>
1738 <?php
1739 foreach ( $post_stati as $post_status ) {
1740 $value = $post_status;
1741 $status = get_post_status_object($post_status);
1742 echo "<option value='" . esc_attr( $value ) . "' " . selected( $value, $filters['post_status'] ) . ">" . esc_html( $status->label ) . "</option>";
1743 }
1744 ?>
1745 <option value="unpublish" <?php selected( 'unpublish', $filters['post_status'] ) ?> > <?php echo __( 'Unpublished', 'edit-flow' ) ?> </option>
1746 </select>
1747 <?php
1748 break;
1749 case 'cat':
1750 // Filter by categories, borrowed from wp-admin/edit.php
1751 if ( taxonomy_exists( 'category' ) ) {
1752 $category_dropdown_args = array(
1753 'show_option_all' => __( 'View all categories', 'edit-flow' ),
1754 'hide_empty' => 0,
1755 'hierarchical' => 1,
1756 'show_count' => 0,
1757 'orderby' => 'name',
1758 'selected' => $filters['cat']
1759 );
1760 wp_dropdown_categories( $category_dropdown_args );
1761 }
1762 break;
1763 case 'author':
1764 $users_dropdown_args = array(
1765 'show_option_all' => __( 'View all users', 'edit-flow' ),
1766 'name' => 'author',
1767 'selected' => $filters['author'],
1768 'who' => 'authors',
1769 );
1770 $users_dropdown_args = apply_filters( 'ef_calendar_users_dropdown_args', $users_dropdown_args );
1771 wp_dropdown_users( $users_dropdown_args );
1772 break;
1773 case 'type':
1774 $supported_post_types = $this->get_post_types_for_module( $this->module );
1775 if ( count( $supported_post_types ) > 1 ) {
1776 ?>
1777 <select id="type" name="cpt">
1778 <option value=""><?php _e( 'View all types', 'edit-flow' ); ?></option>
1779 <?php
1780 foreach ( $supported_post_types as $key => $post_type_name ) {
1781 $all_post_types = get_post_types( null, 'objects' );
1782 echo '<option value="' . esc_attr( $post_type_name ) . '"' . selected( $post_type_name, $filters['cpt'] ) . '>' . esc_html( $all_post_types[$post_type_name]->labels->name ) . '</option>';
1783 }
1784 ?>
1785 </select>
1786 <?php
1787 }
1788 break;
1789 default:
1790 do_action( 'ef_calendar_filter_display', $select_id, $select_name, $filters );
1791 break;
1792 }
1793 }
1794
1795 /**
1796 * When a post is updated, clean the <li> html post cache for it
1797 */
1798 public function action_clean_li_html_cache( $post_id ) {
1799
1800 wp_cache_delete( $post_id . 'can_modify', self::$post_li_html_cache_key );
1801 wp_cache_delete( $post_id . 'read_only', self::$post_li_html_cache_key );
1802 }
1803
1804 /**
1805 * This is a hack! hack! hack! until core is fixed
1806 *
1807 * The calendar uses 'post_date' field to store the position on the calendar
1808 * If a post has a core post status assigned (e.g. 'draft' or 'pending'), the `post_date`
1809 * field will be reset when `wp_update_post()`
1810 * is used: http://core.trac.wordpress.org/browser/tags/3.7.1/src/wp-includes/post.php#L2998
1811 *
1812 * This method temporarily caches the `post_date` field if it needs to be restored.
1813 *
1814 * @uses fix_post_date_on_update_part_two()
1815 */
1816 public function fix_post_date_on_update_part_one( $post_ID, $data ) {
1817
1818 $post = get_post( $post_ID );
1819
1820 // `post_date` is only nooped for these three statuses,
1821 // but don't try to persist if `post_date_gmt` is set
1822 if ( ! in_array( $post->post_status, array( 'draft', 'pending', 'auto-draft' ) )
1823 || '0000-00-00 00:00:00' !== $post->post_date_gmt
1824 || '0000-00-00 00:00:00' !== $data['post_date_gmt'] )
1825 return;
1826
1827 $this->post_date_cache[ $post_ID ] = $post->post_date;
1828
1829 }
1830
1831 /**
1832 * This is a hack! hack! hack! until core is fixed
1833 *
1834 * The calendar uses 'post_date' field to store the position on the calendar
1835 * If a post has a core post status assigned (e.g. 'draft' or 'pending'), the `post_date`
1836 * field will be reset when `wp_update_post()`
1837 * is used: http://core.trac.wordpress.org/browser/tags/3.7.1/src/wp-includes/post.php#L2998
1838 *
1839 * This method restores the `post_date` field if it needs to be restored.
1840 *
1841 * @uses fix_post_date_on_update_part_one()
1842 */
1843 public function fix_post_date_on_update_part_two( $post_ID, $post_after, $post_before ) {
1844 global $wpdb;
1845
1846 if ( empty( $this->post_date_cache[ $post_ID ] ) )
1847 return;
1848
1849 $post_date = $this->post_date_cache[ $post_ID ];
1850 unset( $this->post_date_cache[ $post_ID ] );
1851 $wpdb->update( $wpdb->posts, array( 'post_date' => $post_date ), array( 'ID' => $post_ID ) );
1852 clean_post_cache( $post_ID );
1853 }
1854
1855 } // EF_Calendar
1856
1857 } // class_exists('EF_Calendar')
1858