Get current decimal delimiter in event subscriber / parse localized string to float - intl

How can I get the current number format (decimal separator, thousands seperator) according to the store-front's locale in a subscriber of the ProductListingCriteriaEvent event in Shopware 6 ?
Goal is to parse strings to float which are given in the current locale (1.000,00 in DE and 1,000.00 in EN).
I looked at the event's context, but did not find the locale information.
I did the same logic in Twig before (which seems a bit crazy):
{% set thousands_separator = 1000.1|format_number(locale=app.locale)|replace({'0':'','1':''})|slice(-2,1) %}
{% set decimals_seperator = 1000.1|format_number(locale=app.locale)|replace({'0':'','1':''})|slice(-1,1) %}
{% set floatValue = stringValue | replace({thousands_separator:'', decimals_seperator:'.'}) %}
EDIT:
There is \Shopware\Core\Framework\App\AppLocaleProvider::getLocaleFromContext but it provides only the locale code, not the number format information.

You can use the service Shopware\Core\System\Locale\LanguageLocaleCodeProvider to retrieve the locale by the ID of the language. Then just use the \NumberFormatter to parse as float:
$locale = $this->languageLocaleProvider->getLocaleForLanguageId($context->getLanguageId());
$formatter = new \NumberFormatter($locale, \NumberFormatter::DECIMAL);
$float = $formatter->parse('123,456.789');
var_dump($float);
// float(123456.789)
If you need this as a twig filter, you could register your own twig filter:
<service id="MyPlugin\Core\Framework\Adapter\Twig\Filter\ToFloatFilter">
<argument type="service" id="Shopware\Core\System\Locale\LanguageLocaleCodeProvider"/>
<tag name="twig.extension"/>
</service>
class ToFloatFilter extends AbstractExtension
{
private LanguageLocaleCodeProvider $languageLocaleCodeProvider;
public function __construct(LanguageLocaleCodeProvider $languageLocaleProvider)
{
$this->languageLocaleProvider = $languageLocaleProvider;
}
public function getFilters()
{
return [
new TwigFilter('to_float', [$this, 'toFloat'], ['needs_context' => true]),
];
}
public function toFloat($twigContext, $value)
{
$locale = $this->languageLocaleProvider->getLocaleForLanguageId($twigContext['context']->getLanguageId());
$formatter = new \NumberFormatter($locale, \NumberFormatter::DECIMAL);
if (!$value) {
return null;
}
return $formatter->parse($value);
}
}
{{ "123,456.789"|to_float }}
{# 123456.789 #}

Disclaimer: Not a nice solution - see other answers!
I almost literally ported the Twig solution, but I am wondering if there is a more elegant solution for this:
private function parseLocalNumber(string $number, Context $context = null): float
{
$number = str_replace($this->thousandsSeparator, '', $number);
$number = str_replace($this->decimalSeparator, '.', $number);
return floatval($number);
}
private function resolveNumberFormat(Context $context)
{
$locale = $this->appLocaleProvider->getLocaleFromContext($context);
$formattedNumber = (new IntlExtension())->formatNumber(1000.1, [], 'decimal','default', $locale);
$punctuationOnly = strtr($formattedNumber, ['0' => '', '1' => '']);
$this->thousandsSeparator = substr($punctuationOnly, -2, 1);
$this->decimalSeparator = substr($punctuationOnly, -2, 1);
}

Related

output records in the form of html table from moodle database , custom block development

Please check this code for the custom block to be placed in the dashboard. We want to display an HTML table for the records. But it does not add up to the custom block, rather it appears on the top page.
enter image description here
class block_scorecard extends block_base {
function init() {
$this->title = get_string('pluginname', 'block_scorecard');
}
function get_content() {
global $DB;
if ($this->content !== NULL) {
return $this->content;
}
$content = '';
$courses = $DB->get_records('scorm_scoes_track', ['element' => 'cmi.core.total_time']);
$table=new html_table();
$table->head=array('No of attempts','Time modified','status');
foreach ($courses as $course) {
$attempt=$course->attempt;
$timemodified=userdate($course->timemodified, get_string('strftimerecentfull'));
$status=$course->value;
$table->data[]=array($attempt, $timemodified, $status);
}
echo html_writer::table($table);
$this->content = new stdClass;
$this->content->text = $content;
}}
echo html_writer::table($table);
Should be
$content .= html_writer::table($table);

How to extend date picker viewhelper of EXT:powermail?

I need to add a custom validator to the datepicker field. By default, this field comes without any validators.
I've already made the validator settings visible in the TCA of tx_powermail_domain_model_field and added my custom validator as usual.
Now I need the attributes data-parsley-customXXX and data-parsley-error-message added to the HTML input field which is usually done via the the viewhelper in ValidationDataAttributeViewHelper.php:
https://github.com/einpraegsam/powermail/blob/develop/Classes/ViewHelpers/Validation/ValidationDataAttributeViewHelper.php#L342
https://github.com/einpraegsam/powermail/blob/develop/Classes/ViewHelpers/Validation/ValidationDataAttributeViewHelper.php#L348
This is the code I need to extend:
https://github.com/einpraegsam/powermail/blob/develop/Classes/ViewHelpers/Validation/DatepickerDataAttributeViewHelper.php#L32
I found a solution for my problem. As suggested in the comment it's possible to extend the Viewhelper:
ext_localconf.php:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\In2code\Powermail\ViewHelpers\Validation\DatepickerDataAttributeViewHelper::class] = [
'className' => \Vendor\MyExt\Powermail\ViewHelpers\Validation\DatepickerDataAttributeViewHelper::class
];
myext/Classes/Powermail/ViewHelpers/Validation/DatepickerDataAttributeViewHelper.php
<?php
declare(strict_types=1);
namespace Vendor\MyExt\Powermail\ViewHelpers\Validation;
use In2code\Powermail\Domain\Model\Field;
use In2code\Powermail\Utility\LocalizationUtility;
class DatepickerDataAttributeViewHelper extends \In2code\Powermail\ViewHelpers\Validation\DatepickerDataAttributeViewHelper
{
/**
* Returns Data Attribute Array Datepicker settings (FE + BE)
*
* #return array for data attributes
*/
public function render(): array
{
/** #var Field $field */
$field = $this->arguments['field'];
$additionalAttributes = $this->arguments['additionalAttributes'];
$value = $this->arguments['value'];
$additionalAttributes['data-datepicker-force'] =
$this->settings['misc']['datepicker']['forceJavaScriptDatePicker'];
$additionalAttributes['data-datepicker-settings'] = $this->getDatepickerSettings($field);
$additionalAttributes['data-datepicker-months'] = $this->getMonthNames();
$additionalAttributes['data-datepicker-days'] = $this->getDayNames();
$additionalAttributes['data-datepicker-format'] = $this->getFormat($field);
if ($value) {
$additionalAttributes['data-date-value'] = $value;
}
if ($field->getValidation() && $this->isClientValidationEnabled()) {
$value = 1;
if ($field->getValidationConfiguration()) {
$value = $field->getValidationConfiguration();
}
$additionalAttributes['data-parsley-custom' . $field->getValidation()] = $value;
$additionalAttributes['data-parsley-error-message'] =
LocalizationUtility::translate('validationerror_validation.' . $field->getValidation());
}
$this->addMandatoryAttributes($additionalAttributes, $field);
return $additionalAttributes;
}
}

