Zend Framework

Validate_Float doesn't work with local than doesn't have a dot as decimal separator

Details

  • Type: Bug Bug
  • Status: Resolved Resolved
  • Priority: Critical Critical
  • Resolution: Fixed
  • Affects Version/s: 1.5.2
  • Fix Version/s: 1.8.0
  • Component/s: Zend_Validate
  • Labels:
    None

Description

The php function strval use the decimal point character that is defined in the script's locale (category LC_NUMERIC), so depending on the local set in PHP.

For instance, strval(1.2) return "1,2" in "fr_FR.utf8" locale.

So basicaly the isValid() function of the Zend_Validate_Float class does this :

$locale = localeconv();
        
        $valueFiltered = str_replace($locale['thousands_sep'], '', $valueString);
        $valueFiltered = str_replace($locale['decimal_point'], '.', $valueFiltered);
        if (strval(floatval($valueFiltered)) != $valueFiltered) {
          /* only true if the $locale['thousands_sep'] is already '' and $locale['decimal_point'] is already '.' */

Here, the code replace the locale decimal point by '.' and remove the locale thousand separator and then compare this string to the same string, convertred to float and displayed using that same locale decimal point and locale thousand separator ... well, the only chance for this to work is if the locale decimal point IS already a '.' and the locale thousand separator is ''.

I think this should be enough to test if a value is a float :

Zend_Validate_Float, line 68
if (false===ereg('^[0-9]*\.[0-9]+$', $valueFiltered)){
  /* not a float */
}

Issue Links

Activity

Hide
Thomas Weidner added a comment -

Attention:
You should also take in account that users may have set Zend_Locale to a different locale than the one from setlocale (setlocale is not threadsave).

So we could have system set to french and user setting arabic.
Validate should be able also to verify such.

Easiest solution:
Allow a locale to be set
Use Zend_Locale to normalize the value.

Show
Thomas Weidner added a comment - Attention: You should also take in account that users may have set Zend_Locale to a different locale than the one from setlocale (setlocale is not threadsave). So we could have system set to french and user setting arabic. Validate should be able also to verify such. Easiest solution: Allow a locale to be set Use Zend_Locale to normalize the value.
Hide
Adam Kusmierz added a comment -

This should be resolve this issue. It matches old value with converted from string to float and again to string old value.

Show
Adam Kusmierz added a comment - This should be resolve this issue. It matches old value with converted from string to float and again to string old value.
Hide
Thomas Weidner added a comment -

The patch does not work when the user is using Zend_Form with an application locale.
Input could be "1,2" even it system locale is set to "en" because the application locale is set to "fr".

The validation would still fail.
Therefor this fix does not solve the problem, only part of it.

Show
Thomas Weidner added a comment - The patch does not work when the user is using Zend_Form with an application locale. Input could be "1,2" even it system locale is set to "en" because the application locale is set to "fr". The validation would still fail. Therefor this fix does not solve the problem, only part of it.
Hide
Thomas Weidner added a comment -

This is the same patch which I commented... there is no difference.

Show
Thomas Weidner added a comment - This is the same patch which I commented... there is no difference.
Hide
Sven Franke added a comment -

Hi,

I use this 'solution'. Maybe it isn't a correct one but it works for me in the application.

<?php

class Application_Validate_Float extends Zend_Validate_Float {
    
    const NOT_FLOAT = 'notFloat';

    /**
     * Message templates.
     * 
     * @var array
     */
    protected $_messageTemplates = array(
        self::NOT_FLOAT => "'%value%' does not appear to be a float"
    );

    /**
     * Locale
     *
     * @var Zend_Locale
     */    
    protected $_locale;
    
    /**
     * Constructor
     *
     * @param string|Zend_Locale|null $locale
     */
    public function __construct($locale = null) {
        
        $this->setLocale($locale);
        
    }
    
    
    /**
     * Returns the locale option
     *
     * @return string|Zend_Locale|null
     */
    public function getLocale()
    {
        return $this->_locale;
    }

    /**
     * Sets the locale option
     *
     * @param  string|Zend_Locale $locale
     * @return Zend_Validate_Date provides a fluent interface
     */
    public function setLocale($locale = null)
    {
        if ($locale === null) {
            $this->_locale = null;
            return $this;
        }

        require_once 'Zend/Locale.php';
        if (!Zend_Locale::isLocale($locale, true, false)) {
            if (!Zend_Locale::isLocale($locale, false, false)) {
                require_once 'Zend/Validate/Exception.php';
                throw new Zend_Validate_Exception("The locale '$locale' is no known locale");
            }

            $locale = new Zend_Locale($locale);
        }

        $this->_locale = (string) $locale;
        return $this;
    }
    
    
    /**
     * Defined by Zend_Validate_Interface
     *
     * Returns true if and only if $value is a floating-point value
     *
     * @param  string $value
     * @return boolean
     */
    public function isValid($value)
    {
        $valueString = (string) $value;

        $this->_setValue($valueString);

//        // ORIGINAL FROM ZEND_FLOAT (ZF 1.7.0)
//        $locale = localeconv();
//
//        $valueFiltered = str_replace($locale['thousands_sep'], '', $valueString);
//        $valueFiltered = str_replace($locale['decimal_point'], '.', $valueFiltered);
// 
//        if (strval(floatval($valueFiltered)) != $valueFiltered) {
//            $this->_error();
//            return false;
//        }

        // Locale aware.
        if(!Zend_Locale_Format::isFloat($value, array('locale' => $locale))) { 
            $this->_error(); 
            return false; 
        }
        
        return true;
    }
    
}
?>
Show
Sven Franke added a comment - Hi, I use this 'solution'. Maybe it isn't a correct one but it works for me in the application.
<?php

class Application_Validate_Float extends Zend_Validate_Float {
    
    const NOT_FLOAT = 'notFloat';

    /**
     * Message templates.
     * 
     * @var array
     */
    protected $_messageTemplates = array(
        self::NOT_FLOAT => "'%value%' does not appear to be a float"
    );

    /**
     * Locale
     *
     * @var Zend_Locale
     */    
    protected $_locale;
    
    /**
     * Constructor
     *
     * @param string|Zend_Locale|null $locale
     */
    public function __construct($locale = null) {
        
        $this->setLocale($locale);
        
    }
    
    
    /**
     * Returns the locale option
     *
     * @return string|Zend_Locale|null
     */
    public function getLocale()
    {
        return $this->_locale;
    }

    /**
     * Sets the locale option
     *
     * @param  string|Zend_Locale $locale
     * @return Zend_Validate_Date provides a fluent interface
     */
    public function setLocale($locale = null)
    {
        if ($locale === null) {
            $this->_locale = null;
            return $this;
        }

        require_once 'Zend/Locale.php';
        if (!Zend_Locale::isLocale($locale, true, false)) {
            if (!Zend_Locale::isLocale($locale, false, false)) {
                require_once 'Zend/Validate/Exception.php';
                throw new Zend_Validate_Exception("The locale '$locale' is no known locale");
            }

            $locale = new Zend_Locale($locale);
        }

        $this->_locale = (string) $locale;
        return $this;
    }
    
    
    /**
     * Defined by Zend_Validate_Interface
     *
     * Returns true if and only if $value is a floating-point value
     *
     * @param  string $value
     * @return boolean
     */
    public function isValid($value)
    {
        $valueString = (string) $value;

        $this->_setValue($valueString);

//        // ORIGINAL FROM ZEND_FLOAT (ZF 1.7.0)
//        $locale = localeconv();
//
//        $valueFiltered = str_replace($locale['thousands_sep'], '', $valueString);
//        $valueFiltered = str_replace($locale['decimal_point'], '.', $valueFiltered);
// 
//        if (strval(floatval($valueFiltered)) != $valueFiltered) {
//            $this->_error();
//            return false;
//        }

        // Locale aware.
        if(!Zend_Locale_Format::isFloat($value, array('locale' => $locale))) { 
            $this->_error(); 
            return false; 
        }
        
        return true;
    }
    
}
?>
Hide
Thomas Weidner added a comment -

Yes, this solution is the right direction.
It can be used as workaround and shows what I said to be the solution.

There are only part details which will be changed by me.

Thanks Sven

Show
Thomas Weidner added a comment - Yes, this solution is the right direction. It can be used as workaround and shows what I said to be the solution. There are only part details which will be changed by me. Thanks Sven
Hide
Thomas Weidner added a comment -

New feature added with r13372

Show
Thomas Weidner added a comment - New feature added with r13372

People

Vote (3)
Watch (6)

Dates

  • Created:
    Updated:
    Resolved:

Time Tracking

Estimated:
Not Specified
Original Estimate - Not Specified
Remaining:
0m
Remaining Estimate - 0 minutes
Logged:
1h
Time Spent - 1 hour