Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "validator"

sfCustomUniqueValidator:

This snippet allow to check if an entry allready exists in a database but, at the difference of the sfUniqueValidator, you can provide as many fields as desired to perform the verification.

Installation:

The first thing you need to do is to create the file sfCustomUniqueValidator.php in your project lib directory:

<?php
  /**
 * sfCustomUniqueValidator checks if a record exist in the database with all the mentionned fields.
 *
 * ex: Check if a companie with company_name exist in country_id 
 *   class:            sfCustomUniqueValidator
 *   param:
 *     class:          Companies    //the class on which the search is performed
 *     nb_fields:      2            //the number of fields on which the comparison is done
 *     field_1:        company_name //First field of the comparison
 *     field_2:        country_id   //Other country for the comparison
 *
 * @package    lib
 * @author     Joachim Martin
 * @date       15/06/2007
 */
 
class sfCustomUniqueValidator extends sfValidator {
 
   /**
   * Executes this validator.
   *
   * @param mixed A file or parameter value/array
   * @param error An error message reference
   *
   * @return bool true, if this validator executes successfully, otherwise false
   */
 
    public function execute(&$value, &$error) {
 
        $className  = $this->getParameter('class').'Peer';
 
        //Get fields number
        $nb_fields = $this->getParameter('nb_fields');
 
        //Define new criteria       
        $c = new Criteria();
 
        //Loop on the fields
        for($i = 1; $i <= $nb_fields ; $i++) {
            //Retrieve field_$i 
            $check_param = $this->getParameterHolder()->get("field_$i");
            $check_value = $this->getContext()->getRequest()->getParameter($check_param);
 
            //If check value defined        
            if ($check_value != '') {   
                //Adding field to the criteria
                $columnName = call_user_func(array($className, 'translateFieldName'), $check_param, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_COLNAME);
                $c->add($columnName, $check_value);
            }
        }
 
        $object = call_user_func(array($className, 'doSelectOne'), $c);
 
        if ($object)
        {
          $tableMap = call_user_func(array($className, 'getTableMap'));
          foreach ($tableMap->getColumns() as $column)
          {
            if (!$column->isPrimaryKey())
            {
              continue;
            }
 
            $method = 'get'.$column->getPhpName();
            $primaryKey = call_user_func(array($className, 'translateFieldName'), $column->getPhpName(), BasePeer::TYPE_PHPNAME, BasePeer::TYPE_FIELDNAME);
            if ($object->$method() != $this->getContext()->getRequest()->getParameter($primaryKey))
            {
              $error = $this->getParameter('custom_unique_error');
 
              return false;
            }
          }
        }
 
        return true;
    } 
 
    public function initialize ($context, $parameters = null) {
        // initialize parent
        parent::initialize($context);
 
        //Set default parameters value
        $this->setParameter('custom_unique_error','The value is not unique');
 
        $this->getParameterHolder()->add($parameters);
 
        // check parameters
        if (!$this->getParameter('class'))
        {
          throw new sfValidatorException('The "class" parameter is mandatory for the sfCustomUniqueValidator validator.');
        }
 
        if (!$this->getParameter('nb_fields'))
        {
          throw new sfValidatorException('The "nb_fields" parameter is mandatory for the sfCustomUniqueValidator validator.');
        }
 
        return true;
    }
}

Usage:

The following code check if a companie with the same name and same activity exists in the same country

sfCustomUniqueValidator:
      class:                   Companies
      nb_fields:               3
      field_1:                 company_name
      field_2:                 activity_id
      field_3:                 country_id
      custom_unique_error:     This company already exist for this country

class: the model to test

nb_fields: how many fields will be checked

field_x: a field to test, obviously you need to have as many field_x as the nb_fields value

custom_unique_error: your error message

This is my very first contribution to symfony so feel free to comment/optimize.

by joachim martin on 2007-06-21, tagged unique  validation  validator 
(5 comments)

sfFileImageValidator

Description:

This images validations is an extention for the sfFileValidator. You can use it for validate uploaded images maximum width and height, minimum width and height and if the images have square dimensions.

<?php
 
/**
 * sfFileImageValidator allows you to apply constraints to image file upload, it extend the sfFileValidator functions.
 *
 * <b>Optional parameters:</b>
 *
 * # <b>max_height</b>         - [none]                                - Maximum images height in pixels.
 * # <b>max_height_error</b>   - [The file height is too large]        - An error message to use when
 *                                                                       images height is too large.
 * # <b>max_width</b>          - [none]                                - Maximum images width in pixels.
 * # <b>max_width_error</b>    - [The file width is too large]         - An error message to use when
 *                                                                       images width is too large.
 * # <b>min_height</b>         - [none]                                - Minimum images height in pixels.
 * # <b>min_height_error</b>   - [The file height is too small]        - An error message to use when
 *                                                                       images height is too small.
 * # <b>min_width</b>          - [none]                                - Minimum images width in pixels.
 * # <b>min_width_error</b>    - [The file width is too small]         - An error message to use when
 *                                                                       images width is too small.
 * # <b>is_square</b>          - [false]                               - The image is a square
 * # <b>is_square_error</b>    - [The file is not a square]            - An error message to use when
 *                                                                       the images is not a square
 *                                                                       (The width size is not equal
 *                                                                       to the height size).
 * @package    symfony
 * @subpackage validator
 * @author     Daniel Santiago 
 */
 
class sfFileImageValidator extends sfFileValidator
{
  /**
   * Executes this validator.
   *
   * @param mixed A file or parameter value/array
   * @param error An error message reference
   *
   * @return bool true, if this validator executes successfully, otherwise false
   */
 
  public function execute(&$value, &$error)
  {
    if (parent::execute($value, $error))
    {
      list($width, $height) = @getimagesize($value['tmp_name']);
 
      // File is not a square
      $is_square = $this->getParameter('is_square');
      if ($is_square && $width != $height)
      {
        $error = $this->getParameter('is_square_error');
 
        return false;
      }
 
      // File height too large
      $max_height = $this->getParameter('max_height');
      if ($max_height !== null && $max_height < $height)
      {
        $error = $this->getParameter('max_height_error');
 
        return false;
      }
 
      // File width too large
      $max_width = $this->getParameter('max_width');
      if ($max_width !== null && $max_width < $width)
      {
        $error = $this->getParameter('max_width_error');
 
        return false;
      }
 
      // File height too small
      $min_height = $this->getParameter('min_height');
      if ($min_height !== null && $min_height > $height)
      {
        $error = $this->getParameter('min_height_error');
 
        return false;
      }
 
      // File width too small
      $min_width = $this->getParameter('min_width');
      if ($min_width !== null && $min_width > $width)
      {
        $error = $this->getParameter('min_width_error');
 
        return false;
      }
 
      return true;
    }
  }
 
  /**
   * Initializes this validator.
   *
   * @param sfContext The current application context
   * @param array   An associative array of initialization parameters
   *
   * @return bool true, if initialization completes successfully, otherwise false
   */
  public function initialize($context, $parameters = null)
  {
    // initialize parent
    parent::initialize($context, $parameters);
 
    // set defaults
    $this->getParameterHolder()->set('max_height',        null);
    $this->getParameterHolder()->set('max_height_error',  'The file height is too large');
    $this->getParameterHolder()->set('max_width',         null);
    $this->getParameterHolder()->set('max_width_error',   'The file width is too large');
    $this->getParameterHolder()->set('min_height',        null);
    $this->getParameterHolder()->set('min_height_error',  'The file height is too small');
    $this->getParameterHolder()->set('min_width',         null);
    $this->getParameterHolder()->set('min_width_error',   'The file width is too small');
    $this->getParameterHolder()->set('is_square',         false);
    $this->getParameterHolder()->set('is_square_error',   'The file is not a square');
 
    $this->getParameterHolder()->add($parameters);
 
    return true;
  }
}
 

How to use it?

In the YAML validation file put this:

  news{photo}:
    file:     yes
    sfFileImageValidator:
      min_height:       100
      min_height_error: 'The image height is too small, it must have minimum 100px'
      min_width:        120
      min_width_error: 'The image width is too small, it must have minimum 120px'
      max_height:       960
      max_height_error: 'The image height is too large, it must have maximum 960px'
      max_width:        450
      max_width_error: 'The image width is too large, it must have maximum 450px'
      is_square:        true
      is_square_error:  'The images must be a square (The height be equal to the width)'
 
      max_size:         256000
      max_size_error:   'The maximum images size is 250Kb'
      mime_types_error: 'We only accept GIF, PNG and JPEG.'
      mime_types:
        - 'image/jpeg'
        - 'image/png'
        - 'image/gif'
 
by Daniel Santiago on 2007-12-26, tagged image  images  validation  validator 

Login form in Symfony 1.1, with the new form system

Symfony 1.1 comes with a complete new form system. It works completely according to the MVC draft:

Make sure you have a running Symfony 1.1 based project and application and modules. In this example I build the form inside the myModule module and myLogin action.

My form makes use of i18n, which is in my case autoloaded in settings.yml.

This tutorial uses Symfony 1.1 beta4 and RC1. There are a little important changes with respect to beta3, which I don't cover.

I also expect you to have practical knowledge and a little bit experience with Symfony as system. I will not explain how you i18n implements or modules shield with security.yml.

The form design

The form gets two import fields: username and password. Furthermore is there a hidden field in which the URI comes that the user requested, but got redirected to the loginform (through Symfony’s security.yml and settings.yml). This will be used to go to that URI again after a successful login.

Both imput fields are required I will build a custom validation for a correct username/password check. Also we want to make use of a little new protection feature: CSRF.

The form takes uses i18n for multilinguity, I will use English primarily, but the view has been prepared for other languages.

The start: the action

We will begin with the action which contains the primary control.

/**
 * Executes myLogin action
 *
 * Login functionality.
 *
 * @param void
 * @return void
 * @access public
 */
public function executeMyLogin() {
   // Erase auth data
   $this->getUser()->clearCredentials();
   $this->getUser()->setAuthenticated(FALSE);
 
   // Build login form
   $oForm = new inloggenForm();
 
   if ($this->getRequest()->isMethod('post')) { // When called through POST (form submit)
       $oForm->bind(
           array('username' => $this->getRequest()->getParameter('username'),
                 'password' => $this->getRequest()->getParameter('password'),
                 'referrer' => $this->getRequest()->getParameter('referrer'),
                )
      );
 
      // Save orginal requested location in referrer field
      $oForm->setDefault('referrer', $this->getRequest()->getParameter('referrer'));
      if ($oForm->isValid()) { // When validations OK
          $aValues = $oForm->getValues();
          sfContext::getInstance()->getLogger()->debug($aValues['username']);
 
          // Authentification
          $this->getUser()->setAuthenticated(TRUE);
 
          // To requested page
          $this->redirect($this->getRequest()->getParameter('referrer'));
      }
   } else {
      // Save original requested uri in form
      $oForm->setDefault('referrer', sfContext::getInstance()->getRequest()->getUri());
   }
 
   $this->oForm = $oForm; // form to view
}
 

Logic of authentification is set after POST further. Through the sfForm::bind() method couples the input of the user coupled with the form controller.

The form controller

I made this in myModule/lib/form/inloggenForm.class.php. In the form is defined and coupled with the validations and the formatting.

<?php
class inloggenForm extends sfForm {
    /**
     * Prepare validators
     */
    private function _setValidators() {
        $this->oValGebruikersnaam = new sfValidatorAnd(
                                    array(
                                        new sfValidatorString(array('min_length' => 3, 'max_length' => 50),
                                                              array('min_length' => 'The username should be at least three characters long',
                                                                    'max_length' => 'The username should be fifty characters long at most',
                                                              )
                                        ),
                                        new sfValidatorCallback(array('callback'  => array('cmsLoginValidator', 'execute'),
                                                                      'arguments' => array()
                                                                ),
                                                                array('invalid'  => 'This username/password combination is unknown')
                                        ),
                                    ),
                                    array('required'   => TRUE),
                                    array('required'   => 'The username is mandatory')
        );
        $this->oValWachtwoord = new sfValidatorString(array('required' => TRUE),
                                                      array('required'   => 'The password is mandatory')
        );
        $this->oValReferrer = new sfValidatorString(array('required' => FALSE));
    }
 
    /**
     * Prepare widgets
     */
    private function _setWidgets() {
        $this->oWidGebruikersnaam = new sfWidgetFormInput(array(), array('id' => 'username', 'size' => 25));
        $this->oWidWachtwoord     = new sfWidgetFormInputPassword(array(), array('id' => 'password', 'size' => 10));
        $this->oWidReferrer       = new sfWidgetFormInputHidden(array(), array('id' => 'referrer'));
    }
 
    /**
     * Configure form
     */
    public function configure()     {
        $this->_setValidators();
        $this->_setWidgets();
 
        /*
         * Set validators
         */
        $this->setValidators(array('username' => $this->oValGebruikersnaam,
                                   'password'     => $this->oValWachtwoord,
                                   'referrer'       => $this->oValReferrer,
                             )
        );
 
        /*
         * Set widgets
         */
        $this->setWidgets(array('username' => $this->oWidGebruikersnaam,
                                'password'     => $this->oWidWachtwoord,
                                'referrer'       => $this->oWidReferrer,
                          )
        );
 
        /*
         * Set decorator
         */
        $oDecorator = new sfWidgetFormSchemaFormatterDiv($this->getWidgetSchema());
        $this->getWidgetSchema()->addFormFormatter('div', $oDecorator);
        $this->getWidgetSchema()->setFormFormatterName('div');
        $this->getWidgetSchema()->setHelps(array('username' => 'Please enter your username',
                                                 'password' => 'Please enter your password'
                                           )
        );
 
    }
 
    /**
     * Bind override
     */
    public function bind(array $taintedValues = null, array $taintedFiles = array()) {
        $request = sfContext::getInstance()->getRequest();
 
        if ($request->hasParameter(self::$CSRFFieldName))         {
            $taintedValues[self::$CSRFFieldName] = $request->getParameter(self::$CSRFFieldName);
        }
 
        parent::bind($taintedValues, $taintedFiles);
    }
}
 

There are two private methods:

I overloaded the earlier mentioned bind() method to process the CSRF token internally. This fiels is regulated by sfForm internally, you to do need to worry about it much, only with the actual functionality.

The configure() method contains the functionality. The validators are created and linked to the corresponding fields and also are the widgets. Also, the decorator is defined with I will go into later on.

Validators

In _setValidators() sfValidatorBase objects are made for each field.

The password field check is the simplest, only the required input is checked. The object is of the type sfValidatorString (extend sfValidatorBase) without extra controls, only the required attribute with errortext is specified. In principle, you can use each validator as -empty- container.

The username is a combination; it is required, there are restrictions to the the stringlength and a correct login is checked. The stringcontrole/requirement is checked through sfValidatorString, which is quite simple.

The check for a correct login is done through a special validator, that does a callback to an custom function: sfValidatorCallback. This is explained later.

Since these two validators check the same field , the sfValidatorAnd validator is used that combines several validators. All validators must be satisfied. Of course Symfony also offers sfValidatorOr that checks that at least one underlying validator satisfies.

The custom logincheck

As you can see the callback validator calls to a custom class/method: myLoginValidator::execute().

/**
 * Fiel with myLoginValidator klasse
 *
 * @package -
 */
/**
 * Validation correct username/password
 *
 * @author Jordi Backx (Snowkrash)
 * @copyright Copyright © 2008, Jordi Backx (Snowkrash)
 * @package -
 */
class myLoginValidator {
    /**
     * execute validator
     *
     * @param sfValidatorBase Validator instance that calls this method
     * @param string Value of field that sfValidatorCallback checks
     * @param array Arguments for correct working
     *
     * @return value field when OK. Nothing if error (sfValidatorError exception)
     */
    public static function execute ($oValidator, $sValue, $aArguments) {
        if ( /* check OK */ ) { // Return waarde veld indien controle OK
            return $sValue;
        }
        // Throw exception when not OK
        throw new sfValidatorError($oValidator, 'invalid', array('value' => $sValue, 'invalid' => $oValidator->getOption('invalid')));
    }
}
 

I set this here with no further logic, that is application specific, thus that you 'll have to do yourself. The base structure can be used. The three parameters must be defined, otherwise the whole application crashes.

Widgets

In _setWidget() sfWidget objects are made for each field.

The widgets are the form elements: finally <input>, <select> etc tags in combination with labels and errortexts.

Each widget can have HTML attributes, which will be printed inside the form elements.

The view

Finally the form must be printed to the screen through a view template.

