Completed
Branch BUG-10202-persistent-admin-not... (41a214)
by
unknown
42:16 queued 30:56
created

EEH_DTT_Helper::validate_timezone()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 15
nc 3
nop 2
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
1
<?php
2
if (! defined('EVENT_ESPRESSO_VERSION')) {
3
    exit('NO direct script access allowed');
4
}
5
6
7
8
/**
9
 * Event Espresso
10
 * Event Registration and Management Plugin for Wordpress
11
 *
12
 * @package         Event Espresso
13
 * @author          Seth Shoultes
14
 * @copyright    (c)2009-2012 Event Espresso All Rights Reserved.
15
 * @license         http://eventespresso.com/support/terms-conditions/  ** see Plugin Licensing **
16
 * @link            http://www.eventespresso.com
17
 * @version         4.0
18
 *                  ------------------------------------------------------------------------
19
 *                  EEH_DTT_Helper
20
 *                  This is a helper utility class containing a variety for date time formatting helpers for Event
21
 *                  Espresso.
22
 * @package         Event Espresso
23
 * @subpackage      /helpers/EEH_DTT_Helper.helper.php
24
 * @author          Darren Ethier
25
 *                  ------------------------------------------------------------------------
26
 */
27
class EEH_DTT_Helper
28
{
29
30
31
    /**
32
     * return the timezone set for the WP install
33
     *
34
     * @return string valid timezone string for PHP DateTimeZone() class
35
     */
36
    public static function get_timezone()
37
    {
38
        return EEH_DTT_Helper::get_valid_timezone_string();
39
    }
40
41
42
    /**
43
     * get_valid_timezone_string
44
     *    ensures that a valid timezone string is returned
45
     *
46
     * @access protected
47
     * @param string $timezone_string
48
     * @return string
49
     * @throws \EE_Error
50
     */
51
    public static function get_valid_timezone_string($timezone_string = '')
52
    {
53
        // if passed a value, then use that, else get WP option
54
        $timezone_string = ! empty($timezone_string) ? $timezone_string : get_option('timezone_string');
55
        // value from above exists, use that, else get timezone string from gmt_offset
56
        $timezone_string = ! empty($timezone_string) ? $timezone_string
57
            : EEH_DTT_Helper::get_timezone_string_from_gmt_offset();
58
        EEH_DTT_Helper::validate_timezone($timezone_string);
59
        return $timezone_string;
60
    }
61
62
63
    /**
64
     * This only purpose for this static method is to validate that the incoming timezone is a valid php timezone.
65
     *
66
     * @static
67
     * @access public
68
     * @param  string $timezone_string Timezone string to check
69
     * @param bool    $throw_error
70
     * @return bool
71
     * @throws \EE_Error
72
     */
73
    public static function validate_timezone($timezone_string, $throw_error = true)
74
    {
75
        // easiest way to test a timezone string is just see if it throws an error when you try to create a DateTimeZone object with it
76
        try {
77
            new DateTimeZone($timezone_string);
78
        } catch (Exception $e) {
79
            // sometimes we take exception to exceptions
80
            if (! $throw_error) {
81
                return false;
82
            }
83
            throw new EE_Error(
84
                sprintf(
85
                    __(
86
                        'The timezone given (%1$s), is invalid, please check with %2$sthis list%3$s for what valid timezones can be used',
87
                        'event_espresso'
88
                    ),
89
                    $timezone_string,
90
                    '<a href="http://www.php.net/manual/en/timezones.php">',
91
                    '</a>'
92
                )
93
            );
94
        }
95
        return true;
96
    }
97
98
99
    /**
100
     * _create_timezone_object_from_timezone_name
101
     *
102
     * @access protected
103
     * @param string $gmt_offset
104
     * @return string
105
     */
106
    public static function get_timezone_string_from_gmt_offset($gmt_offset = '')
107
    {
108
        $timezone_string = 'UTC';
109
        //if there is no incoming gmt_offset, then because WP hooks in on timezone_string, we need to see if that is
110
        //set because it will override `gmt_offset` via `pre_get_option` filter.  If that's set, then let's just use
111
        //that!  Otherwise we'll leave timezone_string at the default of 'UTC' before doing other logic.
112
        if ($gmt_offset === '') {
113
            //autoloaded so no need to set to a variable.  There will not be multiple hits to the db.
114
            if (get_option('timezone_string')) {
115
                return get_option('timezone_string');
116
            }
117
        }
118
        $gmt_offset = $gmt_offset !== '' ? $gmt_offset : get_option('gmt_offset');
119
        $gmt_offset = (float)$gmt_offset;
120
        //if $gmt_offset is 0, then just return UTC
121
        if ($gmt_offset === (float)0) {
122
            return $timezone_string;
123
        }
124
        if ($gmt_offset !== '') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $gmt_offset (double) and '' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
125
            // convert GMT offset to seconds
126
            $gmt_offset = $gmt_offset * HOUR_IN_SECONDS;
127
            // although we don't know the TZ abbreviation, we know the UTC offset
128
            $timezone_string = timezone_name_from_abbr(null, $gmt_offset);
129
            //only use this timezone_string IF it's current offset matches the given offset
130
            try {
131
                $offset = self::get_timezone_offset(new DateTimeZone($timezone_string));
132
                if ($offset !== $gmt_offset) {
133
                    $timezone_string = false;
134
                }
135
            } catch (Exception $e) {
136
                $timezone_string = false;
137
            }
138
        }
139
        // better have a valid timezone string by now, but if not, sigh... loop thru  the timezone_abbreviations_list()...
140
        $timezone_string = $timezone_string !== false
141
            ? $timezone_string
142
            : EEH_DTT_Helper::get_timezone_string_from_abbreviations_list($gmt_offset);
143
        return $timezone_string;
144
    }
145
146
147
    /**
148
     * Gets the site's GMT offset based on either the timezone string
149
     * (in which case teh gmt offset will vary depending on the location's
150
     * observance of daylight savings time) or the gmt_offset wp option
151
     *
152
     * @return int seconds offset
153
     */
