PluginProbe
Edit Flow / 0.9.9
Edit Flow v0.9.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.9, at modules/calendar/calendar.php

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