<p><?php echo __('You need to log in to be able to use the Content Management System.') ?></p>
<div id="formContainer">
<?php if ($oForm->getErrorSchema()->getErrors()) { ?>
    <div id="formulierFouten">
    <ul>
    <?php foreach ($oForm->getErrorSchema() as $sError) { ?>
        <li><?php echo __($sError) ?></li>
    <?php } ?>
    </ul>
    </div>
<?php } ?>
<form action="<?php echo url_for('myModule/myLogin') ?>" method="post">
    <?php echo $oForm['username']->renderLabel(__($oForm['username']->renderLabelName())); echo $oForm['username']->renderRow(__($oForm->getWidgetSchema()->getHelp('username'))); ?>
    <?php echo $oForm['password']->renderLabel(__($oForm['password']->renderLabelName())); echo $oForm['password']->renderRow(__($oForm->getWidgetSchema()->getHelp('password'))); ?>
    <?php echo $oForm['referrer']->render(array('value' => $oForm->getDefault('referrer'))) ?>
    <?php echo $oForm['_csrf_token'] ?>
    <label for="inloggen">&nbsp;</label><input type="submit" value="Inloggen" id="inloggen" class="aanmeldenSubmit" />
</form>
</div>
 

You can see all the i18n code (__() helper) and some non-Symfony 1.0 form building. Errorlists are built through the errorSchema which is available within the form object, the texts themself can be translated as you can see.

Also the labels and help texts are squeezed through i18n. The field names are in English, because the labels are based on these and must go through i18n. This way everything can be translated.

You can print the whole form with an echo of $oForm (goes through __toString()), but you have more control over the layout when you use specific widgetrender functions, like I do with renderRow(). This method takes the helptext as an argument, with is also translated.

The submit button is no widget, so we place it ourselves the old-fashioned way ... no helper, that is so Symfony 1.0.

CSRF token

That one is new. It is there, but we never defined it. It is created within sfForm and only since beta4 when indicated in settings.yml:

#Form security secret (CSRF protection)
    csrf_secret:             hierjeeigenc0d3     # Unique secret to enable CSRF protection or false to disable
 

You can choose your own code, on which the hash inside the CSRF value is based.

The form functionally is ready, but we want more control over the layout. I am a supporter of the tableless HTML design and the standard formatter of sfForm uses ... tables. Well, we can do better.

The form controller showed the coupling with my own formatter:

/*
 * Set decorator
 */
$oDecorator = new sfWidgetFormSchemaFormatterDiv($this->getWidgetSchema());
$this->getWidgetSchema()->addFormFormatter('div', $oDecorator);
$this->getWidgetSchema()->setFormFormatterName('div');
 

I will now go into this part.

I have a class sfWidgetFormSchemaFormatterDiv in sfWidgetFormSchemaFormatterDiv.class.php made in the application-level lib/ directory so that all modules of can use it.

This takes care of the HTML layout of the form elements.

class sfWidgetFormSchemaFormatterDiv extends sfWidgetFormSchemaFormatter {
    protected
        $rowFormat = '%error%%field%<br />%help%<br />',
        $helpFormat = '<span class="help">%help%</span>',
        $errorRowFormat = '<div>%errors%</div>',
        $errorListFormatInARow = '%errors%',
        $errorRowFormatInARow = '<div class="formError">&darr;&nbsp;%error%&nbsp;&darr;</div>',
        $namedErrorRowFormatInARow = '%name%: %error%<br />',
        $decoratorFormat = '<div id="formContainer">%content%</div>';
}
 

A good article is available that describes this system.

For people that wonder why the label (%label% placeholder) is not used: $rowFormat sets the layout of the renderRow() method and since I want to render the label separately (i18n), it must not be rendered a second time by renderRow().

Conclusion

Hopefully the above can be a good help for your own form in Symfony 1.1. The documentation is quite scarce at the moment, so each bit of help will be welcome.

If the English is somewhat bad, I did a automatic translation of my original Dutch version of the article and tuned that a bit. The reason? I am lazy. ;-)

If you find errors in the above, it is because of copying my code probably. Please mention it in the comments.

Good luck!

by Jordi Backx on 2008-05-10, tagged 11  csrf  decorator  form  formatter  i18n  login  sfform  symfony  validator  widget 

Pass multiple parameters to sfCallbackValidator

I love sfCallbackValidator and use it all the time, but found it was somewhat limiting in that only the value being validated could be passed to the function or method that is doing the validating. So, I've extended it, overriding the execute method:

myCallbackValidator.class.php:

<?php
 
class myCallbackValidator extends sfCallbackValidator {
 
  public function execute(&$value, &$error) {
 
    $callback = $this->getParameterHolder()->get('callback');
    if (!call_user_func($callback, $value, $this->getParameterHolder()->get('parameters'))) {
      $error = $this->getParameterHolder()->get('invalid_error');
      return false;
    }
    return true;
 
  }
 
}

You can specify the parameters in your validation yaml file like so...

  birthdate:
    myCallbackValidator:
      callback:       [myValidationTools, birthDate]
      invalid_error:  Birthdate is invalid. You must be at least 18 years old to apply.
      parameters:
        min_age:      18