154
    public static function get_site_timezone_gmt_offset()
155
    {
156
        $timezone_string = get_option('timezone_string');
157
        if ($timezone_string) {
158
            try {
159
                $timezone = new DateTimeZone($timezone_string);
160
                return $timezone->getOffset(new DateTime()); //in WordPress DateTime defaults to UTC
161
            } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
162
            }
163
        }
164
        $offset = get_option('gmt_offset');
165
        return (int)($offset * HOUR_IN_SECONDS);
166
    }
167
168
169
    /**
170
     * Depending on PHP version, there might not bevalid current timezone strings to match these gmt_offsets in its
171
     * timezone tables.
172
     * To get around that, for these fringe timezones we bump them to a known valid offset.
173
     * This method should ONLY be called after first verifying an timezone_string cannot be retrieved for the offset.
174
     *
175
     * @access public
176
     * @param int $gmt_offset
177
     * @return int
178
     */
179
    public static function adjust_invalid_gmt_offsets($gmt_offset = 0)
180
    {
181
        //make sure $gmt_offset is int
182
        $gmt_offset = (int)$gmt_offset;
183
        switch ($gmt_offset) {
184
            //-12
185
            case -43200:
186
                $gmt_offset = -39600;
187
                break;
188
            //-11.5
189
            case -41400:
190
                $gmt_offset = -39600;
191
                break;
192
            //-10.5
193
            case -37800:
194
                $gmt_offset = -39600;
195
                break;
196
            //-8.5
197
            case -30600:
198
                $gmt_offset = -28800;
199
                break;
200
            //-7.5
201
            case -27000:
202
                $gmt_offset = -25200;
203
                break;
204
            //-6.5
205
            case -23400:
206
                $gmt_offset = -21600;
207
                break;
208
            //-5.5
209
            case -19800:
210
                $gmt_offset = -18000;
211
                break;
212
            //-4.5
213
            case -16200:
214
                $gmt_offset = -14400;
215
                break;
216
            //-3.5
217
            case -12600:
218
                $gmt_offset = -10800;
219
                break;
220
            //-2.5
221
            case -9000:
222
                $gmt_offset = -7200;
223
                break;
224
            //-1.5
225
            case -5400:
226
                $gmt_offset = -3600;
227
                break;
228
            //-0.5
229
            case -1800:
230
                $gmt_offset = 0;
231
                break;
232
            //.5
233
            case 1800:
234
                $gmt_offset = 3600;
235
                break;
236
            //1.5
237
            case 5400:
238
                $gmt_offset = 7200;
239
                break;
240
            //2.5
241
            case 9000:
242
                $gmt_offset = 10800;
243
                break;
244
            //3.5
245
            case 12600:
246
                $gmt_offset = 14400;
247
                break;
248
            //7.5
249
            case 27000:
250
                $gmt_offset = 28800;
251
                break;
252
            //8.5
253
            case 30600:
254
                $gmt_offset = 31500;
255
                break;
256
            //10.5
257
            case 37800:
258
                $gmt_offset = 39600;
259
                break;
260
            //11.5
261
            case 41400:
262
                $gmt_offset = 43200;
263
                break;
264
            //12.75
265
            case 45900:
266
                $gmt_offset = 46800;
267
                break;
268
            //13.75
269
            case 49500:
270
                $gmt_offset = 50400;
271
                break;
272
        }
273
        return $gmt_offset;
274
    }
275
276
277
    /**
278
     * get_timezone_string_from_abbreviations_list
279
     *
280
     * @access public
281
     * @param int  $gmt_offset
282
     * @param bool $coerce If true, we attempt to coerce with our adjustment table @see self::adjust_invalid_gmt_offset.
283
     * @return string
284
     * @throws \EE_Error
285
     */
286
    public static function get_timezone_string_from_abbreviations_list($gmt_offset = 0, $coerce = true)
287
    {
288
        $abbreviations = timezone_abbreviations_list();
289
        foreach ($abbreviations as $abbreviation) {
290
            foreach ($abbreviation as $city) {
291
                if ($city['offset'] === $gmt_offset && $city['dst'] === false) {
292
                    try {
293
                        $offset = self::get_timezone_offset(new DateTimeZone($city['timezone_id']));
294
                        if ($offset !== $gmt_offset) {
295
                            continue;
296
                        } else {
297
                            return $city['timezone_id'];
298
                        }
299
                    } catch (Exception $e) {
300
                        continue;
301
                    }
302
                }
303
            }
304
        }
305
        //if $coerce is true, let's see if we can get a timezone string after the offset is adjusted
306
        if ($coerce == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
307
            $timezone_string = self::get_timezone_string_from_abbreviations_list(
308
                self::adjust_invalid_gmt_offsets($gmt_offset),
309
                false
310
            );
311
            if ($timezone_string) {
312
                return $timezone_string;
313
            }
314
        }
315
        throw new EE_Error(
316
            sprintf(
317
                __(
318
                    'The provided GMT offset (%1$s), is invalid, please check with %2$sthis list%3$s for what valid timezones can be used',
319
                    'event_espresso'
320
                ),
321
                $gmt_offset,
322
                '<a href="http://www.php.net/manual/en/timezones.php">',
323
                '</a>'
324
            )
325
        );
326
    }
327
328
329
330
    /**
331
     * Get Timezone Transitions
332
     *
333
     * @param \DateTimeZone $date_time_zone
334
     * @param null          $time
335
     * @param bool          $first_only
336
     * @return array|mixed
337
     */
338
    public static function get_timezone_transitions(DateTimeZone $date_time_zone, $time = null, $first_only = true)
339
    {
340
        $time        = is_int($time) || $time === null ? $time : strtotime($time);
341
        $time        = preg_match(EE_Datetime_Field::unix_timestamp_regex, $time) ? $time : time();
342
        $transitions = $date_time_zone->getTransitions($time);
343
        return $first_only && ! isset($transitions['ts']) ? reset($transitions) : $transitions;
344
    }