How do I parse a Typoscript file?

I am writing a unit test and want to check if the data in a certain typoscript file satisfies my requirements. How do I convert the text file to an array? The Typo3-Framework is available.
My google research points to using the class \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser but I don't find usage examples ...
(Using Typo3 7.6)
This is working (but possibly there are nicer ways to do this):
<?php
use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
class TyposcriptTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
public function setUp() {
parent::setUp();
$this->objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
}
protected function loadTSFile($filename) {
$content = file_get_contents($filename);
$parser = $this->objectManager->get(TypoScriptParser::class);
$parser->parse($content);
return $parser->setup;
}
public function testTS() {
$array = $this->loadTSFile('...');
$this->assertTrue(isset($array['tx_extension.']['flexForm.']['andsoon.']]), 'Assertion failed. TS content: ' . var_export($array, true));
}
}
Here is the working example for TYPO3 v - 10.4.x
$tsString = 'colors {
backgroundColor = red
fontColor = blue
}
[ip("123.45.*")]
headerImage = fileadmin/img1.jpg
[ELSE]
headerImage = fileadmin/img2.jpg
[GLOBAL]
// Wonder if this works... :-)
wakeMeUp = 7:00';
$TSparserObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
\TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::class
);
$TSparserObject->parse($tsString);
echo '<pre>';
print_r($TSparserObject->setup);
echo '</pre>';

TYPO3 Extbase - Paginate through a large table (100000 records)

