Completed
Branch BUG/detect-wp-json-requests (439367)
by
unknown
48:56 queued 20:49
created

DbSafeDateTime::createFromFormat()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 4
nop 3
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
namespace EventEspresso\core\domain\entities;
4
5
use DateTime;
6
use DateTimeZone;
7
use DomainException;
8
9
/**
10
 * Class DbSafeDateTime
11
 * Some versions of PHP do bad things when you try to serialize a DateTime object for storage.
12
 * This DateTime class extension can be safely serialized and unserialized,
13
 * because the only data it stores is a string containing all o fits relevant details
14
 *
15
 * @package       Event Espresso
16
 * @author        Brent Christensen
17
 */
18
class DbSafeDateTime extends DateTime
19
{
20
21
    // phpcs:disable Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
22
    /**
23
     * @type string db_safe_timestamp_format
24
     */
25
    const db_safe_timestamp_format = 'Y-m-d H:i:s O e';
26
    // phpcs:enable
27
28
    // phpcs:disable PSR2.Classes.PropertyDeclaration.Underscore
29
    /**
30
     * DateTime object converted to a string that includes the date, time, UTC offset, and timezone identifier
31
     *
32
     * @type string $_datetime_string
33
     */
34
    protected $_datetime_string = '';
35
36
    /**
37
     * where to write the error log to
38
     *
39
     * @type string $_error_log_dir
40
     */
41
    protected $_error_log_dir = '';
42
    // phpcs:enable
43
44
45
    /**
46
     * @param string $error_log_dir
47
     */
48
    public function setErrorLogDir($error_log_dir)
49
    {
50
        // if the folder path is writable, then except the path + filename, else keep empty
51
        $this->_error_log_dir = is_writable(str_replace(basename($error_log_dir), '', $error_log_dir))
52
            ? $error_log_dir
53
            : '';
54
    }
55
56
57
    /**
58
     * @return string
59
     */
60
    public function __toString()
61
    {
62
        return $this->format(DbSafeDateTime::db_safe_timestamp_format);
63
    }
64
65
66
    /**
67
     * @return array
68
     */
69
    public function __sleep()
70
    {
71
        $this->_datetime_string = $this->format(DbSafeDateTime::db_safe_timestamp_format);
72
        $date = DateTime::createFromFormat(
73
            DbSafeDateTime::db_safe_timestamp_format,
74
            $this->_datetime_string
75
        );
76
        if (! $date instanceof DateTime) {
77
            try {
78
                // we want a stack trace to determine where the malformed date came from, so...
79
                throw new DomainException('');
80
            } catch (DomainException $e) {
81
                $stack_trace = $e->getTraceAsString();
82
            }
83
            $this->writeToErrorLog(
84
                sprintf(
85
                    __(
86
                        'A valid DateTime could not be generated from "%1$s" because the following errors occurred: %2$s %3$s %2$s PHP version: %4$s %2$s Stack Trace: %5$s',
87
                        'event_espresso'
88
                    ),
89
                    $this->_datetime_string,
90
                    '<br />',
91
                    print_r(DateTime::getLastErrors(), true),
92
                    PHP_VERSION,
93
                    $stack_trace
94
                )
95
            );
96
        }
97
        return array('_datetime_string');
98
    }
99
100
101
    /**
102
     * if an empty or null value got saved to the db for a datetime,
103
     * then some servers and/or PHP itself will incorrectly convert that date string
104
     * resulting in "-0001-11-30" for the year-month-day.
105
     * see the Notes section
106
     *
107
     * @link http://php.net/manual/en/datetime.formats.date.php
108
     * We'll replace those with "0000-00-00" which will allow a valid DateTime object to be created,
109
     * but still result in the internal date for that object being set to "-0001-11-30 10:00:00.000000".
110
     * so we're no better off, but at least things won't go fatal on us.
111
     */
112
    public function __wakeup()
113
    {
114
        $date = self::createFromFormat(
115
            DbSafeDateTime::db_safe_timestamp_format,
116
            $this->_datetime_string
117
        );
118
        if (! $date instanceof DateTime) {
119
            $this->writeToErrorLog(
120
                sprintf(
121
                    __(
122
                        'A valid DateTime could not be recreated from "%1$s" because the following errors occurred: %2$s %3$s %2$s PHP version: %4$s',
123
                        'event_espresso'
124
                    ),
125
                    $this->_datetime_string,
126
                    '<br />',
127
                    print_r(DateTime::getLastErrors(), true),
128
                    PHP_VERSION
129
                )
130
            );
131
        } else {
132
            $this->__construct(
133
                $date->format(\EE_Datetime_Field::mysql_timestamp_format),
134
                new DateTimeZone($date->format('e'))
135
            );
136
        }
137
    }
138
139
140
    /**
141
     * Normalizes incoming date string so that it is a bit more stable for use.
142
     * @param string $date_string
143
     * @return string
144
     */
145
    public static function normalizeInvalidDate($date_string)
146
    {
147
        return str_replace(
148
            array('-0001-11-29', '-0001-11-30', '0000-00-00'),
149
            '0000-01-01',
150
            $date_string
151
        );
152
    }
153
154
155
    /**
156
     * Creates a DbSafeDateTime from ye old DateTime
157
     *
158
     * @param DateTime $datetime
159
     * @return \EventEspresso\core\domain\entities\DbSafeDateTime
160
     */
161
    public static function createFromDateTime(DateTime $datetime)
162
    {
163
        return new DbSafeDateTime(
164
            $datetime->format(\EE_Datetime_Field::mysql_timestamp_format),
165
            new DateTimeZone($datetime->format('e'))
166
        );
167
    }
168
169
170
    /**
171
     * Parse a string into a new DateTime object according to the specified format
172
     *
173
     * @param string       $format   Format accepted by date().
174
     * @param string       $time     String representing the time.
175
     * @param DateTimeZone $timezone A DateTimeZone object representing the desired time zone.
176
     * @return DbSafeDateTime|boolean
177
     * @link https://php.net/manual/en/datetime.createfromformat.php
178
     */
179
    public static function createFromFormat($format, $time, $timezone = null)
180
    {
181
        $time = self::normalizeInvalidDate($time);
182
        // Various php versions handle the third argument differently.  This conditional accounts for that.
183
        $DateTime = $timezone === null
184
            ? parent::createFromFormat($format, $time)
185
            : parent::createFromFormat($format, $time, $timezone);
186
        return $DateTime instanceof DateTime
187
            ? self::createFromDateTime($DateTime)
188
            : $DateTime;
189
    }
190
191
192
    /**
193
     * @param string $message
194
     */
195
    private function writeToErrorLog($message)
196
    {
197
        if (! empty($this->_error_log_dir)) {
198
            /** @noinspection ForgottenDebugOutputInspection */
199
            error_log($message, 3, $this->_error_log_dir);
200
        } else {
201
            /** @noinspection ForgottenDebugOutputInspection */
202
            error_log($message);
203
        }
204
    }
205
}
206