345
346
347
    /**
348
     * Get Timezone Offset for given timezone object.
349
     *
350
     * @param \DateTimeZone $date_time_zone
351
     * @param null          $time
352
     * @return mixed
353
     * @throws \DomainException
354
     */
355
    public static function get_timezone_offset(DateTimeZone $date_time_zone, $time = null)
356
    {
357
        $transitions = self::get_timezone_transitions($date_time_zone, $time);
358
        if (! isset($transitions['offset'])) {
359
            throw new DomainException();
360
        }
361
        return $transitions['offset'];
362
    }
363
364
365
    /**
366
     * @access public
367
     * @param string $timezone_string
368
     */
369
    public static function timezone_select_input($timezone_string = '')
370
    {
371
        // get WP date time format
372
        $datetime_format = get_option('date_format') . ' ' . get_option('time_format');
373
        // if passed a value, then use that, else get WP option
374
        $timezone_string = ! empty($timezone_string) ? $timezone_string : get_option('timezone_string');
375
        // check if the timezone is valid but don't throw any errors if it isn't
376
        $timezone_string = EEH_DTT_Helper::validate_timezone($timezone_string, false);
377
        $gmt_offset      = get_option('gmt_offset');
378
        $check_zone_info = true;
379
        if (empty($timezone_string)) {
380
            // Create a UTC+- zone if no timezone string exists
381
            $check_zone_info = false;
382
            if ($gmt_offset > 0) {
383
                $timezone_string = 'UTC+' . $gmt_offset;
384
            } elseif ($gmt_offset < 0) {
385
                $timezone_string = 'UTC' . $gmt_offset;
386
            } else {
387
                $timezone_string = 'UTC';
388
            }
389
        }
390
        ?>
391
392
        <p>
393
            <label for="timezone_string"><?php _e('timezone'); ?></label>
394
            <select id="timezone_string" name="timezone_string">
395
                <?php echo wp_timezone_choice($timezone_string); ?>
396
            </select>
397
            <br/>
398
            <span class="description"><?php _e('Choose a city in the same timezone as the event.'); ?></span>
399
        </p>
400
401
        <p>
402
        <span><?php
403
            printf(
404
                __('%1$sUTC%2$s time is %3$s'),
405
                '<abbr title="Coordinated Universal Time">',
406
                '</abbr>',
407
                '<code>' . date_i18n($datetime_format, false, true) . '</code>'
408
            );
409
            ?></span>
410
        <?php if (! empty($timezone_string) || ! empty($gmt_offset)) : ?>
411
        <br/><span><?php printf(__('Local time is %1$s'), '<code>' . date_i18n($datetime_format) . '</code>'); ?></span>
412
    <?php endif; ?>
413
414
        <?php if ($check_zone_info && $timezone_string) : ?>
415
        <br/>
416
        <span>
417
					<?php
418
                    // Set TZ so localtime works.
419
                    date_default_timezone_set($timezone_string);
420
                    $now = localtime(time(), true);
421
                    if ($now['tm_isdst']) {
422
                        _e('This timezone is currently in daylight saving time.');
423
                    } else {
424
                        _e('This timezone is currently in standard time.');
425
                    }
426
                    ?>
427
            <br/>
428
            <?php
429
            if (function_exists('timezone_transitions_get')) {
430
                $found                   = false;
431
                $date_time_zone_selected = new DateTimeZone($timezone_string);
432
                $tz_offset               = timezone_offset_get($date_time_zone_selected, date_create());
433
                $right_now               = time();
434
                $tr['isdst']             = false;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$tr was never initialized. Although not strictly required by PHP, it is generally a good practice to add $tr = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
435
                foreach (timezone_transitions_get($date_time_zone_selected) as $tr) {
436
                    if ($tr['ts'] > $right_now) {
437
                        $found = true;
438
                        break;
439
                    }
440
                }
441
                if ($found) {
442
                    $message = $tr['isdst']
443
                        ?
444
                        __(' Daylight saving time begins on: %s.')
445
                        :
446
                        __(' Standard time begins  on: %s.');
447
                    // Add the difference between the current offset and the new offset to ts to get the correct transition time from date_i18n().
448
                    printf(
449
                        $message,
450
                        '<code >' . date_i18n($datetime_format, $tr['ts'] + ($tz_offset - $tr['offset'])) . '</code >'
451
                    );
452
                } else {
453
                    _e('This timezone does not observe daylight saving time.');
454
                }
455
            }
456
            // Set back to UTC.
457
            date_default_timezone_set('UTC');
458
            ?>
459
				</span></p>
460
        <?php
461
    endif;
462
    }
463
464
465
    /**
466
     * This method will take an incoming unix timestamp and add the offset to it for the given timezone_string.
467
     * If no unix timestamp is given then time() is used.  If no timezone is given then the set timezone string for
468
     * the site is used.
469
     * This is used typically when using a Unix timestamp any core WP functions that expect their specially
470
     * computed timestamp (i.e. date_i18n() )
471
     *
472
     * @param int    $unix_timestamp                  if 0, then time() will be used.
473
     * @param string $timezone_string                 timezone_string. If empty, then the current set timezone for the
474
     *                                                site will be used.
475
     * @return int      $unix_timestamp with the offset applied for the given timezone.
476
     */
477
    public static function get_timestamp_with_offset($unix_timestamp = 0, $timezone_string = '')
478
    {
479
        $unix_timestamp  = $unix_timestamp === 0 ? time() : (int)$unix_timestamp;
480
        $timezone_string = self::get_valid_timezone_string($timezone_string);
481
        $TimeZone        = new DateTimeZone($timezone_string);
482
        $DateTime = new DateTime('@' . $unix_timestamp, $TimeZone);
483
        $offset   = timezone_offset_get($TimeZone, $DateTime);
484
        return (int)$DateTime->format('U') + (int)$offset;
485
    }
