PluginProbe
Timed Content / 2.90
Timed Content v2.90
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.90, at lib/class-timedcontentplugin.php

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