1<?php
2/**
3 * Abstract class for the date format transformations plugins
4 */
5
6declare(strict_types=1);
7
8namespace PhpMyAdmin\Plugins\Transformations\Abs;
9
10use PhpMyAdmin\Plugins\TransformationsPlugin;
11use PhpMyAdmin\Sanitize;
12use PhpMyAdmin\Util;
13use stdClass;
14use function checkdate;
15use function gmdate;
16use function htmlspecialchars;
17use function mb_strlen;
18use function mb_strtolower;
19use function mb_substr;
20use function mktime;
21use function preg_match;
22use function strtotime;
23
24/**
25 * Provides common methods for all of the date format transformations plugins.
26 */
27abstract class DateFormatTransformationsPlugin extends TransformationsPlugin
28{
29    /**
30     * Gets the transformation description of the specific plugin
31     *
32     * @return string
33     */
34    public static function getInfo()
35    {
36        return __(
37            'Displays a TIME, TIMESTAMP, DATETIME or numeric unix timestamp'
38            . ' column as formatted date. The first option is the offset (in'
39            . ' hours) which will be added to the timestamp (Default: 0). Use'
40            . ' second option to specify a different date/time format string.'
41            . ' Third option determines whether you want to see local date or'
42            . ' UTC one (use "local" or "utc" strings) for that. According to'
43            . ' that, date format has different value - for "local" see the'
44            . ' documentation for PHP\'s strftime() function and for "utc" it'
45            . ' is done using gmdate() function.'
46        );
47    }
48
49    /**
50     * Does the actual work of each specific transformations plugin.
51     *
52     * @param string        $buffer  text to be transformed
53     * @param array         $options transformation options
54     * @param stdClass|null $meta    meta information
55     *
56     * @return string
57     */
58    public function applyTransformation($buffer, array $options = [], ?stdClass $meta = null)
59    {
60        $buffer = (string) $buffer;
61        // possibly use a global transform and feed it with special options
62        $cfg = $GLOBALS['cfg'];
63        $options = $this->getOptions($options, $cfg['DefaultTransformations']['DateFormat']);
64
65        // further operations on $buffer using the $options[] array.
66        $options[2] = mb_strtolower($options[2]);
67
68        if (empty($options[1])) {
69            if ($options[2] === 'local') {
70                $options[1] = __('%B %d, %Y at %I:%M %p');
71            } else {
72                $options[1] = 'Y-m-d  H:i:s';
73            }
74        }
75
76        $timestamp = -1;
77
78        // INT columns will be treated as UNIX timestamps
79        // and need to be detected before the verification for
80        // MySQL TIMESTAMP
81        if ($meta->type === 'int') {
82            $timestamp = $buffer;
83
84            // Detect TIMESTAMP(6 | 8 | 10 | 12 | 14)
85            // TIMESTAMP (2 | 4) not supported here.
86            // (Note: prior to MySQL 4.1, TIMESTAMP has a display size
87            // for example TIMESTAMP(8) means YYYYMMDD)
88        } else {
89            if (preg_match('/^(\d{2}){3,7}$/', $buffer)) {
90                if (mb_strlen($buffer) == 14 || mb_strlen($buffer) == 8) {
91                    $offset = 4;
92                } else {
93                    $offset = 2;
94                }
95
96                $aDate = [];
97                $aDate['year'] = (int) mb_substr($buffer, 0, $offset);
98                $aDate['month'] = (int) mb_substr($buffer, $offset, 2);
99                $aDate['day'] = (int) mb_substr($buffer, $offset + 2, 2);
100                $aDate['hour'] = (int) mb_substr($buffer, $offset + 4, 2);
101                $aDate['minute'] = (int) mb_substr($buffer, $offset + 6, 2);
102                $aDate['second'] = (int) mb_substr($buffer, $offset + 8, 2);
103
104                if (checkdate($aDate['month'], $aDate['day'], $aDate['year'])) {
105                    $timestamp = mktime(
106                        $aDate['hour'],
107                        $aDate['minute'],
108                        $aDate['second'],
109                        $aDate['month'],
110                        $aDate['day'],
111                        $aDate['year']
112                    );
113                }
114                // If all fails, assume one of the dozens of valid strtime() syntaxes
115                // (https://www.gnu.org/manual/tar-1.12/html_chapter/tar_7.html)
116            } else {
117                if (preg_match('/^[0-9]\d{1,9}$/', $buffer)) {
118                    $timestamp = (int) $buffer;
119                } else {
120                    $timestamp = strtotime($buffer);
121                }
122            }
123        }
124
125        // If all above failed, maybe it's a Unix timestamp already?
126        if ($timestamp < 0 && preg_match('/^[1-9]\d{1,9}$/', $buffer)) {
127            $timestamp = $buffer;
128        }
129
130        // Reformat a valid timestamp
131        if ($timestamp >= 0) {
132            $timestamp -= (int) $options[0] * 60 * 60;
133            $source = $buffer;
134            if ($options[2] === 'local') {
135                $text = Util::localisedDate(
136                    $timestamp,
137                    $options[1]
138                );
139            } elseif ($options[2] === 'utc') {
140                $text = gmdate($options[1], $timestamp);
141            } else {
142                $text = 'INVALID DATE TYPE';
143            }
144
145            return '<dfn onclick="alert(\'' . Sanitize::jsFormat($source, false) . '\');" title="'
146                . htmlspecialchars((string) $source) . '">' . htmlspecialchars((string) $text) . '</dfn>';
147        }
148
149        return htmlspecialchars((string) $buffer);
150    }
151
152    /* ~~~~~~~~~~~~~~~~~~~~ Getters and Setters ~~~~~~~~~~~~~~~~~~~~ */
153
154    /**
155     * Gets the transformation name of the specific plugin
156     *
157     * @return string
158     */
159    public static function getName()
160    {
161        return 'Date Format';
162    }
163}
164