486
487
488
    /**
489
     *    _set_date_time_field
490
     *    modifies EE_Base_Class EE_Datetime_Field objects
491
     *
492
     * @param  EE_Base_Class $obj                 EE_Base_Class object
493
     * @param    DateTime    $DateTime            PHP DateTime object
494
     * @param  string        $datetime_field_name the datetime fieldname to be manipulated
495
     * @return    EE_Base_Class
496
     */
497
    protected static function _set_date_time_field(EE_Base_Class $obj, DateTime $DateTime, $datetime_field_name)
498
    {
499
        // grab current datetime format
500
        $current_format = $obj->get_format();
501
        // set new full timestamp format
502
        $obj->set_date_format(EE_Datetime_Field::mysql_date_format);
503
        $obj->set_time_format(EE_Datetime_Field::mysql_time_format);
504
        // set the new date value using a full timestamp format so that no data is lost
505
        $obj->set($datetime_field_name, $DateTime->format(EE_Datetime_Field::mysql_timestamp_format));
506
        // reset datetime formats
507
        $obj->set_date_format($current_format[0]);
508
        $obj->set_time_format($current_format[1]);
509
        return $obj;
510
    }
511
512
513
    /**
514
     *    date_time_add
515
     *    helper for doing simple datetime calculations on a given datetime from EE_Base_Class
516
     *    and modifying it IN the EE_Base_Class so you don't have to do anything else.
517
     *
518
     * @param  EE_Base_Class $obj                 EE_Base_Class object
519
     * @param  string        $datetime_field_name name of the EE_Datetime_Filed datatype db column to be manipulated
520
     * @param  string        $period              what you are adding. The options are (years, months, days, hours,
521
     *                                            minutes, seconds) defaults to years
522
     * @param  integer       $value               what you want to increment the time by
523
     * @return EE_Base_Class           return the EE_Base_Class object so right away you can do something with it
524
     *                                            (chaining)
525
     */
526 View Code Duplication
    public static function date_time_add(EE_Base_Class $obj, $datetime_field_name, $period = 'years', $value = 1)
527
    {
528
        //get the raw UTC date.
529
        $DateTime = $obj->get_DateTime_object($datetime_field_name);
530
        $DateTime = EEH_DTT_Helper::calc_date($DateTime, $period, $value);
531
        return EEH_DTT_Helper::_set_date_time_field($obj, $DateTime, $datetime_field_name);
532
    }
533
534
535
    /**
536
     *    date_time_subtract
537
     *    same as date_time_add except subtracting value instead of adding.
538
     *
539
     * @param \EE_Base_Class $obj
540
     * @param  string        $datetime_field_name name of the EE_Datetime_Filed datatype db column to be manipulated
541
     * @param string         $period
542
     * @param int            $value
543
     * @return \EE_Base_Class
544
     */
545 View Code Duplication
    public static function date_time_subtract(EE_Base_Class $obj, $datetime_field_name, $period = 'years', $value = 1)
546
    {
547
        //get the raw UTC date
548
        $DateTime = $obj->get_DateTime_object($datetime_field_name);
549
        $DateTime = EEH_DTT_Helper::calc_date($DateTime, $period, $value, '-');
550
        return EEH_DTT_Helper::_set_date_time_field($obj, $DateTime, $datetime_field_name);
551
    }
552
553
554
    /**
555
     * Simply takes an incoming DateTime object and does calculations on it based on the incoming parameters
556
     *
557
     * @param  DateTime $DateTime DateTime object
558
     * @param  string   $period   a value to indicate what interval is being used in the calculation. The options are
559
     *                            'years', 'months', 'days', 'hours', 'minutes', 'seconds'. Defaults to years.
560
     * @param  integer  $value    What you want to increment the date by
561
     * @param  string   $operand  What operand you wish to use for the calculation
562
     * @return \DateTime return whatever type came in.
563
     * @throws \EE_Error
564
     */
565
    protected static function _modify_datetime_object(DateTime $DateTime, $period = 'years', $value = 1, $operand = '+')
566
    {
567
        if (! $DateTime instanceof DateTime) {
568
            throw new EE_Error(
569
                sprintf(
570
                    __('Expected a PHP DateTime object, but instead received %1$s', 'event_espresso'),
571
                    print_r($DateTime, true)
572
                )
573
            );
574
        }
575
        switch ($period) {
576
            case 'years' :
577
                $value = 'P' . $value . 'Y';
578
                break;
579
            case 'months' :
580
                $value = 'P' . $value . 'M';
581
                break;
582
            case 'weeks' :
583
                $value = 'P' . $value . 'W';
584
                break;
585
            case 'days' :
586
                $value = 'P' . $value . 'D';
587
                break;
588
            case 'hours' :
589
                $value = 'PT' . $value . 'H';
590
                break;
591
            case 'minutes' :
592
                $value = 'PT' . $value . 'M';
593
                break;
594
            case 'seconds' :
595
                $value = 'PT' . $value . 'S';
596
                break;
597
        }
598
        switch ($operand) {
599
            case '+':
600
                $DateTime->add(new DateInterval($value));
601
                break;
602
            case '-':
603
                $DateTime->sub(new DateInterval($value));
604
                break;
605
        }
606
        return $DateTime;
607
    }
608
609
610
    /**
611
     * Simply takes an incoming Unix timestamp and does calculations on it based on the incoming parameters
612
     *
613
     * @param  int     $timestamp Unix timestamp
614
     * @param  string  $period    a value to indicate what interval is being used in the calculation. The options are
615
     *                            'years', 'months', 'days', 'hours', 'minutes', 'seconds'. Defaults to years.
616
     * @param  integer $value     What you want to increment the date by
617
     * @param  string  $operand   What operand you wish to use for the calculation
618
     * @return \DateTime return whatever type came in.
619
     * @throws \EE_Error
620
     */
621
    protected static function _modify_timestamp($timestamp, $period = 'years', $value = 1, $operand = '+')
