Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion libraries/src/Form/Field/CalendarField.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
\defined('JPATH_PLATFORM') or die;

use DateTime;
use DateTimeImmutable;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\Registry\Registry;

Expand Down Expand Up @@ -274,7 +276,8 @@ protected function getInput()
{
$tz = date_default_timezone_get();
date_default_timezone_set('UTC');
$this->value = strftime($this->format, strtotime($this->value));
$date = DateTimeImmutable::createFromFormat('U', strtotime($this->value));
$this->value = $date->format(HTMLHelper::strftimeFormatToDateFormat($this->format));
date_default_timezone_set($tz);
}
else
Expand Down
65 changes: 63 additions & 2 deletions libraries/src/HTML/HTMLHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,7 @@ public static function tooltipText($title = '', $content = '', $translate = true
* @param string $value The date value
* @param string $name The name of the text field
* @param string $id The id of the text field
* @param string $format The date format
* @param string $format The date format using the @deprecated strftime format parameters
* @param mixed $attribs Additional HTML attributes
* The array can have the following keys:
* readonly Sets the readonly parameter for the input tag
Expand All @@ -1085,6 +1085,7 @@ public static function tooltipText($title = '', $content = '', $translate = true
*
* @since 1.5
*
* @note This method uses deprecated strftime format parameters for backward compatibility.
*/
public static function calendar($value, $name, $id, $format = '%Y-%m-%d', $attribs = array())
{
Expand Down Expand Up @@ -1131,7 +1132,8 @@ public static function calendar($value, $name, $id, $format = '%Y-%m-%d', $attri
{
$tz = date_default_timezone_get();
date_default_timezone_set('UTC');
$inputvalue = strftime($format, strtotime($value));
$date = \DateTimeImmutable::createFromFormat('U', strtotime($value));
$inputvalue = $date->format(self::strftimeFormatToDateFormat($format));
date_default_timezone_set($tz);
}
else
Expand Down Expand Up @@ -1284,4 +1286,63 @@ private static function checkFileOrder($first, $second)

return '';
}

/**
* Convert strftime format to php date format as strftime is deprecated and we have
* to be able to provide same backward compatibility with existing format strings.
*
* @param $strftimeformat string The format compatible with strftime.
*
* @return string The format compatible with PHP's Date functions.
*
* @since __DEPLOY_VERSION__
*
* @throws \Exception
*
* @note Thanks to @relipse for https://stackoverflow.com/questions/22665959/using-php-strftime-using-date-format-string/62781773#62781773
*/
public static function strftimeFormatToDateFormat(string $strftimeformat): string
{
$unsupported = ['%U', '%V', '%C', '%g', '%G'];
$foundunsupported = [];

foreach ($unsupported as $unsup)
{
if (strpos($strftimeformat, $unsup) !== false)
{
$foundunsupported[] = $unsup;
}
}

if (!empty($foundunsupported))
{
throw new \Exception("Found these unsupported chars: " . implode(",", $foundunsupported) . ' in ' . $strftimeformat);
}

/**
* It is important to note that some do not translate accurately
* ie. lowercase L is supposed to convert to number with a preceding space if it is under 10,
* there is no accurate conversion, so we just use 'g'
*/
$phpdateformat = str_replace(
['%a','%A','%d','%e','%u','%w','%W','%b','%h','%B','%m','%y','%Y', '%D', '%F', '%x', '%n', '%t', '%H', '%k', '%I', '%l', '%M', '%p', '%P',
// %I:%M:%S %p
'%r',
// %H:%M
'%R',
'%S',
// %H:%M:%S
'%T',
'%X', '%z', '%Z', '%c', '%s', '%%'
],
['D','l', 'd', 'j', 'N', 'w', 'W', 'M', 'M', 'F', 'm', 'y', 'Y', 'm/d/y', 'Y-m-d', 'm/d/y', "\n", "\t", 'H', 'G', 'h', 'g', 'i', 'A', 'a', 'h:i:s A', 'H:i', 's', 'H:i:s', 'H:i:s', 'O', 'T',
// Tue Feb 5 00:45:10 2009
'D M j H:i:s Y' ,
'U', '%'
],
$strftimeformat
);

return $phpdateformat;
}
}
61 changes: 61 additions & 0 deletions tests/Unit/Libraries/Cms/Html/HtmlHelperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* @package Joomla.UnitTest
* @subpackage HTML
*
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/

namespace Joomla\Tests\Unit\Libraries\Cms\Html;

use Joomla\CMS\HTML\HTMLHelper;
use Joomla\Tests\Unit\UnitTestCase;

/**
* Test class for HtmlHelperTest.
*
* @since __DEPLOY_VERSION__
*/
class HtmlHelperTest extends UnitTestCase
{
/**
* @var string Base HTML Output with place holders
*
* @since __DEPLOY_VERSION__
*/
private $template = '<div class="field-calendar">
<div class="input-group">
<input
type="text"
id="%s"
name="%s"
value="%s"
class="form-control" data-alt-value="%s" autocomplete="off">
<button type="button" class="btn btn-primary"
id="%s_btn"
title="JLIB_HTML_BEHAVIOR_OPEN_CALENDAR"
data-inputfield="testId" data-button="testId_btn" data-date-format="%s" data-firstday="" data-weekend="0,6" data-today-btn="1" data-week-numbers="1" data-show-time="0" data-show-others="1" data-time24="24" data-only-months-nav="0" data-min-year="" data-max-year="" data-date-type="gregorian" ><span class="icon-calendar" aria-hidden="true"></span>
<span class="visually-hidden">JLIB_HTML_BEHAVIOR_OPEN_CALENDAR</span>
</button>
</div>
</div>
';
/**
* Test the replacement of using deprecated strftime with Date formats
*
* @since __DEPLOY_VERSION__
*/
public function testCalendar()
{
$this->assertEquals(
sprintf($this->template, 'testId', 'testName', 'Mar', 'Mar', 'testId', '%b'),
HTMLHelper::calendar('1978-03-08 06:12:12', 'testName', 'testId', '%b', [])
);

$this->assertEquals(
sprintf($this->template, 'testId', 'testName', '1978-03-08', '1978-03-08', 'testId', '%Y-%m-%d'),
HTMLHelper::calendar('1978-03-08 06:12:12', 'testName', 'testId', '%Y-%m-%d', [])
);
}
}
78 changes: 78 additions & 0 deletions tests/Unit/UnitTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,89 @@
*/
namespace Joomla\Tests\Unit;

use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Document\Document;
use Joomla\CMS\Document\FactoryInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Language;
use Joomla\CMS\WebAsset\WebAssetManager;
use Joomla\Input\Input;
use Joomla\Registry\Registry;
use ReflectionClass;
use stdClass;

/**
* Base Unit Test case for common behaviour across unit tests
*
* @since 4.0.0
*/
abstract class UnitTestCase extends \PHPUnit\Framework\TestCase
{
/**
* @return void
*
* @since __DEPLOY_VERSION__
*/
protected function setUp(): void
{
$this->initJoomla();
}

/**
* Sets up the minimal amount of Joomla, mocked where possible, to run the
* CMS Unit Test Cases
*
* @return void
*
* @since __DEPLOY_VERSION__
*/
protected function initJoomla(): void
{
// Start with the easy mocks.
$input = $this->getMockBuilder(Input::class)
->getMockForAbstractClass();
$factory = $this->createMock(Factory::class);
$lang = $this->getMockBuilder(Language::class)
->getMockForAbstractClass();

// Mock the Document object.
$doc = new Document(
[
'factory' => $this->createMock(FactoryInterface::class),
]
);

// Mock WA and some calls used that we are not directly testing.
$wa = $this->createMock(WebAssetManager::class);
$wa->expects($this->any())->method('__call')->will($this->returnValue($wa));
$doc->setWebAssetManager($wa);

// Inject the mocked document in the mocked factory.
$factory::$document = $doc;

// Mock a template that the app will return from getTemplate().
$template = new stdClass;
$template->template = 'system';
$template->params = new Registry;
$template->inheritable = 0;
$template->parent = '';

// Ensure the application can return all our mocked items.
$app = $this->createMock(CMSApplication::class);
$app->method('__get')
->with('input')
->willReturn($input);
$app->method('getLanguage')
->will($this->returnValue($lang));
$app->method('getDocument')
->will($this->returnValue($factory::$document));
$app->method('getTemplate')
->will($this->returnValue($template));

// Finally, set the application into the factory.
$reflection = new ReflectionClass($factory);
$reflection_property = $reflection->getProperty('application');
$reflection_property->setAccessible(true);
$reflection_property->setValue($factory, $app);
}
}