From f93ab613725ca18d61a474f584fbc071d5d7df2b Mon Sep 17 00:00:00 2001 From: Nick Liu Date: Wed, 22 Jan 2020 00:40:26 +0100 Subject: [PATCH 1/2] Moved readfile() shim to its own trait --- .../Shims/Internal/ReadfileTrait.php | 64 +++++++++++++++++++ e107_handlers/Shims/InternalShims.php | 2 +- e107_handlers/Shims/InternalShimsTrait.php | 50 +-------------- 3 files changed, 67 insertions(+), 49 deletions(-) create mode 100644 e107_handlers/Shims/Internal/ReadfileTrait.php diff --git a/e107_handlers/Shims/Internal/ReadfileTrait.php b/e107_handlers/Shims/Internal/ReadfileTrait.php new file mode 100644 index 000000000..8d1858337 --- /dev/null +++ b/e107_handlers/Shims/Internal/ReadfileTrait.php @@ -0,0 +1,64 @@ + Date: Wed, 22 Jan 2020 09:07:00 +0100 Subject: [PATCH 2/2] Replaced e_date::strptime() with eShims::strptime() - NEW: Added \e107\Shims\Internal\StrptimeTrait, which implements PHP internal function strptime(). On not-Windows, the built-in function is called. If that function fails or if the operating system is Windows, the alternative pure PHP implementation is attempted. The first successful call is returned, or false if none are successful. - MOD: Deprecated e_date::strptime() in favor of eShims::strptime() - FIX: License misatributed for e_date::strptime() (now eShims::strptime()). The library used was public domain, not CC BY-NC-SA 2.0 FR by Lionel Sauron. - MOD: Removed STRPTIME_COMPAT constant now that eShims::strptime() exists - MOD: Removed support for calling e_date::strptime() with: - a localized full month name - a localized abbreviated month name - AM or PM - am or pm because these features were only implemented in Windows mode (STRPTIME_COMPAT). - MOD: php_compatibility_handler.php now defines global strptime() using the eShims::strptime() implementation - NEW: Test all(?) the possibilities of eShims::strptime() --- .../Shims/Internal/StrptimeTrait.php | 163 ++++++++++++++++ e107_handlers/Shims/InternalShimsTrait.php | 1 + e107_handlers/date_handler.php | 180 +----------------- e107_handlers/php_compatibility_handler.php | 11 +- .../tests/unit/e107/Shims/StrptimeTest.php | 94 +++++++++ 5 files changed, 270 insertions(+), 179 deletions(-) create mode 100644 e107_handlers/Shims/Internal/StrptimeTrait.php create mode 100644 e107_tests/tests/unit/e107/Shims/StrptimeTest.php diff --git a/e107_handlers/Shims/Internal/StrptimeTrait.php b/e107_handlers/Shims/Internal/StrptimeTrait.php new file mode 100644 index 000000000..efbda49f9 --- /dev/null +++ b/e107_handlers/Shims/Internal/StrptimeTrait.php @@ -0,0 +1,163 @@ + '%m/%d/%y', '%T' => '%H:%M:%S',); + static $map_r = array( + '%S' => 'tm_sec', + '%M' => 'tm_min', + '%H' => 'tm_hour', + '%I' => 'tm_hour', + '%d' => 'tm_mday', + '%m' => 'tm_mon', + '%Y' => 'tm_year', + '%y' => 'tm_year', + '%W' => 'tm_wday', + '%D' => 'tm_yday', + '%u' => 'unparsed', + ); + + $fullmonth = array(); + $abrevmonth = array(); + + for ($i = 1; $i <= 12; $i++) + { + $k = strftime('%B', mktime(0, 0, 0, $i)); + $fullmonth[$k] = $i; + + $j = strftime('%b', mktime(0, 0, 0, $i)); + $abrevmonth[$j] = $i; + } + + + #-- transform $format into extraction regex + $format = str_replace(array_keys($expand), array_values($expand), $format); + $preg = preg_replace('/(%\w)/', '(\w+)', preg_quote($format)); + + #-- record the positions of all STRFCMD-placeholders + preg_match_all('/(%\w)/', $format, $positions); + $positions = $positions[1]; + + $vals = array(); + + #-- get individual values + if (preg_match("#$preg#", $date, $extracted)) + { + #-- get values + foreach ($positions as $pos => $strfc) + { + $v = $extracted[$pos + 1]; + #-- add + if (isset($map_r[$strfc])) + { + $n = $map_r[$strfc]; + $vals[$n] = ($v > 0) ? (int)$v : $v; + } + else + { + if (!isset($vals['unparsed'])) $vals['unparsed'] = ''; + $vals['unparsed'] .= $v . ' '; + } + } + + #-- fixup some entries + //$vals["tm_wday"] = $names[ substr($vals["tm_wday"], 0, 3) ]; + if ($vals['tm_year'] >= 1900) + { + $vals['tm_year'] -= 1900; + } + elseif ($vals['tm_year'] > 0) + { + $vals['tm_year'] += 100; + } + + if ($vals['tm_mon']) + { + $vals['tm_mon'] -= 1; + } + + if (!isset($vals['tm_sec'])) + { + $vals['tm_sec'] = 0; + } + + if (!isset($vals['tm_min'])) + { + $vals['tm_min'] = 0; + } + + if (!isset($vals['tm_hour'])) + { + $vals['tm_hour'] = 0; + } + + + if (!isset($vals['unparsed'])) + { + $vals['unparsed'] = ''; + } + + $unxTimestamp = mktime($vals['tm_hour'], $vals['tm_min'], $vals['tm_sec'], ($vals['tm_mon'] + 1), $vals['tm_mday'], ($vals['tm_year'] + 1900)); + + $vals['tm_wday'] = (int)strftime('%w', $unxTimestamp); // Days since Sunday (0-6) + $vals['tm_yday'] = (strftime('%j', $unxTimestamp) - 1); // Days since January 1 (0-365) + } + + return !empty($vals) ? $vals : false; + + } +} \ No newline at end of file diff --git a/e107_handlers/Shims/InternalShimsTrait.php b/e107_handlers/Shims/InternalShimsTrait.php index cd195e33a..9ae20ceb3 100644 --- a/e107_handlers/Shims/InternalShimsTrait.php +++ b/e107_handlers/Shims/InternalShimsTrait.php @@ -14,4 +14,5 @@ namespace e107\Shims; trait InternalShimsTrait { use Internal\ReadfileTrait; + use Internal\StrptimeTrait; } \ No newline at end of file diff --git a/e107_handlers/date_handler.php b/e107_handlers/date_handler.php index 0a6ef9b53..ea47fe9f5 100644 --- a/e107_handlers/date_handler.php +++ b/e107_handlers/date_handler.php @@ -770,184 +770,22 @@ class e_date /** - * This work of Lionel SAURON (http://sauron.lionel.free.fr:80) is licensed under the - * Creative Commons Attribution-Noncommercial-Share Alike 2.0 France License. - * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/2.0/fr/ - * or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. - * - * http://snipplr.com/view/4964/emulate-php-5-for-backwards-compatibility/ - * - * Parse a date generated with strftime(). - * - * @author Lionel SAURON and reworked by e107 Inc. for month names. - * @version 1.0 - * @public + * Parse a time/date generated with strftime() + * With extra output keys for localized month * + * @deprecated Use eShims::strptime() instead + * @see eShims::strptime() * @param string $str date string to parse (e.g. returned from strftime()). * @param $format * @return array|bool Returns an array with the $str parsed, or false on error. */ public function strptime($str, $format) { - if(STRPTIME_COMPAT !== TRUE && function_exists('strptime')) // Unix Only. - { - $vals = strptime($str,$format); // PHP5 is more accurate than below. - $vals['tm_amon'] = strftime('%b', mktime(0,0,0, $vals['tm_mon'] +1) ); - $vals['tm_fmon'] = strftime('%B', mktime(0,0,0, $vals['tm_mon'] +1) ); - return $vals; - } - - // Below is for Windows machines. (XXX TODO - Currently not as accurate as Linux PHP5 strptime() function above. ) - - static $expand = array('%D'=>'%m/%d/%y', '%T'=>'%H:%M:%S', ); - - $ampm = (preg_match("/%l|%I|%p|%P/",$format)) ? 'true' : 'false'; - - static $map_r = array( - '%S' =>'tm_sec', - '%M' =>'tm_min', - '%H' =>'tm_hour', - '%I' =>'tm_hour', - '%d' =>'tm_mday', - '%m' =>'tm_mon', - '%Y' =>'tm_year', - '%y' =>'tm_year', - '%W' =>'tm_wday', - '%D' =>'tm_yday', - '%B' =>'tm_fmon', // full month-name - '%b' =>'tm_amon', // abrev. month-name - '%p' =>'tm_AMPM', // AM/PM - '%P' =>'tm_ampm', // am/pm - '%u' =>'unparsed', - - ); - - $fullmonth = array(); - $abrevmonth = array(); - - for ($i = 1; $i <= 12; $i++) - { - $k = strftime('%B',mktime(0,0,0,$i)); - $fullmonth[$k] = $i; - - $j = strftime('%b',mktime(0,0,0,$i)); - $abrevmonth[$j] = $i; - } - - - - #-- transform $format into extraction regex - $format = str_replace(array_keys($expand), array_values($expand), $format); - $preg = preg_replace('/(%\w)/', '(\w+)', preg_quote($format)); - - #-- record the positions of all STRFCMD-placeholders - preg_match_all('/(%\w)/', $format, $positions); - $positions = $positions[1]; - - $vals = array(); - - #-- get individual values - if (preg_match("#$preg#", $str, $extracted)) - { - #-- get values - foreach ($positions as $pos => $strfc) - { - $v = $extracted[$pos + 1]; - #-- add - if (isset($map_r[$strfc])) - { - $n = $map_r[$strfc]; - $vals[$n] = ($v > 0) ? (int) $v : $v; - } - else - { - $vals['unparsed'] .= $v.' '; - } - } - - #-- fixup some entries - //$vals["tm_wday"] = $names[ substr($vals["tm_wday"], 0, 3) ]; - if ($vals['tm_year'] >= 1900) - { - $vals['tm_year'] -= 1900; - } - elseif ($vals['tm_year'] > 0) - { - $vals['tm_year'] += 100; - } - - if ($vals['tm_mon']) - { - $vals['tm_mon'] -= 1; - } - else - { - - if(isset($fullmonth[$vals['tm_fmon']])) - { - $vals['tm_mon'] = $fullmonth[$vals['tm_fmon']]; - } - elseif(isset($abrevmonth[$vals['tm_amon']])) - { - $vals['tm_mon'] = $abrevmonth[$vals['tm_amon']]; - } - - } - - if($ampm && isset($vals['tm_hour'])) - { - if($vals['tm_hour'] == 12 && ($vals['tm_AMPM'] == 'AM' || $vals['tm_ampm'] == 'am')) - { - $vals['tm_hour'] = 0; - } - - if($vals['tm_hour'] < 12 && ($vals['tm_AMPM'] == 'PM' || $vals['tm_ampm'] == 'pm')) - { - $vals['tm_hour'] = intval($vals['tm_hour']) + 12; - } - - } - - //$vals['tm_sec'] -= 1; always increasing tm_sec + 1 ?????? - - #-- calculate wday/yday - //$vals['tm_mon'] = $vals['tm_mon'] + 1; // returns months from 0 - 11 so we need to +1 - - if (!isset($vals['tm_sec'])) - { - $vals['tm_sec'] = 0; - } - - if (!isset($vals['tm_min'])) - { - $vals['tm_min'] = 0; - } - - if (!isset($vals['tm_hour'])) - { - $vals['tm_hour'] = 0; - } - - - if (!isset($vals['unparsed'])) - { - $vals['unparsed'] = ''; - } - - $unxTimestamp = mktime($vals['tm_hour'], $vals['tm_min'], $vals['tm_sec'], ($vals['tm_mon'] + 1), $vals['tm_mday'], ($vals['tm_year'] + 1900)); - - $vals['tm_amon'] = strftime('%b', mktime($vals['tm_hour'], $vals['tm_min'], $vals['tm_sec'], $vals['tm_mon'] + 1)); - $vals['tm_fmon'] = strftime('%B', mktime($vals['tm_hour'], $vals['tm_min'], $vals['tm_sec'], $vals['tm_mon'] + 1)); - $vals['tm_wday'] = (int) strftime('%w', $unxTimestamp); // Days since Sunday (0-6) - $vals['tm_yday'] = (strftime('%j', $unxTimestamp) - 1); // Days since January 1 (0-365) - - - //var_dump($vals, $str, strftime($format, $unxTimestamp), $unxTimestamp); - } - - return !empty($vals) ? $vals : false; - - } + $vals = eShims::strptime($str, $format); // PHP5 is more accurate than below. + $vals['tm_amon'] = strftime('%b', mktime(0, 0, 0, $vals['tm_mon'] + 1)); + $vals['tm_fmon'] = strftime('%B', mktime(0, 0, 0, $vals['tm_mon'] + 1)); + return $vals; + } diff --git a/e107_handlers/php_compatibility_handler.php b/e107_handlers/php_compatibility_handler.php index e26abfe77..21eac532e 100644 --- a/e107_handlers/php_compatibility_handler.php +++ b/e107_handlers/php_compatibility_handler.php @@ -26,17 +26,12 @@ if (!defined('e107_INIT')) */ - - if (!function_exists('strptime')) { - - define('STRPTIME_COMPAT', true); - function strptime($str, $format) + function strptime($date, $format) { - return e107::getDate()->strptime($str,$format); - } - + return eShims::strptime($date, $format); + } } //PHP < 5.2 compatibility diff --git a/e107_tests/tests/unit/e107/Shims/StrptimeTest.php b/e107_tests/tests/unit/e107/Shims/StrptimeTest.php new file mode 100644 index 000000000..0623d6922 --- /dev/null +++ b/e107_tests/tests/unit/e107/Shims/StrptimeTest.php @@ -0,0 +1,94 @@ +testStrptimeImplementation([\e107\Shims\InternalShims::class, 'strptime']); + } + + public function testStrptimeDefaultLegacy() + { + $this->testStrptimeImplementation([\eShims::class, 'strptime']); + } + + public function testStrptimeAlt() + { + $this->testStrptimeImplementation([\e107\Shims\InternalShims::class, 'strptime_alt']); + } + + protected function testStrptimeImplementation($implementation) + { + $this->testStrptimeDateOnly($implementation); + $this->testStrptimeDateTime($implementation); + $this->testStrptimeUnparsed($implementation); + $this->testStrptimeInvalid($implementation); + } + + protected function testStrptimeDateOnly($implementation) + { + $actual = call_user_func($implementation, '2018/05/13', '%Y/%m/%d'); + $expected = array( + 'tm_year' => 118, + 'tm_mon' => 4, + 'tm_mday' => 13, + 'tm_sec' => 0, + 'tm_min' => 0, + 'tm_hour' => 0, + 'unparsed' => '', + 'tm_wday' => 0, + 'tm_yday' => 132, + ); + $this->assertEquals($expected, $actual); + } + + protected function testStrptimeDateTime($implementation) + { + $actual = call_user_func($implementation, '2018/05/13 20:10', '%Y/%m/%d %H:%M'); + $expected = array( + 'tm_year' => 118, + 'tm_mon' => 4, + 'tm_mday' => 13, + 'tm_hour' => 20, + 'tm_min' => 10, + 'tm_sec' => 0, + 'unparsed' => '', + 'tm_wday' => 0, + 'tm_yday' => 132, + ); + $this->assertEquals($expected, $actual); + } + + protected function testStrptimeUnparsed($implementation) + { + $actual = call_user_func($implementation, '1607-09-04 08:10 PM', '%Y-%m-%d %l:%M %P'); + $expected = array( + 'tm_year' => 1707, + 'tm_mon' => 8, + 'tm_mday' => 4, + 'tm_hour' => 0, + 'tm_min' => 10, + 'tm_sec' => 0, + 'unparsed' => '08 PM ', + 'tm_wday' => 2, + 'tm_yday' => 246, + ); + $this->assertEquals($expected, $actual); + } + + protected function testStrptimeInvalid($implementation) + { + $actual = call_user_func($implementation, 'garbage', '%Y-%m-%d'); + $this->assertFalse($actual); + } +} \ No newline at end of file