622
    {
623
        if (! preg_match(EE_Datetime_Field::unix_timestamp_regex, $timestamp)) {
624
            throw new EE_Error(
625
                sprintf(
626
                    __('Expected a Unix timestamp, but instead received %1$s', 'event_espresso'),
627
                    print_r($timestamp, true)
628
                )
629
            );
630
        }
631
        switch ($period) {
632
            case 'years' :
633
                $value = YEAR_IN_SECONDS * $value;
634
                break;
635
            case 'months' :
636
                $value = YEAR_IN_SECONDS / 12 * $value;
637
                break;
638
            case 'weeks' :
639
                $value = WEEK_IN_SECONDS * $value;
640
                break;
641
            case 'days' :
642
                $value = DAY_IN_SECONDS * $value;
643
                break;
644
            case 'hours' :
645
                $value = HOUR_IN_SECONDS * $value;
646
                break;
647
            case 'minutes' :
648
                $value = MINUTE_IN_SECONDS * $value;
649
                break;
650
        }
651
        switch ($operand) {
652
            case '+':
653
                $timestamp += $value;
654
                break;
655
            case '-':
656
                $timestamp -= $value;
657
                break;
658
        }
659
        return $timestamp;
660
    }
661
662
663
    /**
664
     * Simply takes an incoming UTC timestamp or DateTime object and does calculations on it based on the incoming
665
     * parameters and returns the new timestamp or DateTime.
666
     *
667
     * @param  int | DateTime $DateTime_or_timestamp DateTime object or Unix timestamp
668
     * @param  string         $period                a value to indicate what interval is being used in the
669
     *                                               calculation. The options are 'years', 'months', 'days', 'hours',
670
     *                                               'minutes', 'seconds'. Defaults to years.
671
     * @param  integer        $value                 What you want to increment the date by
672
     * @param  string         $operand               What operand you wish to use for the calculation
673
     * @return mixed string|DateTime          return whatever type came in.
674
     */
675
    public static function calc_date($DateTime_or_timestamp, $period = 'years', $value = 1, $operand = '+')
676
    {
677
        if ($DateTime_or_timestamp instanceof DateTime) {
678
            return EEH_DTT_Helper::_modify_datetime_object($DateTime_or_timestamp, $period, $value, $operand);
679
        } elseif (preg_match(EE_Datetime_Field::unix_timestamp_regex, $DateTime_or_timestamp)) {
680
            return EEH_DTT_Helper::_modify_timestamp($DateTime_or_timestamp, $period, $value, $operand);
681
        } else {
682
            //error
683
            return $DateTime_or_timestamp;
684
        }
685
    }
686
687
688
    /**
689
     * The purpose of this helper method is to receive an incoming format string in php date/time format
690
     * and spit out the js and moment.js equivalent formats.
691
     * Note, if no format string is given, then it is assumed the user wants what is set for WP.
692
     * Note, js date and time formats are those used by the jquery-ui datepicker and the jquery-ui date-
693
     * time picker.
694
     *
695
     * @see http://stackoverflow.com/posts/16725290/ for the code inspiration.
696
     * @param null $date_format_string
697
     * @param null $time_format_string
698
     * @return array
699
     *                array(
700
     *                'js' => array (
701
     *                'date' => //date format
702
     *                'time' => //time format
703
     *                ),
704
     *                'moment' => //date and time format.
705
     *                )
706
     */
707
    public static function convert_php_to_js_and_moment_date_formats(
708
        $date_format_string = null,
709
        $time_format_string = null
710
    ) {
711
        if ($date_format_string === null) {
712
            $date_format_string = get_option('date_format');
713
        }
714
        if ($time_format_string === null) {
715
            $time_format_string = get_option('time_format');
716
        }
717
        $date_format = self::_php_to_js_moment_converter($date_format_string);
718
        $time_format = self::_php_to_js_moment_converter($time_format_string);
719
        return array(
720
            'js'     => array(
721
                'date' => $date_format['js'],
722
                'time' => $time_format['js'],
723
            ),
724
            'moment' => $date_format['moment'] . ' ' . $time_format['moment'],
725
        );
726
    }
727
728
729
    /**
730
     * This converts incoming format string into js and moment variations.
731
     *
732
     * @param string $format_string incoming php format string
733
     * @return array js and moment formats.
734
     */
735
    protected static function _php_to_js_moment_converter($format_string)
