PluginProbe
Timed Content / 2.81
Timed Content v2.81
2.99 trunk 1.0 1.1 1.2 2.0 2.1 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.10 2.11 2.12 2.15 2.2 2.3 2.3.1 2.4 2.5 2.5.1 2.50 2.51 2.52 All 67 releases
timed-content / lib / class-timedcontentplugin.php

class-timedcontentplugin.php in Timed Content 2.81, at lib/class-timedcontentplugin.php

2,538 lines 87.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class TimedContentPlugin {
4 var $rule_freq_array;
5 var $rule_days_array;
6 var $rule_ordinal_array;
7 var $rule_ordinal_days_array;
8 var $rule_occurrence_custom_fields;
9 var $rule_pattern_custom_fields;
10 var $rule_recurrence_custom_fields;
11 var $rule_exceptions_custom_fields;
12 var $meridiem;
13 var $show_period;
14 var $show_period_labels;
15 var $show_leading_zero;
16 var $jquery_ui_datetime_datepicker_i18n;
17 var $jquery_ui_datetime_timepicker_i18n;
18 var $current_timezone;
19
20 /**
21 * Constructor
22 */
23 function __construct() {
24 add_filter( 'timed_content_filter', 'convert_smilies' );
25 add_filter( 'timed_content_filter', 'convert_chars' );
26 add_filter( 'timed_content_filter', 'prepend_attachment' );
27 add_filter( 'timed_content_filter', 'do_shortcode' );
28 add_filter( 'manage_' . TIMED_CONTENT_RULE_TYPE . '_posts_columns', array( $this, 'add_desc_column_head' ) );
29 add_filter( 'pre_get_posts', array( $this, 'timed_content_pre_get_posts' ) );
30 add_filter( 'post_updated_messages', array( $this, 'timed_content_rule_updated_messages' ), 1 );
31 add_filter( 'dashboard_glance_items', array( $this, 'add_rules_count' ) );
32
33 add_action( 'init', array( $this, 'init' ), 2 );
34 add_action( 'wp_head', array( $this, 'add_header_code' ), 1 );
35 add_action( 'manage_' . TIMED_CONTENT_RULE_TYPE . '_posts_custom_column', array( $this, 'add_desc_column_content' ), 10, 2 );
36 add_action( 'admin_enqueue_scripts', array( $this, 'add_admin_header_code' ), 1 );
37 add_action( 'admin_init', array( $this, 'set_tinymce_plugin_vars' ), 1 );
38 add_action( 'admin_init', array( $this, 'init_tinymce_plugin' ), 2 );
39 add_action( 'wp_ajax_timedContentPluginGetTinyMCEDialog', array( $this, 'timed_content_plugin_get_tinymce_dialog' ), 1 );
40 add_action( 'wp_ajax_timedContentPluginGetRulePeriodsAjax', array( $this, 'timed_content_plugin_get_rule_periods_ajax' ), 1 );
41 add_action( 'wp_ajax_timedContentPluginGetScheduleDescriptionAjax', array( $this, 'timed_content_plugin_get_schedule_description_ajax' ), 1 );
42 add_action( 'admin_head', array( $this, 'add_post_type_icons' ), 1 );
43
44 add_shortcode( TIMED_CONTENT_SHORTCODE_CLIENT, array( $this, 'client_show_html' ) );
45 add_shortcode( TIMED_CONTENT_SHORTCODE_SERVER, array( $this, 'server_show_html' ) );
46 add_shortcode( TIMED_CONTENT_SHORTCODE_RULE, array( $this, 'rules_show_html' ) );
47
48 $this->set_format_timezone( date_default_timezone_get() );
49 }
50
51 /**
52 * Initialise plugin
53 */
54 function init() {
55 global $wp_locale;
56
57 load_plugin_textdomain( 'timed-content', false, '/timed-content/lang/' );
58
59 $this->rule_freq_array = array(
60 0 => __( 'hourly', 'timed-content' ),
61 1 => __( 'daily', 'timed-content' ),
62 2 => __( 'weekly', 'timed-content' ),
63 3 => __( 'monthly', 'timed-content' ),
64 4 => __( 'yearly', 'timed-content' ),
65 );
66
67 $this->rule_days_array = array(
68 0 => __( 'Sunday', 'timed-content' ),
69 1 => __( 'Monday', 'timed-content' ),
70 2 => __( 'Tuesday', 'timed-content' ),
71 3 => __( 'Wednesday', 'timed-content' ),
72 4 => __( 'Thursday', 'timed-content' ),
73 5 => __( 'Friday', 'timed-content' ),
74 6 => __( 'Saturday', 'timed-content' ),
75 );
76
77 $this->rule_ordinal_array = array(
78 0 => __( 'first', 'timed-content' ),
79 1 => __( 'second', 'timed-content' ),
80 2 => __( 'third', 'timed-content' ),
81 3 => __( 'fourth', 'timed-content' ),
82 4 => __( 'last', 'timed-content' ),
83 );
84
85 $this->rule_ordinal_days_array = array(
86 0 => __( 'Sunday', 'timed-content' ),
87 1 => __( 'Monday', 'timed-content' ),
88 2 => __( 'Tuesday', 'timed-content' ),
89 3 => __( 'Wednesday', 'timed-content' ),
90 4 => __( 'Thursday', 'timed-content' ),
91 5 => __( 'Friday', 'timed-content' ),
92 6 => __( 'Saturday', 'timed-content' ),
93 7 => __( 'day', 'timed-content' ),
94 );
95
96 $this->jquery_ui_datetime_datepicker_i18n = array(
97 'closeText' => _x( 'Done', 'jQuery UI Datepicker Close label', 'timed-content' ), // Display text for close link
98 'prevText' => _x( 'Prev', 'jQuery UI Datepicker Previous label', 'timed-content' ), // Display text for previous month link
99 'nextText' => _x( 'Next', 'jQuery UI Datepicker Next label', 'timed-content' ), // Display text for next month link
100 'currentText' => _x( 'Today', 'jQuery UI Datepicker Today label', 'timed-content' ), // Display text for current month link
101 'weekHeader' => _x( 'Wk', 'jQuery UI Datepicker Week label', 'timed-content' ), // Column header for week of the year
102 // Replace the text indices for the following arrays with 0-based arrays
103 'monthNames' => $this->strip_array_indices( $wp_locale->month ), // Names of months for drop-down and formatting
104 'monthNamesShort' => $this->strip_array_indices( $wp_locale->month_abbrev ), // For formatting
105 'dayNames' => $this->strip_array_indices( $wp_locale->weekday ), // For formatting
106 'dayNamesShort' => $this->strip_array_indices( $wp_locale->weekday_abbrev ), // For formatting
107 'dayNamesMin' => $this->strip_array_indices( $wp_locale->weekday_initial ), // Column headings for days starting at Sunday
108 'dateFormat' => 'yy-mm-dd',
109 'firstDay' => get_option( 'start_of_week' ),
110 'isRTL' => $wp_locale->is_rtl(),
111 'showMonthAfterYear' => false, // True if the year select precedes month, false for month then year
112 'yearSuffix' => '', // Additional text to append to the year in the month headers
113 );
114
115 $tf = get_option( 'time_format' );
116 if ( false !== strpos( $tf, 'A' ) ) {
117 $this->meridiem = array( $wp_locale->meridiem['AM'], $wp_locale->meridiem['PM'] );
118 $this->show_period = true;
119 $this->show_period_labels = true;
120 $this->show_leading_zero = false;
121 } elseif ( false !== strpos( $tf, 'a' ) ) {
122 $this->meridiem = array( $wp_locale->meridiem['am'], $wp_locale->meridiem['pm'] );
123 $this->show_period = true;
124 $this->show_period_labels = true;
125 $this->show_leading_zero = false;
126 } else {
127 $this->meridiem = array( '', '' );
128 $this->show_period = false;
129 $this->show_period_labels = false;
130 $this->show_leading_zero = true;
131 }
132
133 $this->jquery_ui_datetime_timepicker_i18n = array(
134 'hourText' => _x( 'Hour', "jQuery UI Timepicker 'Hour' label", 'timed-content' ),
135 'minuteText' => _x( 'Minute', "jQuery UI Timepicker 'Minute' label", 'timed-content' ),
136 'timeSeparator' => _x( ':', 'jQuery UI Datepicker: Character used to separate hours and minutes in translated language', 'timed-content' ),
137 'closeButtonText' => _x( 'Done', "jQuery UI Timepicker 'Done' label", 'timed-content' ),
138 'nowButtonText' => _x( 'Now', "jQuery UI Timepicker 'Now' label", 'timed-content' ),
139 'deselectButtonText' => _x( 'Deselect', "jQuery UI Timepicker 'Deselect' label", 'timed-content' ),
140 'amPmText' => array( '', '' ),
141 'showPeriod' => false,
142 'showPeriodLabels' => false,
143 'showLeadingZero' => false,
144 'timeFormat' => 'G:i',
145 );
146
147 $this->timed_content_rule_type_init();
148 $this->setup_custom_fields();
149 }
150
151 /**
152 * Creates the Timed Content Rule post type and registers it with WordPress
153 */
154 function timed_content_rule_type_init() {
155 $labels = array(
156 'name' => _x( 'Timed Content rules', 'post type general name', 'timed-content' ),
157 'singular_name' => _x( 'Timed Content rule', 'post type singular name', 'timed-content' ),
158 'add_new' => _x(
159 'Add new',
160 'Menu item/button label on Timed Content Rules admin page',
161 'timed-content'
162 ),
163 'add_new_item' => __( 'Add new Timed Content rule', 'timed-content' ),
164 'edit_item' => __( 'Edit Timed Content rule', 'timed-content' ),
165 'new_item' => __( 'New Timed Content rule', 'timed-content' ),
166 'view_item' => __( 'View Timed Content rule', 'timed-content' ),
167 'search_items' => __( 'Search Timed Content rules', 'timed-content' ),
168 'not_found' => __( 'No Timed Content rules found', 'timed-content' ),
169 'not_found_in_trash' => __( 'No Timed Content rules found in trash', 'timed-content' ),
170 'parent_item_colon' => '',
171 'menu_name' => _x( 'Timed Content rules', 'post type general name', 'timed-content' ),
172 );
173 $args = array(
174 'labels' => $labels,
175 'description' => __(
176 'Create regular schedules to show or hide selected content in a page or post.',
177 'timed-content'
178 ),
179 'public' => false,
180 'publicly_queryable' => false,
181 'exclude_from_search' => false,
182 'show_ui' => true,
183 'show_in_menu' => true,
184 'show_in_nav_menus' => true,
185 'show_in_admin_bar' => true,
186 'query_var' => false,
187 'rewrite' => false,
188 'capability_type' => 'post',
189 'has_archive' => false,
190 'hierarchical' => false,
191 'menu_position' => 5,
192 'supports' => array( 'title' ),
193 );
194 register_post_type( TIMED_CONTENT_RULE_TYPE, $args );
195 }
196
197
198 /**
199 * Filter to change sort order to title
200 */
201 function timed_content_pre_get_posts( $query ) {
202 if ( $query->is_admin ) {
203 if ( $query->get( 'post_type' ) === TIMED_CONTENT_RULE_TYPE ) {
204 $query->set( 'orderby', 'title' );
205 $query->set( 'order', 'ASC' );
206 }
207 }
208
209 return $query;
210 }
211
212 /**
213 * Filter to customize CRUD messages for Timed Content Rules
214 */
215 function timed_content_rule_updated_messages( $messages ) {
216 global $post;
217
218 if ( ! empty( $post ) ) {
219 /* translators: date and time format to activate rule. http://ca2.php.net/manual/en/function.date.php*/
220 $post_date = date_i18n( __( 'M j, Y @ G:i', 'timed-content' ), strtotime( $post->post_date ) );
221 } else {
222 $post_date = '';
223 }
224
225 $messages[ TIMED_CONTENT_RULE_TYPE ] = array(
226 0 => '', // Unused. Messages start at index 1.
227 1 => __( 'Timed Content Rule updated.', 'timed-content' ),
228 2 => __( 'Custom field updated.', 'timed-content' ),
229 3 => __( 'Custom field deleted.', 'timed-content' ),
230 4 => __( 'Timed Content Rule updated.', 'timed-content' ),
231 5 => isset( $_GET['revision'] ) ? sprintf(
232 /* translators: %s: date and time of the revision */
233 __(
234 'Timed Content Rule restored to revision from %s',
235 'timed-content'
236 ),
237 wp_post_revision_title( (int) $_GET['revision'], false )
238 ) : false,
239 6 => __( 'Timed Content Rule published.', 'timed-content' ),
240 7 => __( 'Timed Content Rule saved.', 'timed-content' ),
241 8 => __( 'Timed Content Rule submitted.', 'timed-content' ),
242 9 => sprintf(
243 /* translators: %s: date and time to activate rule. */
244 __( 'Timed Content Rule scheduled for: %s.', 'timed-content' ),
245 '<strong>' . $post_date . '</strong>'
246 ),
247 10 => __( 'Timed Content Rule draft updated.', 'timed-content' ),
248 );
249
250 return $messages;
251 }
252
253 /**
254 * Convert localized date/time string to English
255 */
256 function date_time_to_english( $date, $time = '' ) {
257 $months = array(
258 'January',
259 'February',
260 'March',
261 'April',
262 'May',
263 'June',
264 'July',
265 'August',
266 'September',
267 'October',
268 'November',
269 'December',
270 );
271 $months_i18n = array(
272 __( 'January', 'timed-content' ),
273 __( 'February', 'timed-content' ),
274 __( 'March', 'timed-content' ),
275 __( 'April', 'timed-content' ),
276 __( 'May', 'timed-content' ),
277 __( 'June', 'timed-content' ),
278 __( 'July', 'timed-content' ),
279 __( 'August', 'timed-content' ),
280 __( 'September', 'timed-content' ),
281 __( 'October', 'timed-content' ),
282 __( 'November', 'timed-content' ),
283 __( 'December', 'timed-content' ),
284 );
285 $english_date = str_replace( $months_i18n, $months, $date );
286
287 return $english_date . ' ' . $time;
288 }
289
290 /**
291 * Advances a date by a set number of days
292 */
293 function get_next_day( $current, $interval_multiplier ) {
294 return strtotime( $interval_multiplier . ' day', $current );
295 }
296
297 /**
298 * Advances a date/time by a set number of hours
299 */
300 function get_next_hour( $current, $interval_multiplier ) {
301 return strtotime( $interval_multiplier . ' hour', $current );
302 }
303
304 /**
305 * Advances a date/time by a set number of weeks. If given an array of days of the week, this function will
306 * advance the date/time to the next day in that array in the jumped-to week. Use this function if you're
307 * repeating an action on specific days of the week (i.e., on Weekdays, Tuesdays and Thursdays, etc.).
308 */
309 function get_next_week( $current, $interval_multiplier, $days = array() ) {
310 // If $days is empty, advance $interval_multiplier weeks from $current and return the timestamp
311 if ( empty( $days ) ) {
312 return strtotime( $interval_multiplier . ' week', $current );
313 }
314
315 // Otherwise, set up an array combining the days of the week to repeat on and the current day
316 // (keys and values of the array will be the same, and the array is sorted)
317 $current_day_of_week_index = $this->format_timestamp( 'w', $current );
318 $days = array_merge( array( $current_day_of_week_index ), $days );
319 $days = array_unique( $days );
320 $days = array_values( $days );
321 sort( $days );
322 $days_of_week = array_combine( $days, $days );
323
324 // If the current day is the last one of the days of the week to repeat on, jump ahead to
325 // the next week to be repeating on and get the earliest day in the array
326 if ( max( $days_of_week ) === $current_day_of_week_index ) {
327 $pattern = ( ( 7 - $current_day_of_week_index ) + ( 7 * ( $interval_multiplier - 1 ) ) + ( min( array_keys( $days_of_week ) ) ) ) . ' day';
328 } else {
329 // Otherwise, cycle through the array until we find the next day to repeat on
330 $next_day_of_week_index = $current_day_of_week_index;
331 do {
332 } while ( ! isset( $days_of_week[ ++$next_day_of_week_index ] ) );
333 $pattern = ( $next_day_of_week_index - $current_day_of_week_index ) . ' day';
334 }
335
336 return strtotime( $pattern, $current );
337 }
338
339 /**
340 * Advances a date by a set number of months. When the date of the first active period lies
341 * on the 29th, 30th, or 31st of the month, this function will return a date on the the last day
342 * of the month for those months not containing those days.
343 */
344 function get_next_month( $current, $start, $interval_multiplier ) {
345 // For most days in the month, it's pretty easy. Get the day of month of the starting date.
346 $start_day = $this->format_timestamp( 'j', $start );
347
348 // If it's before or on the 28th, just jump the number of months and be done with it.
349 if ( $start_day <= 28 ) {
350 return strtotime( $interval_multiplier . ' month', $current );
351 }
352
353 // If it's on the 29th, 30th, or 31st, it gets tricky. Some months don't have those days - so on those
354 // months we need to repeat on the last day of the month instead, but we also need to jump back to the
355 // correct day the following month. Let's say we want to repeat something on the 31st every month: this
356 // is what we expect to see for a pattern:
357 //
358 // .
359 // .
360 // .
361 // December 31st
362 // January 31st
363 // February 28th
364 // March 31st
365 // April 30th
366 // .
367 // .
368 // .
369 //
370 // Unfortunately, PHP relative date handling isn't that smart (add "+1 month" to January 31st, and you
371 // end up in March), so we'll have to figure it out ourselves by figuring out how many days to jump instead.
372
373 // We'll need to calculate this for each interval and return the timestamp after the last jump.
374 $temp_current = $current;
375 for ( $i = 0; $i < $interval_multiplier; $i++ ) {
376 // The pattern for jumping will be different in each interval.
377 /** @noinspection PhpUnusedLocalVariableInspection */
378 $temp_pattern = '';
379
380 // Get the number of days in the month of the current date.
381 $last_day_this_month = $this->format_timestamp( 't', strtotime( 'this month', $temp_current ) );
382
383 // Get the number of days for the next month relative to the current date .
384 // Subtract 3 days from the next month to counter known month skipping bugs in PHP's relative date
385 // handling, that being the difference between the shortest possible month (non-leap February - 28 days)
386 // and the longest (Jan., Mar., May, Jul., Aug., Oct., Dec. - 31 days). This may be fixed in PHP 5.3.x
387 // but this should be backwards-compatible anyway.
388 $last_day_next_month = $this->format_timestamp( 't', strtotime( '-3 day next month', $temp_current ) );
389
390 // If the current month is longer than next month, follow this block
391 if ( $last_day_this_month > $last_day_next_month ) {
392 // If we're repeating on the last day of this month, jump the number of days next month
393 if ( $start_day === $last_day_this_month ) {
394 $temp_pattern = $last_day_next_month . ' days';
395 } elseif ( $start_day > $last_day_next_month ) {
396 // If the start day doesn't exist in the next month (i.e., no "31st" in June), jump the
397 // number of days next month plus the difference between the start day and the number of days this month
398 $temp_pattern = ( $last_day_this_month + $last_day_next_month - $start_day ) . ' days';
399 } else {
400 // Otherwise, jump ahead the number of days in this month
401 $temp_pattern = $last_day_this_month . ' days';
402 }
403 } elseif ( $last_day_this_month < $last_day_next_month ) {
404 // Or, if the current month is shorter than next month
405
406 // If the start day doesn't exist in this month (i.e., no "31st" in June), jump the
407 // number of days next month plus the difference between the start day and the number of days this month
408 if ( $start_day >= $last_day_this_month ) {
409 $temp_pattern = $start_day . ' days';
410 } else {
411 // Otherwise, jump ahead the number of days in this month
412 $temp_pattern = $last_day_this_month . ' days';
413 }
414 } else {
415 // If the current month and next month are equally long, jumping by "1 month" is fine
416 $temp_pattern = '1 month';
417 }
418
419 $temp_current = strtotime( $temp_pattern, $temp_current );
420 }
421
422 return $temp_current;
423 }
424
425 /**
426 * Advances a date to the 'n'th weekday of the next month (eg., first Wednesday, third Monday, last Friday, etc.).
427 *
428 * Note: If $ordinal is set to '4' and $day is set to '7', it wil return the last day of the month.
429 */
430 function get_nth_weekday_of_month( $current, $ordinal, $day ) {
431 // First, get the month/year we need to work with
432 $the_month = $this->format_timestamp( 'F', $current );
433 $the_year = $this->format_timestamp( 'Y', $current );
434 $last_day_this_month = $this->format_timestamp( 't', $current );
435
436 // Get the time for the $current timestamp
437 $current_time = $this->format_timestamp( 'g:i A', $current );
438 $the_day = '';
439
440 if ( 7 === $day ) { // If $day is "day of the month", get the day of month based on the ordinal
441 switch ( $ordinal ) {
442 case 0:
443 $the_day = '1';
444 break; // First day of the month //
445 case 1:
446 $the_day = '2';
447 break; // Second day of the month //
448 case 2:
449 $the_day = '3';
450 break; // Third day of the month //
451 case 3:
452 $the_day = '4';
453 break; // Fourth day of the month //
454 case 4:
455 $the_day = $last_day_this_month;
456 break; // Last day of the month //
457 default:
458 $the_day = '1';
459 break;
460 }
461 } else { // If $day is one of the days of the week...
462 $day_range = array();
463 switch ( $ordinal ) { // ...get a 7-day range based on the ordinal...
464 case 0:
465 $day_range = range( 1, 7 );
466 break; // First 7 days of the month //
467 case 1:
468 $day_range = range( 8, 14 );
469 break; // Second 7 days of the month //
470 case 2:
471 $day_range = range( 15, 21 );
472 break; // Third 7 days of the month //
473 case 3:
474 $day_range = range( 22, 28 );
475 break; // Fourth 7 days of the month //
476 case 4:
477 $day_range = range( $last_day_this_month - 6, $last_day_this_month );
478 break; // Last 7 days of the month //
479 default:
480 $day_range = range( 1, 7 );
481 break;
482 }
483 foreach ( $day_range as $a_day ) { // ...and find the matching weekday in that range.
484 if ( $this->format_timestamp( 'w', strtotime( $the_month . ' ' . $a_day . ', ' . $the_year ) ) === $day ) {
485 $the_day = $a_day;
486 break;
487 }
488 }
489 }
490
491 // Build the date string for the correct day and return its timestamp
492 $pattern = $the_month . ' ' . $the_day . ', ' . $the_year . ', ' . $current_time;
493
494 return strtotime( $pattern );
495
496 }
497
498 /**
499 * Advances a date by a set number of years
500 */
501 function get_next_year( $current, $interval_multiplier ) {
502 return strtotime( $interval_multiplier . ' year', $current );
503 }
504
505 /**
506 * Validates the various Timed Content Rule parameters and returns a series of error messages.
507 */
508 function validate( $args ) {
509 $errors = array();
510
511 $instance_start = DateTime::createFromFormat(
512 'Y-m-d H:i',
513 $args['instance_start']['date'] . ' ' . $args['instance_start']['time']
514 );
515 if ( false !== $instance_start ) {
516 $instance_start = $instance_start->getTimestamp();
517 }
518 $instance_end = DateTime::createFromFormat(
519 'Y-m-d H:i',
520 $args['instance_end']['date'] . ' ' . $args['instance_end']['time']
521 );
522 if ( false !== $instance_end ) {
523 $instance_end = $instance_end->getTimestamp();
524 }
525 $end_date = DateTime::createFromFormat( 'Y-m-d', $args['end_date'] );
526 if ( false !== $end_date ) {
527 $end_date = $end_date->getTimestamp();
528 }
529
530 if ( empty( $args['instance_start']['date'] ) ) {
531 $errors[] = __( 'Starting date must not be empty.', 'timed-content' );
532 }
533 if ( empty( $args['instance_start']['time'] ) ) {
534 $errors[] = __( 'Starting time must not be empty.', 'timed-content' );
535 }
536 if ( empty( $args['instance_end']['date'] ) ) {
537 $errors[] = __( 'Ending date must not be empty.', 'timed-content' );
538 }
539 if ( empty( $args['instance_end']['time'] ) ) {
540 $errors[] = __( 'Ending time must not be empty.', 'timed-content' );
541 }
542 if ( empty( $args['interval_multiplier'] ) ) {
543 $errors[] = __( 'Interval must not be empty.', 'timed-content' );
544 } elseif ( ! is_numeric( $args['interval_multiplier'] ) ) {
545 $errors[] = __( 'Number of recurrences must be a number.', 'timed-content' );
546 }
547 if ( 'recurrence_duration_num_repeat' === $args['recurr_type'] ) {
548 if ( empty( $args['num_repeat'] ) ) {
549 $errors[] = __( 'Number of repetitions must not be empty.', 'timed-content' );
550 } elseif ( ! is_numeric( $args['num_repeat'] ) ) {
551 $errors[] = __( 'Number of repetitions must be a number.', 'timed-content' );
552 }
553 } elseif ( 'recurrence_duration_end_date' === $args['recurr_type'] ) {
554 if ( empty( $args['end_date'] ) ) {
555 $errors[] = __( 'End date must not be empty.', 'timed-content' );
556 }
557 if ( $instance_end > $end_date ) {
558 $errors[] = __( 'Recurrence end date must be after ending date/time.', 'timed-content' );
559 }
560 }
561 if ( empty( $args['instance_start'] ) ) {
562 $errors[] = __( 'Starting date/time must be valid.', 'timed-content' );
563 }
564 if ( empty( $args['instance_end'] ) ) {
565 $errors[] = __( 'Ending date/time must be valid.', 'timed-content' );
566 }
567 if ( $instance_start > $instance_end ) {
568 $errors[] = __( 'Starting date/time must be before ending date/time.', 'timed-content' );
569 }
570
571 return $errors;
572 }
573
574 /**
575 * Helper to determine if the current rule loop is already finished
576 */
577 function loop_test( $recurr_type, $current, $last_occurrence_start, $period_count, $num_repeat ) {
578 if ( 'recurrence_duration_end_date' === $recurr_type ) {
579 return $current <= $last_occurrence_start;
580 }
581
582 return $period_count < $num_repeat;
583 }
584
585 /**
586 * Calculates the active periods for a Timed Content Rule
587 *
588 * Note: if $args['human_readable'] is set to true, then the result will be in human readable form
589 */
590 function get_rule_periods( $args ) {
591 global $post;
592
593 $active_periods = array();
594 $period_count = 0;
595
596 $human_readable = (bool) $args['human_readable'];
597 $freq = $args['freq'];
598 $timezone = $args['timezone'];
599 $recurr_type = $args['recurr_type'];
600 $num_repeat = intval( $args['num_repeat'] );
601 $end_date = $args['end_date'];
602 $days_of_week = $args['days_of_week'];
603 $interval_multiplier = $args['interval_multiplier'];
604 $instance_start_date = $args['instance_start']['date'];
605 $instance_start_time = $args['instance_start']['time'];
606 $instance_end_date = $args['instance_end']['date'];
607 $instance_end_time = $args['instance_end']['time'];
608 $monthly_pattern = $args['monthly_pattern'];
609 $monthly_pattern_ord = $args['monthly_pattern_ord'];
610 $monthly_pattern_day = $args['monthly_pattern_day'];
611 $exceptions_dates = $args['exceptions_dates'];
612
613 $this->set_format_timezone( $timezone );
614 $right_now_t = time();
615
616 // use debug parameter if current user is allowed to edit the post
617 if ( isset( $_GET['tctest'] ) && ! empty( $post ) && current_user_can( 'edit_post', $post->post_id ) ) {
618 $dt = DateTime::createFromFormat( 'Y-m-d H:i:s', sanitize_text_field( $_GET['tctest'] ), $this->current_timezone );
619 if ( false !== $dt ) {
620 $right_now_t = $dt->getTimestamp();
621 }
622 }
623
624 // Beginning of first occurrence
625 $instance_start = strtotime( $this->date_time_to_english( $instance_start_date, $instance_start_time ) . ' ' . $timezone );
626 $start_has_dst = $this->format_timestamp( 'I', $instance_start );
627
628 // End of first occurrence
629 $instance_end = strtotime( $this->date_time_to_english( $instance_end_date, $instance_end_time ) . ' ' . $timezone );
630
631 $current = $instance_start;
632 $end_current = $instance_end;
633
634 if ( 'recurrence_duration_num_repeat' === $recurr_type ) {
635 $last_occurrence_start = strtotime( TIMED_CONTENT_TIME_END );
636 } else {
637 $last_occurrence_start = strtotime(
638 $this->date_time_to_english(
639 $end_date,
640 $instance_start_time
641 ) . ' ' . $timezone
642 );
643 }
644
645 switch ( $freq ) {
646 case TIMED_CONTENT_FREQ_HOURLY:
647 $day_limit = 1;
648 break;
649 case TIMED_CONTENT_FREQ_DAILY:
650 $day_limit = 7;
651 break;
652 case TIMED_CONTENT_FREQ_WEEKLY:
653 $day_limit = 21;
654 break;
655 case TIMED_CONTENT_FREQ_MONTHLY:
656 $day_limit = 80;
657 break;
658 default: // TIMED_CONTENT_FREQ_YEARLY:
659 $day_limit = 380;
660 break;
661 }
662 $future_repeats = 0;
663 while (
664 $this->loop_test( $recurr_type, $current, $last_occurrence_start, $period_count, $num_repeat ) &&
665 ( $current < $right_now_t || $future_repeats < 20 )
666 ) {
667 $exception_period = false;
668 $current_date = $this->format_timestamp( 'Y-m-d', $current );
669 if ( is_array( $exceptions_dates ) ) {
670 foreach ( $exceptions_dates as $exceptions_date ) {
671 if ( is_numeric( $exceptions_date ) ) {
672 $exceptions_date = $this->format_timestamp( 'Y-m-d', $exceptions_date );
673 }
674 if ( $current_date === $exceptions_date ) {
675 $exception_period = true;
676 break;
677 }
678 }
679 }
680
681 if ( ! $exception_period && $current > $right_now_t - $day_limit * 86400 ) {
682 // Adjust current date offset if start DST differs from current DST
683 $current_adjusted = $current;
684 $current_has_dst = $this->format_timestamp( 'I', $current );
685 if ( '1' === $start_has_dst && '0' === $current_has_dst ) {
686 $current_adjusted += 3600;
687 } if ( '0' === $start_has_dst && '1' === $current_has_dst ) {
688 $current_adjusted -= 3600;
689 }
690
691 if ( $current > $right_now_t ) {
692 $future_repeats++;
693 }
694 $end_current = $current_adjusted + ( $instance_end - $instance_start );
695 if ( true === $human_readable ) {
696 $active_periods[ $period_count ]['start'] = $this->format_timestamp( TIMED_CONTENT_DATE_FORMAT_OUTPUT, $current_adjusted );
697 $active_periods[ $period_count ]['end'] = $this->format_timestamp( TIMED_CONTENT_DATE_FORMAT_OUTPUT, $end_current );
698 $active_periods[ $period_count ]['timezone'] = $timezone;
699 if ( $right_now_t < $current ) {
700 $active_periods[ $period_count ]['status'] = 'upcoming';
701 $active_periods[ $period_count ]['time'] = sprintf(
702 /* translators: %s: time difference */
703 _x(
704 '%s from now.',
705 'Human readable time difference',
706 'timed-content'
707 ),
708 human_time_diff( $current, $right_now_t )
709 );
710 } elseif ( ( $current <= $right_now_t ) && ( $right_now_t <= $end_current ) ) {
711 $active_periods[ $period_count ]['status'] = 'active';
712 $active_periods[ $period_count ]['time'] = __( 'Right now!', 'timed-content' );
713 } else {
714 $active_periods[ $period_count ]['status'] = 'expired';
715 $active_periods[ $period_count ]['time'] = sprintf(
716 /* translators: %s: time difference */
717 _x(
718 '%s ago.',
719 'Human readable time difference',
720 'timed-content'
721 ),
722 human_time_diff( $end_current, $right_now_t )
723 );
724 }
725 } else {
726 $active_periods[ $period_count ]['start'] = $current_adjusted;
727 $active_periods[ $period_count ]['end'] = $end_current;
728 $active_periods[ $period_count ]['timezone'] = $timezone;
729 }
730
731 $period_count++;
732 }
733
734 switch ( $freq ) {
735 case TIMED_CONTENT_FREQ_HOURLY:
736 $current = $this->get_next_hour( $current, $interval_multiplier );
737 break;
738 case TIMED_CONTENT_FREQ_DAILY:
739 $current = $this->get_next_day( $current, $interval_multiplier );
740 break;
741 case TIMED_CONTENT_FREQ_WEEKLY:
742 $current = $this->get_next_week( $current, $interval_multiplier, $days_of_week );
743 break;
744 case TIMED_CONTENT_FREQ_MONTHLY:
745 $current = $this->get_next_month( $current, $instance_start, $interval_multiplier );
746 $temp_current = $current;
747 if ( 'yes' === $monthly_pattern ) {
748 $current = $this->get_nth_weekday_of_month(
749 $current,
750 $monthly_pattern_ord,
751 $monthly_pattern_day
752 );
753 } else {
754 $current = $temp_current;
755 }
756 break;
757 default: // TIMED_CONTENT_FREQ_YEARLY:
758 $current = $this->get_next_year( $current, $interval_multiplier );
759 break;
760 }
761 }
762
763 return $active_periods;
764 }
765
766 /**
767 * Get stored rule periods by ID
768 */
769 function get_rule_periods_by_id( $id, $human_readable = false ) {
770 if ( TIMED_CONTENT_RULE_TYPE !== get_post_type( $id ) ) {
771 return array();
772 }
773
774 $prefix = TIMED_CONTENT_RULE_POSTMETA_PREFIX;
775 $args = array();
776
777 $args['human_readable'] = (bool) $human_readable;
778 $args['freq'] = get_post_meta( $id, $prefix . 'frequency', true );
779 $args['timezone'] = get_post_meta( $id, $prefix . 'timezone', true );
780 $args['recurr_type'] = get_post_meta( $id, $prefix . 'recurrence_duration', true );
781 $args['num_repeat'] = get_post_meta( $id, $prefix . 'recurrence_duration_num_repeat', true );
782 $args['end_date'] = get_post_meta( $id, $prefix . 'recurrence_duration_end_date', true );
783 $args['days_of_week'] = get_post_meta( $id, $prefix . 'weekly_days_of_week_to_repeat', true );
784 switch ( intval( $args['freq'] ) ) {
785 case 0:
786 $args['interval_multiplier'] = get_post_meta( $id, $prefix . 'hourly_num_of_hours', true );
787 break;
788 case 1:
789 $args['interval_multiplier'] = get_post_meta( $id, $prefix . 'daily_num_of_days', true );
790 break;
791 case 2:
792 $args['interval_multiplier'] = get_post_meta( $id, $prefix . 'weekly_num_of_weeks', true );
793 break;
794 case 3:
795 $args['interval_multiplier'] = get_post_meta( $id, $prefix . 'monthly_num_of_months', true );
796 break;
797 case 4:
798 $args['interval_multiplier'] = get_post_meta( $id, $prefix . 'yearly_num_of_years', true );
799 break;
800 default:
801 $args['interval_multiplier'] = 1;
802 }
803 $args['instance_start'] = get_post_meta( $id, $prefix . 'instance_start', true );
804 $args['instance_end'] = get_post_meta( $id, $prefix . 'instance_end', true );
805 $args['monthly_pattern'] = get_post_meta( $id, $prefix . 'monthly_nth_weekday_of_month', true );
806 $args['monthly_pattern_ord'] = get_post_meta( $id, $prefix . 'monthly_nth_weekday_of_month_nth', true );
807 $args['monthly_pattern_day'] = get_post_meta( $id, $prefix . 'monthly_nth_weekday_of_month_weekday', true );
808
809 $exceptions_dates = get_post_meta( $id, $prefix . 'exceptions_dates' );
810 if ( false !== $exceptions_dates && isset( $exceptions_dates[0] ) && is_array( $exceptions_dates[0] ) ) {
811 $args['exceptions_dates'] = $exceptions_dates[0];
812 } else {
813 $args['exceptions_dates'] = false;
814 }
815
816 $args = $this->convert_date_time_parameters_to_iso( $args );
817
818 return $this->get_rule_periods( $args );
819 }
820
821 /**
822 * Helper to get a sanitized post parameter
823 */
824 function get_post_param( $name ) {
825 $full_name = TIMED_CONTENT_RULE_POSTMETA_PREFIX . $name;
826 if ( ! isset( $_POST[ $full_name ] ) ) {
827 return '';
828 }
829
830 return sanitize_text_field( $_POST[ $full_name ] );
831 }
832
833 /**
834 * Helper to get a sanitized post array parameter
835 */
836 function get_post_array_param( $name ) {
837 $full_name = TIMED_CONTENT_RULE_POSTMETA_PREFIX . $name;
838
839 if ( ! isset( $_POST[ $full_name ] ) || ! is_array( $_POST[ $full_name ] ) ) {
840 return array();
841 }
842
843 $array = array();
844 foreach ( $_POST[ $full_name ] as $key => $value ) {
845 $array[ sanitize_text_field( $key ) ] = sanitize_text_field( $value );
846 }
847
848 return $array;
849 }
850
851 /**
852 * Get rule periods based on the contents of the form fields of the Add Timed Content Rule and
853 * Edit Timed Content Rule screens. Output is sent as JSON.
854 */
855 function timed_content_plugin_get_rule_periods_ajax() {
856 if ( current_user_can( 'edit_posts' ) || current_user_can( 'edit_pages' ) ) {
857 $prefix = TIMED_CONTENT_RULE_POSTMETA_PREFIX;
858 $args = array();
859
860 $args['human_readable'] = $this->get_post_param( 'human_readable' );
861 $args['freq'] = $this->get_post_param( 'frequency' );
862 $args['timezone'] = $this->get_post_param( 'timezone' );
863 $args['recurr_type'] = $this->get_post_param( 'recurrence_duration' );
864 $args['num_repeat'] = $this->get_post_param( 'recurrence_duration_num_repeat' );
865 $args['end_date'] = $this->get_post_param( 'recurrence_duration_end_date' );
866 $args['days_of_week'] = $this->get_post_array_param( 'weekly_days_of_week_to_repeat' );
867 $args['interval_multiplier'] = $this->get_post_param( 'interval_multiplier' );
868 $args['instance_start'] = $this->get_post_array_param( 'instance_start' );
869 $args['instance_end'] = $this->get_post_array_param( 'instance_end' );
870 $args['monthly_pattern'] = $this->get_post_param( 'monthly_nth_weekday_of_month' );
871 $args['monthly_pattern_ord'] = $this->get_post_param( 'monthly_nth_weekday_of_month_nth' );
872 $args['monthly_pattern_day'] = $this->get_post_param( 'monthly_nth_weekday_of_month_weekday' );
873 $args['exceptions_dates'] = $this->get_post_array_param( 'exceptions_dates' );
874
875 $errors = $this->validate( $args );
876 if ( count( $errors ) === 0 ) {
877 header( 'Content-Type: application/json' );
878 echo json_encode( $this->get_rule_periods( $args ) );
879 }
880 }
881 die();
882 }
883
884 /**
885 * Returns a human-readable description of a Timed Content Rule
886 */
887 function get_schedule_description( $args ) {
888 $interval_multiplier = 1;
889 $desc = '';
890
891 $errors = $this->validate( $args );
892 if ( $errors ) {
893 $message = sprintf(
894 '<div class="tcr-warning">' .
895 '<p class="heading">%s</p>' .
896 '<p>%s</p>' .
897 '<ul><li>%s</li></ul>' .
898 '<p>%s</p>',
899 __( 'Warning!', 'timed-content' ),
900 __( 'Some problems have been detected. Although you can still publish this rule, it may not work the way you expect.', 'timed-content' ),
901 implode( '</li><li>', $errors ),
902 __( 'Check that all of the conditions for this rule are correct, and use <b>Show projected dates/times</b> to ensure your rule is working properly.', 'timed-content' )
903 );
904
905 return $message;
906 }
907
908 if ( $args['action'] ) {
909 $action = __( 'Show the content', 'timed-content' );
910 } else {
911 $action = __( 'Hide the content', 'timed-content' );
912 }
913 $freq = $args['freq'];
914 $timezone = $args['timezone'];
915 $recurr_type = $args['recurr_type'];
916 $num_repeat = intval( $args['num_repeat'] );
917 $end_date = $args['end_date'];
918 $days_of_week = $args['days_of_week'];
919 $interval_multiplier = $args['interval_multiplier'];
920 $instance_start_date = $args['instance_start']['date'];
921 $instance_start_time = $args['instance_start']['time'];
922 $instance_end_date = $args['instance_end']['date'];
923 $instance_end_time = $args['instance_end']['time'];
924 $monthly_pattern = $args['monthly_pattern'];
925 $monthly_pattern_ord = $args['monthly_pattern_ord'];
926 $monthly_pattern_day = $args['monthly_pattern_day'];
927 $exceptions_dates = $args['exceptions_dates'];
928
929 $desc = sprintf(
930 /* translators: %1$s: action, %2$s: start date, %3$s: start time, %4$s: end date, %5$s: end time */
931 _x(
932 '%1$s on %2$s @ %3$s until %4$s @ %5$s.',
933 'Perform action (%1$s) from date/time of first active period (%2$s @ %3$s) until date/time of last active period (%4$s @ %5$s).',
934 'timed-content'
935 ),
936 $action,
937 $instance_start_date,
938 $instance_start_time,
939 $instance_end_date,
940 $instance_end_time
941 );
942
943 if ( 0 === $freq ) {
944 $desc .= '<br />' . sprintf(
945 /* translators: %d numerical hours value */
946 _n(
947 'Repeat this action every %d hour.',
948 'Repeat this action every %d hours.',
949 $interval_multiplier,
950 'timed-content'
951 ),
952 $interval_multiplier
953 );
954 } elseif ( 1 === $freq ) {
955 $desc .= '<br />' . sprintf(
956 /* translators: %d numerical days value */
957 _n(
958 'Repeat this action every %d day.',
959 'Repeat this action every %d days.',
960 $interval_multiplier,
961 'timed-content'
962 ),
963 $interval_multiplier
964 );
965 } elseif ( 2 === $freq ) {
966 if ( ( $days_of_week ) && ( is_array( $days_of_week ) ) ) {
967 $days = array();
968 $days_list = '';
969 foreach ( $days_of_week as $v ) {
970 $days[] = $this->rule_days_array[ $v ];
971 }
972 switch ( count( $days ) ) {
973 case 1:
974 $days_list = $days[0];
975 break;
976 case 2:
977 $days_list = sprintf(
978 /* translators: %1$s, %2$s: weekday */
979 _x( '%1$s and %2$s', 'List of two weekdays', 'timed-content' ),
980 $days[0],
981 $days[1]
982 );
983 break;
984 case 3:
985 $days_list = sprintf(
986 /* translators: %1$s, %2$s, %3$s: weekday */
987 _x(
988 '%1$s, %2$s and %3$s',
989 'List of three weekdays',
990 'timed-content'
991 ),
992 $days[0],
993 $days[1],
994 $days[2]
995 );
996 break;
997 case 4:
998 $days_list = sprintf(
999 /* translators: %1$s, %2$s, %3$s, %4$s: weekday */
1000 _x(
1001 '%1$s, %2$s, %3$s and %4$s',
1002 'List of four weekdays',
1003 'timed-content'
1004 ),
1005 $days[0],
1006 $days[1],
1007 $days[2],
1008 $days[3]
1009 );
1010 break;
1011 case 5:
1012 $days_list = sprintf(
1013 /* translators: %1$s, %2$s, %3$s, %4$s, %5$s: weekday */
1014 _x(
1015 '%1$s, %2$s, %3$s, %4$s and %5$s',
1016 'List of five weekdays',
1017 'timed-content'
1018 ),
1019 $days[0],
1020 $days[1],
1021 $days[2],
1022 $days[3],
1023 $days[4]
1024 );
1025 break;
1026 case 6:
1027 $days_list = sprintf(
1028 /* translators: %1$s, %2$s, %3$s, %4$s, %5$s, %6$s: weekday */
1029 _x(
1030 '%1$s, %2$s, %3$s, %4$s, %5$s and %6$s',
1031 'List of six weekdays',
1032 'timed-content'
1033 ),
1034 $days[0],
1035 $days[1],
1036 $days[2],
1037 $days[3],
1038 $days[4],
1039 $days[5]
1040 );
1041 break;
1042 case 7:
1043 $days_list = sprintf(
1044 /* translators: %1$s, %2$s, %3$s, %4$s, %5$s, %6$s, %7$s: weekday */
1045 _x(
1046 '%1$s, %2$s, %3$s, %4$s, %5$s, %6$s and %7$s',
1047 'List of all weekdays',
1048 'timed-content'
1049 ),
1050 $days[0],
1051 $days[1],
1052 $days[2],
1053 $days[3],
1054 $days[4],
1055 $days[5],
1056 $days[6]
1057 );
1058 break;
1059 }
1060 if ( 1 === $interval_multiplier ) {
1061 $desc .= '<br />' . sprintf(
1062 /* translators: %s: weekday */
1063 _x(
1064 'Repeat this action every week on %s.',
1065 'List the weekdays to repeat the rule when frequency is every week. %s is the list of weekdays.',
1066 'timed-content'
1067 ),
1068 $days_list
1069 );
1070 } else {
1071 $desc .= '<br />' . sprintf(
1072 /* translators: %1$d: number of weeks, %2$s: weekday */
1073 _x(
1074 'Repeat this action every %1$d weeks on %2$s.',
1075 'List the weekdays to repeat the rule when frequency is every %1$d weeks. %2$s is the list of weekdays.',
1076 'timed-content'
1077 ),
1078 $interval_multiplier,
1079 $days_list
1080 );
1081 }
1082 } else {
1083 $desc .= '<br />' . sprintf(
1084 /* translators: %d: number of weeks */
1085 _n(
1086 'Repeat this action every %d week.',
1087 'Repeat this action every %d weeks.',
1088 $interval_multiplier,
1089 'timed-content'
1090 ),
1091 $interval_multiplier
1092 );
1093 }
1094 } elseif ( 3 === $freq ) {
1095 if ( 'yes' === $monthly_pattern ) {
1096 if ( 1 === $interval_multiplier ) {
1097 $desc .= '<br />' . sprintf(
1098 /* translators: %1$s: recurrence, %2$s: weekday */
1099 _x(
1100 'Repeat this action every month on the %1$s %2$s of the month.',
1101 "Example: 'Repeat this action every month on the second Friday of the month.'",
1102 'timed-content'
1103 ),
1104 $this->rule_ordinal_array[ $monthly_pattern_ord ],
1105 $this->rule_ordinal_days_array[ $monthly_pattern_day ]
1106 );
1107 } else {
1108 $desc .= '<br />' . sprintf(
1109 /* translators: %1$d: month count, %2$s: recurrence, %3$s: weekday */
1110 _x(
1111 'Repeat this action every %1$d months on the %2$s %3$s of the month.',
1112 "Example: 'Repeat this action every 2 months on the second Friday of the month.'",
1113 'timed-content'
1114 ),
1115 $interval_multiplier,
1116 $this->rule_ordinal_array[ $monthly_pattern_ord ],
1117 $this->rule_ordinal_days_array[ $monthly_pattern_day ]
1118 );
1119 }
1120 } else {
1121 $desc .= '<br />' . sprintf(
1122 /* translators: %d: month count */
1123 _n(
1124 'Repeat this action every %d month.',
1125 'Repeat this action every %d months.',
1126 $interval_multiplier,
1127 'timed-content'
1128 ),
1129 $interval_multiplier
1130 );
1131 }
1132 } elseif ( 4 === $freq ) {
1133 $desc .= '<br />' . sprintf(
1134 /* translators: %d: year count */
1135 _n(
1136 'Repeat this action every %d year.',
1137 'Repeat this action every %d years.',
1138 $interval_multiplier,
1139 'timed-content'
1140 ),
1141 $interval_multiplier
1142 );
1143 }
1144
1145 if ( 'recurrence_duration_num_repeat' === $recurr_type ) {
1146 $desc .= '<br />' . sprintf(
1147 /* translators: %d: number of recurrences */
1148 _n(
1149 'This rule will be active for %d recurrence.',
1150 'This rule will be active for %d recurrences.',
1151 $num_repeat,
1152 'timed-content'
1153 ),
1154 $num_repeat
1155 );
1156 } elseif ( 'recurrence_duration_end_date' === $recurr_type ) {
1157 $desc .= '<br />' . sprintf( /* translators: %s: end date */ __( 'This rule will be active until %s.', 'timed-content' ), $end_date );
1158 }
1159
1160 if ( ( $exceptions_dates ) && ( is_array( $exceptions_dates ) ) ) {
1161 sort( $exceptions_dates, SORT_NUMERIC );
1162 $exceptions_dates = array_unique( $exceptions_dates );
1163 if ( 0 === $exceptions_dates[0] ) {
1164 array_shift( $exceptions_dates );
1165 }
1166 if ( ! empty( $exceptions_dates ) ) {
1167 $desc .= '<br />' . sprintf(
1168 /* translators: %s: list of dates */
1169 __(
1170 'This rule will be inactive on the following dates: %s.',
1171 'timed-content'
1172 ),
1173 join( ', ', $exceptions_dates )
1174 );
1175 }
1176 }
1177
1178 $desc .= '<br />' . sprintf(
1179 /* translators: %s: timezone */
1180 __( 'All times are in the %s timezone.', 'timed-content' ),
1181 $timezone
1182 );
1183
1184 return $desc;
1185 }
1186
1187 /**
1188 * Get the description of a rule
1189 */
1190 function get_schedule_description_by_id( $id ) {
1191 $defaults = array();
1192
1193 foreach ( $this->rule_occurrence_custom_fields as $field ) {
1194 $defaults[ $field['name'] ] = $field['default'];
1195 }
1196 foreach ( $this->rule_pattern_custom_fields as $field ) {
1197 $defaults[ $field['name'] ] = $field['default'];
1198 }
1199 foreach ( $this->rule_recurrence_custom_fields as $field ) {
1200 $defaults[ $field['name'] ] = $field['default'];
1201 }
1202 foreach ( $this->rule_exceptions_custom_fields as $field ) {
1203 $defaults[ $field['name'] ] = $field['default'];
1204 }
1205
1206 $prefix = TIMED_CONTENT_RULE_POSTMETA_PREFIX;
1207 $args = array();
1208
1209 $args['action'] = ( false === get_post_meta(
1210 $id,
1211 $prefix . 'action',
1212 true
1213 ) ? $defaults['action'] : get_post_meta( $id, $prefix . 'action', true ) );
1214 $args['freq'] = ( false === get_post_meta(
1215 $id,
1216 $prefix . 'frequency',
1217 true
1218 ) ? $defaults['frequency'] : get_post_meta( $id, $prefix . 'frequency', true ) );
1219 $args['timezone'] = ( false === get_post_meta(
1220 $id,
1221 $prefix . 'timezone',
1222 true
1223 ) ? $defaults['timezone'] : get_post_meta( $id, $prefix . 'timezone', true ) );
1224 $args['recurr_type'] = ( false === get_post_meta(
1225 $id,
1226 $prefix . 'recurrence_duration',
1227 true
1228 ) ? $defaults['recurrence_duration'] : get_post_meta(
1229 $id,
1230 $prefix . 'recurrence_duration',
1231 true
1232 ) );
1233 $args['num_repeat'] = ( false === get_post_meta(
1234 $id,
1235 $prefix . 'recurrence_duration_num_repeat',
1236 true
1237 ) ? $defaults['recurrence_duration_num_repeat'] : get_post_meta(
1238 $id,
1239 $prefix . 'recurrence_duration_num_repeat',
1240 true
1241 ) );
1242 $args['end_date'] = ( false === get_post_meta(
1243 $id,
1244 $prefix . 'recurrence_duration_end_date',
1245 true
1246 ) ? $defaults['recurrence_duration_end_date'] : get_post_meta(
1247 $id,
1248 $prefix . 'recurrence_duration_end_date',
1249 true
1250 ) );
1251 $args['days_of_week'] = ( false === get_post_meta(
1252 $id,
1253 $prefix . 'weekly_days_of_week_to_repeat',
1254 true
1255 ) ? $defaults['weekly_days_of_week_to_repeat'] : get_post_meta(
1256 $id,
1257 $prefix . 'weekly_days_of_week_to_repeat',
1258 true
1259 ) );
1260 switch ( $args['freq'] ) {
1261 case '0':
1262 $args['interval_multiplier'] = ( false === get_post_meta(
1263 $id,
1264 $prefix . 'hourly_num_of_hours',
1265 true
1266 ) ? $defaults['hourly_num_of_hours'] : get_post_meta(
1267 $id,
1268 $prefix . 'hourly_num_of_hours',
1269 true
1270 ) );
1271 break;
1272 case '1':
1273 $args['interval_multiplier'] = ( false === get_post_meta(
1274 $id,
1275 $prefix . 'daily_num_of_days',
1276 true
1277 ) ? $defaults['daily_num_of_days'] : get_post_meta(
1278 $id,
1279 $prefix . 'daily_num_of_days',
1280 true
1281 ) );
1282 break;
1283 case '2':
1284 $args['interval_multiplier'] = ( false === get_post_meta(
1285 $id,
1286 $prefix . 'weekly_num_of_weeks',
1287 true
1288 ) ? $defaults['weekly_num_of_weeks'] : get_post_meta(
1289 $id,
1290 $prefix . 'weekly_num_of_weeks',
1291 true
1292 ) );
1293 break;
1294 case '3':
1295 $args['interval_multiplier'] = ( false === get_post_meta(
1296 $id,
1297 $prefix . 'monthly_num_of_months',
1298 true
1299 ) ? $defaults['monthly_num_of_months'] : get_post_meta(
1300 $id,
1301 $prefix . 'monthly_num_of_months',
1302 true
1303 ) );
1304 break;
1305 case '4':
1306 $args['interval_multiplier'] = ( false === get_post_meta(
1307 $id,
1308 $prefix . 'yearly_num_of_years',
1309 true
1310 ) ? $defaults['yearly_num_of_years'] : get_post_meta(
1311 $id,
1312 $prefix . 'yearly_num_of_years',
1313 true
1314 ) );
1315 break;
1316 }
1317 $args['instance_start'] = ( false === get_post_meta(
1318 $id,
1319 $prefix . 'instance_start',
1320 true
1321 ) ? $defaults['instance_start'] : get_post_meta( $id, $prefix . 'instance_start', true ) );
1322 if ( ! is_array( $args['instance_start'] ) ) {
1323 $args['instance_start'] = array(
1324 'date' => '',
1325 'time' => '',
1326 );
1327 }
1328 $args['instance_end'] = ( false === get_post_meta(
1329 $id,
1330 $prefix . 'instance_end',
1331 true
1332 ) ? $defaults['instance_end'] : get_post_meta( $id, $prefix . 'instance_end', true ) );
1333 if ( ! is_array( $args['instance_end'] ) ) {
1334 $args['instance_end'] = array(
1335 'date' => '',
1336 'time' => '',
1337 );
1338 }
1339 $args['monthly_pattern'] = ( false === get_post_meta(
1340 $id,
1341 $prefix . 'monthly_nth_weekday_of_month',
1342 true
1343 ) ? $defaults['monthly_nth_weekday_of_month'] : get_post_meta(
1344 $id,
1345 $prefix . 'monthly_nth_weekday_of_month',
1346 true
1347 ) );
1348 $args['monthly_pattern_ord'] = ( false === get_post_meta(
1349 $id,
1350 $prefix . 'monthly_nth_weekday_of_month_nth',
1351 true
1352 ) ? $defaults['monthly_nth_weekday_of_month_nth'] : get_post_meta(
1353 $id,
1354 $prefix . 'monthly_nth_weekday_of_month_nth',
1355 true
1356 ) );
1357 $args['monthly_pattern_day'] = ( false === get_post_meta(
1358 $id,
1359 $prefix . 'monthly_nth_weekday_of_month_weekday',
1360 true
1361 ) ? $defaults['monthly_nth_weekday_of_month_weekday'] : get_post_meta(
1362 $id,
1363 $prefix . 'monthly_nth_weekday_of_month_weekday',
1364 true
1365 ) );
1366 $exceptions_dates = get_post_meta( $id, $prefix . 'exceptions_dates' );
1367 if ( false !== $exceptions_dates && isset( $exceptions_dates[0] ) && is_array( $exceptions_dates[0] ) ) {
1368 $args['exceptions_dates'] = $exceptions_dates[0];
1369 } else {
1370 $args['exceptions_dates'] = $defaults['exceptions_dates'];
1371 }
1372
1373 $args = $this->convert_date_time_parameters_to_iso( $args );
1374
1375 return $this->get_schedule_description( $args );
1376 }
1377
1378 /**
1379 * Get the descroption based on the contents of the form fields of the Add Timed Content Rule and
1380 * Edit Timed Content Rule screens. Output is sent to output as plain text.
1381 */
1382 function timed_content_plugin_get_schedule_description_ajax() {
1383 if ( current_user_can( 'edit_posts' ) || current_user_can( 'edit_pages' ) ) {
1384 $prefix = TIMED_CONTENT_RULE_POSTMETA_PREFIX;
1385 $args = array();
1386
1387 $args['action'] = $this->get_post_param( $prefix . 'action' );
1388 $args['freq'] = $this->get_post_param( 'frequency' );
1389 $args['timezone'] = $this->get_post_param( 'timezone' );
1390 $args['recurr_type'] = $this->get_post_param( 'recurrence_duration' );
1391 $args['num_repeat'] = $this->get_post_param( 'recurrence_duration_num_repeat' );
1392 $args['end_date'] = $this->get_post_param( 'recurrence_duration_end_date' );
1393 $args['days_of_week'] = $this->get_post_array_param( 'weekly_days_of_week_to_repeat' );
1394 $args['interval_multiplier'] = $this->get_post_param( 'interval_multiplier' );
1395 $args['instance_start'] = $this->get_post_array_param( 'instance_start' );
1396 $args['instance_end'] = $this->get_post_array_param( 'instance_end' );
1397 $args['monthly_pattern'] = $this->get_post_param( 'monthly_nth_weekday_of_month' );
1398 $args['monthly_pattern_ord'] = $this->get_post_param( 'monthly_nth_weekday_of_month_nth' );
1399 $args['monthly_pattern_day'] = $this->get_post_param( 'monthly_nth_weekday_of_month_weekday' );
1400 $args['exceptions_dates'] = $this->get_post_array_param( 'exceptions_dates' );
1401
1402 // response output
1403 header( 'Content-Type: text/plain' );
1404 echo wp_kses_post( $this->get_schedule_description( $args ) );
1405 }
1406 die();
1407 }
1408
1409 /**
1410 * Processes the [timed-content-client] shortcode.
1411 */
1412 function client_show_html( $atts, $content = null ) {
1413 $show_attr = '';
1414 $hide_attr = '';
1415 $atts_parsed = shortcode_atts(
1416 array(
1417 'show' => '0:00:000',
1418 'hide' => '0:00:000',
1419 'display' => 'div',
1420 ),
1421 $atts
1422 );
1423
1424 $show_min = 0;
1425 $show_sec = 0;
1426 $show_fade = 0;
1427 $hide_min = 0;
1428 $hide_sec = 0;
1429 $hide_fade = 0;
1430
1431 $show_values = explode( ':', $atts_parsed['show'] );
1432 if ( false !== $show_values ) {
1433 if ( isset( $show_values[0] ) ) {
1434 $show_min = intval( $show_values[0] );
1435 }
1436 if ( isset( $show_values[1] ) ) {
1437 $show_sec = intval( $show_values[1] );
1438 }
1439 if ( isset( $show_values[2] ) ) {
1440 $show_fade = intval( $show_values[2] );
1441 }
1442 }
1443 $hide_values = explode( ':', $atts_parsed['hide'] );
1444 if ( false !== $hide_values ) {
1445 if ( isset( $hide_values[0] ) ) {
1446 $hide_min = intval( $hide_values[0] );
1447 }
1448 if ( isset( $hide_values[1] ) ) {
1449 $hide_sec = intval( $hide_values[1] );
1450 }
1451 if ( isset( $hide_values[2] ) ) {
1452 $hide_fade = intval( $hide_values[2] );
1453 }
1454 }
1455
1456 if ( ( $show_min + $show_sec ) > 0 ) {
1457 $show_attr = sprintf( '_show_%d_%d_%d', $show_min, $show_sec, $show_fade );
1458 }
1459 if ( ( $hide_min + $hide_sec ) > 0 ) {
1460 $hide_attr = sprintf( '_hide_%d_%d_%d', $hide_min, $hide_sec, $hide_fade );
1461 }
1462
1463 $the_class = TIMED_CONTENT_SHORTCODE_CLIENT . $show_attr . $hide_attr;
1464 $the_tag = ( 'div' === $atts_parsed['display'] ? 'div' : 'span' );
1465
1466 $the_filter = 'timed_content_filter';
1467 $the_filter = apply_filters( 'timed_content_filter_override', $the_filter );
1468
1469 return '<'
1470 . $the_tag
1471 . " class='"
1472 . $the_class
1473 . "'"
1474 . ( ( '' !== $show_attr ) ? " style='display: none;'" : '' ) . '>'
1475 . str_replace( ']]>', ']]&gt;', apply_filters( $the_filter, $content ) )
1476 . '</' . $the_tag . '>';
1477 }
1478
1479 /**
1480 * Processes the [timed-content-server] shortcode.
1481 */
1482 function server_show_html( $atts, $content = null ) {
1483 global $post;
1484
1485 $atts_parsed = shortcode_atts(
1486 array(
1487 'show' => 0,
1488 'hide' => 0,
1489 'debug' => 'false',
1490 ),
1491 $atts
1492 );
1493 $show = $atts_parsed['show'];
1494 $hide = $atts_parsed['hide'];
1495 $debug = $atts_parsed['debug'];
1496
1497 // Get time and timezone object for "show" time
1498 $pos = strrpos( $show, ' ' );
1499 if ( false !== $pos ) {
1500 $show_time = substr( $show, 0, $pos );
1501 $show_tzname = substr( $show, $pos + 1 );
1502 } else {
1503 $show_time = $show;
1504 $show_tzname = date_default_timezone_get();
1505 }
1506 try {
1507 $show_tz = new DateTimeZone( $show_tzname );
1508 } catch ( Exception $e ) {
1509 $show_tz = new DateTimeZone( 'UTC' );
1510 }
1511
1512 // Create time and timezone object for "hide" time
1513 $pos = strrpos( $hide, ' ' );
1514 if ( false !== $pos ) {
1515 $hide_time = substr( $hide, 0, $pos );
1516 $hide_tzname = substr( $hide, $pos + 1 );
1517 } else {
1518 $hide_time = $hide;
1519 $hide_tzname = date_default_timezone_get();
1520 }
1521 try {
1522 $hide_tz = new DateTimeZone( $hide_tzname );
1523 } catch ( Exception $e ) {
1524 $hide_tz = new DateTimeZone( 'UTC' );
1525 }
1526
1527 // Try to parse date as ISO first
1528 $show_dt = DateTime::createFromFormat( 'Y-m-d G:i', $show_time, $show_tz );
1529 // Fallback to American format
1530 if ( false === $show_dt ) {
1531 $show_dt = DateTime::createFromFormat( 'm/d/Y G:i', $show_time, $show_tz );
1532 }
1533
1534 if ( false !== $show_dt ) {
1535 $show_t = $show_dt->getTimeStamp();
1536 } else {
1537 // If nothing else worked so far, try strtotime()
1538 // as it was before version 2.50
1539 $show_t = strtotime( $show );
1540 if ( false === $show_t ) {
1541 $show_t = 0;
1542 }
1543 $show_dt = new DateTime();
1544 $show_dt->setTimeStamp( $show_t );
1545 $show_dt->setTimezone( $show_tz );
1546 }
1547
1548 // Try to parse date as ISO first
1549 $hide_dt = DateTime::createFromFormat( 'Y-m-d G:i', $hide_time, $hide_tz );
1550 if ( false === $hide_dt ) {
1551 $hide_dt = DateTime::createFromFormat( 'm/d/Y G:i', $hide_time, $hide_tz );
1552 }
1553 if ( false !== $hide_dt ) {
1554 $hide_t = $hide_dt->getTimeStamp();
1555 } else {
1556 // If nothing else worked so far, try strtotime()
1557 // as it was before version 2.50
1558 $hide_t = strtotime( $hide );
1559 if ( false === $hide_t ) {
1560 $hide_t = 0;
1561 }
1562 $hide_dt = new DateTime();
1563 $hide_dt->setTimeStamp( $hide_t );
1564 $hide_dt->setTimezone( $hide_tz );
1565 }
1566
1567 $right_now_t = time();
1568 $debug_message = '';
1569
1570 // use debug parameter if current user is allowed to edit the post
1571 if ( isset( $_GET['tctest'] ) && ! empty( $post ) && current_user_can( 'edit_post', $post->post_id ) ) {
1572 $dt = DateTime::createFromFormat( 'Y-m-d H:i:s', sanitize_text_field( $_GET['tctest'] ) );
1573 if ( false !== $dt ) {
1574 $right_now_t = $dt->getTimestamp();
1575 }
1576 }
1577
1578 $the_filter = 'timed_content_filter';
1579 $the_filter = apply_filters( 'timed_content_filter_override', $the_filter );
1580
1581 $show_content = false;
1582 if ( ( $show_t <= $right_now_t ) && ( $right_now_t <= $hide_t || 0 === $hide_t ) ) {
1583 $show_content = true;
1584 }
1585
1586 if ( ( ( 'true' === $debug ) || ( ( ! $show_content ) && ( 'when_hidden' === $debug ) ) ) && ( ! empty( $post ) && current_user_can( 'edit_post', $post->post_id ) ) ) {
1587
1588 $right_now = $this->format_timestamp( TIMED_CONTENT_DATE_FORMAT_OUTPUT, $right_now_t );
1589
1590 if ( $show_t > $right_now_t ) {
1591 $show_diff_str = sprintf(
1592 /* translators: %s: time difference */
1593 _x( '%s from now.', 'Human readable time difference', 'timed-content' ),
1594 human_time_diff( $show_t, $right_now_t )
1595 );
1596 } else {
1597 $show_diff_str = sprintf(
1598 /* translators: %s: time difference */
1599 _x( '%s ago.', 'Human readable time difference', 'timed-content' ),
1600 human_time_diff( $show_t, $right_now_t )
1601 );
1602 }
1603 if ( $hide_t > $right_now_t ) {
1604 $hide_diff_str = sprintf(
1605 /* translators: %s: time difference */
1606 _x( '%s from now.', 'Human readable time difference', 'timed-content' ),
1607 human_time_diff( $hide_t, $right_now_t )
1608 );
1609 } else {
1610 $hide_diff_str = sprintf(
1611 /* translators: %s: time difference */
1612 _x( '%s ago.', 'Human readable time difference', 'timed-content' ),
1613 human_time_diff( $hide_t, $right_now_t )
1614 );
1615 }
1616
1617 $debug_message = "<div class=\"tcr-warning\">\n";
1618 $debug_message .= '<p class="heading">' . _x( 'Notice', 'Noun', 'timed-content' ) . "</p>\n";
1619 $debug_message .= '<p>' . sprintf(
1620 /* translators: %1$s: shortcode, %2$s attribute in shortcode */
1621 __(
1622 'Debugging has been turned on for a %1$s shortcode on this post/page. Only website users who are currently logged in and can edit this post/page will see this. To turn off this message, remove the %2$s attribute from the shortcode.',
1623 'timed-content'
1624 ),
1625 '<code>[timed-content-server]</code>',
1626 '<code>debug</code>'
1627 ) . "</p>\n";
1628
1629 if ( 0 === $show_t ) {
1630 $debug_message .= '<p>' . sprintf(
1631 /* translators: %s: attribute name */
1632 __( 'The %s attribute is not set or invalid.', 'timed-content' ),
1633 '<code>show</code>'
1634 ) . "</p>\n";
1635 } else {
1636 $debug_message .= '<p>' . sprintf(
1637 /* translators: %s: attribute name */
1638 __( 'The %s attribute is currently set to', 'timed-content' ),
1639 '<code>show</code>'
1640 ) . ': ' . $show . ",<br />\n "
1641 . __(
1642 'The Timed Content plugin thinks the intended date/time is',
1643 'timed-content'
1644 ) . ': ' . $show_dt->format( TIMED_CONTENT_DATE_FORMAT_OUTPUT )
1645 . ' (' . $show_diff_str . ")</p>\n";
1646 }
1647
1648 if ( 0 === $hide ) {
1649 $debug_message .= '<p>' . sprintf(
1650 /* translators: %s: attribute name */
1651 __( 'The %s attribute is not set or invalid.', 'timed-content' ),
1652 '<code>hide</code>'
1653 ) . "</p>\n";
1654 } else {
1655 $debug_message .= '<p>' . sprintf(
1656 /* translators: %s: attribute name */
1657 __( 'The %s attribute is currently set to', 'timed-content' ),
1658 '<code>hide</code>'
1659 ) . ': ' . $hide . ",<br />\n"
1660 . __(
1661 'The Timed Content plugin thinks the intended date/time is',
1662 'timed-content'
1663 ) . ': ' . $hide_dt->format( TIMED_CONTENT_DATE_FORMAT_OUTPUT )
1664 . ' (' . $hide_diff_str . ").</p>\n";
1665 }
1666
1667 $debug_message .= '<p>' . __(
1668 'Current date:',
1669 'timed-content'
1670 ) . '&nbsp;' . $right_now . "</p>\n";
1671 $debug_message .= '<p>' . __( 'Content filter:', 'timed-content' ) . '&nbsp;' . $the_filter . "</p>\n";
1672 $debug_message .= '<p>' . _x( 'Content:', 'Noun', 'timed-content' ) . '</p><p>' . $content . "</p>\n";
1673
1674 if ( true === $show_content ) {
1675 $debug_message .= '<p>' . __( 'The plugin will show the content.', 'timed-content' ) . '</p>';
1676 } else {
1677 $debug_message .= '<p>' . __( 'The plugin will hide the content.', 'timed-content' ) . '</p>';
1678 }
1679
1680 $debug_message .= "</div>\n";
1681 }
1682
1683 if ( true === $show_content ) {
1684 if ( ! empty( $post ) ) {
1685 do_action( 'timed_content_server_show', $post->ID, $show, $hide, $content );
1686 } else {
1687 do_action( 'timed_content_server_show', null, $show, $hide, $content );
1688 }
1689
1690 return $debug_message . str_replace( ']]>', ']]&gt;', apply_filters( $the_filter, $content ) ) . "\n";
1691 } else {
1692 if ( ! empty( $post ) ) {
1693 do_action( 'timed_content_server_hide', $post->ID, $show, $hide, $content );
1694 } else {
1695 do_action( 'timed_content_server_hide', null, $show, $hide, $content );
1696 }
1697
1698 return $debug_message . "\n";
1699 }
1700
1701 }
1702
1703 /**
1704 * Processes the [timed-content-rule] shortcode.
1705 */
1706 function rules_show_html( $atts, $content = null ) {
1707 global $post;
1708
1709 $atts_parsed = shortcode_atts( array( 'id' => 0 ), $atts );
1710 $id = $atts_parsed['id'];
1711
1712 if ( ! is_numeric( $id ) ) {
1713 $page = get_page_by_title( $id, OBJECT, TIMED_CONTENT_RULE_TYPE );
1714 if ( null === $page ) {
1715 return '';
1716 }
1717 $id = $page->ID;
1718 }
1719 if ( TIMED_CONTENT_RULE_TYPE !== get_post_type( $id ) ) {
1720 return '';
1721 }
1722
1723 $prefix = TIMED_CONTENT_RULE_POSTMETA_PREFIX;
1724 $right_now_t = time();
1725 $rule_is_active = false;
1726
1727 // use debug parameter if current user is allowed to edit the post
1728 if ( isset( $_GET['tctest'] ) && current_user_can( 'edit_post', $post->post_id ) ) {
1729 $dt = DateTime::createFromFormat( 'Y-m-d H:i:s', sanitize_text_field( $_GET['tctest'] ) );
1730 if ( false !== $dt ) {
1731 $right_now_t = $dt->getTimestamp();
1732 }
1733 }
1734
1735 $active_periods = $this->get_rule_periods_by_id( $id, false );
1736 $action_is_show = (bool) get_post_meta( $id, $prefix . 'action', true );
1737
1738 foreach ( $active_periods as $period ) {
1739 if ( ( $period['start'] <= $right_now_t ) && ( $right_now_t <= $period['end'] ) ) {
1740 $rule_is_active = true;
1741 break;
1742 }
1743 }
1744
1745 $the_filter = 'timed_content_filter';
1746 $the_filter = apply_filters( 'timed_content_filter_override', $the_filter );
1747
1748 if ( ( true === $rule_is_active && true === $action_is_show ) || ( false === $rule_is_active && false === $action_is_show ) ) {
1749 if ( ! empty( $post ) ) {
1750 do_action( 'timed_content_rule_show', $post->ID, $id, $content );
1751 } else {
1752 do_action( 'timed_content_rule_show', null, $id, $content );
1753 }
1754
1755 return str_replace( ']]>', ']]&gt;', apply_filters( $the_filter, $content ) );
1756 } else {
1757 if ( ! empty( $post ) ) {
1758 do_action( 'timed_content_rule_hide', $post->ID, $id, $content );
1759 } else {
1760 do_action( 'timed_content_rule_hide', null, $id, $content );
1761 }
1762
1763 return '';
1764 }
1765 }
1766
1767 /**
1768 * Enqueues the JavaScript code necessary for the functionality of the [timed-content-client] shortcode.
1769 */
1770 function add_header_code() {
1771 if ( ! is_admin() ) {
1772 wp_enqueue_style( 'timed-content-css', TIMED_CONTENT_CSS, false, TIMED_CONTENT_VERSION );
1773 wp_enqueue_script(
1774 'timed-content_js',
1775 TIMED_CONTENT_PLUGIN_URL . '/js/timed-content.js',
1776 array( 'jquery' ),
1777 TIMED_CONTENT_VERSION
1778 );
1779 }
1780 }
1781
1782 /**
1783 * Enqueues the CSS code necessary for custom icons for the Timed Content Rules management screens
1784 * and the TinyMCE editor.
1785 */
1786 function add_post_type_icons() {
1787 wp_enqueue_style( 'timed-content-dashicons', TIMED_CONTENT_CSS_DASHICONS, false, TIMED_CONTENT_VERSION );
1788 ?>
1789 <style type="text/css" media="screen">
1790 #adminmenu #menu-posts-<?php echo TIMED_CONTENT_RULE_TYPE; ?>.menu-icon-post div.wp-menu-image:before {
1791 font-family: 'timed-content-dashicons' !important;
1792 content: '\e601';
1793 }
1794
1795 #dashboard_right_now a.<?php echo TIMED_CONTENT_RULE_TYPE; ?>-count:before {
1796 font-family: 'timed-content-dashicons' !important;
1797 content: '\e601' !important;
1798 }
1799
1800 .mce-i-timed_content:before {
1801 font: 400 24px/1 'timed-content-dashicons' !important;
1802 padding: 0;
1803 vertical-align: top;
1804 margin-left: -2px;
1805 padding-right: 2px;
1806 content: '\e601';
1807 }
1808 </style>
1809 <?php
1810 }
1811
1812 /**
1813 * Enqueues the JavaScript code necessary for the functionality of the Timed Content Rules management screens.
1814 */
1815 function add_admin_header_code() {
1816 if ( ( isset( $_GET['post_type'] ) && TIMED_CONTENT_RULE_TYPE === $_GET['post_type'] )
1817 || ( isset( $post_type ) && TIMED_CONTENT_RULE_TYPE === $post_type )
1818 || ( isset( $_GET['post'] ) && TIMED_CONTENT_RULE_TYPE === get_post_type( sanitize_text_field( $_GET['post'] ) ) ) ) {
1819 wp_enqueue_style( 'thickbox' );
1820 wp_enqueue_style( 'timed-content-css', TIMED_CONTENT_CSS, false, TIMED_CONTENT_VERSION );
1821 // Enqueue the JavaScript file that manages the meta box UI
1822 wp_enqueue_script(
1823 'timed-content-admin_js',
1824 TIMED_CONTENT_PLUGIN_URL . '/js/timed-content-admin.js',
1825 array( 'jquery' ),
1826 TIMED_CONTENT_VERSION
1827 );
1828 // Enqueue the JavaScript file that makes AJAX requests
1829 wp_enqueue_script(
1830 'timed-content-ajax_js',
1831 TIMED_CONTENT_PLUGIN_URL . '/js/timed-content-ajax.js',
1832 array( 'jquery', 'thickbox' ),
1833 TIMED_CONTENT_VERSION
1834 );
1835
1836 // Set up local variables used in the Admin JavaScript file
1837 wp_localize_script(
1838 'timed-content-admin_js',
1839 'timedContentRuleAdmin',
1840 array(
1841 'no_exceptions_label' => __( '- No exceptions set -', 'timed-content' ),
1842 )
1843 );
1844
1845 // Set up local variables used in the AJAX JavaScript file
1846 wp_localize_script(
1847 'timed-content-ajax_js',
1848 'timedContentRuleAjax',
1849 array(
1850 'ajaxurl' => admin_url( 'admin-ajax.php' ),
1851 'start_label' => _x(
1852 'Start',
1853 'Scheduled Dates/Times dialog - Beginning of active period table header',
1854 'timed-content'
1855 ),
1856 'end_label' => _x(
1857 'End',
1858 'Scheduled Dates/Times dialog - End of active period table header',
1859 'timed-content'
1860 ),
1861 'dialog_label' => _x(
1862 'Scheduled dates/times',
1863 'Scheduled Dates/Times dialog - dialog header',
1864 'timed-content'
1865 ),
1866 'button_loading_label' => __( 'Calculating dates/times', 'timed-content' ),
1867 'button_finished_label' => __( 'Show projected dates/times', 'timed-content' ),
1868 'dialog_width' => 800,
1869 'dialog_height' => 500,
1870 'error' => __( 'Error', 'timed-content' ),
1871 'error_desc' => __(
1872 'Something unexpected has happened along the way. The specific details are below:',
1873 'timed-content'
1874 ),
1875 )
1876 );
1877 }
1878 }
1879
1880 /**
1881 * Initializes the TinyMCE plugin bundled with this WordPress plugin
1882 */
1883 function init_tinymce_plugin() {
1884 if ( ( ! current_user_can( 'edit_posts' ) ) && ( ! current_user_can( 'edit_pages' ) ) ) {
1885 return;
1886 }
1887
1888 // Add only in Rich Editor mode
1889 if ( get_user_option( 'rich_editing' ) === 'true' ) {
1890 add_filter( 'mce_external_plugins', array( &$this, 'add_timed_content_tinymce_plugin' ) );
1891 add_filter( 'mce_buttons', array( &$this, 'register_tinymce_button' ) );
1892 }
1893 }
1894
1895 /**
1896 * Sets up variables to use in the TinyMCE plugin's plugin.js.
1897 */
1898 function set_tinymce_plugin_vars() {
1899 global $wp_version;
1900 if ( ( ! current_user_can( 'edit_posts' ) ) && ( ! current_user_can( 'edit_pages' ) ) ) {
1901 return;
1902 }
1903
1904 // Add only in Rich Editor mode
1905 if ( get_user_option( 'rich_editing' ) === 'true' ) {
1906 if ( version_compare( $wp_version, '3.8', '<' ) ) {
1907 $image = '/clock.gif';
1908 } else {
1909 $image = '';
1910 }
1911 wp_localize_script(
1912 'editor',
1913 'timedContentAdminTinyMCEOptions',
1914 array(
1915 'version' => TIMED_CONTENT_VERSION,
1916 'desc' => __( 'Add Timed Content shortcodes', 'timed-content' ),
1917 'image' => $image,
1918 )
1919 );
1920 }
1921 }
1922
1923 /**
1924 * Sets up the button for the associated TinyMCE plugin for use in the editor menubar.
1925 */
1926 function register_tinymce_button( $buttons ) {
1927 array_push( $buttons, '|', 'timed_content' );
1928
1929 return $buttons;
1930 }
1931
1932 /**
1933 * Loads the associated TinyMCE plugin into TinyMCE's plugin array
1934 */
1935 function add_timed_content_tinymce_plugin( $plugin_array ) {
1936 $plugin_array['timed_content'] = TIMED_CONTENT_PLUGIN_URL . '/tinymce_plugin/plugin.js';
1937
1938 return $plugin_array;
1939 }
1940
1941 /**
1942 * Generates JavaScript array of objects describing Timed Content rules. Used in the dialog box created by
1943 * timedContentPlugin::timedContentPluginGetTinyMCEDialog().
1944 */
1945 function get_rules_js() {
1946 $the_js = "var rules = [\n";
1947 $args = array(
1948 'post_type' => TIMED_CONTENT_RULE_TYPE,
1949 'posts_per_page' => -1,
1950 'post_status' => 'publish',
1951 );
1952 $the_rules = get_posts( $args );
1953 foreach ( $the_rules as $rule ) {
1954 $desc = $this->get_schedule_description_by_id( $rule->ID );
1955 $desc = str_replace( '<br />', ' ', $desc );
1956 // Only add a rule if there's no errors or warnings
1957 if ( false === strpos( $desc, 'tcr-warning' ) ) {
1958 $the_js .= " { 'ID': " . $rule->ID . ", 'title': '" . esc_js(
1959 ( ( strlen( $rule->post_title ) > 0 ) ? $rule->post_title : _x(
1960 '(no title)',
1961 'No Timed Content Rule title',
1962 'timed-content'
1963 ) )
1964 ) . "', 'desc': '" . esc_js( $desc ) . "' },\n";
1965 }
1966 }
1967 if ( empty( $the_rules ) ) {
1968 $the_js .= " { 'ID': -999, 'title': ' ---- ', 'desc': '" . __(
1969 'No Timed Content Rules found',
1970 'timed-content'
1971 ) . "' }\n";
1972 }
1973
1974 $the_js .= "];\n";
1975
1976 return $the_js;
1977 }
1978
1979 /**
1980 * Display a dialog box for this plugin's associated TinyMCE plugin. Called from TinyMCE via AJAX.
1981 */
1982 function timed_content_plugin_get_tinymce_dialog() {
1983 wp_enqueue_style( TIMED_CONTENT_SLUG . '-jquery-ui-css', TIMED_CONTENT_JQUERY_UI_CSS );
1984 wp_enqueue_script( 'jquery-ui-datepicker' );
1985 wp_register_style(
1986 TIMED_CONTENT_SLUG . '-jquery-ui-timepicker-css',
1987 TIMED_CONTENT_JQUERY_UI_TIMEPICKER_CSS
1988 );
1989 wp_enqueue_style( TIMED_CONTENT_SLUG . '-jquery-ui-timepicker-css' );
1990 wp_register_script(
1991 TIMED_CONTENT_SLUG . '-jquery-ui-timepicker-js',
1992 TIMED_CONTENT_JQUERY_UI_TIMEPICKER_JS,
1993 array( 'jquery', 'jquery-ui-datepicker' ),
1994 TIMED_CONTENT_VERSION
1995 );
1996 wp_enqueue_script( TIMED_CONTENT_SLUG . '-jquery-ui-timepicker-js' );
1997 if ( ! ( wp_script_is( TIMED_CONTENT_SLUG . '-jquery-ui-datetime-i18n-js', 'registered' ) ) ) {
1998 wp_register_script(
1999 TIMED_CONTENT_SLUG . '-jquery-ui-datetime-i18n-js',
2000 TIMED_CONTENT_PLUGIN_URL . '/js/timed-content-datetime-i18n.js',
2001 array( 'jquery', 'jquery-ui-datepicker', TIMED_CONTENT_SLUG . '-jquery-ui-timepicker-js' ),
2002 TIMED_CONTENT_VERSION
2003 );
2004 wp_enqueue_script( TIMED_CONTENT_SLUG . '-jquery-ui-datetime-i18n-js' );
2005 wp_localize_script(
2006 TIMED_CONTENT_SLUG . '-jquery-ui-datetime-i18n-js',
2007 'TimedContentJQDatepickerI18n',
2008 $this->jquery_ui_datetime_datepicker_i18n
2009 );
2010 wp_localize_script(
2011 TIMED_CONTENT_SLUG . '-jquery-ui-datetime-i18n-js',
2012 'TimedContentJQTimepickerI18n',
2013 $this->jquery_ui_datetime_timepicker_i18n
2014 );
2015 }
2016
2017 wp_register_script(
2018 'TIMED_CONTENT_SLUG' . '-tinymce-popup-js',
2019 includes_url() . '/js/tinymce/tiny_mce_popup.js',
2020 null,
2021 TIMED_CONTENT_VERSION
2022 );
2023 wp_enqueue_script( 'TIMED_CONTENT_SLUG' . '-tinymce-popup-js' );
2024 wp_register_script(
2025 'TIMED_CONTENT_SLUG' . '-tinymce-mctabs-js',
2026 includes_url() . '/js/tinymce/utils/mctabs.js',
2027 null,
2028 TIMED_CONTENT_VERSION
2029 );
2030 wp_enqueue_script( 'TIMED_CONTENT_SLUG' . '-tinymce-mctabs-js' );
2031
2032 ob_start();
2033 require __DIR__ . '/../tinymce_plugin/dialog.php';
2034 $content = ob_get_contents();
2035 ob_end_clean();
2036 echo $content;
2037 die();
2038 }
2039
2040 /**
2041 * Add custom columns to the Timed Content Rules overview page
2042 */
2043 function add_desc_column_head( $defaults ) {
2044 unset( $defaults['date'] );
2045 $defaults['description'] = __( 'Description', 'timed-content' );
2046 $defaults['shortcode'] = __( 'Shortcode', 'timed-content' );
2047
2048 return $defaults;
2049 }
2050
2051 /**
2052 * Display content associated with custom columns on the Timed Content rules overview page
2053 */
2054 function add_desc_column_content( $column_name, $post_id ) {
2055 if ( 'shortcode' === $column_name ) {
2056 echo sprintf(
2057 '<code>[%s id="%s"]...[/%s]</code>',
2058 TIMED_CONTENT_SHORTCODE_RULE,
2059 $post_id,
2060 TIMED_CONTENT_SHORTCODE_RULE
2061 );
2062 }
2063 if ( 'description' === $column_name ) {
2064 $desc = $this->get_schedule_description_by_id( $post_id );
2065 if ( $desc ) {
2066 echo sprintf( '<em>%s</em>', $desc );
2067 }
2068 }
2069 }
2070
2071 /**
2072 * Display a count of Timed Content rules in the Dashboard's Right Now widget
2073 */
2074 function add_rules_count() {
2075 $items = [];
2076 if ( ! post_type_exists( TIMED_CONTENT_RULE_TYPE ) ) {
2077 return;
2078 }
2079
2080 $num_posts = wp_count_posts( TIMED_CONTENT_RULE_TYPE );
2081 $num = number_format_i18n( $num_posts->publish );
2082 $text = _n(
2083 'Timed Content rule',
2084 'Timed Content rules',
2085 intval( $num_posts->publish ),
2086 'timed-content'
2087 );
2088 if ( current_user_can( 'edit_posts' ) || current_user_can( 'edit_pages' ) ) {
2089 $items[] = '<a class="'.TIMED_CONTENT_RULE_TYPE.'-count" href="edit.php?post_type=' . TIMED_CONTENT_RULE_TYPE . '">'
2090 . $num
2091 . ' '
2092 . $text
2093 . '</a>';
2094 }
2095
2096 if ( $num_posts->pending > 0 ) {
2097 $num = number_format_i18n( $num_posts->pending );
2098 $text = _n(
2099 'Timed Content rule pending',
2100 'Timed Content rules pending',
2101 intval( $num_posts->pending ),
2102 'timed-content'
2103 );
2104 if ( current_user_can( 'edit_posts' ) || current_user_can( 'edit_pages' ) ) {
2105 $items[] = '<a class="'.TIMED_CONTENT_RULE_TYPE.'-count" href="edit.php?post_status=pending&post_type=' . TIMED_CONTENT_RULE_TYPE . '">'
2106 . $num
2107 . ' '
2108 . $text
2109 . '</a>';
2110 }
2111 }
2112
2113 return $items;
2114 }
2115
2116 /**
2117 * Setup custom fields for Timed Content rules
2118 */
2119 function setup_custom_fields() {
2120 global $post;
2121
2122 $now_ts = time();
2123 $now_plus1h_dt = new DateTime();
2124 $now_plus2h_dt = new DateTime();
2125 $now_plus1y_dt = new DateTime();
2126 $now_plus1h_dt->setTimeStamp( $now_ts );
2127 $now_plus2h_dt->setTimeStamp( $now_ts );
2128 $now_plus1y_dt->setTimeStamp( $now_ts );
2129 $now_plus1h_dt->add( new DateInterval( 'PT1H' ) );
2130 $now_plus2h_dt->add( new DateInterval( 'PT2H' ) );
2131 $now_plus1y_dt->add( new DateInterval( 'P1Y' ) );
2132
2133 $post_id = ( isset( $_GET['post'] ) && ( TIMED_CONTENT_RULE_TYPE === get_post_type( $_GET['post'] ) ) ? intval( $_GET['post'] ) : intval( 0 ) );
2134 $exceptions_dates = get_post_meta( $post_id, TIMED_CONTENT_RULE_POSTMETA_PREFIX . 'exceptions_dates' );
2135 if ( false !== $exceptions_dates && isset( $exceptions_dates[0] ) && is_array( $exceptions_dates[0] ) ) {
2136 $timed_content_rules_exceptions_dates = $exceptions_dates[0];
2137 sort( $timed_content_rules_exceptions_dates, SORT_NUMERIC );
2138 $timed_content_rules_exceptions_dates = array_unique( $timed_content_rules_exceptions_dates );
2139
2140 // If the exceptions are stored as timestamps, convert them to ISO first
2141 $num = 0;
2142 while ( $num < count( $timed_content_rules_exceptions_dates ) ) {
2143 if ( is_numeric( $timed_content_rules_exceptions_dates[ $num ] ) ) {
2144 $timed_content_rules_exceptions_dates[ $num ] = $this->format_timestamp( 'Y-m-d', $timed_content_rules_exceptions_dates[ $num ] );
2145 }
2146 $num++;
2147 }
2148
2149 $timed_content_rules_exceptions_dates_array = array_combine( $timed_content_rules_exceptions_dates, $timed_content_rules_exceptions_dates );
2150 } else {
2151 $timed_content_rules_exceptions_dates_array = array( '0' => __( '- No exceptions set -', 'timed-content' ) );
2152 }
2153
2154 $this->rule_occurrence_custom_fields = array(
2155 array(
2156 'name' => 'action',
2157 'display' => 'block',
2158 'title' => __( 'Action', 'timed-content' ),
2159 'description' => __( 'Sets the action to be performed when the rule is active.', 'timed-content' ),
2160 'type' => 'radio',
2161 'values' => array(
2162 '1' => __( 'Show the content', 'timed-content' ),
2163 '0' => __( 'Hide the content', 'timed-content' ),
2164 ),
2165 'default' => '1',
2166 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2167 'capability' => 'edit_posts',
2168 ),
2169 array(
2170 'name' => 'instance_start',
2171 'display' => 'block',
2172 'title' => __( 'Starting date/time', 'timed-content' ),
2173 'description' => __( 'Sets the date and time for the beginning of the first active period for this rule.', 'timed-content' ),
2174 'type' => 'datetime',
2175 'default' => array(
2176 'date' => $this->format_timestamp( 'Y-m-d', $now_plus1h_dt->getTimeStamp() ),
2177 'time' => $this->format_timestamp( 'H:i', $now_plus1h_dt->getTimeStamp() ),
2178 ),
2179 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2180 'capability' => 'edit_posts',
2181 ),
2182 array(
2183 'name' => 'instance_end',
2184 'display' => 'block',
2185 'title' => __( 'Ending date/time', 'timed-content' ),
2186 'description' => __( 'Sets the date and time for the end of the first active period for this rule.', 'timed-content' ),
2187 'type' => 'datetime',
2188 'default' => array(
2189 'date' => $this->format_timestamp( 'Y-m-d', $now_plus2h_dt->getTimeStamp() ),
2190 'time' => $this->format_timestamp( 'H:i', $now_plus2h_dt->getTimeStamp() ),
2191 ),
2192 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2193 'capability' => 'edit_posts',
2194 ),
2195 array(
2196 'name' => 'timezone',
2197 'display' => 'block',
2198 'title' => __( 'Timezone', 'timed-content' ),
2199 'description' => __( 'Select the timezone you wish to use for this rule.', 'timed-content' ),
2200 'type' => 'timezone-list',
2201 'default' => get_option( 'timezone_string' ),
2202 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2203 'capability' => 'edit_posts',
2204 ),
2205 );
2206
2207 $this->rule_pattern_custom_fields = array(
2208 array(
2209 'name' => 'frequency',
2210 'display' => 'block',
2211 'title' => __( 'Frequency', 'timed-content' ),
2212 'description' => __( 'Sets the frequency at which the action should be repeated.', 'timed-content' ),
2213 'type' => 'list',
2214 'default' => '1',
2215 'values' => $this->rule_freq_array,
2216 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2217 'capability' => 'edit_posts',
2218 ),
2219 array(
2220 'name' => 'hourly_num_of_hours',
2221 'display' => 'none',
2222 'title' => __( 'Interval of recurrences', 'timed-content' ),
2223 'description' => __( 'Repeat this action every X hours.', 'timed-content' ),
2224 'type' => 'number',
2225 'default' => '1',
2226 'min' => '1',
2227 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2228 'capability' => 'edit_posts',
2229 ),
2230 array(
2231 'name' => 'daily_num_of_days',
2232 'display' => 'none',
2233 'title' => __( 'Interval of recurrences', 'timed-content' ),
2234 'description' => __( 'Repeat this action every X days.', 'timed-content' ),
2235 'type' => 'number',
2236 'default' => '1',
2237 'min' => '1',
2238 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2239 'capability' => 'edit_posts',
2240 ),
2241 array(
2242 'name' => 'weekly_num_of_weeks',
2243 'display' => 'none',
2244 'title' => __( 'Interval of recurrences', 'timed-content' ),
2245 'description' => __( 'Repeat this action every X weeks.', 'timed-content' ),
2246 'type' => 'number',
2247 'default' => '1',
2248 'min' => '1',
2249 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2250 'capability' => 'edit_posts',
2251 ),
2252 array(
2253 'name' => 'weekly_days_of_week_to_repeat',
2254 'display' => 'none',
2255 'title' => __( 'Repeat on the following days', 'timed-content' ),
2256 'description' => __( 'Repeat this action on these days of the week <strong>instead</strong> of the day of week the starting date/time falls on.', 'timed-content' ),
2257 'type' => 'checkbox-list',
2258 'default' => array(),
2259 'values' => $this->rule_days_array,
2260 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2261 'capability' => 'edit_posts',
2262 ),
2263 array(
2264 'name' => 'monthly_num_of_months',
2265 'display' => 'none',
2266 'title' => __( 'Interval of recurrences', 'timed-content' ),
2267 'description' => __( 'Repeat this action every X months.', 'timed-content' ),
2268 'type' => 'number',
2269 'default' => '1',
2270 'min' => '1',
2271 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2272 'capability' => 'edit_posts',
2273 ),
2274 array(
2275 'name' => 'monthly_nth_weekday_of_month',
2276 'display' => 'none',
2277 'title' => __( 'Repeat on a specific weekday of the month', 'timed-content' ),
2278 'description' => __( 'Repeat this action on a specific weekday of the month (for example, "every third Tuesday"). Check this box to select a pattern below.', 'timed-content' ),
2279 'type' => 'checkbox',
2280 'default' => 'no',
2281 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2282 'capability' => 'edit_posts',
2283 ),
2284 array(
2285 'name' => 'monthly_nth_weekday_of_month_nth',
2286 'display' => 'none',
2287 'title' => __( 'Weekday ordinal', 'timed-content' ),
2288 'description' => __( 'Select a value for week of the month (for example "first", "second", etc.).', 'timed-content' ),
2289 'type' => 'list',
2290 'default' => 0,
2291 'values' => $this->rule_ordinal_array,
2292 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2293 'capability' => 'edit_posts',
2294 ),
2295 array(
2296 'name' => 'monthly_nth_weekday_of_month_weekday',
2297 'display' => 'none',
2298 'title' => __( 'Day of the week', 'timed-content' ),
2299 'description' => __( 'Select the day of week.', 'timed-content' ),
2300 'type' => 'list',
2301 'default' => 0,
2302 'values' => $this->rule_ordinal_days_array,
2303 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2304 'capability' => 'edit_posts',
2305 ),
2306 array(
2307 'name' => 'yearly_num_of_years',
2308 'display' => 'none',
2309 'title' => __( 'Interval of recurrences', 'timed-content' ),
2310 'description' => __( 'Repeat this action every X years.', 'timed-content' ),
2311 'type' => 'number',
2312 'default' => '1',
2313 'min' => '1',
2314 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2315 'capability' => 'edit_posts',
2316 ),
2317 );
2318
2319 $this->rule_recurrence_custom_fields = array(
2320 array(
2321 'name' => 'recurrence_duration',
2322 'display' => 'block',
2323 'title' => __( 'How often to repeat this action', 'timed-content' ),
2324 'description' => '',
2325 'type' => 'radio',
2326 'values' => array(
2327 'recurrence_duration_end_date' => __( 'Keep repeating until a given date', 'timed-content' ),
2328 'recurrence_duration_num_repeat' => __( 'Repeat a set number of times', 'timed-content' ),
2329 ),
2330 'default' => 'recurrence_duration_end_date',
2331 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2332 'capability' => 'edit_posts',
2333 ),
2334 array(
2335 'name' => 'recurrence_duration_end_date',
2336 'display' => 'none',
2337 'title' => __( 'End Date', 'timed-content' ),
2338 'description' => __( 'Using the settings above, repeat this action until this date.', 'timed-content' ),
2339 'type' => 'date',
2340 'default' => $this->format_timestamp( 'Y-m-d', $now_plus1y_dt->getTimeStamp() ),
2341 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2342 'capability' => 'edit_posts',
2343 ),
2344 array(
2345 'name' => 'recurrence_duration_num_repeat',
2346 'display' => 'none',
2347 'title' => __( 'Number of repetitions', 'timed-content' ),
2348 'description' => __( 'Using the settings above, repeat this action this many times.', 'timed-content' ),
2349 'type' => 'number',
2350 'default' => '1',
2351 'min' => '1',
2352 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2353 'capability' => 'edit_posts',
2354 ),
2355 );
2356
2357 $this->rule_exceptions_custom_fields = array(
2358 array(
2359 'name' => 'exceptions_dates_picker',
2360 'display' => 'block',
2361 'title' => __( 'Add exception date:', 'timed-content' ),
2362 'description' => __( 'Select a date to add to the exception dates list.', 'timed-content' ),
2363 'type' => 'date',
2364 'default' => '',
2365 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2366 'capability' => 'edit_posts',
2367 ),
2368 array(
2369 'name' => 'exceptions_dates',
2370 'display' => 'block',
2371 'title' => __( 'Exception dates list', 'timed-content' ),
2372 'description' => __( 'Dates that this Timed Content rule will not be active. Double-click on a date to remove it from the list.', 'timed-content' ),
2373 'type' => 'menu',
2374 'values' => $timed_content_rules_exceptions_dates_array,
2375 'size' => '10',
2376 'default' => array(),
2377 'scope' => array( TIMED_CONTENT_RULE_TYPE ),
2378 'capability' => 'edit_posts',
2379 ),
2380 );
2381
2382 $rule_description = '';
2383 if ( isset( $_GET['post'] ) ) {
2384 $post_id = intval( $_GET['post'] );
2385 if ( get_post_type( $post_id ) === TIMED_CONTENT_RULE_TYPE ) {
2386 $rule_description = $this->get_schedule_description_by_id( $post_id );
2387 }
2388 }
2389
2390 $scf = new CustomFieldsInterface(
2391 'timed_content_rule_schedule',
2392 __( 'Rule description/schedule', 'timed-content' ),
2393 '',
2394 TIMED_CONTENT_RULE_POSTMETA_PREFIX,
2395 array( TIMED_CONTENT_RULE_TYPE ),
2396 array(),
2397 $this->jquery_ui_datetime_datepicker_i18n,
2398 $this->jquery_ui_datetime_timepicker_i18n,
2399 $rule_description
2400 );
2401 $ocf = new CustomFieldsInterface(
2402 'timed_content_rule_initial_event',
2403 __( 'Action/Initial Event', 'timed-content' ),
2404 __( 'Set the action to be taken and when it should first run.', 'timed-content' ),
2405 TIMED_CONTENT_RULE_POSTMETA_PREFIX,
2406 array( TIMED_CONTENT_RULE_TYPE ),
2407 $this->rule_occurrence_custom_fields,
2408 $this->jquery_ui_datetime_datepicker_i18n,
2409 $this->jquery_ui_datetime_timepicker_i18n
2410 );
2411 $pcf = new CustomFieldsInterface(
2412 'timed_content_rule_recurrence',
2413 __( 'Repeating Pattern', 'timed-content' ),
2414 __( 'Set how often the action should repeat.', 'timed-content' ),
2415 TIMED_CONTENT_RULE_POSTMETA_PREFIX,
2416 array( TIMED_CONTENT_RULE_TYPE ),
2417 $this->rule_pattern_custom_fields,
2418 $this->jquery_ui_datetime_datepicker_i18n,
2419 $this->jquery_ui_datetime_timepicker_i18n
2420 );
2421 $rcf = new CustomFieldsInterface(
2422 'timed_content_rule_stop_condition',
2423 __( 'Stopping Condition', 'timed-content' ),
2424 __( 'Set how long or how many times the action should occur.', 'timed-content' ),
2425 TIMED_CONTENT_RULE_POSTMETA_PREFIX,
2426 array( TIMED_CONTENT_RULE_TYPE ),
2427 $this->rule_recurrence_custom_fields,
2428 $this->jquery_ui_datetime_datepicker_i18n,
2429 $this->jquery_ui_datetime_timepicker_i18n
2430 );
2431 $ecf = new CustomFieldsInterface(
2432 'timed_content_rule_exceptions',
2433 __( 'Exceptions', 'timed-content' ),
2434 __( 'Set up any exceptions to this Timed Content Rule.', 'timed-content' ),
2435 TIMED_CONTENT_RULE_POSTMETA_PREFIX,
2436 array( TIMED_CONTENT_RULE_TYPE ),
2437 $this->rule_exceptions_custom_fields,
2438 $this->jquery_ui_datetime_datepicker_i18n,
2439 $this->jquery_ui_datetime_timepicker_i18n
2440 );
2441 }
2442
2443 /**
2444 * Strips indices from an array
2445 */
2446 function strip_array_indices( $array_to_strip ) {
2447 foreach ( $array_to_strip as $array_item ) {
2448 $new_array[] = $array_item;
2449 }
2450
2451 return $new_array;
2452 }
2453
2454 /**
2455 * Convert dates and times to ISO format if needed
2456 */
2457 function convert_date_time_parameters_to_iso( $args ) {
2458 $date_parsed = date_create_from_format( 'Y-m-d', $args['instance_start']['date'] );
2459 if ( false === $date_parsed ) {
2460 $date_source = strtotime( $this->date_time_to_english( $args['instance_start']['date'] ) );
2461 $args['instance_start']['date'] = $this->format_timestamp( 'Y-m-d', $date_source );
2462 }
2463
2464 $date_parsed = date_create_from_format( 'Y-m-d', $args['instance_end']['date'] );
2465 if ( false === $date_parsed ) {
2466 $date_source = strtotime( $this->date_time_to_english( $args['instance_end']['date'] ) );
2467 $args['instance_end']['date'] = $this->format_timestamp( 'Y-m-d', $date_source );
2468 }
2469
2470 $args['instance_start']['time'] = $this->convert_time_to_iso( $args['instance_start']['time'] );
2471
2472 $args['instance_end']['time'] = $this->convert_time_to_iso( $args['instance_end']['time'] );
2473
2474 $date_parsed = date_create_from_format( 'Y-m-d', $args['end_date'] );
2475 if ( false === $date_parsed ) {
2476 $date_source = strtotime( $this->date_time_to_english( $args['end_date'] ) );
2477 $args['end_date'] = $this->format_timestamp( 'Y-m-d', $date_source );
2478 }
2479
2480 if ( is_array( $args['exceptions_dates'] ) ) {
2481 foreach ( $args['exceptions_dates'] as $key => $value ) {
2482 $date_parsed = date_create_from_format( 'Y-m-d', $value );
2483 if ( false === $date_parsed ) {
2484 $date_source = strtotime( $this->date_time_to_english( $args['end_date'] ) );
2485 $args['exceptions_dates'][ $key ] = $this->format_timestamp( 'Y-m-d', $date_source );
2486 }
2487 }
2488 }
2489
2490 return $args;
2491 }
2492
2493 /**
2494 * Convert time to ISO format if needed
2495 */
2496 function convert_time_to_iso( $time ) {
2497 if ( strpos( $time, 'AM' ) !== false ) {
2498 $time_base = trim( substr( $time, 0, strlen( $time ) - 2 ) );
2499 $time_dt = date_create_from_format( 'G:i', $time_base );
2500 if ( false !== $time_dt ) {
2501 $time = $this->format_timestamp( 'H:i', $time_dt->getTimestamp() );
2502 }
2503 } elseif ( strpos( $time, 'PM' ) !== false ) {
2504 $time_base = trim( substr( $time, 0, strlen( $time ) - 2 ) );
2505 $time_dt = date_create_from_format( 'G:i', $time_base );
2506 if ( false !== $time_dt ) {
2507 $time = $this->format_timestamp( 'H:i', $time_dt->getTimestamp() + 43200 );
2508 }
2509 }
2510
2511 return $time;
2512 }
2513
2514 /**
2515 * Set the timezone to be used for format_date()
2516 */
2517 function set_format_timezone( $timezone ) {
2518 $this->current_timezone = new DateTimeZone( $timezone );
2519 if ( false === $this->current_timezone ) {
2520 $this->current_timezone = new DateTimeZone( 'UTC' );
2521 }
2522 }
2523 /**
2524 * Format a given timestamp with the specified timezone
2525 */
2526 function format_timestamp( $format, $timestamp ) {
2527 try {
2528 $dt = new DateTime();
2529 $dt->setTimezone( $this->current_timezone );
2530 $dt->setTimestamp( $timestamp );
2531 } catch ( Exception $e ) {
2532 return '';
2533 }
2534
2535 return $dt->format( $format );
2536 }
2537 }
2538