I have a fairly large table with about 100000 records. If I don't set the limit in the repository
Repository:
public function paginateRequest() {
$query = $this->createQuery();
$result = $query->setLimit(1000)->execute();
//$result = $query->execute();
return $result;
}
/**
* action list
*
* #return void
*/
public function listAction() {
$this->view->assign('records', $this->leiRepository->paginateRequest());
//$this->view->assign('records', $this->leiRepository->findAll());
}
... the query and the page breaks although I'm using f:widget.paginate . As per the docs https://fluidtypo3.org/viewhelpers/fluid/master/Widget/PaginateViewHelper.html I was hoping that I can render only the itemsPerPage and 'parginate' through the records ...
List.hmtl
<f:if condition="{records}">
<f:widget.paginate objects="{records}" as="paginatedRecords" configuration="{itemsPerPage: 100, insertAbove: 0, insertBelow: 1, maximumNumberOfLinks: 10}">
<f:for each="{paginatedRecords}" as="record">
<tr>
<td><f:link.action action="show" pageUid="43" arguments="{record:record}"> {record.name}</f:link.action></td>
<td><f:link.action action="show" pageUid="43" arguments="{record:record}"> {record.lei}</f:link.action></td>
</tr>
</f:for>
</f:widget.paginate>
Model:
class Lei extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
...
/**
* abc
*
* #lazy
* #var string
*/
protected $abc = '';
...
I use in TYPO3 9.5. The next function in repository:
public function paginated($page = 1, $size = 9){
$query = $this->createQuery();
$begin = ($page-1) * $size;
$query->setOffset($begin);
$query->setLimit($size);
return $query->execute();
}
And in the controller I am using arguments as parameter to send the page to load in a Rest action.
public function listRestAction()
{
$arguments = $this->request->getArguments();
$totalElements = $this->repository->total();
$pages = ceil($totalElements/9);
$next_page = '';
$prev_page = '';
#GET Page to load
if($arguments['page'] AND $arguments['page'] != ''){
$page_to_load = $arguments['page'];
} else {
$page_to_load = 1;
}
#Configuration of pagination
if($page_to_load == $pages){
$prev = $page_to_load - 1;
$prev_page = "http://example.com/rest/news/page/$prev";
} elseif($page_to_load == 1){
$next = $page_to_load + 1;
$next_page = "http://example.com/rest/news/page/$next";
} else {
$prev = $page_to_load - 1;
$prev_page = "http://example.com/rest/news/page/$prev";
$next = $page_to_load + 1;
$next_page = "http://example.com/rest/news/page/$next";
}
$jsonPreparedElements = array();
$jsonPreparedElements['info']['count'] = $totalElements;
$jsonPreparedElements['info']['pages'] = $pages;
$jsonPreparedElements['info']['next'] = $next_page;
$jsonPreparedElements['info']['prev'] = $prev_page;
$result = $this->repository->paginated($page_to_load);
$collection_parsed_results = array();
foreach ($result as $news) {
array_push($collection_parsed_results, $news->parsedArray());
}
$jsonPreparedElements['results'] = $collection_parsed_results;
$this->view->assign('value', $jsonPreparedElements);
}
The result of this, is a JSON like this:
{
"info": {
"count": 25,
"pages": 3,
"next": "",
"prev": "http://example.com/rest/news/page/2"
},
"results": [
{ ....}
] }
How large / complex are the objects you want to paginate through? If they have subobjects that you dont need in the list view, add #lazy annotation to those relations inside the model.
Due to this large amount of records, you should keep them as simple as possible in the list view. You can try to only give the result as array to the list view using $this->leiRepository->findAll()->toArray() or return only the raw result from your repository by adding true to execute(true).
You can also create an array of list items yourself in a foreach in the controller and only add the properties you really need inside the list.
If your problem is the performance, just use the default findAll()-Method.
The built-in defaultQuerySettings in \TYPO3\CMS\Extbase\Persistence\Repository set their offset and limit based on the Pagination widget, if not set otherwise.
If the performance issue persists, you may have to consider writing a custom query for your database request, that only requests the data your view actually displays. The process is described in the documentation: https://docs.typo3.org/typo3cms/ExtbaseFluidBook/6-Persistence/3-implement-individual-database-queries.html

zend currency negative sign

Hello i am using Zend_currency
class Currency extends Zend_View_Helper_Abstract
{
public function currency($number, $locale = 'it_IT') {
$currency = new Zend_Currency($locale);
$number = $number + 0.00;//convert to float
return $currency->toCurrency((float) $number);
}
}
in a some view .phtml file
echo $this->currency($gimme_my_money);
and this is what i get
€ 19.373,25
-€ 116,07
how can i get it to print negative numbers like
€ -116,07
Just overwrite the format option like this:
$cur = new Zend_Currency(array('format' => '¤ #,##0.00;¤ -#,##0.00'));
The trick is in the second part of the string (after the comma), I've checked it for Italian locale and the format string provided there is ¤ #,##0.00.
This is tested with ZF 1.11.7
I don't think this formatting option is built into Zend_Currency.
What you can do, is move the currency symbol to the right side:
$this->view->total = new Zend_Currency(array('value' => $total, 'position' => Zend_Currency::RIGHT));
And then your currencies will be displayed with the currency symbol on the right:
-19.373,25 €
If you want a custom formatting, with the negative sign after the symbol, (€ -116,07), you will have to write your own currency formatter or built on top of Zend_Currency
try this:
class My_View_Helper_Currency extends Zend_View_Helper_Abstract
{
/**
* Format a numeric currency value and return it as a string
*
* #param int|float $value any value that return true with is_numeric
* #param array $options additional options to pass to the currency
* constructor
* #param string $locale locale value
*
* #throws InvalidParameterException if the $value parameter is not numeric
* #return string the formatted value
*/
public function currency($value, $options = array(), $locale = null)
{
if (!is_numeric($value)) {
throw new InvalidArgumentException(
'Numeric argument expected ' . gettype($value) . ' given'
);
}
$options = array_merge($options, array('value' => $value));
$currency = new Zend_Currency($options, $locale);
return $currency->toString();
}
}