736
    {
737
        /**
738
         * This is a map of symbols for formats.
739
         * The index is the php symbol, the equivalent values are in the array.
740
         *
741
         * @var array
742
         */
743
        $symbols_map      = array(
744
            // Day
745
            //01
746
            'd' => array(
747
                'js'     => 'dd',
748
                'moment' => 'DD',
749
            ),
750
            //Mon
751
            'D' => array(
752
                'js'     => 'D',
753
                'moment' => 'ddd',
754
            ),
755
            //1,2,...31
756
            'j' => array(
757
                'js'     => 'd',
758
                'moment' => 'D',
759
            ),
760
            //Monday
761
            'l' => array(
762
                'js'     => 'DD',
763
                'moment' => 'dddd',
764
            ),
765
            //ISO numeric representation of the day of the week (1-6)
766
            'N' => array(
767
                'js'     => '',
768
                'moment' => 'E',
769
            ),
770
            //st,nd.rd
771
            'S' => array(
772
                'js'     => '',
773
                'moment' => 'o',
774
            ),
775
            //numeric representation of day of week (0-6)
776
            'w' => array(
777
                'js'     => '',
778
                'moment' => 'd',
779
            ),
780
            //day of year starting from 0 (0-365)
781
            'z' => array(
782
                'js'     => 'o',
783
                'moment' => 'DDD' //note moment does not start with 0 so will need to modify by subtracting 1
784
            ),
785
            // Week
786
            //ISO-8601 week number of year (weeks starting on monday)
787
            'W' => array(
788
                'js'     => '',
789
                'moment' => 'w',
790
            ),
791
            // Month
792
            // January...December
793
            'F' => array(
794
                'js'     => 'MM',
795
                'moment' => 'MMMM',
796
            ),
797
            //01...12
798
            'm' => array(
799
                'js'     => 'mm',
800
                'moment' => 'MM',
801
            ),
802
            //Jan...Dec
803
            'M' => array(
804
                'js'     => 'M',
805
                'moment' => 'MMM',
806
            ),
807
            //1-12
808
            'n' => array(
809
                'js'     => 'm',
810
                'moment' => 'M',
811
            ),
812
            //number of days in given month
813
            't' => array(
814
                'js'     => '',
815
                'moment' => '',
816
            ),
817
            // Year
818
            //whether leap year or not 1/0
819
            'L' => array(
820
                'js'     => '',
821
                'moment' => '',
822
            ),
823
            //ISO-8601 year number
824
            'o' => array(
825
                'js'     => '',
826
                'moment' => 'GGGG',
827
            ),
828
            //1999...2003
829
            'Y' => array(
830
                'js'     => 'yy',
831
                'moment' => 'YYYY',
832
            ),
833
            //99...03
834
            'y' => array(
835
                'js'     => 'y',
836
                'moment' => 'YY',
837
            ),
838
            // Time
839
            // am/pm
840
            'a' => array(
841
                'js'     => 'tt',
842
                'moment' => 'a',
843
            ),
844
            // AM/PM
845
            'A' => array(
846
                'js'     => 'TT',
847
                'moment' => 'A',
848
            ),
849
            // Swatch Internet Time?!?
850
            'B' => array(
851
                'js'     => '',
852
                'moment' => '',
853
            ),
854
            //1...12
855
            'g' => array(
856
                'js'     => 'h',
857
                'moment' => 'h',
858
            ),
859
            //0...23
860
            'G' => array(
861
                'js'     => 'H',
862
                'moment' => 'H',
863
            ),
864
            //01...12
865
            'h' => array(
866
                'js'     => 'hh',
867
                'moment' => 'hh',
868
            ),
869
            //00...23
870
            'H' => array(
871
                'js'     => 'HH',
872
                'moment' => 'HH',
873
            ),
874
            //00..59
875
            'i' => array(
876
                'js'     => 'mm',
877
                'moment' => 'mm',
878
            ),
879
            //seconds... 00...59
880
            's' => array(
881
                'js'     => 'ss',
882
                'moment' => 'ss',
883
            ),
884
            //microseconds
885
            'u' => array(
886
                'js'     => '',
887
                'moment' => '',
888
            ),
889
        );
890
        $jquery_ui_format = "";
891
        $moment_format    = "";
892
        $escaping         = false;
893
        for ($i = 0; $i < strlen($format_string); $i++) {
894
            $char = $format_string[$i];
895
            if ($char === '\\') { // PHP date format escaping character
896
                $i++;
897
                if ($escaping) {
898
                    $jquery_ui_format .= $format_string[$i];
899
                    $moment_format    .= $format_string[$i];
900
                } else {
901
                    $jquery_ui_format .= '\'' . $format_string[$i];
902
                    $moment_format    .= $format_string[$i];
903
                }
904
                $escaping = true;
905
            } else {
906
                if ($escaping) {
907
                    $jquery_ui_format .= "'";
908
                    $moment_format    .= "'";
909
                    $escaping         = false;
910
                }
911
                if (isset($symbols_map[$char])) {
912
                    $jquery_ui_format .= $symbols_map[$char]['js'];
913
                    $moment_format    .= $symbols_map[$char]['moment'];
914
                } else {
915
                    $jquery_ui_format .= $char;
916
                    $moment_format    .= $char;
917
                }
918
            }
919
        }
920
        return array('js' => $jquery_ui_format, 'moment' => $moment_format);
921
    }
922
923
924
    /**
925
     * This takes an incoming format string and validates it to ensure it will work fine with PHP.
926
     *
927
     * @param string $format_string   Incoming format string for php date().
928
     * @return mixed bool|array  If all is okay then TRUE is returned.  Otherwise an array of validation
929
     *                                errors is returned.  So for client code calling, check for is_array() to
930
     *                                indicate failed validations.
931
     */
932
    public static function validate_format_string($format_string)
933
    {
934
        $error_msg = array();
935
        //time format checks
936
        switch (true) {
937
            case   strpos($format_string, 'h') !== false  :
938
            case   strpos($format_string, 'g') !== false :
939
                /**
940
                 * if the time string has a lowercase 'h' which == 12 hour time format and there
941
                 * is not any ante meridiem format ('a' or 'A').  Then throw an error because its
942
                 * too ambiguous and PHP won't be able to figure out whether 1 = 1pm or 1am.
943
                 */
944
                if (strpos(strtoupper($format_string), 'A') === false) {
945
                    $error_msg[] = __(
946
                        'There is a  time format for 12 hour time but no  "a" or "A" to indicate am/pm.  Without this distinction, PHP is unable to determine if a "1" for the hour value equals "1pm" or "1am".',
947
                        'event_espresso'
948
                    );
949
                }
950
                break;
951
        }
952
        return empty($error_msg) ? true : $error_msg;
953
    }
954
955
956
    /**
957
     *     If the the first date starts at midnight on one day, and the next date ends at midnight on the
958
     *     very next day then this method will return true.
959
     *    If $date_1 = 2015-12-15 00:00:00 and $date_2 = 2015-12-16 00:00:00 then this function will return true.
960
     *    If $date_1 = 2015-12-15 03:00:00 and $date_2 = 2015-12_16 03:00:00 then this function will return false.
961
     *    If $date_1 = 2015-12-15 00:00:00 and $date_2 = 2015-12-15 00:00:00 then this function will return true.
962
     *
963
     * @param mixed $date_1
964
     * @param mixed $date_2
965
     * @return bool
966
     */
967
    public static function dates_represent_one_24_hour_date($date_1, $date_2)
968
    {
969
970
        if (
971
            (! $date_1 instanceof DateTime || ! $date_2 instanceof DateTime)
972
            || ($date_1->format(EE_Datetime_Field::mysql_time_format) != '00:00:00'
973
                || $date_2->format(
974
                    EE_Datetime_Field::mysql_time_format
975
                ) != '00:00:00')
976
        ) {
977
            return false;
978
        }
979
        return $date_2->format('U') - $date_1->format('U') == 86400 ? true : false;
980
    }