And then the parameters can be accessed in the callback function like so:

  public static function birthDate($string, $params = null) {
    if (isset($params['min_age'])) {
      ... etc...
by east3rd on 2007-06-27, tagged callback  validation  validator 
(2 comments)

Credit Card Validator II

Here is another variation on the credit card validator. In your validate/[action].yml file, you can implement this helper like so:

fields:
  cc_type:
    required:
      msg:      Please select a card type
    sfStringValidator:
      values:       [Visa, MasterCard, Discover, American Express]
      values_error: Please select a credit card type
      insensitive:  true
 
  cc_number:
    required:
      msg:      Please provide a credit card number
    myCreditCardValidator:
      card_name: cc_type    # refers to field name in form that contains card type, like Visa, MasteCard, etc.

Place this file, myCreditCardValidator.class.php, in you application's /lib directory and clear the cache.

<?php
 
/**
 * This class has been converted to a Symfony Validator from original code
 * created by John Gardner, 4th January 2005. 
 * http://www.braemoor.co.uk/software/index.shtml
 *
 * Symfony conversion by Scott Meves, Stereo Interactive & Design, 2007
 * http://www.stereointeractive.com
 * 
 * This routine checks the credit card number. The following checks are made:
 * 
 * 1. A number has been provided
 * 2. The number is a right length for the card
 * 3. The number has an appropriate prefix for the card
 * 4. The number has a valid modulus 10 number check digit if required
 * 
 **/
 
class myCreditCardValidator extends sfValidator
{    
 
    static protected $CARDS = array (
        array ('name'           => 'American Express', 
            'length'            => '15', 
            'prefixes'      => '34,37',
            'checkdigit'    => true
    ),
        array ('name'           => 'Carte Blanche', 
               'length'         => '14', 
               'prefixes'   => '300,301,302,303,304,305,36,38',
               'checkdigit' => true
        ),
        array ('name'       => 'Diners Club', 
               'length'         => '14',
               'prefixes'   => '300,301,302,303,304,305,36,38',
               'checkdigit' => true
    ),
        array ('name'       => 'Discover', 
               'length'         => '16', 
               'prefixes'   => '6011',
               'checkdigit' => true
    ),
        array ('name'       => 'Enroute', 
               'length'         => '15', 
               'prefixes'   => '2014,2149',
               'checkdigit' => true
    ),
        array ('name'       => 'JCB', 
               'length'         => '15,16', 
               'prefixes'   => '3,1800,2131',
               'checkdigit' => true
    ),
        array ('name'       => 'Maestro', 
               'length'         => '16', 
               'prefixes'   => '5020,6',
               'checkdigit' => true
    ),
        array ('name'       => 'MasterCard', 
               'length'         => '16', 
               'prefixes'   => '51,52,53,54,55',
               'checkdigit' => true
    ),
        array ('name'       => 'Solo', 
               'length'         => '16,18,19', 
               'prefixes'   => '6334, 6767',
               'checkdigit' => true
    ),
        array ('name'       => 'Switch', 
               'length'         => '16,18,19', 
               'prefixes'   => '4903,4905,4911,4936,564182,633110,6333,6759',
               'checkdigit' => true
    ),
        array ('name'       => 'Visa', 
               'length'         => '13,16', 
               'prefixes'   => '4',
               'checkdigit' => true
    ),
        array ('name'       => 'Visa Electron', 
               'length'         => '16', 
               'prefixes'   => '417500,4917,4913',
               'checkdigit' => true
        )
  );
 
  public function initialize($context, $parameters = null)
  {
    // initialize parent
    parent::initialize($context);
 
    // set defaults
    $parameterHolder = $this->getParameterHolder();
    $parameterHolder->set('cc_error_type',      'Unknown card type');
    $parameterHolder->set('cc_error_missing', 'No card number provided');
    $parameterHolder->set('cc_error_format',    'Credit card number has invalid format');
    $parameterHolder->set('cc_error_number',    'Credit card number is invalid');
    $parameterHolder->set('cc_error_length',    'Credit card number is wrong length');
 
    $this->getParameterHolder()->add($parameters);
 
    return true;
  }
 
  public function execute(&$value, &$error)
  {
    $cardName = $this->getParameterHolder()->get('card_name');
    $cardName = $this->getContext()->getRequest()->getParameter($cardName);
 
    $cardNumber = $value;
 
      // Establish card type
      $cardType = -1;
      for ($i=0; $i<sizeof(self::$CARDS); $i++) 
        {   // See if it is this card (ignoring the case of the string)
        if (strtolower($cardName) == strtolower(self::$CARDS[$i]['name']))
            {
          $cardType = $i;
          break;
        }
      }
 
      // If card type not found, report an error
      if ($cardType == -1) 
        {
            $error = $this->getParameterHolder()->get('cc_error_type');
        return false; 
      }
 
      // Ensure that the user has provided a credit card number
      if (strlen($cardNumber) == 0)
        {
         $error = $this->getParameterHolder()->get('cc_error_missing');
         return false; 
      }
 
      // Remove any non-digits   from the credit card number
      $cardNo = preg_replace('/[^0-9]/', '', $cardNumber);
 
      // Check that the number is numeric and of the right sort of length.
      if (!eregi('^[0-9]{13,19}$',$cardNo))
        {
         $error = $this->getParameterHolder()->get('cc_error_format');
         return false; 
      }
 
      // Now check the modulus 10 check digit - if required
      if (self::$CARDS[$cardType]['checkdigit'])
        {
        $checksum = 0;   // running checksum total
        $mychar = "";    // next char to process
        $j = 1;          // takes value of 1 or 2
 
        // Process each digit one by one starting at the right
        for ($i = strlen($cardNo) - 1; $i >= 0; $i--) 
            {
          // Extract the next digit and multiply by 1 or 2 on alternative digits.      
          $calc = $cardNo{$i} * $j;
 
          // If the result is in two digits add 1 to the checksum total
          if ($calc > 9) {
            $checksum = $checksum + 1;
            $calc = $calc - 10;
          }
 
          // Add the units element to the checksum total
          $checksum = $checksum + $calc;
 
          // Switch the value of j
          if ($j ==1) {$j = 2;} else {$j = 1;};
        } 
 
        // All done - if checksum is divisible by 10, it is a valid modulus 10.
        // If not, report an error.
        if ($checksum % 10 != 0)
            {
            $error = $this->getParameterHolder()->get('cc_error_number');
            return false; 
        }
      }  
 
      // The following are the card-specific checks we undertake.
 
      // Load an array with the valid prefixes for this card
      $prefix = split(',',self::$CARDS[$cardType]['prefixes']);
 
      // Now see if any of them match what we have in the card number  
      $prefixValid = false; 
      for ($i=0; $i<sizeof($prefix); $i++)
        {
        $exp = '^' . $prefix[$i];
        if (ereg($exp,$cardNo))
            {
          $prefixValid = true;
          break;
        }
      }
 
      // If it isn't a valid prefix there's no point at looking at the length
      if (!$prefixValid)
        {
         $error = $this->getParameterHolder()->get('cc_error_number');
         return false; 
      }
 
      // See if the length is valid for this card
      $lengthValid = false;
      $lengths = split(',',self::$CARDS[$cardType]['length']);
      for ($j=0; $j<sizeof($lengths); $j++)
        {
        if (strlen($cardNo) == $lengths[$j])
            {
          $lengthValid = true;
          break;
        }
      }
 
      // See if all is OK by seeing if the length was valid. 
      if (!$lengthValid)
        {
         $error = $this->getParameterHolder()->get('cc_error_length');
         return false; 
      };   
 
      // The credit card is in the required format.
      return true;
  }
}
by scott meves on 2007-04-08, tagged cc  creditcard  validation  validator 

Credit Card validator

sfValidator extension based on Credit Card Validator code by Harish Chauhan (from phpclasses.org). With this extension you can validate this type of credit cards: VISA, MASTERCARD, DISCOVER, AMEX, DINERS,JCB, Australian Bankcard, EnRoute And Switch Solo.

IMHO isn't a bad idea to include anything like this in standard sfValidator code. While here you have the source code of my adaptation of credit cards validator.

<?php
 
/*
 
Symfony integration as sfValidator of CCVAL
Date - Jun 17, 2006
Author - Oriol Rius (oriol@joor.net)
 
Credit CArd Validator
Date - Jan 14, 2005
Author - Harish Chauhan
 
 
ABOUT
This PHP script will calidate credit cards by checking there length
and pattern and checksum using mod 10.
 
Supported credit cards are VISA, MASTERCARD, DISCOVER, AMEX, DINERS,
JCB, Australian Bankcard, EnRoute And Switch Solo.
*/
 
class CCVAL extends sfValidator
{
 
    public function execute (&$value, &$error)
    {       
        // Recuperamos parámetros validar
        $num_param = $this->getParameterHolder()->get('num');
        $num = $this->getContext()->getRequest()->getParameter($num_param);
        $tipo_param = $this->getParameterHolder()->get('tipo');
        $tipo = $this->getContext()->getRequest()->getParameter($tipo_param);
 
        // Lanzamos la validación
        $validada=$this->_isVAlidCreditCard($num,$tipo,false);
 
        // Informamos de como ha ido la validación
        sfContext::getInstance()->getLogger()->info("CCVAL.class.php: Tipo: ".$tipo." Num: ".$num." Validada: ".$validada);             
 
        if ($validada==false)
        {
            $error = $this->getParameterHolder()->get('error');
            return false;
        }
        return true;
    }
 
    public function initialize ($context, $parameters = null)
    {
        // initialize parent
        parent::initialize($context);
 
        $this->getParameterHolder()->add($parameters);
 
        return true;
    }
 
    /**
     * Testing checksum
     *
     * @param integer $ccnum
     * @return boolean
     */
    private function _checkSum($ccnum)
    {
        $checksum = 0;
        for ($i=(2-(strlen($ccnum) % 2)); $i<=strlen($ccnum); $i+=2)
        {
            $checksum += (int)($ccnum{$i-1});
        }
        // Analyze odd digits in even length strings or even digits in odd length strings.
        for ($i=(strlen($ccnum)% 2) + 1; $i<strlen($ccnum); $i+=2)
        {
            $digit = (int)($ccnum{$i-1}) * 2;
            if ($digit < 10)
            { $checksum += $digit; }
            else
            { $checksum += ($digit-9); }
        }
        if (($checksum % 10) == 0)
        return true;
        else
        return false;
 
    }
 
    /**
     * Launch validation
     *
     * @param integer $ccnum
     * @param string $type
     * @param boolean $returnobj
     * @return boolean
     */
    private function _isVAlidCreditCard($ccnum,$type="",$returnobj=false)
    {
        $creditcard=array(  "visa"=>"/^4\d{3}-?\d{4}-?\d{4}-?\d{4}$/",
        "mastercard"=>"/^5[1-5]\d{2}-?\d{4}-?\d{4}-?\d{4}$/",
        "discover"=>"/^6011-?\d{4}-?\d{4}-?\d{4}$/",
        "amex"=>"/^3[4,7]\d{13}$/",
        "diners"=>"/^3[0,6,8]\d{12}$/",
        "bankcard"=>"/^5610-?\d{4}-?\d{4}-?\d{4}$/",
        "jcb"=>"/^[3088|3096|3112|3158|3337|3528]\d{12}$/",
        "enroute"=>"/^[2014|2149]\d{11}$/",
        "switch"=>"/^[4903|4911|4936|5641|6333|6759|6334|6767]\d{12}$/");
        if(empty($type))
        {
            $match=false;
            foreach($creditcard as $type=>$pattern)
            if(preg_match($pattern,$ccnum)==1)
            {
                $match=true;
                break;
            }
 
            if(!$match)
            return false;
            else
            {
                if($returnobj)
                {
                    $return=new stdclass;
                    $return->valid=$this->_checkSum($ccnum);
                    $return->ccnum=$ccnum;
                    $return->type=$type;
                    return $return;
                }
                else
                return $this->_checkSum($ccnum);
            }
 
        }
        else
        {
            if(@preg_match($creditcard[strtolower(trim($type))],$ccnum)==0)
            return false;
            else
            {
                if($returnobj)
                {
                    $return=new stdclass;
                    $return->valid=$this->_checkSum($ccnum);
                    $return->ccnum=$ccnum;
                    $return->type=$type;
                    return $return;
                }
                else
                return $this->_checkSum($ccnum);
            }
        }
    }
}
?>

An example of how you can call CCVAL validator from validate yml file:

methods:
  post: [ntarjeta]
names:
  ntarjeta:
    required:     Yes
    required_msg: Credit Card number is required
    validators:   validarCC
 
validarCC:
    class:       CCVAL
    param:
      num:       ntarjeta
      tipo:      tipoCC
      error:     Your credit card number is invalid

Sorry for my poor english.

by Oriol Rius on 2006-06-24, tagged cc  validation  validator 

unset all not needed form widgets

Common Probem.

You have a large and small form. For example a contact form.

Form orginal includes 50 fields and your small form is extend form the orginal form and includes 15 fields. normaly you have to unset very 35 field in the small one.

But you could also write this lines:

class ContactSimpleForm extends ContactForm
{
  public function configure()
  {
 
    parent::configure();
 
    $this->wantedFields = array(
        'firstname',
        'lastname',
    );
 
    foreach ($this as $fieldName => $widget){
        if (!in_array($fieldName, $this->wantedFields)){
            unset($this->widgetSchema[$fieldName]);
            unset($this->validatorSchema[$fieldName]);
        }
    }
}
 
by excessive demon on 2009-09-18, tagged form  unset  validator  widget 
(3 comments)

Password Strength validator

This its a password strength validator, with ajax request for checking the password field.

First create a validator in lib/validators/sfPasswordStrengthValidator.class.php

<?php
class sfPasswordStrengthValidator extends sfValidator
{
    public function execute (&$value, &$error)
    {
        $weakness = $this->Password_Strength($value);
 
        if($weakness==1) {
            $error = $this->getParameter('strength_error');
            return false;
        }
 
        return $weakness;
    }
 
    public function initialize ($context, $parameters = null)
    {
        // Initialize parent
        parent::initialize($context);
 
        // Set default parameters value
        $this->setParameter('strength_error', 'Weak password');
 
        // Set parameters
        $this->getParameterHolder()->add($parameters);
 
        return true;
    }
        // Thanks for: Alix Axel Weblog
    // URL: http://www.alixaxel.com/wordpress/wp-content/2007/06/Password_Strength.phps
    function Password_Strength($password, $username = null)
    {
        if (!empty($username))
        {
            $password = str_replace($username, '', $password);
        }
 
        $strength = 0;
        $password_length = strlen($password);
 
        if ($password_length < 5)
        {
            return $strength;
        }
 
        else
        {
            $strength = $password_length * 4;
        }
 
        for ($i = 2; $i <= 4; $i++)
        {
            $temp = str_split($password, $i);
 
            $strength -= (ceil($password_length / $i) - count(array_unique($temp)));
        }
 
        preg_match_all('/[0-9]/', $password, $numbers);
 
        if (!empty($numbers))
        {
            $numbers = count($numbers[0]);
 
            if ($numbers >= 3)
            {
                $strength += 5;
            }
        }
 
        else
        {
            $numbers = 0;
        }
 
        preg_match_all('/[|!@#$%&*\/=?,;.:\-_+~^¨\\\]/', $password, $symbols);
 
        if (!empty($symbols))
        {
            $symbols = count($symbols[0]);
 
            if ($symbols >= 2)
            {
                $strength += 5;
            }
        }
 
        else
        {
            $symbols = 0;
        }
 
        preg_match_all('/[a-z]/', $password, $lowercase_characters);
        preg_match_all('/[A-Z]/', $password, $uppercase_characters);
 
        if (!empty($lowercase_characters))
        {
            $lowercase_characters = count($lowercase_characters[0]);
        }
 
        else
        {
            $lowercase_characters = 0;
        }
 
        if (!empty($uppercase_characters))
        {
            $uppercase_characters = count($uppercase_characters[0]);
        }
 
        else
        {
            $uppercase_characters = 0;
        }
 
        if (($lowercase_characters > 0) && ($uppercase_characters > 0))
        {
            $strength += 10;
        }
 
        $characters = $lowercase_characters + $uppercase_characters;
 
        if (($numbers > 0) && ($symbols > 0))
        {
            $strength += 15;
        }
 
        if (($numbers > 0) && ($characters > 0))
        {
            $strength += 15;
        }
 
        if (($symbols > 0) && ($characters > 0))
        {
            $strength += 15;
        }
 
        if (($numbers == 0) && ($symbols == 0))
        {
            $strength -= 10;
        }
 
        if (($symbols == 0) && ($characters == 0))
        {
            $strength -= 10;
        }
 
        if ($strength < 0)
        {
            $strength = 0;
        }
 
        if ($strength > 100)
        {
            $strength = 100;
        }
 
        return $strength;
    }
}
 

Then in your view template use:

<?php echo observe_field('password', array(
                          'update'   => 'passwordStatus',
                          'url'      => 'sfGuardAuth/checkPasswordStrength',
                          'with' => "'id='+encodeURIComponent($('password').value)",
                          'loading'=>"Element.show('indicator_passwordstatus')",
                          'complete'=>"Element.hide('indicator_passwordstatus');Element.show('passwordStatus');",
                      )) ?>
 

That code will monitor changes at password input field, and submit the updated value to the defined route.

Then add the following to your actions file:

public function executeCheckPasswordStrength() {
        $password = $this->getRequestParameter('id');
        $strengthValidator = new sfPasswordStrengthValidator();
        $strengthValidator->initialize($this->getContext());
        $error='none';
        $score = $strengthValidator->execute($password,$error);
        if(!$score)
        return $this->renderText('too short');
        if($score < 20) {
            return $this->renderText('not weak');
        } else if($score < 50) {
            return $this->renderText('relevant');
        } else {
            return $this->renderText('strong');
        }
 
        return true;
    }
 

Cheers

by Lucas Peres on 2007-10-05, tagged validation  validator 

Validator for latitude / longitude

<?php
/**
 * This validator accepts either a four-member array such as
 * array( 'deg' => 12, 'min' => 34, 'sec' => 56, 'dir' => 'N' )
 * or a numeric value such as 12.3456
 */
class ynValidatorLatLong extends sfValidatorBase
{
  protected function configure($options = array(), $messages = array())
  {
    parent::configure( $options, $messages );
 
    $this->addRequiredOption('axis');  // 'latitude' || 'longitude'
 
    $this->addMessage('invalid_axis', 'The selected axis is invalid.');
  }
 
  protected function doClean( $value )
  {
    $max = 0;
 
    switch ( $this->getOption('axis') ) {
      case 'latitude':
        $max = 90;
        break;
 
      case 'longitude':
        $max = 180;
        break;
 
      default:
        throw new sfValidatorError($this, 'invalid_axis');
    }
 
    if ( is_array( $value ) ) {
      $value = $this->arrayToInt( $value );
    }
 
    if ( ! is_numeric( $value ) ) {
      throw new sfValidatorError($this, 'invalid');
    }
 
    if ( abs( $value ) > $max ) {
      throw new sfValidatorError($this, 'invalid');
    }
 
    return $value;
  }
 
  protected function arrayToInt( array $value )
  {
    if ( count( $value ) != 4 ) {
      throw new sfValidatorError($this, 'invalid');
    }
 
    if (
      ! isset( $value['deg'] )
      || ! isset( $value['min'] )
      || ! isset( $value['sec'] )
      || ! isset( $value['dir'] )
    ) {
      throw new sfValidatorError($this, 'invalid');
    }
 
    if ( preg_match( '/\D/', $value['deg'].$value['min'].$value['sec'] ) ) {
      // non-integer component
      throw new sfValidatorError($this, 'invalid');
    }
 
    $multiplier = 1;
 
    switch ( strtolower( $value['dir'] ) ) {
      case 'n':
        $axis = 'latitude';
        break;
 
      case 's':
        $axis = 'latitude';
        $multiplier = -1;
        break;
 
      case 'e':
        $axis = 'longitude';
        break;
 
      case 'w':
        $axis = 'longitude';
        $multiplier = -1;
        break;
 
      default:
        throw new sfValidatorError($this, 'invalid');
    }
 
    if ( $axis != $this->getOption('axis') ) {
      throw new sfValidatorError($this, 'invalid');
    }
 
    return ( $value['deg'] + $value['min']/60 + $value['sec']/60/60 ) * $multiplier;
  }
}
 
/*
 * UNIT TEST
 */
 
<?php
require_once dirname(__FILE__).'/../../bootstrap/unit.php';
 
$t = new lime_test();
 
$lat_validator = new ynValidatorLatLong( array( 'axis' => 'latitude' ) );
$lon_validator = new ynValidatorLatLong( array( 'axis' => 'longitude' ) );
 
$lat_good = array(
  array( 'deg' => 50, 'min' => 12, 'sec' => 14, 'dir' => 'N' ),
  array( 'deg' => 50, 'min' => 12, 'sec' => 14, 'dir' => 'S' ),
  50.456789,
  50,
  -50.456789,
  -50
);
 
$lat_bad = array(
  'three members' => array( 'deg' => 50, 'min' => 12, 'sec' => 14 ),
  'invalid n/s 1' => array( 'deg' => 50, 'min' => 12, 'sec' => 14, 'dir' => 'F' ),
  'invalid n/s 2' => array( 'deg' => 50, 'min' => 12, 'sec' => 14, 'dir' => 'E' ),
  'non-integer member' => array( 'deg' => 50, 'min' => 12, 'sec' => 14.2, 'dir' => 'N' ),
  'out of range' => array( 'deg' => 100, 'min' => 12, 'sec' => 14, 'dir' => 'N' ),
);
 
$lon_good = array(
  array( 'deg' => 150, 'min' => 12, 'sec' => 14, 'dir' => 'E' ),
  array( 'deg' => 150, 'min' => 12, 'sec' => 14, 'dir' => 'W' ),
  50.456789,
  50,
  -50.456789,
  -50
);
 
$lon_bad = array(
  'three members' => array( 'deg' => 150, 'min' => 12, 'sec' => 14 ),
  'invalid e/w 1' => array( 'deg' => 150, 'min' => 12, 'sec' => 14, 'dir' => 'F' ),
  'invalid e/w 2' => array( 'deg' => 150, 'min' => 12, 'sec' => 14, 'dir' => 'N' ),
  'non-integer member' => array( 'deg' => 150, 'min' => 12, 'sec' => 14.2, 'dir' => 'E' ),
  'out of range' => array( 'deg' => 200, 'min' => 12, 'sec' => 14, 'dir' => 'E' ),
);
 
foreach ( $lat_good as $value ) {
  try {
    $clean = $lat_validator->clean( $value );
    $t->ok( is_numeric( $clean ), 'is numeric' );
 
    if ( is_array( $value ) ) {
      switch( $value['dir'] ) {
        case 'E':
          $multiplier = 1;
          break;
        case 'W':
          $multiplier = -1;
          break;
      }
 
      $t->ok( abs($clean) >= $value['deg'] && abs($clean) <= $value['deg']+1, "value $clean makes sense" );
      $t->ok( $clean * $multipler >= 0, 'direction correct' );
    }
    else {
      $t->is( $value, $clean, 'value makes sense' );
    }
  }
  catch( sfValidatorError $e ) {
    $t->fail( $e->getMessage() );
  }
}
 
foreach ( $lat_bad as $message => $value ) {
  try {
    $lat_validator->clean( $value );
    $t->fail( $message );
  }
  catch( sfValidatorError $e ) {
    $t->pass( $message );
  }
}
 
foreach ( $lon_good as $value ) {
  try {
    $clean = $lon_validator->clean( $value );
    $t->ok( is_numeric( $clean ), 'is numeric' );
 
    if ( is_array( $value ) ) {
      switch( $value['dir'] ) {
        case 'E':
          $multiplier = 1;
          break;
        case 'W':
          $multiplier = -1;
          break;
      }
 
      $t->ok( abs($clean) >= $value['deg'] && abs($clean) <= $value['deg']+1, "value $clean makes sense" );
      $t->ok( $clean * $multipler >= 0, 'direction correct' );
    }
    else {
      $t->is( $value, $clean, 'value makes sense' );
    }
  }
  catch( sfValidatorError $e ) {
    $t->fail( $e->getMessage() );
  }
}
 
foreach ( $lon_bad as $message => $value ) {
  try {
    $lon_validator->clean( $value );
    $t->fail( $message );
  }
  catch( sfValidatorError $e ) {
    $t->pass( $message );
  }
}
 
by yitznewton on 2011-02-27, tagged coordinates  form  latitude  longitude  validator 

sfValidatorDomain - a smart validator for domain names - it knows 2 and 3 levels TLDs

class sfValidatorDomain extends sfValidatorBase
{
 
  /**
   * @see sfValidatorBase
   */
  protected function doClean($value)
  {
    $value = preg_replace('!^https?:\/\/!', '' , $value);
    $parsed_url = parse_url(trim('http://'.$value));
    $host = $parsed_url['host'];
    if((strpos($host, '.') === false) || preg_match('/\.$/', $host) || preg_match('/^\./', $host)  )
    {
      throw new sfValidatorError($this, 'Invalid host', array('value' => $value));
    }
 
    $levels = 2;
    foreach(self::$twoLevelTlds as $tld)
    {
      if($tld == $host)
      {
        throw new sfValidatorError($this, 'Invalid host', array('value' => $value));
      }
        if(preg_match('/\.'.$tld.'$/', $host))
      {
        $levels = 3;
        break;
      }
    };
    $parts = explode('.', $host);
    $parts = array_slice($parts, count($parts) - $levels, $levels);
 
    return strtolower(implode('.', $parts));
  }
 
  private static $twoLevelTlds = array(
        '2000.hu',
        'ab.ca',
        'ab.se',
        'abo.pa',
        'ac.ae',
        'ac.am',
        'ac.at',
        'ac.bd',
        'ac.be',
        'ac.cn',
        'ac.com',
        'ac.cr',
        'ac.cy',
        'ac.fj',
        'ac.fk',
        'ac.gg',
        'ac.gn',
        'ac.hu',
        'ac.id',
        'ac.il',
        'ac.im',
        'ac.in',
        'ac.ir',
        'ac.je',
        'ac.jp',
        'ac.ke',
        'ac.kr',
        'ac.lk',
        'ac.ma',
        'ac.me',
        'ac.mw',
        'ac.ng',
        'ac.nz',
        'ac.om',
        'ac.pa',
        'ac.pg',
        'ac.rs',
        'ac.ru',
        'ac.rw',
        'ac.se',
        'ac.th',
        'ac.tj',
        'ac.tz',
        'ac.ug',
        'ac.uk',
        'ac.vn',
        'ac.yu',
        'ac.za',
        'ac.zm',
        'ac.zw',
        'act.au',
        'ad.jp',
        'adm.br',
        'adult.ht',
        'adv.br',
        'adygeya.ru',
        'aero.mv',
        'aero.tt',
        'aeroport.fr',
        'agr.br',
        'agrar.hu',
        'agro.pl',
        'ah.cn',
        'aichi.jp',
        'aid.pl',
        'ak.us',
        'akita.jp',
        'al.us',
        'aland.fi',
        'alderney.gg',
        'alt.na',
        'alt.za',
        'altai.ru',
        'am.br',
        'amur.ru',
        'amursk.ru',
        'aomori.jp',
        'ar.us',
        'arkhangelsk.ru',
        'army.mil',
        'arq.br',
        'art.br',
        'art.do',
        'art.dz',
        'art.ht',
        'art.pl',
        'arts.co',
        'arts.ro',
        'arts.ve',
        'asn.au',
        'asn.lv',
        'ass.dz',
        'assedic.fr',
        'assn.lk',
        'asso.dz',
        'asso.fr',
        'asso.gp',
        'asso.ht',
        'asso.mc',
        'asso.re',
        'astrakhan.ru',
        'at.tf',
        'at.tt',
        'atm.pl',
        'ato.br',
        'au.com',
        'au.tt',
        'augustow.pl',
        'auto.pl',
        'av.tr',
        'avocat.fr',
        'avoues.fr',
        'az.us',
        'babia-gora.pl',
        'baikal.ru',
        'barreau.fr',
        'bashkiria.ru',
        'bbs.tr',
        'bc.ca',
        'bd.se',
        'be.tt',
        'bedzin.pl',
        'bel.tr',
        'belgie.be',
        'belgorod.ru',
        'beskidy.pl',
        'bg.tf',
        'bialowieza.pl',
        'bialystok.pl',
        'bib.ve',
        'bielawa.pl',
        'bieszczady.pl',
        'bio.br',
        'bir.ru',
        'biz.az',
        'biz.bh',
        'biz.cy',
        'biz.et',
        'biz.fj',
        'biz.ly',
        'biz.mv',
        'biz.nr',
        'biz.om',
        'biz.pk',
        'biz.pl',
        'biz.pr',
        'biz.tj',
        'biz.tr',
        'biz.tt',
        'biz.ua',
        'biz.vn',
        'bj.cn',
        'bl.uk',
        'bmd.br',
        'boleslawiec.pl',
        'bolt.hu',
        'bourse.za',
        'br.com',
        'brand.se',
        'british-library.uk',
        'bryansk.ru',
        'buryatia.ru',
        'busan.kr',
        'bydgoszcz.pl',
        'bytom.pl',
        'c.se',
        'ca.tf',
        'ca.tt',
        'ca.us',
        'casino.hu',
        'cbg.ru',
        'cc.bh',
        'cci.fr',
        'ch.tf',
        'ch.vu',
        'chambagri.fr',
        'chel.ru',
        'chelyabinsk.ru',
        'cherkassy.ua',
        'chernigov.ua',
        'chernovtsy.ua',
        'chiba.jp',
        'chirurgiens-dentistes.fr',
        'chita.ru',
        'chukotka.ru',
        'chungbuk.kr',
        'chungnam.kr',
        'chuvashia.ru',
        'cieszyn.pl',
        'cim.br',
        'city.hu',
        'city.za',
        'ck.ua',
        'club.tw',
        'cmw.ru',
        'cn.com',
        'cn.ua',
        'cng.br',
        'cnt.br',
        'co.ae',
        'co.ag',
        'co.am',
        'co.ao',
        'co.at',
        'co.ba',
        'co.bw',
        'co.ck',
        'co.cr',
        'co.dk',
        'co.ee',
        'co.fk',
        'co.gg',
        'co.hu',
        'co.id',
        'co.il',
        'co.im',
        'co.in',
        'co.ir',
        'co.je',
        'co.jp',
        'co.ke',
        'co.kr',
        'co.ls',
        'co.ma',
        'co.me',
        'co.mu',
        'co.mw',
        'co.mz',
        'co.nz',
        'co.om',
        'co.rs',
        'co.rw',
        'co.st',
        'co.th',
        'co.tj',
        'co.tt',
        'co.tv',
        'co.tz',
        'co.ua',
        'co.ug',
        'co.uk',
        'co.us',
        'co.uz',
        'co.ve',
        'co.vi',
        'co.yu',
        'co.za',
        'co.zm',
        'co.zw',
        'com.ac',
        'com.ae',
        'com.af',
        'com.ag',
        'com.ai',
        'com.al',
        'com.am',
        'com.an',
        'com.ar',
        'com.au',
        'com.aw',
        'com.az',
        'com.ba',
        'com.bb',
        'com.bd',
        'com.bh',
        'com.bm',
        'com.bn',
        'com.bo',
        'com.br',
        'com.bs',
        'com.bt',
        'com.bz',
        'com.cd',
        'com.ch',
        'com.cn',
        'com.co',
        'com.cu',
        'com.cy',
        'com.dm',
        'com.do',
        'com.dz',
        'com.ec',
        'com.ee',
        'com.eg',
        'com.er',
        'com.es',
        'com.et',
        'com.fj',
        'com.fk',
        'com.fr',
        'com.ge',
        'com.gh',
        'com.gi',
        'com.gn',
        'com.gp',
        'com.gr',
        'com.gt',
        'com.gu',
        'com.hk',
        'com.hn',
        'com.hr',
        'com.ht',
        'com.io',
        'com.jm',
        'com.jo',
        'com.kg',
        'com.kh',
        'com.ki',
        'com.kw',
        'com.ky',
        'com.kz',
        'com.la',
        'com.lb',
        'com.lc',
        'com.li',
        'com.lk',
        'com.lr',
        'com.lv',
        'com.ly',
        'com.mg',
        'com.mk',
        'com.mm',
        'com.mn',
        'com.mo',
        'com.mt',
        'com.mu',
        'com.mv',
        'com.mw',
        'com.mx',
        'com.my',
        'com.na',
        'com.nc',
        'com.nf',
        'com.ng',
        'com.ni',
        'com.np',
        'com.nr',
        'com.om',
        'com.pa',
        'com.pe',
        'com.pf',
        'com.pg',
        'com.ph',
        'com.pk',
        'com.pl',
        'com.pr',
        'com.ps',
        'com.pt',
        'com.py',
        'com.qa',
        'com.re',
        'com.ro',
        'com.ru',
        'com.rw',
        'com.sa',
        'com.sb',
        'com.sc',
        'com.sd',
        'com.sg',
        'com.sh',
        'com.st',
        'com.sv',
        'com.sy',
        'com.tj',
        'com.tn',
        'com.tr',
        'com.tt',
        'com.tw',
        'com.ua',
        'com.uy',
        'com.uz',
        'com.vc',
        'com.ve',
        'com.vi',
        'com.vn',
        'com.vu',
        'com.ws',
        'com.ye',
        'conf.au',
        'conf.lv',
        'consulado.st',
        'coop.br',
        'coop.ht',
        'coop.mv',
        'coop.mw',
        'coop.tt',
        'cpa.pro',
        'cq.cn',
        'cri.nz',
        'crimea.ua',
        'csiro.au',
        'ct.us',
        'cul.na',
        'cv.ua',
        'cz.tf',
        'czeladz.pl',
        'czest.pl',
        'd.se',
        'daegu.kr',
        'daejeon.kr',
        'dagestan.ru',
        'dc.us',
        'de.com',
        'de.net',
        'de.tf',
        'de.tt',
        'de.us',
        'de.vu',
        'dk.org',
        'dk.tt',
        'dlugoleka.pl',
        'dn.ua',
        'dnepropetrovsk.ua',
        'dni.us',
        'dns.be',
        'donetsk.ua',
        'dp.ua',
        'dpn.br',
        'dr.tr',
        'dudinka.ru',
        'e-burg.ru',
        'e.se',
        'e12.ve',
        'e164.arpa',
        'ebiz.tw',
        'ecn.br',
        'ed.ao',
        'ed.cr',
        'ed.jp',
        'edu.ac',
        'edu.af',
        'edu.ai',
        'edu.al',
        'edu.am',
        'edu.an',
        'edu.ar',
        'edu.au',
        'edu.az',
        'edu.ba',
        'edu.bb',
        'edu.bd',
        'edu.bh',
        'edu.bm',
        'edu.bn',
        'edu.bo',
        'edu.br',
        'edu.bt',
        'edu.ck',
        'edu.cn',
        'edu.co',
        'edu.cu',
        'edu.dm',
        'edu.do',
        'edu.dz',
        'edu.ec',
        'edu.ee',
        'edu.eg',
        'edu.er',
        'edu.es',
        'edu.et',
        'edu.ge',
        'edu.gh',
        'edu.gi',
        'edu.gp',
        'edu.gr',
        'edu.gt',
        'edu.gu',
        'edu.hk',
        'edu.hn',
        'edu.ht',
        'edu.hu',
        'edu.in',
        'edu.it',
        'edu.jm',
        'edu.jo',
        'edu.kg',
        'edu.kh',
        'edu.kw',
        'edu.ky',
        'edu.kz',
        'edu.lb',
        'edu.lc',
        'edu.lk',
        'edu.lr',
        'edu.lv',
        'edu.ly',
        'edu.me',
        'edu.mg',
        'edu.mm',
        'edu.mn',
        'edu.mo',
        'edu.mt',
        'edu.mv',
        'edu.mw',
        'edu.mx',
        'edu.my',
        'edu.na',
        'edu.ng',
        'edu.ni',
        'edu.np',
        'edu.nr',
        'edu.om',
        'edu.pa',
        'edu.pe',
        'edu.pf',
        'edu.ph',
        'edu.pk',
        'edu.pl',
        'edu.pr',
        'edu.ps',
        'edu.pt',
        'edu.py',
        'edu.qa',
        'edu.rs',
        'edu.ru',
        'edu.rw',
        'edu.sa',
        'edu.sb',
        'edu.sc',
        'edu.sd',
        'edu.sg',
        'edu.sh',
        'edu.sk',
        'edu.st',
        'edu.sv',
        'edu.tf',
        'edu.tj',
        'edu.tr',
        'edu.tt',
        'edu.tw',
        'edu.ua',
        'edu.uk',
        'edu.uy',
        'edu.ve',
        'edu.vi',
        'edu.vn',
        'edu.vu',
        'edu.ws',
        'edu.ye',
        'edu.yu',
        'edu.za',
        'edunet.tn',
        'ehime.jp',
        'ekloges.cy',
        'elblag.pl',
        'elk.pl',
        'embaixada.st',
        'eng.br',
        'ens.tn',
        'ernet.in',
        'erotica.hu',
        'erotika.hu',
        'es.kr',
        'es.tt',
        'esp.br',
        'etc.br',
        'eti.br',
        'eu.com',
        'eu.org',
        'eu.tf',
        'eu.tt',
        'eun.eg',
        'experts-comptables.fr',
        'f.se',
        'fam.pk',
        'far.br',
        'fareast.ru',
        'fax.nr',
        'fed.us',
        'fgov.be',
        'fh.se',
        'fhs.no',
        'fhsk.se',
        'fhv.se',
        'fi.cr',
        'fie.ee',
        'film.hu',
        'fin.ec',
        'fin.tn',
        'firm.co',
        'firm.ht',
        'firm.in',
        'firm.ro',
        'firm.ve',
        'fj.cn',
        'fl.us',
        'fm.br',
        'fnd.br',
        'folkebibl.no',
        'forum.hu',
        'fot.br',
        'fr.tt',
        'fr.vu',
        'from.hr',
        'fst.br',
        'fukui.jp',
        'fukuoka.jp',
        'fukushima.jp',
        'fylkesbibl.no',
        'g.se',
        'g12.br',
        'ga.us',
        'game.tw',
        'games.hu',
        'gangwon.kr',
        'gb.com',
        'gb.net',
        'gbr.me',
        'gc.ca',
        'gd.cn',
        'gda.pl',
        'gdansk.pl',
        'geek.nz',
        'gen.in',
        'gen.nz',
        'gen.tr',
        'geometre-expert.fr',
        'ggf.br',
        'gifu.jp',
        'glogow.pl',
        'gmina.pl',
        'gniezno.pl',
        'go.cr',
        'go.id',
        'go.jp',
        'go.ke',
        'go.kr',
        'go.th',
        'go.tj',
        'go.tz',
        'go.ug',
        'gob.bo',
        'gob.do',
        'gob.es',
        'gob.gt',
        'gob.hn',
        'gob.mx',
        'gob.ni',
        'gob.pa',
        'gob.pe',
        'gob.pk',
        'gob.sv',
        'gob.ve',
        'gok.pk',
        'gon.pk',
        'gop.pk',
        'gorlice.pl',
        'gos.pk',
        'gouv.fr',
        'gouv.ht',
        'gouv.rw',
        'gov.ac',
        'gov.ae',
        'gov.af',
        'gov.ai',
        'gov.al',
        'gov.am',
        'gov.ar',
        'gov.au',
        'gov.az',
        'gov.ba',
        'gov.bb',
        'gov.bd',
        'gov.bf',
        'gov.bh',
        'gov.bm',
        'gov.bo',
        'gov.br',
        'gov.bt',
        'gov.by',
        'gov.ch',
        'gov.ck',
        'gov.cn',
        'gov.co',
        'gov.cu',
        'gov.cx',
        'gov.cy',
        'gov.dm',
        'gov.do',
        'gov.dz',
        'gov.ec',
        'gov.eg',
        'gov.er',
        'gov.et',
        'gov.fj',
        'gov.fk',
        'gov.ge',
        'gov.gg',
        'gov.gh',
        'gov.gi',
        'gov.gn',
        'gov.gr',
        'gov.gu',
        'gov.hk',
        'gov.hu',
        'gov.ie',
        'gov.il',
        'gov.im',
        'gov.in',
        'gov.io',
        'gov.ir',
        'gov.it',
        'gov.je',
        'gov.jm',
        'gov.jo',
        'gov.jp',
        'gov.kg',
        'gov.kh',
        'gov.kw',
        'gov.ky',
        'gov.kz',
        'gov.lb',
        'gov.lc',
        'gov.li',
        'gov.lk',
        'gov.lr',
        'gov.lt',
        'gov.lu',
        'gov.lv',
        'gov.ly',
        'gov.ma',
        'gov.me',
        'gov.mg',
        'gov.mm',
        'gov.mn',
        'gov.mo',
        'gov.mt',
        'gov.mv',
        'gov.mw',
        'gov.my',
        'gov.ng',
        'gov.np',
        'gov.nr',
        'gov.om',
        'gov.ph',
        'gov.pk',
        'gov.pl',
        'gov.pr',
        'gov.ps',
        'gov.pt',
        'gov.py',
        'gov.qa',
        'gov.rs',
        'gov.ru',
        'gov.rw',
        'gov.sa',
        'gov.sb',
        'gov.sc',
        'gov.sd',
        'gov.sg',
        'gov.sh',
        'gov.sk',
        'gov.st',
        'gov.sy',
        'gov.tj',
        'gov.tn',
        'gov.to',
        'gov.tp',
        'gov.tr',
        'gov.tt',
        'gov.tv',
        'gov.tw',
        'gov.ua',
        'gov.uk',
        'gov.ve',
        'gov.vi',
        'gov.vn',
        'gov.ws',
        'gov.ye',
        'gov.za',
        'gov.zm',
        'gov.zw',
        'govt.nz',
        'gr.jp',
        'grajewo.pl',
        'greta.fr',
        'grozny.ru',
        'grp.lk',
        'gs.cn',
        'gsm.pl',
        'gub.uy',
        'guernsey.gg',
        'gunma.jp',
        'gv.ao',
        'gv.at',
        'gwangju.kr',
        'gx.cn',
        'gyeongbuk.kr',
        'gyeonggi.kr',
        'gyeongnam.kr',
        'gz.cn',
        'h.se',
        'ha.cn',
        'hb.cn',
        'he.cn',
        'health.vn',
        'herad.no',
        'hi.cn',
        'hi.us',
        'hiroshima.jp',
        'hk.cn',
        'hl.cn',
        'hn.cn',
        'hokkaido.jp',
        'hotel.hu',
        'hotel.lk',
        'hs.kr',
        'hu.com',
        'huissier-justice.fr',
        'hyogo.jp',
        'i.se',
        'ia.us',
        'ibaraki.jp',
        'icnet.uk',
        'id.au',
        'id.fj',
        'id.ir',
        'id.lv',
        'id.ly',
        'id.us',
        'idf.il',
        'idn.sg',
        'idrett.no',
        'idv.hk',
        'idv.tw',
        'if.ua',
        'il.us',
        'ilawa.pl',
        'imb.br',
        'in-addr.arpa',
        'in.rs',
        'in.th',
        'in.ua',
        'in.us',
        'incheon.kr',
        'ind.br',
        'ind.er',
        'ind.gg',
        'ind.gt',
        'ind.in',
        'ind.je',
        'ind.tn',
        'inf.br',
        'inf.cu',
        'info.au',
        'info.az',
        'info.bh',
        'info.co',
        'info.cu',
        'info.cy',
        'info.ec',
        'info.et',
        'info.fj',
        'info.ht',
        'info.hu',
        'info.mv',
        'info.nr',
        'info.pl',
        'info.pr',
        'info.ro',
        'info.sd',
        'info.tn',
        'info.tr',
        'info.tt',
        'info.ve',
        'info.vn',
        'ing.pa',
        'ingatlan.hu',
        'inima.al',
        'int.am',
        'int.ar',
        'int.az',
        'int.bo',
        'int.co',
        'int.lk',
        'int.mv',
        'int.mw',
        'int.pt',
        'int.ru',
        'int.rw',
        'int.tf',
        'int.tj',
        'int.tt',
        'int.ve',
        'int.vn',
        'intl.tn',
        'ip6.arpa',
        'iris.arpa',
        'irkutsk.ru',
        'isa.us',
        'ishikawa.jp',
        'isla.pr',
        'it.ao',
        'it.tt',
        'its.me',
        'ivano-frankivsk.ua',
        'ivanovo.ru',
        'iwate.jp',
        'iwi.nz',
        'iz.hr',
        'izhevsk.ru',
        'jamal.ru',
        'jar.ru',
        'jaworzno.pl',
        'jeju.kr',
        'jelenia-gora.pl',
        'jeonbuk.kr',
        'jeonnam.kr',
        'jersey.je',
        'jet.uk',
        'jgora.pl',
        'jl.cn',
        'jobs.tt',
        'jogasz.hu',
        'jor.br',
        'joshkar-ola.ru',
        'js.cn',
        'jx.cn',
        'k-uralsk.ru',
        'k.se',
        'k12.ec',
        'k12.il',
        'k12.tr',
        'kagawa.jp',
        'kagoshima.jp',
        'kalisz.pl',
        'kalmykia.ru',
        'kaluga.ru',
        'kamchatka.ru',
        'kanagawa.jp',
        'kanazawa.jp',
        'karelia.ru',
        'karpacz.pl',
        'kartuzy.pl',
        'kaszuby.pl',
        'katowice.pl',
        'kawasaki.jp',
        'kazan.ru',
        'kazimierz-dolny.pl',
        'kchr.ru',
        'kemerovo.ru',
        'kepno.pl',
        'ketrzyn.pl',
        'kg.kr',
        'kh.ua',
        'khabarovsk.ru',
        'khakassia.ru',
        'kharkov.ua',
        'kherson.ua',
        'khmelnitskiy.ua',
        'khv.ru',
        'kids.us',
        'kiev.ua',
        'kirov.ru',
        'kirovograd.ua',
        'kitakyushu.jp',
        'klodzko.pl',
        'km.ua',
        'kms.ru',
        'kobe.jp',
        'kobierzyce.pl',
        'kochi.jp',
        'koenig.ru',
        'kolobrzeg.pl',
        'komforb.se',
        'komi.ru',
        'kommunalforbund.se',
        'kommune.no',
        'komvux.se',
        'konin.pl',
        'konskowola.pl',
        'konyvelo.hu',
        'kostroma.ru',
        'kr.ua',
        'krakow.pl',
        'krasnoyarsk.ru',
        'ks.ua',
        'ks.us',
        'kuban.ru',
        'kumamoto.jp',
        'kurgan.ru',
        'kursk.ru',
        'kustanai.ru',
        'kutno.pl',
        'kuzbass.ru',
        'kv.ua',
        'ky.us',
        'kyonggi.kr',
        'kyoto.jp',
        'la.us',
        'lakas.hu',
        'lanarb.se',
        'lanbib.se',
        'lapy.pl',
        'law.pro',
        'law.za',
        'lebork.pl',
        'legnica.pl',
        'lel.br',
        'lezajsk.pl',
        'lg.jp',
        'lg.ua',
        'limanowa.pl',
        'lipetsk.ru',
        'lkd.co.im',
        'ln.cn',
        'lodz.pl',
        'lomza.pl',
        'lowicz.pl',
        'ltd.co.im',
        'ltd.cy',
        'ltd.gg',
        'ltd.gi',
        'ltd.je',
        'ltd.lk',
        'ltd.uk',
        'lubin.pl',
        'lublin.pl',
        'lugansk.ua',
        'lukow.pl',
        'lutsk.ua',
        'lviv.ua',
        'm.se',
        'ma.us',
        'magadan.ru',
        'magnitka.ru',
        'mail.pl',
        'malbork.pl',
        'malopolska.pl',
        'maori.nz',
        'mari-el.ru',
        'mari.ru',
        'marine.ru',
        'mat.br',
        'matsuyama.jp',
        'mazowsze.pl',
        'mazury.pl',
        'mb.ca',
        'md.us',
        'me.uk',
        'me.us',
        'med.br',
        'med.ec',
        'med.ee',
        'med.ht',
        'med.ly',
        'med.om',
        'med.pa',
        'med.pro',
        'med.sa',
        'med.sd',
        'medecin.fr',
        'media.hu',
        'media.pl',
        'mi.th',
        'mi.us',
        'miasta.pl',
        'mie.jp',
        'mielec.pl',
        'mielno.pl',
        'mil.ac',
        'mil.ae',
        'mil.am',
        'mil.ar',
        'mil.az',
        'mil.ba',
        'mil.bd',
        'mil.bo',
        'mil.br',
        'mil.by',
        'mil.co',
        'mil.do',
        'mil.ec',
        'mil.eg',
        'mil.er',
        'mil.fj',
        'mil.ge',
        'mil.gh',
        'mil.gt',
        'mil.gu',
        'mil.hn',
        'mil.id',
        'mil.in',
        'mil.io',
        'mil.jo',
        'mil.kg',
        'mil.kh',
        'mil.kr',
        'mil.kw',
        'mil.kz',
        'mil.lb',
        'mil.lt',
        'mil.lu',
        'mil.lv',
        'mil.mg',
        'mil.mv',
        'mil.my',
        'mil.no',
        'mil.np',
        'mil.nz',
        'mil.om',
        'mil.pe',
        'mil.ph',
        'mil.pl',
        'mil.ru',
        'mil.rw',
        'mil.se',
        'mil.sh',
        'mil.sk',
        'mil.st',
        'mil.tj',
        'mil.tr',
        'mil.tw',
        'mil.uk',
        'mil.uy',
        'mil.ve',
        'mil.ye',
        'mil.za',
        'miyagi.jp',
        'miyazaki.jp',
        'mk.ua',
        'mn.us',
        'mo.cn',
        'mo.us',
        'mob.nr',
        'mobi.tt',
        'mobil.nr',
        'mobile.nr',
        'mod.gi',
        'mod.om',
        'mod.uk',
        'mordovia.ru',
        'mosreg.ru',
        'mragowo.pl',
        'ms.kr',
        'ms.us',
        'msk.ru',
        'mt.us',
        'muni.il',
        'murmansk.ru',
        'mus.br',
        'museum.mn',
        'museum.mv',
        'museum.mw',
        'museum.no',
        'museum.om',
        'museum.tt',
        'music.mobi',
        'mytis.ru',
        'n.se',
        'nagano.jp',
        'nagasaki.jp',
        'nagoya.jp',
        'nakhodka.ru',
        'naklo.pl',
        'nalchik.ru',
        'name.ae',
        'name.az',
        'name.cy',
        'name.et',
        'name.fj',
        'name.hr',
        'name.mv',
        'name.my',
        'name.pr',
        'name.tj',
        'name.tr',
        'name.tt',
        'name.vn',
        'nara.jp',
        'nat.tn',
        'national-library-scotland.uk',
        'naturbruksgymn.se',
        'navy.mil',
        'nb.ca',
        'nc.us',
        'nd.us',
        'ne.jp',
        'ne.ke',
        'ne.kr',
        'ne.tz',
        'ne.ug',
        'ne.us',
        'nel.uk',
        'net.ac',
        'net.ae',
        'net.af',
        'net.ag',
        'net.ai',
        'net.al',
        'net.am',
        'net.an',
        'net.ar',
        'net.au',
        'net.az',
        'net.ba',
        'net.bb',
        'net.bd',
        'net.bh',
        'net.bm',
        'net.bn',
        'net.bo',
        'net.br',
        'net.bs',
        'net.bt',
        'net.bz',
        'net.cd',
        'net.ch',
        'net.ck',
        'net.cn',
        'net.co',
        'net.cu',
        'net.cy',
        'net.dm',
        'net.do',
        'net.dz',
        'net.ec',
        'net.eg',
        'net.er',
        'net.et',
        'net.fj',
        'net.fk',
        'net.ge',
        'net.gg',
        'net.gn',
        'net.gp',
        'net.gr',
        'net.gt',
        'net.gu',
        'net.hk',
        'net.hn',
        'net.ht',
        'net.id',
        'net.il',
        'net.im',
        'net.in',
        'net.io',
        'net.ir',
        'net.je',
        'net.jm',
        'net.jo',
        'net.jp',
        'net.kg',
        'net.kh',
        'net.ki',
        'net.kw',
        'net.ky',
        'net.kz',
        'net.la',
        'net.lb',
        'net.lc',
        'net.li',
        'net.lk',
        'net.lr',
        'net.lu',
        'net.lv',
        'net.ly',
        'net.ma',
        'net.me',
        'net.mm',
        'net.mo',
        'net.mt',
        'net.mu',
        'net.mv',
        'net.mw',
        'net.mx',
        'net.my',
        'net.na',
        'net.nc',
        'net.nf',
        'net.ng',
        'net.ni',
        'net.np',
        'net.nr',
        'net.nz',
        'net.om',
        'net.pa',
        'net.pe',
        'net.pg',
        'net.ph',
        'net.pk',
        'net.pl',
        'net.pr',
        'net.ps',
        'net.pt',
        'net.py',
        'net.qa',
        'net.ru',
        'net.rw',
        'net.sa',
        'net.sb',
        'net.sc',
        'net.sd',
        'net.sg',
        'net.sh',
        'net.st',
        'net.sy',
        'net.tf',
        'net.th',
        'net.tj',
        'net.tn',
        'net.tr',
        'net.tt',
        'net.tw',
        'net.ua',
        'net.uk',
        'net.uy',
        'net.uz',
        'net.vc',
        'net.ve',
        'net.vi',
        'net.vn',
        'net.vu',
        'net.ws',
        'net.ye',
        'net.za',
        'new.ke',
        'news.hu',
        'nf.ca',
        'ngo.lk',
        'ngo.ph',
        'ngo.pl',
        'ngo.za',
        'nh.us',
        'nhs.uk',
        'nic.im',
        'nic.in',
        'nic.tt',
        'nic.uk',
        'nieruchomosci.pl',
        'niigata.jp',
        'nikolaev.ua',
        'nj.us',
        'nkz.ru',
        'nl.ca',
        'nls.uk',
        'nm.cn',
        'nm.us',
        'nnov.ru',
        'no.com',
        'nom.ad',
        'nom.ag',
        'nom.br',
        'nom.co',
        'nom.es',
        'nom.fk',
        'nom.fr',
        'nom.mg',
        'nom.ni',
        'nom.pa',
        'nom.pe',
        'nom.pl',
        'nom.re',
        'nom.ro',
        'nom.ve',
        'nom.za',
        'nome.pt',
        'norilsk.ru',
        'not.br',
        'notaires.fr',
        'nov.ru',
        'novosibirsk.ru',
        'nowaruda.pl',
        'ns.ca',
        'nsk.ru',
        'nsn.us',
        'nsw.au',
        'nt.au',
        'nt.ca',
        'nt.ro',
        'ntr.br',
        'nu.ca',
        'nui.hu',
        'nv.us',
        'nx.cn',
        'ny.us',
        'nysa.pl',
        'o.se',
        'od.ua',
        'odessa.ua',
        'odo.br',
        'off.ai',
        'og.ao',
        'oh.us',
        'oita.jp',
        'ok.us',
        'okayama.jp',
        'okinawa.jp',
        'olawa.pl',
        'olecko.pl',
        'olkusz.pl',
        'olsztyn.pl',
        'omsk.ru',
        'on.ca',
        'opoczno.pl',
        'opole.pl',
        'or.at',
        'or.cr',
        'or.id',
        'or.jp',
        'or.ke',
        'or.kr',
        'or.th',
        'or.tz',
        'or.ug',
        'or.us',
        'orenburg.ru',
        'org.ac',
        'org.ae',
        'org.ag',
        'org.ai',
        'org.al',
        'org.am',
        'org.an',
        'org.ar',
        'org.au',
        'org.az',
        'org.ba',
        'org.bb',
        'org.bd',
        'org.bh',
        'org.bm',
        'org.bn',
        'org.bo',
        'org.br',
        'org.bs',
        'org.bt',
        'org.bw',
        'org.bz',
        'org.cd',
        'org.ch',
        'org.ck',
        'org.cn',
        'org.co',
        'org.cu',
        'org.cy',
        'org.dm',
        'org.do',
        'org.dz',
        'org.ec',
        'org.ee',
        'org.eg',
        'org.er',
        'org.es',
        'org.et',
        'org.fj',
        'org.fk',
        'org.ge',
        'org.gg',
        'org.gh',
        'org.gi',
        'org.gn',
        'org.gp',
        'org.gr',
        'org.gt',
        'org.gu',
        'org.hk',
        'org.hn',
        'org.ht',
        'org.hu',
        'org.il',
        'org.im',
        'org.in',
        'org.io',
        'org.ir',
        'org.je',
        'org.jm',
        'org.jo',
        'org.jp',
        'org.kg',
        'org.kh',
        'org.ki',
        'org.kw',
        'org.ky',
        'org.kz',
        'org.la',
        'org.lb',
        'org.lc',
        'org.li',
        'org.lk',
        'org.lr',
        'org.ls',
        'org.lu',
        'org.lv',
        'org.ly',
        'org.ma',
        'org.me',
        'org.mg',
        'org.mk',
        'org.mm',
        'org.mn',
        'org.mo',
        'org.mt',
        'org.mu',
        'org.mv',
        'org.mw',
        'org.mx',
        'org.my',
        'org.na',
        'org.nc',
        'org.ng',
        'org.ni',
        'org.np',
        'org.nr',
        'org.nz',
        'org.om',
        'org.pa',
        'org.pe',
        'org.pf',
        'org.ph',
        'org.pk',
        'org.pl',
        'org.pr',
        'org.ps',
        'org.pt',
        'org.py',
        'org.qa',
        'org.ro',
        'org.rs',
        'org.ru',
        'org.sa',
        'org.sb',
        'org.sc',
        'org.sd',
        'org.se',
        'org.sg',
        'org.sh',
        'org.st',
        'org.sv',
        'org.sy',
        'org.tj',
        'org.tn',
        'org.tr',
        'org.tt',
        'org.tw',
        'org.ua',
        'org.uk',
        'org.uy',
        'org.uz',
        'org.vc',
        'org.ve',
        'org.vi',
        'org.vn',
        'org.vu',
        'org.ws',
        'org.ye',
        'org.yu',
        'org.za',
        'org.zm',
        'org.zw',
        'oryol.ru',
        'osaka.jp',
        'oskol.ru',
        'ostroda.pl',
        'ostroleka.pl',
        'ostrowiec.pl',
        'ostrowwlkp.pl',
        'otc.au',
        'oz.au',
        'pa.us',
        'palana.ru',
        'parliament.cy',
        'parliament.uk',
        'parti.se',
        'pb.ao',
        'pc.pl',
        'pe.ca',
        'pe.kr',
        'penza.ru',
        'per.kh',
        'per.sg',
        'perm.ru',
        'perso.ht',
        'pharmacien.fr',
        'pila.pl',
        'pisz.pl',
        'pl.tf',
        'pl.ua',
        'plc.co.im',
        'plc.ly',
        'plc.uk',
        'plo.ps',
        'podhale.pl',
        'podlasie.pl',
        'pol.dz',
        'pol.ht',
        'pol.tr',
        'police.uk',
        'polkowice.pl',
        'poltava.ua',
        'pomorskie.pl',
        'pomorze.pl',
        'port.fr',
        'powiat.pl',
        'poznan.pl',
        'pp.az',
        'pp.ru',
        'pp.se',
        'ppg.br',
        'prd.fr',
        'prd.mg',
        'press.cy',
        'press.ma',
        'press.se',
        'presse.fr',
        'pri.ee',
        'principe.st',
        'priv.at',
        'priv.hu',
        'priv.me',
        'priv.no',
        'priv.pl',
        'pro.ae',
        'pro.br',
        'pro.cy',
        'pro.ec',
        'pro.fj',
        'pro.ht',
        'pro.mv',
        'pro.om',
        'pro.pr',
        'pro.tt',
        'pro.vn',
        'prochowice.pl',
        'pruszkow.pl',
        'przeworsk.pl',
        'psc.br',
        'psi.br',
        'pskov.ru',
        'ptz.ru',
        'pub.sa',
        'publ.pt',
        'pulawy.pl',
        'pvt.ge',
        'pyatigorsk.ru',
        'qc.ca',
        'qc.com',
        'qh.cn',
        'qld.au',
        'qsl.br',
        'radom.pl',
        'rawa-maz.pl',
        're.kr',
        'realestate.pl',
        'rec.br',
        'rec.co',
        'rec.ro',
        'rec.ve',
        'red.sv',
        'reklam.hu',
        'rel.ht',
        'rel.pl',
        'res.in',
        'ri.us',
        'rnd.ru',
        'rnrt.tn',
        'rns.tn',
        'rnu.tn',
        'rovno.ua',
        'rs.ba',
        'ru.com',
        'ru.tf',
        'rubtsovsk.ru',
        'rv.ua',
        'ryazan.ru',
        'rybnik.pl',
        'rzeszow.pl',
        's.se',
        'sa.au',
        'sa.com',
        'sa.cr',
        'saga.jp',
        'saitama.jp',
        'sakhalin.ru',
        'samara.ru',
        'sanok.pl',
        'saotome.st',
        'sapporo.jp',
        'saratov.ru',
        'sark.gg',
        'sc.cn',
        'sc.ke',
        'sc.kr',
        'sc.ug',
        'sc.us',
        'sch.ae',
        'sch.gg',
        'sch.id',
        'sch.ir',
        'sch.je',
        'sch.lk',
        'sch.ly',
        'sch.ng',
        'sch.om',
        'sch.sa',
        'sch.sd',
        'sch.uk',
        'sch.zm',
        'school.fj',
        'school.nz',
        'school.za',
        'sci.eg',
        'sd.cn',
        'sd.us',
        'se.com',
        'se.tt',
        'sebastopol.ua',
        'sec.ps',
        'sejny.pl',
        'sendai.jp',
        'seoul.kr',
        'sex.hu',
        'sex.pl',
        'sg.tf',
        'sh.cn',
        'shiga.jp',
        'shimane.jp',
        'shizuoka.jp',
        'shop.ht',
        'shop.hu',
        'shop.pl',
        'simbirsk.ru',
        'sk.ca',
        'sklep.pl',
        'skoczow.pl',
        'slask.pl',
        'sld.do',
        'sld.pa',
        'slg.br',
        'slupsk.pl',
        'smolensk.ru',
        'sn.cn',
        'snz.ru',
        'soc.lk',
        'soros.al',
        'sos.pl',
        'sosnowiec.pl',
        'spb.ru',
        'sport.hu',
        'srv.br',
        'sshn.se',
        'stalowa-wola.pl',
        'starachowice.pl',
        'stargard.pl',
        'stat.no',
        'stavropol.ru',
        'store.co',
        'store.ro',
        'store.st',
        'store.ve',
        'stv.ru',
        'suli.hu',
        'sumy.ua',
        'surgut.ru',
        'suwalki.pl',
        'swidnica.pl',
        'swiebodzin.pl',
        'swinoujscie.pl',
        'sx.cn',
        'syzran.ru',
        'szczecin.pl',
        'szczytno.pl',
        'szex.hu',
        'szkola.pl',
        't.se',
        'takamatsu.jp',
        'tambov.ru',
        'targi.pl',
        'tarnobrzeg.pl',
        'tas.au',
        'tatarstan.ru',
        'te.ua',
        'tec.ve',
        'tel.no',
        'tel.nr',
        'tel.tr',
        'telecom.na',
        'telememo.au',
        'ternopil.ua',
        'test.ru',
        'tgory.pl',
        'tirana.al',
        'tj.cn',
        'tld.am',
        'tlf.nr',
        'tm.cy',
        'tm.fr',
        'tm.hu',
        'tm.mc',
        'tm.mg',
        'tm.mt',
        'tm.pl',
        'tm.ro',
        'tm.se',
        'tm.za',
        'tmp.br',
        'tn.us',
        'tochigi.jp',
        'tokushima.jp',
        'tokyo.jp',
        'tom.ru',
        'tomsk.ru',
        'torun.pl',
        'tottori.jp',
        'tourism.pl',
        'tourism.tn',
        'toyama.jp',
        'tozsde.hu',
        'travel.pl',
        'travel.tt',
        'trd.br',
        'tsaritsyn.ru',
        'tsk.ru',
        'tula.ru',
        'tur.br',
        'turek.pl',
        'turystyka.pl',
        'tuva.ru',
        'tv.bo',
        'tv.br',
        'tv.sd',
        'tver.ru',
        'tw.cn',
        'tx.us',
        'tychy.pl',
        'tyumen.ru',
        'u.se',
        'udm.ru',
        'udmurtia.ru',
        'uk.com',
        'uk.net',
        'uk.tt',
        'ulan-ude.ru',
        'ulsan.kr',
        'unam.na',
        'unbi.ba',
        'uniti.al',
        'unsa.ba',
        'upt.al',
        'uri.arpa',
        'urn.arpa',
        'us.com',
        'us.tf',
        'us.tt',
        'ustka.pl',
        'ut.us',
        'utazas.hu',
        'utsunomiya.jp',
        'uu.mt',
        'uy.com',
        'uz.ua',
        'uzhgorod.ua',
        'va.us',
        'vatican.va',
        'vdonsk.ru',
        'vet.br',
        'veterinaire.fr',
        'vgs.no',
        'vic.au',
        'video.hu',
        'vinnica.ua',
        'vladikavkaz.ru',
        'vladimir.ru',
        'vladivostok.ru',
        'vn.ua',
        'volgograd.ru',
        'vologda.ru',
        'voronezh.ru',
        'vrn.ru',
        'vt.us',
        'vyatka.ru',
        'w.se',
        'wa.au',
        'wa.us',
        'wakayama.jp',
        'walbrzych.pl',
        'warmia.pl',
        'warszawa.pl',
        'waw.pl',
        'weather.mobi',
        'web.co',
        'web.do',
        'web.id',
        'web.lk',
        'web.pk',
        'web.tj',
        'web.tr',
        'web.ve',
        'web.za',
        'wegrow.pl',
        'wi.us',
        'wielun.pl',
        'wlocl.pl',
        'wloclawek.pl',
        'wodzislaw.pl',
        'wolomin.pl',
        'wroc.pl',
        'wroclaw.pl',
        'wv.us',
        'www.ro',
        'wy.us',
        'x.se',
        'xj.cn',
        'xz.cn',
        'y.se',
        'yakutia.ru',
        'yamagata.jp',
        'yamaguchi.jp',
        'yamal.ru',
        'yamanashi.jp',
        'yaroslavl.ru',
        'yekaterinburg.ru',
        'yk.ca',
        'yn.cn',
        'yokohama.jp',
        'yuzhno-sakhalinsk.ru',
        'z.se',
        'za.com',
        'za.pl',
        'zachpomor.pl',
        'zagan.pl',
        'zaporizhzhe.ua',
        'zarow.pl',
        'zgora.pl',
        'zgorzelec.pl',
        'zgrad.ru',
        'zhitomir.ua',
        'zj.cn',
        'zlg.br',
        'zp.ua',
        'zt.ua'
  );
}
 
by Sorin Neacsu on 2009-08-18, tagged domain  validator 
(2 comments)

Validator for Hebrew-character integers

The Hebrew alphabet has its own traditional representation for integers. Here is a validator to clean them into conventional Arabic integers. There may be gotchas involved with the Unicode processing. I developed this on a 64-bit Ubuntu system.

<?php
class ynValidatorIntegerHebrew extends sfValidatorInteger
{
  protected function configure($options = array(), $messages = array())
  {
    parent::configure( $options, $messages );
 
    // value to add to Hebrew entries, useful for assumed millenia
    $this->addOption('add_to_hebrew', 0);
  }
 
  public function clean( $value )
  {
    if ( preg_match( '/^\p{Hebrew}+(?:"\p{Hebrew})?$/u', $value ) ) {
      $value = $this->convertStringToInt( $value );
    }
 
    return parent::clean( $value );
  }
 
  /**
   * Iterates through the Hebrew characters in the string, converting
   * each to its Unicode code and evaluating for numerical value
   *
   * @param string $value The integer represented in Hebrew characters
   * @return integer
   */
  protected function convertStringToInt( $value )
  {
    if ( ! is_string( $value ) ) {
      throw new UnexpectedValueException( 'Argument must be a string' );
    }
 
    preg_match_all( '/\p{Hebrew}/u', $value, $matches );
 
    $value_int = 0;
 
    foreach ( $matches[0] as $char ) {
      $char     = iconv( 'UTF-8', 'UTF-32', $char );
      $char_dec = unpack( 'L*', $char );
      $char_dec = $char_dec[2];
 
      if ( $char_dec > 1510 ) {
        $base = $char_dec - 1510;
        $value_int += $base * 100;
      }
      elseif ( $char_dec > 1496 ) {
        $base = $char_dec - 1496;
 
        // have to deal with final forms
        if ( $base > 2 ) $base--;
        if ( $base > 4 ) $base--;
        if ( $base > 5 ) $base--;
        if ( $base > 8 ) $base--;
        if ( $base > 9 ) $base--;
 
        $value_int += $base * 10;
      }
      else {
        $base = $char_dec - 1487;
        $value_int += $base;
      }
    }
 
    $value_int += $this->getOption('add_to_hebrew');
 
    return $value_int;
  }
}
 
// UNIT TEST
 
// The Hebrew literals are displaying oddly because of right-to-left; it should
// still work
 
require_once dirname(__FILE__).'/../../bootstrap/Doctrine.php';
 
$t = new lime_test();
 
$val = new ynValidatorIntegerHebrew();
 
$t->is( $val->clean('א'), 1 );
$t->is( $val->clean('ב'), 2 );
$t->is( $val->clean('ג'), 3 );
$t->is( $val->clean('ד'), 4 );
$t->is( $val->clean('ה'), 5 );
$t->is( $val->clean('ו'), 6 );
$t->is( $val->clean('ז'), 7 );
$t->is( $val->clean('ח'), 8 );
$t->is( $val->clean('ט'), 9 );
$t->is( $val->clean('י'), 10 );
$t->is( $val->clean('כ'), 20 );
$t->is( $val->clean('ך'), 20 );
$t->is( $val->clean('ל'), 30 );
$t->is( $val->clean('מ'), 40 );
$t->is( $val->clean('ם'), 40 );
$t->is( $val->clean('נ'), 50 );
$t->is( $val->clean('ן'), 50 );
$t->is( $val->clean('ס'), 60 );
$t->is( $val->clean('ע'), 70 );
$t->is( $val->clean('פ'), 80 );
$t->is( $val->clean('ף'), 80 );
$t->is( $val->clean('צ'), 90 );
$t->is( $val->clean('ץ'), 90 );
$t->is( $val->clean('ק'), 100 );
$t->is( $val->clean('ר'), 200 );
$t->is( $val->clean('ש'), 300 );
$t->is( $val->clean('ת'), 400 );
$t->is( $val->clean('תשע'), 770 );
$t->is( $val->clean('תתרתשע'), 1770 );
 
$val->setOption('add_to_hebrew', 5000);
$t->is( $val->clean('תשע'), 5770 );
 
by yitznewton on 2011-02-17, tagged hebrew  integer  validator 

Validator and widget for fuzzy dates

If I understand correctly, ISO-8601 allows for dates represented as YYYY, YYYY-MM, and YYYY-MM-DD, among others. I did not see the possibility of supporting these date formats with the built-in widgets and validators, so I wrote some.

class ynWidgetFormDateFuzzy extends sfWidgetFormDate
{
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    // convert value to an array
    $default = array('year' => null, 'month' => null, 'day' => null);
    if (is_array($value))
    {
      $value = array_merge($default, $value);
    }
    else if ( preg_match( '/^(\d+)$/', $value, $matches ) )
    {
      $value = array(
        'year'  => $matches[1],
        'month' => '',
        'day'   => '',
      );
    }
    else if ( preg_match( '/^(\d+)-(\d+)$/', $value, $matches ) )
    {
      $value = array(
        'year'  => $matches[1],
        'month' => $matches[2],
        'day'   => '',
      );
    }
    else
    {
      $value = (string) $value == (string) (integer) $value ? (integer) $value : strtotime($value);
      if (false === $value)
      {
        $value = $default;
      }
      else
      {
        $value = array('year' => date('Y', $value), 'month' => date('n', $value), 'day' => date('j', $value));
      }
    }
 
    $date = array();
    $emptyValues = $this->getOption('empty_values');
 
    $date['%day%'] = $this->renderDayWidget(
      $name.'[day]',
      $value['day'] ? (int) $value['day'] : '',
      array(
        'choices' => array('' => $emptyValues['day']) + $this->getOption('days'),
        'id_format' => $this->getOption('id_format')
      ),
      array_merge($this->attributes, $attributes)
    );
 
    $date['%month%'] = $this->renderMonthWidget(
      $name.'[month]',
      $value['month'] ? (int) $value['month'] : '',
      array(
        'choices' => array('' => $emptyValues['month']) + $this->getOption('months'),
        'id_format' => $this->getOption('id_format')
      ),
      array_merge($this->attributes, $attributes)
    );
 
    $date['%year%'] = $this->renderYearWidget($name.'[year]', $value['year'], array('choices' => $this->getOption('can_be_empty') ? array('' => $emptyValues['year']) + $this->getOption('years') : $this->getOption('years'), 'id_format' => $this->getOption('id_format')), array_merge($this->attributes, $attributes));
 
    return strtr($this->getOption('format'), $date);
  }
}
 
 
class ynValidatorDateFuzzy extends sfValidatorBase
{
  protected function configure($options = array(), $messages = array())
  {
    $this->addMessage('max', 'The date must be before %max%.');
    $this->addMessage('min', 'The date must be after %min%.');
 
    $this->addOption('min', null);
    $this->addOption('max', null);
  }
 
  protected function doClean($value)
  {
    if ( ! is_array( $value ) ) {
      throw new sfValidatorError($this, 'bad_format');
    }
 
    // convert array to date string
    if (is_array($value))
    {
      $value = $this->convertDateArrayToString($value);
    }
 
    if ($max = $this->getOption('max')) {
      $date_max = new DateTime( $max );
 
      if ( $this->lowestPossible( $value )->format('Ymd') > $date_max->format('Ymd') ) {
        throw new sfValidatorError($this, 'max', array('value' => $value, 'max' => 'out of range'));
      }
    }
 
    if ($min = $this->getOption('min')) {
      $date_min = new DateTime( $min );
 
      if ( $this->highestPossible( $value )->format('Ymd') < $date_min->format('Ymd') ) {
        throw new sfValidatorError($this, 'max', array('value' => $value, 'min' => 'out of range'));
      }
    }
 
    return $value;
  }
 
  protected function convertDateArrayToString($value)
  {
    // all elements must be empty or a number
    foreach (array('year', 'month', 'day', 'hour', 'minute', 'second') as $key)
    {
      if (isset($value[$key]) && !preg_match('#^\d+$#', $value[$key]) && !empty($value[$key]))
      {
        throw new sfValidatorError($this, 'invalid', array('value' => $value));
      }
    }
 
    // check empty value correspondence
 
    if (
      ! $value['year'] && ! $value['month'] && ! $value['day']
    ) {
      return $this->getEmptyValue();
    }
    else if (
      ! $value['year']
      && ($value['month'] || $value['day'])
    ) {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
    else if (
      $value['year'] && ! $value['month'] && $value['day']
    ) {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
 
    if ( $value['month'] && ! in_array( (int) $value['month'], range(1,12) ) ) {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
 
    if ( $value['day'] && ! in_array( (int) $value['day'], range(1,31) ) ) {
      throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }
 
    $clean = '';
 
    if ( $value['year'] ) {
      $clean .= sprintf( '%04d', intval( $value['year'] ) );
    }
 
    if ( $value['month'] ) {
      $clean .= sprintf( '-%02d', intval( $value['month'] ) );
    }
 
    if ( $value['day'] ) {
      $clean .= sprintf( '-%02d', intval( $value['day'] ) );
    }
 
    return $clean;
  }
 
  protected function highestPossible( $date )
  {
    if ( preg_match( '/^\d+$/', $date, $matches ) ) {
      return new DateTime( $date . '-12-31' );
    }
    else if ( preg_match( '/^\d+-(\d+)$/', $date, $matches ) ) {
      switch ( $matches[1] ) {
        case '2':
          return new DateTime( $date . '-28' );
        case '1':
        case '3':
        case '5':
        case '7':
        case '8':
        case '10':
        case '12':
          return new DateTime( $date . '-31' );
        default:
          return new DateTime( $date . '-30' );
      }
    }
    else {
      return new DateTime( $date );
    }
  }
 
  protected function lowestPossible( $date )
  {
    if ( preg_match( '/^\d+$/', $date, $matches ) ) {
      return new DateTime( $date . '-01-01' );
    }
    else if ( preg_match( '/^\d+-(\d+)$/', $date, $matches ) ) {
      return new DateTime( $date . '-01' );
    }
    else {
      return new DateTime( $date );
    }
  }
}
 
by yitznewton on 2011-02-16, tagged date  validator  widget 

symfony 1.x form framework: how to add extra fields into forms?

Assumption: there is a UserMessage class and the related form UserMessageForm. Both were generated by Doctrine based on a schema.yml declaration. Problem: one wishes to you add input fields into this form.

Approach: one can add any pairs of widgets/validators which have no direct relationship to the presented model. Doing so, one need to care about two main aspects:

  1. configuring the form with the extra field
  2. processing of the submitted value (if any) at the proper moment without disturbing the big rest of form functionality.

For step 1, configuring the form: add the widget and the validator to the form.

In .../UserMessageForm.class.php:
 
  public function configure()
  {
    $this->widgetSchema['comment_for_log'] = new sfWidgetFormInputText();
    $this->validatorSchema['comment_for_log'] = new sfValidatorString();
  }
 

For step 2 processing user input after submission: process the value and act accordingly. Typically this happens only after validation did succeed (i.e. not validation errors did occur).

For this purpose, a dedicated method for each field gets called (in case that the method exists). The following example checks whether a non-empty text was entered by the user. If so, it does store a log entry. The function in this example returns a boolean 'false' value to tell symfony not to process this value further.

In .../UserMessageForm.class.php:
 
  public function updateCommentForLogColumn($value)  // constructed method name with camelized field name
  {
    if (! empty($value)) {     // e.g. only if the user entered a comment (btw this is the cleaned value)
      // do whatever you need to do based on the contents of this field.
      // e.g. we create and save a log entry as follows:
      $logEntry = new AppLogEntry();    // assume we have this sort of logging feature
      $logEntry->setComment($value);    // the comment entered by the user
      $logEntry->save();
    }
    return false;  // tell symfony that we have cared about this value, so no further processing will occur
  }
}
 

Basically that's it.

An alternative to the dedicated updateYourSpecialFieldColumn() method could be to override the doUpdate($values) method in the form, allowing to do more general things because there is access to all submitted values (that's basically the only difference between the two approaches, though). I personally prefer the dedicated method whenever possible, as it doesn't disturb any other logic that may (at the same time, or sometimes later) implemented in the same form.

Another good thing is that since all this code is all packed in a Doctrine transaction, so either both the form object (UserMessage) will be saved as well as the log (LogEntry) be generated, or none of the two objects be altered/created at all.

(15.9.2010/SCH)

by Raphael Schumacher on 2010-09-15, tagged doctrine  fields  form  symfony14  validator  widget 

CPF e CNPJ Validator

<?php
class sfValidatorCpfCnpj extends sfValidatorBase {
 
  /**
   * Configures the current validator
   *
   * Available options (required):
   *
   *  * tipo: CPF or CNPJ
   *
   * @param array $options   An array of options
   * @param array $messages  An array of error messages
   *
   * @see sfValidatorBase
   **/
 
  protected function configure($options = array(), $messages = array())
  {
    $this->addRequiredOption('tipo');
    $this->setMessage('invalid', '%tipo% %value% não é válido.');
  }
 
  protected function validaCPF($cpf) {
        $cpf = str_replace(array('.','-'),'',$cpf);
 
        $soma = 0;
 
        if (strlen($cpf) <> 11)
        return false;
 
        // Verifica 1º digito
        for ($i = 0; $i < 9; $i++):
            $soma += (($i+1) * $cpf[$i]);
        endfor;
 
        $d1 = ($soma % 11);
 
        if ($d1 == 10):
            $d1 = 0;
        endif;
 
        $soma = 0;
 
        // Verifica 2º digito
        for ($i = 9, $j = 0; $i > 0; $i--, $j++):
            $soma += ($i * $cpf[$j]);
        endfor;
 
        $d2 = ($soma % 11);
 
        if ($d2 == 10):
            $d2 = 0;
        endif;
 
        if ($d1 == $cpf[9] && $d2 == $cpf[10]):
            return true;
        else:
            return false;
        endif;
    }
 
    protected function validaCNPJ($cnpj) {
 
        $cnpj = str_replace(array('.','/','-'),'',$cnpj);
 
        if (strlen($cnpj) <> 14)
        return false;
 
        $soma = 0;
 
        $soma += ($cnpj[0] * 5);
        $soma += ($cnpj[1] * 4);
        $soma += ($cnpj[2] * 3);
        $soma += ($cnpj[3] * 2);
        $soma += ($cnpj[4] * 9);
        $soma += ($cnpj[5] * 8);
        $soma += ($cnpj[6] * 7);
        $soma += ($cnpj[7] * 6);
        $soma += ($cnpj[8] * 5);
        $soma += ($cnpj[9] * 4);
        $soma += ($cnpj[10] * 3);
        $soma += ($cnpj[11] * 2);
 
        $d1 = $soma % 11;
        $d1 = $d1 < 2 ? 0 : 11 - $d1;
 
        $soma = 0;
        $soma += ($cnpj[0] * 6);
        $soma += ($cnpj[1] * 5);
        $soma += ($cnpj[2] * 4);
        $soma += ($cnpj[3] * 3);
        $soma += ($cnpj[4] * 2);
        $soma += ($cnpj[5] * 9);
        $soma += ($cnpj[6] * 8);
        $soma += ($cnpj[7] * 7);
        $soma += ($cnpj[8] * 6);
        $soma += ($cnpj[9] * 5);
        $soma += ($cnpj[10] * 4);
        $soma += ($cnpj[11] * 3);
        $soma += ($cnpj[12] * 2);
 
 
        $d2 = $soma % 11;
        $d2 = $d2 < 2 ? 0 : 11 - $d2;
 
        if ($cnpj[12] == $d1 && $cnpj[13] == $d2):
            return true;
        else:
            return false;
        endif;
    }
 
  protected function doClean($value)
  {
    $tipo = $this->getOption('tipo');
 
    if($tipo == 'CPF'):
            if(!$this->validaCPF($value)):
                throw new sfValidatorError($this, 'invalid', array('tipo' => $this->getOption('tipo'), 'value' => $value));
                return false;
            endif;
    elseif($tipo == 'CNPJ'):
            if(!$this->validaCNPJ($value)):
                throw new sfValidatorError($this, 'invalid', array('tipo' => $this->getOption('tipo'), 'value' => $value));
                return false;
            endif;
    else:
        throw new sfValidatorError($this, 'invalid', array('tipo' => $this->getOption('tipo'), 'value' => $value));
        return false;
    endif;
 
    return $value;
  }  
}
 
?>
 
by Anderson da Veiga on 2010-02-27, tagged 12  13  14  cnpj  cpf  symfony  validator 

Validator to make a field read-only

Sometimes, when you display the form for a Doctrine object, you want to make some fields read-only..

You can unset the field, and just display the value in the template. But it's better to still display an input form. You can also mess with the template, etc.

Here's a validator to achive the same goal, with no need to touch the template.

class sfValidatorReadOnlyField extends sfValidatorBase
{
  /**
   * Configures the current validator
   * 
   * Available options (required):
   *
   *  * object: The current form object
   *  * field:  The current form field
   *
   * Available error codes:
   *
   * * changed
   *
   * @param array $options   An array of options
   * @param array $messages  An array of error messages
   *
   * @see sfValidatorBase
   **/
  protected function configure($options = array(), $messages = array())
  {
    $this->addRequiredOption('object');
    $this->addRequiredOption('field');
 
    $this->addMessage('changed', 'You don\'t want to mess with me, kid!');
  }
 
  /**
   * @see sfValidatorBase
   */
  protected function doClean($value)
  {
    // Ok, we could just reset the value, but it's so more fun this way
    $object = $this->getOption('object');
    if ($value != $object->get($this->getOption('field')))
    {
      throw new sfValidatorError($this, 'changed');
    }
 
    return (string) $value;
  }
}
 

In your form class, use this code :

public function configure()
{// make some fields readonly
    $this->widgetSchema['first_name']->setAttribute('readonly', 'readonly');
    $this->widgetSchema['last_name']->setAttribute('readonly', 'readonly');
    $this->widgetSchema['email_address']->setAttribute('readonly', 'readonly');
 
    $this->validatorSchema['first_name'] = new sfValidatorReadOnlyField(array('field' => 'first_name', 'object' => $this->getObject()));
    $this->validatorSchema['last_name'] = new sfValidatorReadOnlyField(array('field' => 'last_name', 'object' => $this->getObject()));
    $this->validatorSchema['email_address'] = new sfValidatorReadOnlyField(array('field' => 'email_address', 'object' => $this->getObject()));
 
…
}
 
by Thibault Jouannic on 2010-02-15, tagged form  validator 
(1 comment)

Verify form field matches existing field: sfValidatorPropelExisting

This snippet may be used as a post-validator to verify that a column exists within a Propel table. It's a trivial derivative of the sfValidatorPropelUnique (so much so I left the copyrights in place). It's strictly a that validator with the logic reversed.

/*
 * This file is part of the symfony package.
 * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
 
/**
 * sfValidatorPropelExisting validates that the uniqueness of a column.
 *
 * Warning: sfValidatorPropelExisting is susceptible to race conditions.
 * To avoid this issue, wrap the validation process and the model saving
 * inside a transaction.
 *
 * @package    symfony
 * @subpackage validator
 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
 * @version    SVN: $Id: sfValidatorPropelExisting.class.php 13249 2008-11-22 16:10:11Z fabien $
 */
class sfValidatorPropelExisting extends sfValidatorSchema
{
  /**
   * Constructor.
   *
   * @param array  $options   An array of options
   * @param array  $messages  An array of error messages
   *
   * @see sfValidatorSchema
   */
  public function __construct($options = array(), $messages = array())
  {
    parent::__construct(null, $options, $messages);
  }
 
  /**
   * Configures the current validator.
   *
   * Available options:
   *
   *  * model:              The model class (required)
   *  * column:             The unique column name in Propel field name format (required)
   *                        If the uniquess is for several columns, you can pass an array of field names
   *  * field               Field name used by the form, other than the column name
   *  * primary_key:        The primary key column name in Propel field name format (optional, will be introspected if not provided)
   *                        You can also pass an array if the table has several primary keys
   *  * connection:         The Propel connection to use (null by default)
   *  * throw_global_error: Whether to throw a global error (false by default) or an error tied to the first field related to the column option array
   *
   * @see sfValidatorBase
   */
  protected function configure($options = array(), $messages = array())
  {
    $this->addRequiredOption('model');
    $this->addRequiredOption('column');
    $this->addOption('field', null);
    $this->addOption('primary_key', null);
    $this->addOption('connection', null);
    $this->addOption('throw_global_error', false);
 
    $this->setMessage('invalid', 'An object with the same "%column%" already exist.');
  }
 
  /**
   * @see sfValidatorBase
   */
  protected function doClean($values)
  {
    if (!is_array($values))
    {
      throw new InvalidArgumentException('You must pass an array parameter to the clean() method (this validator can only be used as a post validator).');
    }
 
    if (!is_array($this->getOption('column')))
    {
      $this->setOption('column', array($this->getOption('column')));
    }
 
    if (!is_array($field = $this->getOption('field')))
    {
      $this->setOption('field', $field ? array($field) : array());
    }
    $fields = $this->getOption('field');
 
    $criteria = new Criteria();
    foreach ($this->getOption('column') as $i => $column)
    {
      $name = isset($fields[$i]) ? $fields[$i] : $column;
      if (!array_key_exists($name, $values))
      {
        // one of the column has be removed from the form
        return $values;
      }
 
      $colName = call_user_func(array(constant($this->getOption('model').'::PEER'), 'translateFieldName'), $column, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_COLNAME);
 
      $criteria->add($colName, $values[$name]);
    }
 
    $object = call_user_func(array(constant($this->getOption('model').'::PEER'), 'doSelectOne'), $criteria, $this->getOption('connection'));
 
    // if no object or if we're updating the object, it's ok
    if (!is_null($object) && !$this->isUpdate($object, $values))
    {
      return $values;
    }
 
    $error = new sfValidatorError($this, 'invalid', array('column' => implode(', ', $this->getOption('column'))));
 
    if ($this->getOption('throw_global_error'))
    {
      throw $error;
    }
 
    $columns = $this->getOption('column');
 
    throw new sfValidatorErrorSchema($this, array($columns[0] => $error));
  }
 
  /**
   * Returns whether the object is being updated.
   *
   * @param BaseObject  $object   A Propel object
   * @param array       $values   An array of values
   *
   * @return Boolean     true if the object is being updated, false otherwise
   */
  protected function isUpdate(BaseObject $object, $values)
  {
    // check each primary key column
    foreach ($this->getPrimaryKeys() as $column)
    {
      $columnPhpName = call_user_func(array(constant($this->getOption('model').'::PEER'), 'translateFieldName'), $column, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_PHPNAME);
      $method = 'get'.$columnPhpName;
      if (!isset($values[$column]) or $object->$method() != $values[$column])
      {
        return false;
      }
    }
 
    return true;
  }
 
  /**
   * Returns the primary keys for the model.
   *
   * @return array An array of primary keys
   */
  protected function getPrimaryKeys()
  {
    if (is_null($this->getOption('primary_key')))
    {
      $primaryKeys = array();
      $tableMap = call_user_func(array(constant($this->getOption('model').'::PEER'), 'getTableMap'));
      foreach ($tableMap->getColumns() as $column)
      {
        if (!$column->isPrimaryKey())
        {
          continue;
        }
 
        $primaryKeys[] = call_user_func(array(constant($this->getOption('model').'::PEER'), 'translateFieldName'), $column->getPhpName(), BasePeer::TYPE_PHPNAME, BasePeer::TYPE_FIELDNAME);
      }
 
      $this->setOption('primary_key', $primaryKeys);
    }
 
    if (!is_array($this->getOption('primary_key')))
    {
      $this->setOption('primary_key', array($this->getOption('primary_key')));
    }
 
    return $this->getOption('primary_key');
  }
}
 
by Mike Crowe on 2009-10-24, tagged propel  validator 

How to validate that at least one checkbox has been checked

I spent some time trying to figure out how to make sure that one checkbox was checked before saving a form in symfony 1.2.

I read about Global Validators (http://www.symfony-project.org/forms/1_2/en/02-Form-Validation#chapter_02_global_validators) but I couldn't find anything to fit my needs. Finally, after looking at this snipped: http://www.symfony-project.org/cookbook/1_2/en/conditional-validator , I created the validation I needed, here is the snipped to help someone in the same situation:

class SampleForm extends BaseSampleForm
{
  public function configure()
  {
 
  // add a post validator
    $this->validatorSchema->setPostValidator(
      new sfValidatorCallback(array('callback' => array($this, 'checkAtLeastOne')))
    );
 
 
    unset(
      $this['created_at'], $this['updated_at']
 
    );
  }
 
  public function checkAtLeastOne($validator, $values)
  {
    if (!$values['sunday'] && !$values['monday'] && !$values['tuesday'] && !$values['wednesday'] && !$values['thursday'] && !$values['friday'] && !$values['saturday'])
    {
      // no checkbox was checked, throw an error
      throw new sfValidatorError($validator, 'Check at least one day of the week');
    }
 
    // at least one checkbox is checked, return the clean values
    return $values;
  }
 
 
}
 
by carlos m on 2009-06-26, tagged box  check  checkbox  checked  form  global  validator 
(2 comments)

sfValidatorFileImage : a simple validator for image file (symfony 1.2)

Description :

This is a class for symfony 1.2 for images. It extends the class sfValidatorFile.

<?php
 
/**
 * sfValidatorFileImage
 *
 * Original validator for symfony 1.0 : http://snippets.symfony-project.org/snippet/259
 *
 * @package    symfony
 * @subpackage validator
 * @author     Yoann Brieux <yoann |dot¤ brieux #at] gmail ~dot} com>
 * @version    0.1
 */
class sfValidatorFileImage extends sfValidatorFile
{
  /**
   * @param array $options   An array of options
   * @param array $messages  An array of error messages
   *
   * @see sfValidatorFile
   */
  protected function configure($options = array(), $messages = array())
  {
    parent::configure($options, $messages);
 
    $this->addMessage('invalid_image', '%value% is an incorrect image file.');
    $this->addMessage('max_height', '"%value%" height is too long (%max_height% pixels max).');
    $this->addMessage('min_height', '"%value%" height is too short (%min_height% pixels min).');
    $this->addMessage('max_width', '"%value%" width is too long (%max_width% pixels max).');
    $this->addMessage('min_width', '"%value%" width is too short (%min_width% pixels min).');
 
    $this->addOption('max_height');
    $this->addOption('min_height');
    $this->addOption('max_width');
    $this->addOption('min_width');    
    $this->addOption('is_only_image',false);    
  }
 
  /**
   * @see sfValidatorFile
   */
  protected function doClean($value)
  {
    $clean = parent::doClean($value);
 
    $size = @getimagesize($clean->getTempName());
 
    if (!$size && !$this->getOption('is_only_image')) 
    {
        return $clean;
    }
 
    if (!$size){    
      throw new sfValidatorError($this, 'invalid_image', array('value' => $value['name']));
    }
 
    list($width, $height) = $size;
 
    if($this->getOption('max_height') < $height){
        throw new sfValidatorError($this, 'max_height', array('value' => $value['name'], 'max_height' => $this->getOption('max_height')));
    }
 
    if($this->getOption('min_height') > $height){
        throw new sfValidatorError($this, 'min_height', array('value' => $value['name'], 'min_height' => $this->getOption('min_height')));
    }
 
    if($this->getOption('max_width') < $width){
        throw new sfValidatorError($this, 'max_width', array('value' => $value['name'], 'max_width' => $this->getOption('max_width')));
    }
 
    if($this->getOption('min_width') > $width){
        throw new sfValidatorError($this, 'min_width', array('value' => $value['name'], 'min_width' => $this->getOption('min_width')));
    }
 
    return $clean;
  }
}
 

How to use it?

In the your form library :

    $this->validatorSchema['field'] = new sfValidatorFileImage(array(
        'required'                          =>  false,
        'path'                              =>  sfConfig::get('sf_upload_dir').'/news/',
        'max_height'                        =>  238,
        'min_height'                        =>  238,
        'max_width'                         =>  270,
        'min_width'                         =>  270,
        'mime_types'                        =>  array('image/jpeg','image/pjpeg','image/png','image/x-png','image/gif','application/x-shockwave-flash')
        )
    );
 

If the variable "is_only_image" is true and function "getimagesize" return false (is not a valid image for example), this validator cause an sfValidatorError exception with the message "[your file] is an incorrect image file."

by Yoann Brieux on 2009-06-15, tagged 12  file  files  image  images  symfony  validator  validators 

Custom Validator for multiple checkboxes

While developing in symfony 1.0 I ran into the issue to validate how many checkboxes where actually checked. I created a custom validator class which was able to check for a minimum and a maximum value and throw corresponding error messages. Since I moved the project to symfony 1.2 I needed to update the validator, here is my solution (just place it somewhere in an autoloading directory like %project_root_dir%/lib and clean your cache):

class customValidatorPropelChoiceMany extends sfValidatorPropelChoiceMany
{
    protected function configure($options = array(), $messages = array())
    {
        parent::configure($options, $messages);
 
        $this->addOption('min', null);
        $this->addOption('max', null);
 
        $this->addMessage('min', 'Please select at least %min% fields.');
        $this->addMessage('max', 'Please do not select more than %max% fields.');
    }
 
    protected function doClean($value)
    {
        $value = parent::doClean($value);
 
        if ($this->getOption('multiple'))
        {
            if (count($value) < $this->getOption('min'))
            {
                throw new sfValidatorError($this, 'min', array('min' => $this->getOption('min')));
            }
            if (count($value) > $this->getOption('max'))
            {
                throw new sfValidatorError($this, 'max', array('max' => $this->getOption('max')));
            }
        }
 
        return $value;
    }
}
 

This code can easily be used to validate regular checkboxes (those which don't rely on a propel object), should be as easy as renaming the class and changing the parent to sfValidatorChoiceMany.

by Volker on 2009-03-04, tagged array  checkboxes  choice  propel  validator 

Latitude Longitude Validator

A Latitude Longitude validator that allows you to specify very exact constraints for the co-ordinates that are submitted.

<?php
 
/*
 * This file is part of the symfony package.
 * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
 
/**
 * myLatLngValidator 
 * 
 * @description This class validator will validate the entered latitude and
 * longitudes submitted using a form or other method.
 * @subpackage validator
 * @author Nathan Rzepecki
 * @created 21/4/2008
 */
class myLatLngValidator extends sfValidator
{
  /**
   * Executes this validator.
   * 
   * @param mixed A file or parameter value/array
   * @param error An error message reference
   *
   * @return bool true, if this validator executes successfully, otherwise false
   */
  public function execute(&$value, &$error)
  {
//
//      echo '<pre>value is: '.$value.'</pre>';
//      echo '<pre>min_type: '.$this->getParameterHolder()->get('min_type').'</pre>';
//      echo '<pre>max type: '.$this->getParameterHolder()->get('max_type').'</pre>';
//      echo '<pre>min: '.$this->getParameterHolder()->get('min').'</pre>';
//      echo '<pre>max: '.$this->getParameterHolder()->get('max').'</pre>';
//      exit;
 
//      echo '<pre>value: '.$value.'</pre>';
//      echo '<pre>max type: '.$this->getParameterHolder()->get('max_type').'</pre>';
//      echo '<pre>min type: '.$this->getParameterHolder()->get('min_type').'</pre>';
//      echo '<pre>min: '.$this->getParameterHolder()->get('min').'</pre>';
//      echo '<pre>max: '.$this->getParameterHolder()->get('max').'</pre>';
//      echo '<pre>'..'</pre>';
 
    /*
     * If the min_type was set
     */
      if ($this->getParameterHolder()->get('min_type') == '+')
      {
         // type was positive
         $re = '/^\d{1,3}(\.(\d*)){0,1}$/';
         if (!preg_match($re, $value))
         {
           $error = $this->getParameterHolder()->get('min_type_error');
           return false;
         } // end if
      }
      elseif ($this->getParameterHolder()->get('min_type') == '-')
      {
        // type was negative
        $re = '/^-\d{1,3}(\.(\d*)){0,1}$/';
         if (!preg_match($re, $value))
         {
           $error = $this->getParameterHolder()->get('min_type_error');
           return false;
         } // end if
      }
      else
      {
        // don't care if it is plus or minus
         $re = '/^-{0,1}\d{1,3}(\.(\d*)){0,1}$/';
         if (!preg_match($re, $value))
         {
           $error = $this->getParameterHolder()->get('min_type_error');
           return false;
         } // end if
      } // end if elseif else
 
 
    /*
     * if max_type was set
     */            
      if ($this->getParameterHolder()->get('max_type') == '+')
      {
         // type was positive
         $re = '/^\d{1,3}(\.(\d*)){0,1}$/';
         if (!preg_match($re, $value))
         {
           $error = $this->getParameterHolder()->get('max_type_error');
           return false;
         } // end if
      }
      elseif ($this->getParameterHolder()->get('max_type') == '-')
      {
        // type was negative
        $re = '/^-\d{1,3}\.{0,1}\d+$/';  // negative digit
         if (!preg_match($re, $value))
         {
           $error = $this->getParameterHolder()->get('max_type_error');
           return false;
         } // end if
      }
      else
      {
        // don't care if it is plus or minus
         $re = '/^-{0,1}\d{1,3}(\.(\d*)){0,1}$/';
         if (!preg_match($re, $value))
         {
           $error = $this->getParameterHolder()->get('max_type_error');
           return false;
         } // end if
      } // end if elseif else
 
 
    /*
     * if max value was set
     */
     if ($this->getParameterHolder()->get('max'))
     {
       $re = '/^-\d{1,3}(\.(\d*)){0,1}$/';  // negative digit
       if (preg_match($re, $this->getParameterHolder()->get('max')))
       {
          // yes its a negative so remove the prefix -
          $maxValue = substr($this->getParameterHolder()->get('max'), 1);
 
          if (!preg_match($re, $value))
          {
            $error = $this->getParameterHolder()->get('max_type_error');
            return false;
          }
          else
          {
            // both the max value and the value are negative so test that the value is less than the max value
            if (!($maxValue > substr($value, 1)))
            {
              $error = $this->getParameterHolder()->get('max_error');
              return false;
            } // end if 
          } // end if else          
       } 
       else
       {
         $maxValue = $this->getParameterHolder()->get('max');
 
         $re = '/^\d{1,3}(\.(\d*)){0,1}$/';  // positive digit
         if (preg_match($re, $value))
         {
           // Yeah the value is a positive so make sure its within range
           if (!($maxValue > $value))
           {
              $error = $this->getParameterHolder()->get('max_error');
              return false;
           } // end uf
         }
         else
         {
           $error = $this->getParameterHolder()->get('max_type_error');
           return false;
         } // end if else positive
 
       }// end if
 
     } // end if
 
 
    /*
     * if min value was set
     */
     if ($this->getParameterHolder()->get('min'))
     {
       $re = '/^-\d{1,3}(\.(\d*)){0,1}$/';  // negative digit
       if (preg_match($re, $this->getParameterHolder()->get('min')))
       {
          // yes its a negative so remove the prefix -
          $minValue = substr($this->getParameterHolder()->get('min'), 1);
 
          if (!preg_match($re, $value))
          {
            // the min value was negative so the value should be nexative. This is not so error it
            $error = $this->getParameterHolder()->get('min_type_error');
            return false;
          }
          else
          {
            // both the max value and the value are negative so test that the value is less than the max value
            if (!(substr($value, 1) > $minValue))
            {
              $error = $this->getParameterHolder()->get('min_error');
              return false;
            } // end if 
          } // end if else          
       } 
       else
       {
         $minValue = $this->getParameterHolder()->get('min');
 
         $re = '/^\d{1,3}(\.(\d*)){0,1}$/';  // positive digit
         if (preg_match($re, $value))
         {
           // Yeah the value is a positive so make sure its within range
           if (!($value > $minValue))
           {
              $error = $this->getParameterHolder()->get('min_error');
              return false;
           } // end if
         }
         else
         {
           $error = $this->getParameterHolder()->get('min_type_error');
           return false;
         } // end if else positive
 
       }// end if
 
     } // end if
 
    // was not returned out of any of the above so must be good
    return true;
  }
 
  /**
   * Initializes this validator.
   *
   * @param sfContext The current application context
   * @param array   An associative array of initialization parameters
   *
   * @return bool true, if initialization completes successfully, otherwise false
   */
  public function initialize($context, $parameters = null)
  {
    // initialize parent
    parent::initialize($context, $parameters);
 
    // set defaults
    $this->getParameterHolder()->set('max',            null);
    $this->getParameterHolder()->set('max_error',      'The lat / lng is not within range');
    $this->getParameterHolder()->set('max_type',       null);
    $this->getParameterHolder()->set('max_type_error', 'Your lat / lng did not match the specified type');
    $this->getParameterHolder()->set('min',            null);
    $this->getParameterHolder()->set('min_error',     'The lat / lng is not within range');
    $this->getParameterHolder()->set('min_type',       null);
    $this->getParameterHolder()->set('min_type_error', 'Your lat / lng did not match the specified type');
    //$this->getParameterHolder()->set('', null);
 
    $this->getParameterHolder()->add($parameters);
 
    return true;
  }// end function
} // end class myLatLngValidator
 

You use it like so.

  latitude:
    required: 
      msg:  Please enter the latitude or position the marker where your farm is
    myLatLngValidator:
      min: -9
      min_error: Your entered latitude is not within min range
      min_type: '-'
      min_type_error: Your latitude should be in the negative range
      max: -45
      max_error: Your entered latitude is not within max range
      max_type: '-'
      max_type_error: Your latitude should be in the negative range  
 
  longitude:
    required:
      msg: Please enter the longitude or position the marker where your farm is
    myLatLngValidator:
      min: 111
      min_error: Your longitude is not within min range
      min_type: '+'
      min_type_error: Your longitude should be in the positive range
      max: 154
      max_error: Your longitude is not within max range
      max_type: '+'
      max_type_error: Your longitude should be in the positive range
 

I may clean it up in the future. This was so I could constrain the points to be within Australia.

by lionslair on 2008-04-22, tagged latitude  longitude  validator 

Test a validator class

If you create a custom validator, you might want to test it by creating a unit test script.

This is a sample unit test script for testing validators.

test/unit/myFooBarValidatorTest.php

<?php
$app='frontend'; // Necessary for fonctional boostrap
include(dirname(__FILE__).'/../test/bootstrap/unit.php');
include(dirname(__FILE__).'/../test/bootstrap/functional.php');
require_once(dirname(__FILE__).'/../../lib/validator/myFooBarValidator.class.php');
 
$context = sfContext::getInstance();
$request = $context->getRequest();
 
$manager = new sfValidatorManager();
$manager->initialize($context);
$validator = new myFooBarValidator();
$validator->initialize($context);
 
// The values to validate.
// You can make a second array with values
// that are supposed to fail and do another
// loop below.
$values = Array('foo', 'bar');
 
$t = new lime_test(count($values) * 2, new lime_output_color());
 
$t->diag('myFooBarValidator()');
 
foreach ( $values as $value ) {
  // Re-initialize the validation entry
  // Without this, the first failure would
  // cause any additional validation to
  // be skipped
  $manager->registerName('myname', false);
  $manager->registerValidator('myname', $validator);
 
  $request->setParameter('myname', $value);
  $retval = $manager->execute();
  $t->is($retval, true);
  $t->is($request->getErrors(), Array());
 
  // We remove the error so that the next loop
  // does not carry the error.
  $request->removeError('myname');
}
by Christian Roy on 2007-06-04, tagged test  validator 

CUIT/CUIL Validator

This is a CUIT validator for argentinian users.

/**
 * albaCUITValidator Valida el CUIT
 *
 * @package    alba
 * @author     Héctor Sanchez <hsanchez@pressenter.com.ar>
 * @author     José Luis Di Biase <josx@interorganic.com.ar>
 * @author     Fernando Toledo <ftoledo@pressenter.com.ar>
 * @version    SVN: $Id: albaCUITValidator.class.php 4347 2007-02-28 21:19:57Z josx $
 * @filesource
 * @license GPL
 */
 
class albaCUITValidator extends sfValidator {
 
    function initialize($context, $parameters = null)
    {
        // initialize parent
        parent::initialize($context);
 
        // set defaults
        $this->getParameterHolder()->set('cuit_error', 'CUIT Invalido');
        $this->getParameterHolder()->add($parameters);
        return true;
    }
 
    public function execute(&$value, &$error)
    {
        $cuit = $value;
        $coeficiente = array(5,4,3,2,7,6,5,4,3,2);
        $cuit_rearmado = "";
        //separo cualquier caracter que no tenga que ver con numeros
        for ($i=0; $i < strlen($cuit); $i= $i +1) {
            if ((Ord(substr($cuit, $i, 1)) >= 48) && (Ord(substr($cuit, $i, 1)) <= 57))
                $cuit_rearmado = $cuit_rearmado . substr($cuit, $i, 1);
        }
 
        // si no estan todos los digitos
        if (strlen($cuit_rearmado) <> 11) {
            $error = $this->getParameterHolder()->get('cuit_error');
            return false;
        } else {
            $sumador = 0;
            $verificador = substr($cuit_rearmado, 10, 1); //tomo el digito verificador
 
            for ($i=0; $i <=9; $i=$i+1)
                $sumador = $sumador + (substr($cuit_rearmado, $i, 1)) * $coeficiente[$i];//separo cada digito y lo multiplico por el coeficiente
 
            $resultado = $sumador % 11;
            $resultado = 11 - $resultado;  //saco el digito verificador
 
            if (intval($verificador) <> $resultado) {
                $error = $this->getParameterHolder()->get('cuit_error');
                return false;
            } else {
                //$cuit_rearmado = substr($cuit_rearmado, 0, 2) . "-" . substr($cuit_rearmado, 2, 8) . "-" . substr($cuit_rearmado, 10, 1);
                return true;
            }
        }
    }
}
?>
by Héctor Sanchez on 2007-04-30, tagged cuil  cuit  validation  validator 

CNPJ and CPF validator

A CNPJ and CPF validator for brazilian users...

Just add this in the module/lib/myCpfValidator.class.php path...

//Symfony integration as sfValidator of CPF and //CNPJ
//Date - Jul 11, 2006
//Author - Lucas Peres (mysyfy@gmail.com)
 
//CNPJ and CPF Validator
//Author: Marcelo Bom Jardim
 
//ABOUT
//This PHP script will validate CNPJ and CPF //number by checking there length
//and some other validations.
 
 
class myCpfValidator extends sfValidator {
 
    private function validaCPF($cpf) {
        $soma = 0;
 
        if (strlen($cpf) <> 11)
        return false;
 
        // Verifica 1º digito
        for ($i = 0; $i < 9; $i++) {
            $soma += (($i+1) * $cpf[$i]);
        }
 
        $d1 = ($soma % 11);
 
        if ($d1 == 10) {
            $d1 = 0;
        }
 
        $soma = 0;
 
        // Verifica 2º digito
        for ($i = 9, $j = 0; $i > 0; $i--, $j++) {
            $soma += ($i * $cpf[$j]);
        }
 
        $d2 = ($soma % 11);
 
        if ($d2 == 10) {
            $d2 = 0;
        }
 
        if ($d1 == $cpf[9] && $d2 == $cpf[10]) {
            return true;
        }
        else {
            return false;
        }
    }
 
    private function validaCNPJ($cnpj) {
 
        if (strlen($cnpj) <> 14)
        return false;
 
        $soma = 0;
 
        $soma += ($cnpj[0] * 5);
        $soma += ($cnpj[1] * 4);
        $soma += ($cnpj[2] * 3);
        $soma += ($cnpj[3] * 2);
        $soma += ($cnpj[4] * 9);
        $soma += ($cnpj[5] * 8);
        $soma += ($cnpj[6] * 7);
        $soma += ($cnpj[7] * 6);
        $soma += ($cnpj[8] * 5);
        $soma += ($cnpj[9] * 4);
        $soma += ($cnpj[10] * 3);
        $soma += ($cnpj[11] * 2);
 
        $d1 = $soma % 11;
        $d1 = $d1 < 2 ? 0 : 11 - $d1;
 
        $soma = 0;
        $soma += ($cnpj[0] * 6);
        $soma += ($cnpj[1] * 5);
        $soma += ($cnpj[2] * 4);
        $soma += ($cnpj[3] * 3);
        $soma += ($cnpj[4] * 2);
        $soma += ($cnpj[5] * 9);
        $soma += ($cnpj[6] * 8);
        $soma += ($cnpj[7] * 7);
        $soma += ($cnpj[8] * 6);
        $soma += ($cnpj[9] * 5);
        $soma += ($cnpj[10] * 4);
        $soma += ($cnpj[11] * 3);
        $soma += ($cnpj[12] * 2);
 
 
        $d2 = $soma % 11;
        $d2 = $d2 < 2 ? 0 : 11 - $d2;
 
        if ($cnpj[12] == $d1 && $cnpj[13] == $d2) {
            return true;
        } else {
            return false;
        }
    }
 
 
 
    public function execute (&$value, &$error)
    {
        if($this->getParameter('tipo') == 'cpf') {
            if(!$this->validaCPF($value)) {
                $error = $this->getParameter('msg_error');
                return false;
            }
        } else if($this->getParameter('tipo') == 'cnpj') {
            if(!$this->validaCNPJ($value)) {
                $error = $this->getParameter('msg_error');
                return false;
            }
        } else {
            $error = $this->getParameter('msg_error');
            return false;
        }       
        return true;
    }
 
    public function initialize ($context, $parameters = null)
    {
 
        // initialize parent
        parent::initialize($context, $parameters);
 
        // set defaults
        $this->setParameter('msg_error', 'Invalid input');
 
        $this->getParameterHolder()->add($parameters);
 
        return true;
 
 
    }
}

Then at the module/validate.yml add:

names:
  [...]
  cpf:
    required:      Yes
    required_msg:  O campo CPF &eacute; requerido
    validators:    cpfValidator
 
cpfValidator:
  class:          myCpfValidator    
  param:
    tipo:         cpf
    msg_error:    CPF inv&aacute;lido
by Lucas Peres on 2006-07-12, tagged cnpj  validation  validator 
(1 comment)