981
982
983
    /**
984
     * This returns the appropriate query interval string that can be used in sql queries involving mysql Date
985
     * Functions.
986
     *
987
     * @param string $timezone_string    A timezone string in a valid format to instantiate a DateTimeZone object.
988
     * @param string $field_for_interval The Database field that is the interval is applied to in the query.
989
     * @return string
990
     */
991
    public static function get_sql_query_interval_for_offset($timezone_string, $field_for_interval)
992
    {
993
        try {
994
            /** need to account for timezone offset on the selects */
995
            $DateTimeZone = new DateTimeZone($timezone_string);
996
        } catch (Exception $e) {
997
            $DateTimeZone = null;
998
        }
999
        /**
1000
         * Note get_option( 'gmt_offset') returns a value in hours, whereas DateTimeZone::getOffset returns values in seconds.
1001
         * Hence we do the calc for DateTimeZone::getOffset.
1002
         */
1003
        $offset         = $DateTimeZone instanceof DateTimeZone ? ($DateTimeZone->getOffset(new DateTime('now')))
1004
                                                                  / HOUR_IN_SECONDS : get_option('gmt_offset');
1005
        $query_interval = $offset < 0
1006
            ? 'DATE_SUB(' . $field_for_interval . ', INTERVAL ' . $offset * -1 . ' HOUR)'
1007
            : 'DATE_ADD(' . $field_for_interval . ', INTERVAL ' . $offset . ' HOUR)';
1008
        return $query_interval;
1009
    }
1010
1011
1012
    /**
1013
     * Retrieves the site's default timezone and returns it formatted so it's ready for display
1014
     * to users. If you want to customize how its displayed feel free to fetch the 'timezone_string'
1015
     * and 'gmt_offset' WordPress options directly; or use the filter
1016
     * FHEE__EEH_DTT_Helper__get_timezone_string_for_display
1017
     * (although note that we remove any HTML that may be added)
1018
     *
1019
     * @return string
1020
     */
1021
    public static function get_timezone_string_for_display()
1022
    {
1023
        $pretty_timezone = apply_filters('FHEE__EEH_DTT_Helper__get_timezone_string_for_display', '');
1024
        if (! empty($pretty_timezone)) {
1025
            return esc_html($pretty_timezone);
1026
        }
1027
        $timezone_string = get_option('timezone_string');
1028
        if ($timezone_string) {
1029
            static $mo_loaded = false;
1030
            // Load translations for continents and cities just like wp_timezone_choice does
1031
            if (! $mo_loaded) {
1032
                $locale = get_locale();
1033
                $mofile = WP_LANG_DIR . '/continents-cities-' . $locale . '.mo';
1034
                load_textdomain('continents-cities', $mofile);
1035
                $mo_loaded = true;
1036
            }
1037
            //well that was easy.
1038
            $parts = explode('/', $timezone_string);
1039
            //remove the continent
1040
            unset($parts[0]);
1041
            $t_parts = array();
1042
            foreach ($parts as $part) {
1043
                $t_parts[] = translate(str_replace('_', ' ', $part), 'continents-cities');
1044
            }
1045
            return implode(' - ', $t_parts);
1046
        }
1047
        //they haven't set the timezone string, so let's return a string like "UTC+1"
1048
        $gmt_offset = get_option('gmt_offset');
1049
        if (intval($gmt_offset) >= 0) {
1050
            $prefix = '+';
1051
        } else {
1052
            $prefix = '';
1053
        }
1054
        $parts = explode('.', (string)$gmt_offset);
1055
        if (count($parts) === 1) {
1056
            $parts[1] = '00';
1057
        } else {
1058
            //convert the part after the decimal, eg "5" (from x.5) or "25" (from x.25)
1059
            //to minutes, eg 30 or 15, respectively
1060
            $hour_fraction = (float)('0.' . $parts[1]);
1061
            $parts[1]      = (string)$hour_fraction * 60;
1062
        }
1063
        return sprintf(__('UTC%1$s', 'event_espresso'), $prefix . implode(':', $parts));
1064
    }
1065
1066
1067
1068
    /**
1069
     * So PHP does this awesome thing where if you are trying to get a timestamp
1070
     * for a month using a string like "February" or "February 2017",
1071
     * and you don't specify a day as part of your string,
1072
     * then PHP will use whatever the current day of the month is.
1073
     * IF the current day of the month happens to be the 30th or 31st,
1074
     * then PHP gets really confused by a date like February 30,
1075
     * so instead of saying
1076
     *      "Hey February only has 28 days (this year)...
1077
     *      ...you must have meant the last day of the month!"
1078
     * PHP does the next most logical thing, and bumps the date up to March 2nd,
1079
     * because someone requesting February 30th obviously meant March 1st!
1080
     * The way around this is to always set the day to the first,
1081
     * so that the month will stay on the month you wanted.
1082
     * this method will add that "1" into your date regardless of the format.
1083
     *
1084
     * @param string $month
1085
     * @return string
1086
     */
1087
    public static function first_of_month_timestamp($month = '')
1088
    {
1089
        $month = (string)$month;
1090
        $year  = '';
1091
        // check if the incoming string has a year in it or not
1092
        if (preg_match('/\b\d{4}\b/', $month, $matches)) {
1093
            $year = $matches[0];
1094
            // ten remove that from the month string as well as any spaces
1095
            $month = trim(str_replace($year, '', $month));
1096
            // add a space before the year
1097
            $year = " {$year}";
1098
        }
1099
        // return timestamp for something like "February 1 2017"
1100
        return strtotime("{$month} 1{$year}");
1101
    }
1102
1103
1104
    /**
1105
     * This simply returns the timestamp for tomorrow (midnight next day) in this sites timezone.  So it may be midnight
1106
     * for this sites timezone, but the timestamp could be some other time GMT.
1107
     */
1108
    public static function tomorrow()
1109
    {
1110
        //The multiplication of -1 ensures that we switch positive offsets to negative and negative offsets to positive
1111
        //before adding to the timestamp.  Why? Because we want tomorrow to be for midnight the next day in THIS timezone
1112
        //not an offset from midnight in UTC.  So if we're starting with UTC 00:00:00, then we want to make sure the
1113
        //final timestamp is equivalent to midnight in this timezone as represented in GMT.
1114
        return strtotime('tomorrow') + (self::get_site_timezone_gmt_offset() * -1);
1115
    }
1116
1117
1118
    /**
1119
     * **
1120
     * Gives a nicely-formatted list of timezone strings.
1121
     * Copied from the core wp function by the same name so we could customize to remove UTC offsets.
1122
     *
1123
     * @since     4.9.40.rc.008
1124
     * @staticvar bool $mo_loaded
1125
     * @staticvar string $locale_loaded
1126
     * @param string $selected_zone Selected timezone.
1127
     * @param string $locale        Optional. Locale to load the timezones in. Default current site locale.
1128
     * @return string
1129
     */
1130
    public static function wp_timezone_choice($selected_zone, $locale = null)
1131
    {
1132
        static $mo_loaded = false, $locale_loaded = null;
1133
        $continents = array(
1134
            'Africa',
1135
            'America',
1136
            'Antarctica',
1137
            'Arctic',
1138
            'Asia',
1139
            'Atlantic',
1140
            'Australia',
1141
            'Europe',
1142
            'Indian',
1143
            'Pacific',
1144
        );
1145
        // Load translations for continents and cities.
1146
        if (! $mo_loaded || $locale !== $locale_loaded) {
1147
            $locale_loaded = $locale ? $locale : get_locale();
1148
            $mofile        = WP_LANG_DIR . '/continents-cities-' . $locale_loaded . '.mo';
1149
            unload_textdomain('continents-cities');
1150
            load_textdomain('continents-cities', $mofile);
1151
            $mo_loaded = true;
1152
        }
1153
        $zonen = array();
1154
        foreach (timezone_identifiers_list() as $zone) {
1155
            $zone = explode('/', $zone);
1156
            if (! in_array($zone[0], $continents)) {
1157
                continue;
1158
            }
1159
            // This determines what gets set and translated - we don't translate Etc/* strings here, they are done later
1160
            $exists    = array(
1161
                0 => (isset($zone[0]) && $zone[0]),
1162
                1 => (isset($zone[1]) && $zone[1]),
1163
                2 => (isset($zone[2]) && $zone[2]),
1164
            );
1165
            $exists[3] = ($exists[0] && 'Etc' !== $zone[0]);
1166
            $exists[4] = ($exists[1] && $exists[3]);
1167
            $exists[5] = ($exists[2] && $exists[3]);
1168
            $zonen[] = array(
1169
                'continent'   => ($exists[0] ? $zone[0] : ''),
1170
                'city'        => ($exists[1] ? $zone[1] : ''),
1171
                'subcity'     => ($exists[2] ? $zone[2] : ''),
1172
                't_continent' => ($exists[3] ? translate(str_replace('_', ' ', $zone[0]), 'continents-cities') : ''),
1173
                't_city'      => ($exists[4] ? translate(str_replace('_', ' ', $zone[1]), 'continents-cities') : ''),
1174
                't_subcity'   => ($exists[5] ? translate(str_replace('_', ' ', $zone[2]), 'continents-cities') : ''),
1175
            );
1176
        }
1177
        usort($zonen, '_wp_timezone_choice_usort_callback');
1178
        $structure = array();
1179
        if (empty($selected_zone)) {
1180
            $structure[] = '<option selected="selected" value="">' . __('Select a city') . '</option>';
1181
        }
1182
        foreach ($zonen as $key => $zone) {
1183
            // Build value in an array to join later
1184
            $value = array($zone['continent']);
1185
            if (empty($zone['city'])) {
1186
                // It's at the continent level (generally won't happen)
1187
                $display = $zone['t_continent'];
1188
            } else {
1189
                // It's inside a continent group
1190
                // Continent optgroup
1191
                if (! isset($zonen[$key - 1]) || $zonen[$key - 1]['continent'] !== $zone['continent']) {
1192
                    $label       = $zone['t_continent'];
1193
                    $structure[] = '<optgroup label="' . esc_attr($label) . '">';
1194
                }
1195
                // Add the city to the value
1196
                $value[] = $zone['city'];
1197
                $display = $zone['t_city'];
1198
                if (! empty($zone['subcity'])) {
1199
                    // Add the subcity to the value
1200
                    $value[] = $zone['subcity'];
1201
                    $display .= ' - ' . $zone['t_subcity'];
1202
                }
1203
            }
1204
            // Build the value
1205
            $value    = join('/', $value);
1206
            $selected = '';
1207
            if ($value === $selected_zone) {
1208
                $selected = 'selected="selected" ';
1209
            }
1210
            $structure[] = '<option '
1211
                           . $selected
1212
                           . 'value="'
1213
                           . esc_attr($value)
1214
                           . '">'
1215
                           . esc_html($display)
1216
                           . "</option>";
1217
            // Close continent optgroup
1218
            if (! empty($zone['city'])
1219
                && (! isset($zonen[$key + 1])
1220
                    || (isset($zonen[$key + 1])
1221
                        && $zonen[$key
1222
                                  + 1]['continent']
1223
                           !== $zone['continent']))) {
1224
                $structure[] = '</optgroup>';
1225
            }
1226
        }
1227
        return join("\n", $structure);
1228
    }
1229
1230
1231
    /**
1232
     * Shim for the WP function `get_user_locale` that was added in WordPress 4.7.0
1233
     *
1234
     * @param int|WP_User $user_id
1235
     * @return string
1236
     */
1237
    public static function get_user_locale($user_id = 0)
1238
    {
1239
        if (function_exists('get_user_locale')) {
1240
            return get_user_locale($user_id);
1241
        }
1242
        return get_locale();
1243
    }
1244
1245
}// end class EEH_DTT_Helper
1246