Create associations between offices and ads
authorGustavo Martin Morcuende <gu.martinm@gmail.com>
Thu, 17 May 2012 01:57:43 +0000 (03:57 +0200)
committerGustavo Martin Morcuende <gu.martinm@gmail.com>
Thu, 17 May 2012 01:57:43 +0000 (03:57 +0200)
Using double list and the plguin sfFormExtraPlugin

43 files changed:
apps/companyfront/modules/office/actions/actions.class.php
apps/companyfront/modules/office/templates/_formCustomOfficeAds.php [new file with mode: 0644]
apps/companyfront/modules/office/templates/_list.php
apps/companyfront/modules/office/templates/linkSuccess.php [new file with mode: 0644]
config/ProjectConfiguration.class.php
lib/form/doctrine/OfficeAdsForm.class.php
lib/model/doctrine/AdTable.class.php
lib/model/doctrine/OfficeAdsTable.class.php
plugins/.filemap
plugins/.registry/.channel.pear.symfony-project.com/symfony.reg
plugins/.registry/.channel.plugins.symfony-project.org/sfformextraplugin.reg [new file with mode: 0644]
plugins/sfFormExtraPlugin/LICENSE [new file with mode: 0644]
plugins/sfFormExtraPlugin/README [new file with mode: 0644]
plugins/sfFormExtraPlugin/bin/prove.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/form/sfFormLanguage.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/validator/doctrine/sfValidatorDoctrineNestedSetLevel.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/validator/sfValidatorBlacklist.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/validator/sfValidatorDefault.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/validator/sfValidatorReCaptcha.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/validator/sfValidatorSchemaTimeInterval.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormDoctrineChoiceGrouped.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormDoctrineJQueryAutocompleter.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormJQueryAutocompleter.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormJQueryDate.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormPropelChoiceGrouped.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormPropelJQueryAutocompleter.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormReCaptcha.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormSelectDoubleList.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormSelectUSState.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormTextareaTinyMCE.class.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/test/bootstrap.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/test/form/sfFormLanguageTest.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/test/validator/sfValidatorBlacklistTest.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/test/validator/sfValidatorDefaultTest.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/test/validator/sfValidatorReCaptchaTest.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/test/validator/sfValidatorSchemaTimeIntervalTest.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/test/widget/sfWidgetFormReCaptchaTest.php [new file with mode: 0644]
plugins/sfFormExtraPlugin/web/css/jquery.autocompleter.css [new file with mode: 0644]
plugins/sfFormExtraPlugin/web/images/indicator.gif [new file with mode: 0644]
plugins/sfFormExtraPlugin/web/images/next.png [new file with mode: 0644]
plugins/sfFormExtraPlugin/web/images/previous.png [new file with mode: 0644]
plugins/sfFormExtraPlugin/web/js/double_list.js [new file with mode: 0644]
plugins/sfFormExtraPlugin/web/js/jquery.autocompleter.js [new file with mode: 0644]

index f134f0e..14c3305 100644 (file)
@@ -136,4 +136,26 @@ class officeActions extends sfActions
       $this->redirect('office/edit?id='.$office->getId());
     }
   }
+
+  public function executeLink(sfWebRequest $request)
+  {
+    $this->forward404Unless($office = Doctrine_Core::getTable('Office')->find(array($request->getParameter('id'))), sprintf('Object office does not exist (%s).', $request->getParameter('id')));
+
+    //Get user Id
+    $userId = $this->getUser()->getGuardUser()->getId();
+
+    //Get company owned by that user and insert value in form
+    $companyUserId = CompanyTable::getInstance()->findOneByUserId($userId)->getId();
+
+    //Get id number sent by the user (never trust the users)
+    $officeId = $request->getParameter('id');
+
+    $companyOfficeId = $office->getCompanyId();
+
+    $this->forward404Unless($companyOfficeId == $companyUserId, sprintf('Office does not exist (%s).', $request->getParameter('id')));
+
+    $officeAds = OfficeAdsTable::getInstance()->findOneByOfficeId($officeId);
+
+    $this->form = new OfficeAdsForm($officeAds, array('companyId' => $companyOfficeId));
+  }
 }
diff --git a/apps/companyfront/modules/office/templates/_formCustomOfficeAds.php b/apps/companyfront/modules/office/templates/_formCustomOfficeAds.php
new file mode 100644 (file)
index 0000000..74bb006
--- /dev/null
@@ -0,0 +1,23 @@
+<?php use_stylesheets_for_form($form) ?>
+<?php use_javascripts_for_form($form) ?>
+<?php use_javascript('/sfFormExtraPlugin/js/double_list.js') ?>
+
+<form action="<?php echo url_for('office/'.($form->getObject()->isNew() ? 'create' : 'update').(!$form->getObject()->isNew() ? '?id='.$form->getObject()->getId() : '')) ?>" method="post" <?php $form->isMultipart() and print 'enctype="multipart/form-data" ' ?>>
+<?php if (!$form->getObject()->isNew()): ?>
+<input type="hidden" name="sf_method" value="put" />
+<?php endif; ?>
+  <table>
+    <tfoot>
+      <tr>
+        <td colspan="2">
+          <?php echo $form->renderHiddenFields(false) ?>
+          &nbsp;<a href="<?php echo url_for('office/index') ?>"><?php echo __('Back to list') ?></a>
+          <input type="submit" value=<?php echo __('Save') ?> />
+        </td>
+      </tr>
+    </tfoot>
+    <tbody>
+        <?php echo $form ?>
+    </tbody>
+  </table>
+</form>
index 67c935d..12cfef5 100644 (file)
@@ -22,7 +22,7 @@
       <td><?php echo $office->getLongitude() ?></td>
          <td><?php echo $office->getLatitude() ?></td>
          <td><a href="<?php echo url_for('office/edit?id='.$office->getId()) ?>"><img src="/images/pencil_add.png" alt="" title="" border="0" /></a></td>
-      <td><a><img src="/images/link.png" alt="" title="" border="0" /></a></td>
+      <td><a href="<?php echo url_for('office/link?id='.$office->getId()) ?>"><img src="/images/link.png" alt="" title="" border="0" /></a></td>
       <td><?php echo link_to('<img src="/images/inadminpanel/images/trash.png" alt="" title="" border="0" />', 'office/delete?id='.$office->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?')) ?></td>
 
     </tr>
diff --git a/apps/companyfront/modules/office/templates/linkSuccess.php b/apps/companyfront/modules/office/templates/linkSuccess.php
new file mode 100644 (file)
index 0000000..f4174d5
--- /dev/null
@@ -0,0 +1,3 @@
+<h2><?php echo __('Link to Ads') ?></h2>
+
+<?php include_partial('formCustomOfficeAds', array('form' => $form)) ?>
index 1015543..a302bcb 100644 (file)
@@ -9,5 +9,6 @@ class ProjectConfiguration extends sfProjectConfiguration
   {
     $this->enablePlugins('sfDoctrinePlugin');
     $this->enablePlugins('sfDoctrineGuardPlugin');
+    $this->enablePlugins('sfFormExtraPlugin');
   }
 }
index f6ea014..f1689bd 100644 (file)
@@ -5,12 +5,50 @@
  *
  * @package    mobiads
  * @subpackage form
- * @author     Your name here
- * @version    SVN: $Id: sfDoctrineFormTemplate.php 23810 2009-11-12 11:07:44Z Kris.Wallsmith $
+ * @author     Gustavo Martin Morcuende
+ * @version
  */
 class OfficeAdsForm extends BaseOfficeAdsForm
 {
   public function configure()
   {
+    //Narrow down options.
+    //We must just show those ads owned by the office's company.
+    $query = AdTable::getInstance()->getAdsByCompanyIdQuery($this->getOption('companyId'));
+
+    $this->useFields(array('ad_id'));
+
+    $this->widgetSchema['ad_id'] = new sfWidgetFormDoctrineChoice(array('model'          => $this->getRelatedModelName('Ad'),
+                                                                        'add_empty'      => false,
+                                                                        'multiple'       => true,
+                                                                        'expanded'       => false,
+                                                                        'renderer_class' => 'sfWidgetFormSelectDoubleList',
+                                                                        'query'          => $query));
+
+    $this->validatorSchema['ad_id'] =  new sfValidatorDoctrineChoice(array('model'    => $this->getRelatedModelName('Ad'),
+                                                                           'multiple' => true,
+                                                                           'query'    => $query));
+  }
+
+ /**
+  * Overriding updateDefaultsFromObject method in order to preselect fields.
+  *
+  * The already chosen ads must be shown as selected.
+  * see: lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/form/sfFormDoctrine.class.php
+  */
+  protected function updateDefaultsFromObject()
+  {
+    parent::updateDefaultsFromObject();
+
+    //We just preselect fields when the Doctrine Record Object comes from the data base.
+    if (!$this->getObject()->isNew())
+    {
+        $officeAds = OfficeAdsTable::getInstance()->findByOfficeId($this->getObject()->getOfficeId());
+        foreach ($officeAds as $officeAd)
+        {
+            $already_chosen[] = $officeAd->getAdId();
+        }
+        $this->setDefault('ad_id', $already_chosen);
+    }
   }
 }
index d721969..1a7b840 100644 (file)
@@ -16,4 +16,15 @@ class AdTable extends Doctrine_Table
     {
         return Doctrine_Core::getTable('Ad');
     }
-}
\ No newline at end of file
+
+
+   /**
+    * Returns ads by company id.
+    *
+    * @return related ads to a company id as Doctrine Query
+    */
+    public function getAdsByCompanyIdQuery($companyId)
+    {
+        return $this->createQuery('ad')->where('ad.company_id = ?', $companyId);
+    }
+}
index 4caab69..cb12534 100644 (file)
@@ -16,4 +16,4 @@ class OfficeAdsTable extends Doctrine_Table
     {
         return Doctrine_Core::getTable('OfficeAds');
     }
-}
\ No newline at end of file
+}
index 074fe08..49d70a6 100644 (file)
@@ -1 +1 @@
-a:1:{s:4:"data";a:86:{s:48:"sfdoctrineguardplugin/config/doctrine/schema.yml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:73:"sfdoctrineguardplugin/config/sfDoctrineGuardPluginConfiguration.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:55:"sfdoctrineguardplugin/data/fixtures/fixtures.yml.sample";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:36:"sfdoctrineguardplugin/data/tasks/.sf";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:42:"sfdoctrineguardplugin/i18n/sf_guard.es.xml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:42:"sfdoctrineguardplugin/i18n/sf_guard.fr.xml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:80:"sfdoctrineguardplugin/lib/filter/doctrine/PluginsfGuardGroupFormFilter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:85:"sfdoctrineguardplugin/lib/filter/doctrine/PluginsfGuardPermissionFormFilter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/lib/filter/doctrine/PluginsfGuardUserFormFilter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:82:"sfdoctrineguardplugin/lib/form/base/BasesfGuardRequestForgotPasswordForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:88:"sfdoctrineguardplugin/lib/form/doctrine/base/BasesfGuardChangeUserPasswordForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:76:"sfdoctrineguardplugin/lib/form/doctrine/base/BasesfGuardFormSignin.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/lib/form/doctrine/base/BasesfGuardRegisterForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/lib/form/doctrine/base/BasesfGuardUserAdminForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:72:"sfdoctrineguardplugin/lib/form/doctrine/PluginsfGuardGroupForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:77:"sfdoctrineguardplugin/lib/form/doctrine/PluginsfGuardPermissionForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:71:"sfdoctrineguardplugin/lib/form/doctrine/PluginsfGuardUserForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/lib/form/doctrine/sfGuardChangeUserPasswordForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:67:"sfdoctrineguardplugin/lib/form/doctrine/sfGuardFormSignin.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:69:"sfdoctrineguardplugin/lib/form/doctrine/sfGuardRegisterForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:70:"sfdoctrineguardplugin/lib/form/doctrine/sfGuardUserAdminForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:73:"sfdoctrineguardplugin/lib/form/sfGuardRequestForgotPasswordForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardForgotPassword.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:83:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardForgotPasswordTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:69:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardGroup.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardGroupPermission.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:84:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardGroupPermissionTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:74:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardGroupTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:74:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardPermission.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardPermissionTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:75:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardRememberKey.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:80:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardRememberKeyTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:68:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardUser.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:73:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardUserGroup.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardUserGroupTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardUserPermission.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:83:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardUserPermissionTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:73:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardUserTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:58:"sfdoctrineguardplugin/lib/routing/sfGuardRouting.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:60:"sfdoctrineguardplugin/lib/task/sfGuardAddGroupTask.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:65:"sfdoctrineguardplugin/lib/task/sfGuardAddPermissionTask.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:66:"sfdoctrineguardplugin/lib/task/sfGuardChangePasswordTask.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:62:"sfdoctrineguardplugin/lib/task/sfGuardCreateUserTask.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:69:"sfdoctrineguardplugin/lib/task/sfGuardPromoteSuperAdminTask.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:60:"sfdoctrineguardplugin/lib/user/sfGuardSecurityUser.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:66:"sfdoctrineguardplugin/lib/validator/sfGuardValidatorUser.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:62:"sfdoctrineguardplugin/lib/sfGuardBasicSecurityFilter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:59:"sfdoctrineguardplugin/lib/sfGuardRememberMeFilter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:67:"sfdoctrineguardplugin/modules/sfGuardAuth/actions/actions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:70:"sfdoctrineguardplugin/modules/sfGuardAuth/actions/components.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:61:"sfdoctrineguardplugin/modules/sfGuardAuth/config/security.yml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/modules/sfGuardAuth/lib/BasesfGuardAuthActions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:81:"sfdoctrineguardplugin/modules/sfGuardAuth/lib/BasesfGuardAuthComponents.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:68:"sfdoctrineguardplugin/modules/sfGuardAuth/templates/_signin_form.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:69:"sfdoctrineguardplugin/modules/sfGuardAuth/templates/secureSuccess.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:69:"sfdoctrineguardplugin/modules/sfGuardAuth/templates/signinSuccess.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:77:"sfdoctrineguardplugin/modules/sfGuardForgotPassword/actions/actions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:98:"sfdoctrineguardplugin/modules/sfGuardForgotPassword/lib/BasesfGuardForgotPasswordActions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/modules/sfGuardForgotPassword/templates/_new_password.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/modules/sfGuardForgotPassword/templates/_send_request.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/modules/sfGuardForgotPassword/templates/changeSuccess.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/modules/sfGuardForgotPassword/templates/indexSuccess.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:68:"sfdoctrineguardplugin/modules/sfGuardGroup/actions/actions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:63:"sfdoctrineguardplugin/modules/sfGuardGroup/config/generator.yml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:91:"sfdoctrineguardplugin/modules/sfGuardGroup/lib/sfGuardGroupGeneratorConfiguration.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:84:"sfdoctrineguardplugin/modules/sfGuardGroup/lib/sfGuardGroupGeneratorHelper.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:73:"sfdoctrineguardplugin/modules/sfGuardPermission/actions/actions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:68:"sfdoctrineguardplugin/modules/sfGuardPermission/config/generator.yml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:101:"sfdoctrineguardplugin/modules/sfGuardPermission/lib/sfGuardPermissionGeneratorConfiguration.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:94:"sfdoctrineguardplugin/modules/sfGuardPermission/lib/sfGuardPermissionGeneratorHelper.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:71:"sfdoctrineguardplugin/modules/sfGuardRegister/actions/actions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:74:"sfdoctrineguardplugin/modules/sfGuardRegister/actions/components.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:86:"sfdoctrineguardplugin/modules/sfGuardRegister/lib/BasesfGuardRegisterActions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:89:"sfdoctrineguardplugin/modules/sfGuardRegister/lib/BasesfGuardRegisterComponents.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:65:"sfdoctrineguardplugin/modules/sfGuardRegister/templates/_form.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:72:"sfdoctrineguardplugin/modules/sfGuardRegister/templates/indexSuccess.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:67:"sfdoctrineguardplugin/modules/sfGuardUser/actions/actions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:62:"sfdoctrineguardplugin/modules/sfGuardUser/config/generator.yml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/modules/sfGuardUser/lib/BasesfGuardUserActions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:89:"sfdoctrineguardplugin/modules/sfGuardUser/lib/sfGuardUserGeneratorConfiguration.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:82:"sfdoctrineguardplugin/modules/sfGuardUser/lib/sfGuardUserGeneratorHelper.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:31:"sfdoctrineguardplugin/.DS_Store";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:29:"sfdoctrineguardplugin/LICENSE";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:28:"sfdoctrineguardplugin/README";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:53:"sfdoctrineguardplugin/sfDoctrineGuardPlugin-5.0.0.tgz";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:29:"sfdoctrineguardplugin/VERSION";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}}}
\ No newline at end of file
+a:1:{s:4:"data";a:118:{s:48:"sfdoctrineguardplugin/config/doctrine/schema.yml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:73:"sfdoctrineguardplugin/config/sfDoctrineGuardPluginConfiguration.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:55:"sfdoctrineguardplugin/data/fixtures/fixtures.yml.sample";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:36:"sfdoctrineguardplugin/data/tasks/.sf";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:42:"sfdoctrineguardplugin/i18n/sf_guard.es.xml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:42:"sfdoctrineguardplugin/i18n/sf_guard.fr.xml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:80:"sfdoctrineguardplugin/lib/filter/doctrine/PluginsfGuardGroupFormFilter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:85:"sfdoctrineguardplugin/lib/filter/doctrine/PluginsfGuardPermissionFormFilter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/lib/filter/doctrine/PluginsfGuardUserFormFilter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:82:"sfdoctrineguardplugin/lib/form/base/BasesfGuardRequestForgotPasswordForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:88:"sfdoctrineguardplugin/lib/form/doctrine/base/BasesfGuardChangeUserPasswordForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:76:"sfdoctrineguardplugin/lib/form/doctrine/base/BasesfGuardFormSignin.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/lib/form/doctrine/base/BasesfGuardRegisterForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/lib/form/doctrine/base/BasesfGuardUserAdminForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:72:"sfdoctrineguardplugin/lib/form/doctrine/PluginsfGuardGroupForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:77:"sfdoctrineguardplugin/lib/form/doctrine/PluginsfGuardPermissionForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:71:"sfdoctrineguardplugin/lib/form/doctrine/PluginsfGuardUserForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/lib/form/doctrine/sfGuardChangeUserPasswordForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:67:"sfdoctrineguardplugin/lib/form/doctrine/sfGuardFormSignin.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:69:"sfdoctrineguardplugin/lib/form/doctrine/sfGuardRegisterForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:70:"sfdoctrineguardplugin/lib/form/doctrine/sfGuardUserAdminForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:73:"sfdoctrineguardplugin/lib/form/sfGuardRequestForgotPasswordForm.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardForgotPassword.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:83:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardForgotPasswordTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:69:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardGroup.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardGroupPermission.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:84:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardGroupPermissionTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:74:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardGroupTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:74:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardPermission.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardPermissionTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:75:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardRememberKey.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:80:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardRememberKeyTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:68:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardUser.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:73:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardUserGroup.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardUserGroupTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardUserPermission.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:83:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardUserPermissionTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:73:"sfdoctrineguardplugin/lib/model/doctrine/PluginsfGuardUserTable.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:58:"sfdoctrineguardplugin/lib/routing/sfGuardRouting.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:60:"sfdoctrineguardplugin/lib/task/sfGuardAddGroupTask.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:65:"sfdoctrineguardplugin/lib/task/sfGuardAddPermissionTask.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:66:"sfdoctrineguardplugin/lib/task/sfGuardChangePasswordTask.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:62:"sfdoctrineguardplugin/lib/task/sfGuardCreateUserTask.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:69:"sfdoctrineguardplugin/lib/task/sfGuardPromoteSuperAdminTask.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:60:"sfdoctrineguardplugin/lib/user/sfGuardSecurityUser.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:66:"sfdoctrineguardplugin/lib/validator/sfGuardValidatorUser.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:62:"sfdoctrineguardplugin/lib/sfGuardBasicSecurityFilter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:59:"sfdoctrineguardplugin/lib/sfGuardRememberMeFilter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:67:"sfdoctrineguardplugin/modules/sfGuardAuth/actions/actions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:70:"sfdoctrineguardplugin/modules/sfGuardAuth/actions/components.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:61:"sfdoctrineguardplugin/modules/sfGuardAuth/config/security.yml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/modules/sfGuardAuth/lib/BasesfGuardAuthActions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:81:"sfdoctrineguardplugin/modules/sfGuardAuth/lib/BasesfGuardAuthComponents.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:68:"sfdoctrineguardplugin/modules/sfGuardAuth/templates/_signin_form.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:69:"sfdoctrineguardplugin/modules/sfGuardAuth/templates/secureSuccess.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:69:"sfdoctrineguardplugin/modules/sfGuardAuth/templates/signinSuccess.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:77:"sfdoctrineguardplugin/modules/sfGuardForgotPassword/actions/actions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:98:"sfdoctrineguardplugin/modules/sfGuardForgotPassword/lib/BasesfGuardForgotPasswordActions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/modules/sfGuardForgotPassword/templates/_new_password.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/modules/sfGuardForgotPassword/templates/_send_request.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:79:"sfdoctrineguardplugin/modules/sfGuardForgotPassword/templates/changeSuccess.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/modules/sfGuardForgotPassword/templates/indexSuccess.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:68:"sfdoctrineguardplugin/modules/sfGuardGroup/actions/actions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:63:"sfdoctrineguardplugin/modules/sfGuardGroup/config/generator.yml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:91:"sfdoctrineguardplugin/modules/sfGuardGroup/lib/sfGuardGroupGeneratorConfiguration.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:84:"sfdoctrineguardplugin/modules/sfGuardGroup/lib/sfGuardGroupGeneratorHelper.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:73:"sfdoctrineguardplugin/modules/sfGuardPermission/actions/actions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:68:"sfdoctrineguardplugin/modules/sfGuardPermission/config/generator.yml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:101:"sfdoctrineguardplugin/modules/sfGuardPermission/lib/sfGuardPermissionGeneratorConfiguration.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:94:"sfdoctrineguardplugin/modules/sfGuardPermission/lib/sfGuardPermissionGeneratorHelper.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:71:"sfdoctrineguardplugin/modules/sfGuardRegister/actions/actions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:74:"sfdoctrineguardplugin/modules/sfGuardRegister/actions/components.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:86:"sfdoctrineguardplugin/modules/sfGuardRegister/lib/BasesfGuardRegisterActions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:89:"sfdoctrineguardplugin/modules/sfGuardRegister/lib/BasesfGuardRegisterComponents.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:65:"sfdoctrineguardplugin/modules/sfGuardRegister/templates/_form.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:72:"sfdoctrineguardplugin/modules/sfGuardRegister/templates/indexSuccess.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:67:"sfdoctrineguardplugin/modules/sfGuardUser/actions/actions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:62:"sfdoctrineguardplugin/modules/sfGuardUser/config/generator.yml";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:78:"sfdoctrineguardplugin/modules/sfGuardUser/lib/BasesfGuardUserActions.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:89:"sfdoctrineguardplugin/modules/sfGuardUser/lib/sfGuardUserGeneratorConfiguration.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:82:"sfdoctrineguardplugin/modules/sfGuardUser/lib/sfGuardUserGeneratorHelper.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:31:"sfdoctrineguardplugin/.DS_Store";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:29:"sfdoctrineguardplugin/LICENSE";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:28:"sfdoctrineguardplugin/README";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:53:"sfdoctrineguardplugin/sfDoctrineGuardPlugin-5.0.0.tgz";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:29:"sfdoctrineguardplugin/VERSION";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:21:"sfdoctrineguardplugin";}s:31:"sfformextraplugin/bin/prove.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:51:"sfformextraplugin/lib/form/sfFormLanguage.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:84:"sfformextraplugin/lib/validator/doctrine/sfValidatorDoctrineNestedSetLevel.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:62:"sfformextraplugin/lib/validator/sfValidatorBlacklist.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:62:"sfformextraplugin/lib/validator/sfValidatorReCaptcha.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:71:"sfformextraplugin/lib/validator/sfValidatorSchemaTimeInterval.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:60:"sfformextraplugin/lib/validator/sfValidatorDefault.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:72:"sfformextraplugin/lib/widget/sfWidgetFormDoctrineChoiceGrouped.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:78:"sfformextraplugin/lib/widget/sfWidgetFormDoctrineJQueryAutocompleter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:70:"sfformextraplugin/lib/widget/sfWidgetFormJQueryAutocompleter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:61:"sfformextraplugin/lib/widget/sfWidgetFormJQueryDate.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:70:"sfformextraplugin/lib/widget/sfWidgetFormPropelChoiceGrouped.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:76:"sfformextraplugin/lib/widget/sfWidgetFormPropelJQueryAutocompleter.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:60:"sfformextraplugin/lib/widget/sfWidgetFormReCaptcha.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:67:"sfformextraplugin/lib/widget/sfWidgetFormSelectDoubleList.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:64:"sfformextraplugin/lib/widget/sfWidgetFormSelectUSState.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:66:"sfformextraplugin/lib/widget/sfWidgetFormTextareaTinyMCE.class.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:50:"sfformextraplugin/test/form/sfFormLanguageTest.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:61:"sfformextraplugin/test/validator/sfValidatorReCaptchaTest.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:61:"sfformextraplugin/test/validator/sfValidatorBlacklistTest.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:70:"sfformextraplugin/test/validator/sfValidatorSchemaTimeIntervalTest.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:59:"sfformextraplugin/test/validator/sfValidatorDefaultTest.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:59:"sfformextraplugin/test/widget/sfWidgetFormReCaptchaTest.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:36:"sfformextraplugin/test/bootstrap.php";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:50:"sfformextraplugin/web/css/jquery.autocompleter.css";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:39:"sfformextraplugin/web/js/double_list.js";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:48:"sfformextraplugin/web/js/jquery.autocompleter.js";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:42:"sfformextraplugin/web/images/indicator.gif";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:37:"sfformextraplugin/web/images/next.png";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:41:"sfformextraplugin/web/images/previous.png";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:24:"sfformextraplugin/README";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}s:25:"sfformextraplugin/LICENSE";a:2:{i:0;s:27:"plugins.symfony-project.org";i:1;s:17:"sfformextraplugin";}}}
\ No newline at end of file
index 7feaa3d..5b236ff 100644 (file)
@@ -1,4 +1,4 @@
 a:18:{s:4:"name";s:7:"symfony";s:7:"attribs";a:5:{s:7:"version";s:3:"2.0";s:5:"xmlns";s:35:"http://pear.php.net/dtd/package-2.0";s:11:"xmlns:tasks";s:33:"http://pear.php.net/dtd/tasks-1.0";s:9:"xmlns:xsi";s:41:"http://www.w3.org/2001/XMLSchema-instance";s:18:"xsi:schemaLocation";s:159:"http://pear.php.net/dtd/tasks-1.0
     http://pear.php.net/dtd/tasks-1.0.xsd
     http://pear.php.net/dtd/package-2.0
-    http://pear.php.net/dtd/package-2.0.xsd";}s:7:"channel";s:24:"pear.symfony-project.com";s:7:"summary";s:7:"symfony";s:11:"description";s:7:"symfony";s:4:"lead";a:4:{s:4:"name";s:16:"Fabien Potencier";s:4:"user";s:6:"fabpot";s:5:"email";s:36:"fabien.potencier@symfony-project.com";s:6:"active";s:3:"yes";}s:4:"date";s:10:"2012-05-10";s:7:"version";a:2:{s:7:"release";s:6:"1.4.18";s:3:"api";s:5:"1.4.0";}s:9:"stability";a:2:{s:7:"release";s:4:"beta";s:3:"api";s:4:"beta";}s:7:"license";s:11:"MIT License";s:5:"notes";s:1:"-";s:8:"contents";a:1:{s:3:"dir";a:1:{s:7:"attribs";a:1:{s:4:"name";s:1:"/";}}}s:12:"dependencies";a:1:{s:8:"required";a:2:{s:3:"php";a:1:{s:3:"min";s:5:"5.2.4";}s:13:"pearinstaller";a:1:{s:3:"min";s:5:"1.4.3";}}}s:10:"phprelease";a:0:{}s:8:"filelist";a:0:{}s:3:"old";a:7:{s:7:"version";s:6:"1.4.18";s:12:"release_date";s:10:"2012-05-10";s:13:"release_state";s:4:"beta";s:15:"release_license";s:11:"MIT License";s:13:"release_notes";s:1:"-";s:12:"release_deps";a:2:{i:0;a:4:{s:4:"type";s:3:"php";s:3:"rel";s:2:"ge";s:7:"version";s:5:"5.2.4";s:8:"optional";s:2:"no";}i:1;a:6:{s:4:"type";s:3:"pkg";s:7:"channel";s:12:"pear.php.net";s:4:"name";s:4:"PEAR";s:3:"rel";s:2:"ge";s:7:"version";s:5:"1.4.3";s:8:"optional";s:2:"no";}}s:11:"maintainers";a:1:{i:0;a:5:{s:4:"name";s:16:"Fabien Potencier";s:5:"email";s:36:"fabien.potencier@symfony-project.com";s:6:"active";s:3:"yes";s:6:"handle";s:6:"fabpot";s:4:"role";s:4:"lead";}}}s:10:"xsdversion";s:3:"2.0";s:13:"_lastmodified";i:1336675411;}
\ No newline at end of file
+    http://pear.php.net/dtd/package-2.0.xsd";}s:7:"channel";s:24:"pear.symfony-project.com";s:7:"summary";s:7:"symfony";s:11:"description";s:7:"symfony";s:4:"lead";a:4:{s:4:"name";s:16:"Fabien Potencier";s:4:"user";s:6:"fabpot";s:5:"email";s:36:"fabien.potencier@symfony-project.com";s:6:"active";s:3:"yes";}s:4:"date";s:10:"2012-05-17";s:7:"version";a:2:{s:7:"release";s:6:"1.4.18";s:3:"api";s:5:"1.4.0";}s:9:"stability";a:2:{s:7:"release";s:4:"beta";s:3:"api";s:4:"beta";}s:7:"license";s:11:"MIT License";s:5:"notes";s:1:"-";s:8:"contents";a:1:{s:3:"dir";a:1:{s:7:"attribs";a:1:{s:4:"name";s:1:"/";}}}s:12:"dependencies";a:1:{s:8:"required";a:2:{s:3:"php";a:1:{s:3:"min";s:5:"5.2.4";}s:13:"pearinstaller";a:1:{s:3:"min";s:5:"1.4.3";}}}s:10:"phprelease";a:0:{}s:8:"filelist";a:0:{}s:3:"old";a:7:{s:7:"version";s:6:"1.4.18";s:12:"release_date";s:10:"2012-05-17";s:13:"release_state";s:4:"beta";s:15:"release_license";s:11:"MIT License";s:13:"release_notes";s:1:"-";s:12:"release_deps";a:2:{i:0;a:4:{s:4:"type";s:3:"php";s:3:"rel";s:2:"ge";s:7:"version";s:5:"5.2.4";s:8:"optional";s:2:"no";}i:1;a:6:{s:4:"type";s:3:"pkg";s:7:"channel";s:12:"pear.php.net";s:4:"name";s:4:"PEAR";s:3:"rel";s:2:"ge";s:7:"version";s:5:"1.4.3";s:8:"optional";s:2:"no";}}s:11:"maintainers";a:1:{i:0;a:5:{s:4:"name";s:16:"Fabien Potencier";s:5:"email";s:36:"fabien.potencier@symfony-project.com";s:6:"active";s:3:"yes";s:6:"handle";s:6:"fabpot";s:4:"role";s:4:"lead";}}}s:10:"xsdversion";s:3:"2.0";s:13:"_lastmodified";i:1337219150;}
\ No newline at end of file
diff --git a/plugins/.registry/.channel.plugins.symfony-project.org/sfformextraplugin.reg b/plugins/.registry/.channel.plugins.symfony-project.org/sfformextraplugin.reg
new file mode 100644 (file)
index 0000000..33f08e5
--- /dev/null
@@ -0,0 +1,12 @@
+a:23:{s:7:"attribs";a:6:{s:5:"xmlns";s:35:"http://pear.php.net/dtd/package-2.0";s:11:"xmlns:tasks";s:33:"http://pear.php.net/dtd/tasks-1.0";s:9:"xmlns:xsi";s:41:"http://www.w3.org/2001/XMLSchema-instance";s:15:"packagerversion";s:5:"1.9.1";s:7:"version";s:3:"2.0";s:18:"xsi:schemaLocation";s:147:"http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd";}s:4:"name";s:17:"sfFormExtraPlugin";s:7:"channel";s:27:"plugins.symfony-project.org";s:7:"summary";s:31:"Validators, Widgets, and Forms.";s:11:"description";s:31:"Validators, Widgets, and Forms.";s:4:"lead";a:4:{s:4:"name";s:16:"Fabien Potencier";s:4:"user";s:6:"fabpot";s:5:"email";s:36:"fabien.potencier@symfony-project.com";s:6:"active";s:3:"yes";}s:9:"developer";a:3:{i:0;a:4:{s:4:"name";s:17:"Nicolas Perriault";s:4:"user";s:7:"nicolas";s:5:"email";s:37:"nicolas.perriault@symfony-project.com";s:6:"active";s:3:"yes";}i:1;a:4:{s:4:"name";s:14:"Kris Wallsmith";s:4:"user";s:14:"Kris.Wallsmith";s:5:"email";s:34:"kris.wallsmith@symfony-project.com";s:6:"active";s:3:"yes";}i:2;a:4:{s:4:"name";s:10:"Hugo Hamon";s:4:"user";s:10:"hugo.hamon";s:5:"email";s:21:"hugo.hamon@sensio.com";s:6:"active";s:3:"yes";}}s:4:"date";s:10:"2010-08-25";s:4:"time";s:8:"14:00:37";s:7:"version";a:2:{s:7:"release";s:5:"1.1.3";s:3:"api";s:5:"1.0.0";}s:9:"stability";a:2:{s:7:"release";s:6:"stable";s:3:"api";s:6:"stable";}s:7:"license";a:2:{s:7:"attribs";a:1:{s:3:"uri";s:38:"http://www.symfony-project.com/license";}s:8:"_content";s:11:"MIT license";}s:5:"notes";s:1:"-";s:8:"contents";a:1:{s:3:"dir";a:2:{s:7:"attribs";a:1:{s:4:"name";s:1:"/";}s:4:"file";a:32:{i:0;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"dd477c00b9182a8fce0a2931b2afe14f";s:4:"name";s:13:"bin/prove.php";s:4:"role";s:4:"data";}}i:1;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"352b4711bca96a9ef711c02ba51a7487";s:4:"name";s:33:"lib/form/sfFormLanguage.class.php";s:4:"role";s:4:"data";}}i:2;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"1374000a0198f4a174f80795e60e7c71";s:4:"name";s:66:"lib/validator/doctrine/sfValidatorDoctrineNestedSetLevel.class.php";s:4:"role";s:4:"data";}}i:3;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"47573deff2c7264ae1a31ead15940eed";s:4:"name";s:44:"lib/validator/sfValidatorBlacklist.class.php";s:4:"role";s:4:"data";}}i:4;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"71d741ed1a332e2c3c5e5eba43061123";s:4:"name";s:44:"lib/validator/sfValidatorReCaptcha.class.php";s:4:"role";s:4:"data";}}i:5;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"f1af2b598238238258888f18dbc8a257";s:4:"name";s:53:"lib/validator/sfValidatorSchemaTimeInterval.class.php";s:4:"role";s:4:"data";}}i:6;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"122f99df59284f63338fd9a1e055e4ff";s:4:"name";s:42:"lib/validator/sfValidatorDefault.class.php";s:4:"role";s:4:"data";}}i:7;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"b0a625150802410c9a39dee434746908";s:4:"name";s:54:"lib/widget/sfWidgetFormDoctrineChoiceGrouped.class.php";s:4:"role";s:4:"data";}}i:8;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"8d3f91f1b54429c94b71517bd3840e20";s:4:"name";s:60:"lib/widget/sfWidgetFormDoctrineJQueryAutocompleter.class.php";s:4:"role";s:4:"data";}}i:9;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"8ca49e7b709485623ebd124950a0a41f";s:4:"name";s:52:"lib/widget/sfWidgetFormJQueryAutocompleter.class.php";s:4:"role";s:4:"data";}}i:10;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"0a004f96c83c50872775bcc698d5dabb";s:4:"name";s:43:"lib/widget/sfWidgetFormJQueryDate.class.php";s:4:"role";s:4:"data";}}i:11;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"8c909201670d42f4d4ddfe46eab45adb";s:4:"name";s:52:"lib/widget/sfWidgetFormPropelChoiceGrouped.class.php";s:4:"role";s:4:"data";}}i:12;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"45adc6b7bfb2296d7bc77a83a441ac88";s:4:"name";s:58:"lib/widget/sfWidgetFormPropelJQueryAutocompleter.class.php";s:4:"role";s:4:"data";}}i:13;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"9feb211dbd9e4e274b7981f41af339cb";s:4:"name";s:42:"lib/widget/sfWidgetFormReCaptcha.class.php";s:4:"role";s:4:"data";}}i:14;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"83860c7eea89ef17f0e4d88268fbb7fc";s:4:"name";s:49:"lib/widget/sfWidgetFormSelectDoubleList.class.php";s:4:"role";s:4:"data";}}i:15;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"4b4bedb41a4adb487ecc39cc47be4eab";s:4:"name";s:46:"lib/widget/sfWidgetFormSelectUSState.class.php";s:4:"role";s:4:"data";}}i:16;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"b4e4b61fba61b26db3ac30bb5933771b";s:4:"name";s:48:"lib/widget/sfWidgetFormTextareaTinyMCE.class.php";s:4:"role";s:4:"data";}}i:17;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"ceecaca65fdf3b4c5e037ab6e696d06a";s:4:"name";s:32:"test/form/sfFormLanguageTest.php";s:4:"role";s:4:"data";}}i:18;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"6ea19158046962f6ad76776ed3ad8673";s:4:"name";s:43:"test/validator/sfValidatorReCaptchaTest.php";s:4:"role";s:4:"data";}}i:19;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"0c08b48fbc88fb98aa6062809cba54c8";s:4:"name";s:43:"test/validator/sfValidatorBlacklistTest.php";s:4:"role";s:4:"data";}}i:20;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"1f6fd7556f6f3a68b7a31ba859c4175b";s:4:"name";s:52:"test/validator/sfValidatorSchemaTimeIntervalTest.php";s:4:"role";s:4:"data";}}i:21;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"92b4a5838358108197afe3afe8aee679";s:4:"name";s:41:"test/validator/sfValidatorDefaultTest.php";s:4:"role";s:4:"data";}}i:22;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"993dc134aefab7538455472af3a3c5a4";s:4:"name";s:41:"test/widget/sfWidgetFormReCaptchaTest.php";s:4:"role";s:4:"data";}}i:23;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"e04220de43865dc7cf4cc7fe5965873b";s:4:"name";s:18:"test/bootstrap.php";s:4:"role";s:4:"data";}}i:24;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"88def8417a8aefc9fa5d833a4f4d0373";s:4:"name";s:32:"web/css/jquery.autocompleter.css";s:4:"role";s:4:"data";}}i:25;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"460d935f81fd9de0c01656fd4f294757";s:4:"name";s:21:"web/js/double_list.js";s:4:"role";s:4:"data";}}i:26;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"b4dc9ff8b1a5569405fe53a1d4dd4f4e";s:4:"name";s:30:"web/js/jquery.autocompleter.js";s:4:"role";s:4:"data";}}i:27;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"03ce3dcc84af110e9da8699a841e5200";s:4:"name";s:24:"web/images/indicator.gif";s:4:"role";s:4:"data";}}i:28;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"64113e3f418860c6c27caa83d8494c73";s:4:"name";s:19:"web/images/next.png";s:4:"role";s:4:"data";}}i:29;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"4485131fafbc7b6bd6018a67671e9040";s:4:"name";s:23:"web/images/previous.png";s:4:"role";s:4:"data";}}i:30;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"9c80350d278bfbcf24b946d86df8fab6";s:4:"name";s:6:"README";s:4:"role";s:4:"data";}}i:31;a:1:{s:7:"attribs";a:3:{s:6:"md5sum";s:32:"4db2c5c669396b6c794f2fe214cb33e8";s:4:"name";s:7:"LICENSE";s:4:"role";s:4:"data";}}}}}s:12:"dependencies";a:1:{s:8:"required";a:2:{s:3:"php";a:1:{s:3:"min";s:5:"5.2.4";}s:13:"pearinstaller";a:1:{s:3:"min";s:5:"1.4.1";}}}s:10:"phprelease";s:0:"";s:9:"changelog";a:1:{s:7:"release";a:4:{i:0;a:5:{s:7:"version";a:2:{s:7:"release";s:5:"1.1.3";s:3:"api";s:5:"1.0.0";}s:9:"stability";a:2:{s:7:"release";s:6:"stable";s:3:"api";s:6:"stable";}s:7:"license";a:2:{i:0;a:2:{s:7:"attribs";a:1:{s:3:"uri";s:38:"http://www.symfony-project.com/license";}s:8:"_content";s:11:"MIT license";}i:1;s:3:"MIT";}s:4:"date";s:10:"2010-08-25";s:5:"notes";s:575:"* xavier: allowed unassociated list to be left of the associated one (#7780)
+* Fabien: added culture and theme options for sfWidgetFormReCaptcha (#8509)
+* Fabien: updated the autocomplete JavaScript to the latest version
+* Fabien: fixed sfWidgetFormJQueryDate doesn't refresh "disabled" after the date was chosen from calendar (#8817)
+* Xavier: fixed sfWidgetFormJQueryDate doesn't restrict values on page load (#7792)
+* Fabien: fixed JS function names when the widget name contains - (#8836)
+* Fabien: added support for sfWidgetFormDateTime in sfWidgetFormJQueryDate (#8950)";}i:1;a:5:{s:7:"version";a:2:{s:7:"release";s:5:"1.1.2";s:3:"api";s:5:"1.0.0";}s:9:"stability";a:2:{s:7:"release";s:6:"stable";s:3:"api";s:6:"stable";}s:7:"license";a:2:{i:0;a:2:{s:7:"attribs";a:1:{s:3:"uri";s:38:"http://www.symfony-project.com/license";}s:8:"_content";s:11:"MIT license";}i:1;s:3:"MIT";}s:4:"date";s:10:"2010-08-03";s:5:"notes";s:314:"* Kris.Wallsmith: added sfValidatorDefault, which returns a default value rather than throw an error
+* Kris.Wallsmith: fixed blacklist validator test
+* FabianLange: fixed incorrect octal parsing of dates in javascript (#7743)
+* Kris.Wallsmith: fixed reading of can_be_empty option in sfWidgetFormJQueryDate (#8723)";}i:2;a:5:{s:7:"version";a:2:{s:7:"release";s:5:"1.1.1";s:3:"api";s:5:"1.0.0";}s:9:"stability";a:2:{s:7:"release";s:6:"stable";s:3:"api";s:6:"stable";}s:7:"license";a:2:{i:0;a:2:{s:7:"attribs";a:1:{s:3:"uri";s:38:"http://www.symfony-project.com/license";}s:8:"_content";s:11:"MIT license";}i:1;s:3:"MIT";}s:4:"date";s:10:"2009-11-30";s:5:"notes";s:232:"* fabien: added a date_widget option to sfWidgetFormJQueryDate (#5382)
+* fabien: changed the sfFormLanguage to take the current user culture into account (#6752)
+* fabien: added sfWidgetFormDoctrineJQueryAutocompleter (#5118, #4943)";}i:3;a:5:{s:7:"version";a:2:{s:7:"release";s:5:"1.1.0";s:3:"api";s:5:"1.0.0";}s:9:"stability";a:2:{s:7:"release";s:6:"stable";s:3:"api";s:6:"stable";}s:7:"license";a:2:{i:0;a:2:{s:7:"attribs";a:1:{s:3:"uri";s:38:"http://www.symfony-project.com/license";}s:8:"_content";s:11:"MIT license";}i:1;s:3:"MIT";}s:4:"date";s:10:"2009-11-30";s:5:"notes";s:50:"* fabien: made the plugin work for symfony 1.3/1.4";}}}s:8:"filelist";a:32:{s:13:"bin/prove.php";a:4:{s:6:"md5sum";s:32:"dd477c00b9182a8fce0a2931b2afe14f";s:4:"name";s:13:"bin/prove.php";s:4:"role";s:4:"data";s:12:"installed_as";s:77:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/bin/prove.php";}s:33:"lib/form/sfFormLanguage.class.php";a:4:{s:6:"md5sum";s:32:"352b4711bca96a9ef711c02ba51a7487";s:4:"name";s:33:"lib/form/sfFormLanguage.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:97:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/form/sfFormLanguage.class.php";}s:66:"lib/validator/doctrine/sfValidatorDoctrineNestedSetLevel.class.php";a:4:{s:6:"md5sum";s:32:"1374000a0198f4a174f80795e60e7c71";s:4:"name";s:66:"lib/validator/doctrine/sfValidatorDoctrineNestedSetLevel.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:130:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/validator/doctrine/sfValidatorDoctrineNestedSetLevel.class.php";}s:44:"lib/validator/sfValidatorBlacklist.class.php";a:4:{s:6:"md5sum";s:32:"47573deff2c7264ae1a31ead15940eed";s:4:"name";s:44:"lib/validator/sfValidatorBlacklist.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:108:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/validator/sfValidatorBlacklist.class.php";}s:44:"lib/validator/sfValidatorReCaptcha.class.php";a:4:{s:6:"md5sum";s:32:"71d741ed1a332e2c3c5e5eba43061123";s:4:"name";s:44:"lib/validator/sfValidatorReCaptcha.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:108:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/validator/sfValidatorReCaptcha.class.php";}s:53:"lib/validator/sfValidatorSchemaTimeInterval.class.php";a:4:{s:6:"md5sum";s:32:"f1af2b598238238258888f18dbc8a257";s:4:"name";s:53:"lib/validator/sfValidatorSchemaTimeInterval.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:117:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/validator/sfValidatorSchemaTimeInterval.class.php";}s:42:"lib/validator/sfValidatorDefault.class.php";a:4:{s:6:"md5sum";s:32:"122f99df59284f63338fd9a1e055e4ff";s:4:"name";s:42:"lib/validator/sfValidatorDefault.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:106:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/validator/sfValidatorDefault.class.php";}s:54:"lib/widget/sfWidgetFormDoctrineChoiceGrouped.class.php";a:4:{s:6:"md5sum";s:32:"b0a625150802410c9a39dee434746908";s:4:"name";s:54:"lib/widget/sfWidgetFormDoctrineChoiceGrouped.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:118:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormDoctrineChoiceGrouped.class.php";}s:60:"lib/widget/sfWidgetFormDoctrineJQueryAutocompleter.class.php";a:4:{s:6:"md5sum";s:32:"8d3f91f1b54429c94b71517bd3840e20";s:4:"name";s:60:"lib/widget/sfWidgetFormDoctrineJQueryAutocompleter.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:124:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormDoctrineJQueryAutocompleter.class.php";}s:52:"lib/widget/sfWidgetFormJQueryAutocompleter.class.php";a:4:{s:6:"md5sum";s:32:"8ca49e7b709485623ebd124950a0a41f";s:4:"name";s:52:"lib/widget/sfWidgetFormJQueryAutocompleter.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:116:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormJQueryAutocompleter.class.php";}s:43:"lib/widget/sfWidgetFormJQueryDate.class.php";a:4:{s:6:"md5sum";s:32:"0a004f96c83c50872775bcc698d5dabb";s:4:"name";s:43:"lib/widget/sfWidgetFormJQueryDate.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:107:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormJQueryDate.class.php";}s:52:"lib/widget/sfWidgetFormPropelChoiceGrouped.class.php";a:4:{s:6:"md5sum";s:32:"8c909201670d42f4d4ddfe46eab45adb";s:4:"name";s:52:"lib/widget/sfWidgetFormPropelChoiceGrouped.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:116:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormPropelChoiceGrouped.class.php";}s:58:"lib/widget/sfWidgetFormPropelJQueryAutocompleter.class.php";a:4:{s:6:"md5sum";s:32:"45adc6b7bfb2296d7bc77a83a441ac88";s:4:"name";s:58:"lib/widget/sfWidgetFormPropelJQueryAutocompleter.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:122:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormPropelJQueryAutocompleter.class.php";}s:42:"lib/widget/sfWidgetFormReCaptcha.class.php";a:4:{s:6:"md5sum";s:32:"9feb211dbd9e4e274b7981f41af339cb";s:4:"name";s:42:"lib/widget/sfWidgetFormReCaptcha.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:106:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormReCaptcha.class.php";}s:49:"lib/widget/sfWidgetFormSelectDoubleList.class.php";a:4:{s:6:"md5sum";s:32:"83860c7eea89ef17f0e4d88268fbb7fc";s:4:"name";s:49:"lib/widget/sfWidgetFormSelectDoubleList.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:113:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormSelectDoubleList.class.php";}s:46:"lib/widget/sfWidgetFormSelectUSState.class.php";a:4:{s:6:"md5sum";s:32:"4b4bedb41a4adb487ecc39cc47be4eab";s:4:"name";s:46:"lib/widget/sfWidgetFormSelectUSState.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:110:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormSelectUSState.class.php";}s:48:"lib/widget/sfWidgetFormTextareaTinyMCE.class.php";a:4:{s:6:"md5sum";s:32:"b4e4b61fba61b26db3ac30bb5933771b";s:4:"name";s:48:"lib/widget/sfWidgetFormTextareaTinyMCE.class.php";s:4:"role";s:4:"data";s:12:"installed_as";s:112:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormTextareaTinyMCE.class.php";}s:32:"test/form/sfFormLanguageTest.php";a:4:{s:6:"md5sum";s:32:"ceecaca65fdf3b4c5e037ab6e696d06a";s:4:"name";s:32:"test/form/sfFormLanguageTest.php";s:4:"role";s:4:"data";s:12:"installed_as";s:96:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/test/form/sfFormLanguageTest.php";}s:43:"test/validator/sfValidatorReCaptchaTest.php";a:4:{s:6:"md5sum";s:32:"6ea19158046962f6ad76776ed3ad8673";s:4:"name";s:43:"test/validator/sfValidatorReCaptchaTest.php";s:4:"role";s:4:"data";s:12:"installed_as";s:107:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/test/validator/sfValidatorReCaptchaTest.php";}s:43:"test/validator/sfValidatorBlacklistTest.php";a:4:{s:6:"md5sum";s:32:"0c08b48fbc88fb98aa6062809cba54c8";s:4:"name";s:43:"test/validator/sfValidatorBlacklistTest.php";s:4:"role";s:4:"data";s:12:"installed_as";s:107:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/test/validator/sfValidatorBlacklistTest.php";}s:52:"test/validator/sfValidatorSchemaTimeIntervalTest.php";a:4:{s:6:"md5sum";s:32:"1f6fd7556f6f3a68b7a31ba859c4175b";s:4:"name";s:52:"test/validator/sfValidatorSchemaTimeIntervalTest.php";s:4:"role";s:4:"data";s:12:"installed_as";s:116:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/test/validator/sfValidatorSchemaTimeIntervalTest.php";}s:41:"test/validator/sfValidatorDefaultTest.php";a:4:{s:6:"md5sum";s:32:"92b4a5838358108197afe3afe8aee679";s:4:"name";s:41:"test/validator/sfValidatorDefaultTest.php";s:4:"role";s:4:"data";s:12:"installed_as";s:105:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/test/validator/sfValidatorDefaultTest.php";}s:41:"test/widget/sfWidgetFormReCaptchaTest.php";a:4:{s:6:"md5sum";s:32:"993dc134aefab7538455472af3a3c5a4";s:4:"name";s:41:"test/widget/sfWidgetFormReCaptchaTest.php";s:4:"role";s:4:"data";s:12:"installed_as";s:105:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/test/widget/sfWidgetFormReCaptchaTest.php";}s:18:"test/bootstrap.php";a:4:{s:6:"md5sum";s:32:"e04220de43865dc7cf4cc7fe5965873b";s:4:"name";s:18:"test/bootstrap.php";s:4:"role";s:4:"data";s:12:"installed_as";s:82:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/test/bootstrap.php";}s:32:"web/css/jquery.autocompleter.css";a:4:{s:6:"md5sum";s:32:"88def8417a8aefc9fa5d833a4f4d0373";s:4:"name";s:32:"web/css/jquery.autocompleter.css";s:4:"role";s:4:"data";s:12:"installed_as";s:96:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/web/css/jquery.autocompleter.css";}s:21:"web/js/double_list.js";a:4:{s:6:"md5sum";s:32:"460d935f81fd9de0c01656fd4f294757";s:4:"name";s:21:"web/js/double_list.js";s:4:"role";s:4:"data";s:12:"installed_as";s:85:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/web/js/double_list.js";}s:30:"web/js/jquery.autocompleter.js";a:4:{s:6:"md5sum";s:32:"b4dc9ff8b1a5569405fe53a1d4dd4f4e";s:4:"name";s:30:"web/js/jquery.autocompleter.js";s:4:"role";s:4:"data";s:12:"installed_as";s:94:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/web/js/jquery.autocompleter.js";}s:24:"web/images/indicator.gif";a:4:{s:6:"md5sum";s:32:"03ce3dcc84af110e9da8699a841e5200";s:4:"name";s:24:"web/images/indicator.gif";s:4:"role";s:4:"data";s:12:"installed_as";s:88:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/web/images/indicator.gif";}s:19:"web/images/next.png";a:4:{s:6:"md5sum";s:32:"64113e3f418860c6c27caa83d8494c73";s:4:"name";s:19:"web/images/next.png";s:4:"role";s:4:"data";s:12:"installed_as";s:83:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/web/images/next.png";}s:23:"web/images/previous.png";a:4:{s:6:"md5sum";s:32:"4485131fafbc7b6bd6018a67671e9040";s:4:"name";s:23:"web/images/previous.png";s:4:"role";s:4:"data";s:12:"installed_as";s:87:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/web/images/previous.png";}s:6:"README";a:4:{s:6:"md5sum";s:32:"9c80350d278bfbcf24b946d86df8fab6";s:4:"name";s:6:"README";s:4:"role";s:4:"data";s:12:"installed_as";s:70:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/README";}s:7:"LICENSE";a:4:{s:6:"md5sum";s:32:"4db2c5c669396b6c794f2fe214cb33e8";s:4:"name";s:7:"LICENSE";s:4:"role";s:4:"data";s:12:"installed_as";s:71:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/LICENSE";}}s:12:"_lastversion";N;s:7:"dirtree";a:15:{s:67:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/bin";b:1;s:63:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin";b:1;s:72:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/form";b:1;s:67:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib";b:1;s:86:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/validator/doctrine";b:1;s:77:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/validator";b:1;s:74:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/lib/widget";b:1;s:73:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/test/form";b:1;s:68:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/test";b:1;s:78:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/test/validator";b:1;s:75:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/test/widget";b:1;s:71:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/web/css";b:1;s:67:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/web";b:1;s:70:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/web/js";b:1;s:74:"/home/gustavo/symfonyreloaded/mobiads/plugins/sfFormExtraPlugin/web/images";b:1;}s:3:"old";a:7:{s:7:"version";s:5:"1.1.3";s:12:"release_date";s:10:"2010-08-25";s:13:"release_state";s:6:"stable";s:15:"release_license";s:11:"MIT license";s:13:"release_notes";s:1:"-";s:12:"release_deps";a:2:{i:0;a:4:{s:4:"type";s:3:"php";s:3:"rel";s:2:"ge";s:7:"version";s:5:"5.2.4";s:8:"optional";s:2:"no";}i:1;a:6:{s:4:"type";s:3:"pkg";s:7:"channel";s:12:"pear.php.net";s:4:"name";s:4:"PEAR";s:3:"rel";s:2:"ge";s:7:"version";s:5:"1.4.1";s:8:"optional";s:2:"no";}}s:11:"maintainers";a:4:{i:0;a:5:{s:4:"name";s:16:"Fabien Potencier";s:5:"email";s:36:"fabien.potencier@symfony-project.com";s:6:"active";s:3:"yes";s:6:"handle";s:6:"fabpot";s:4:"role";s:4:"lead";}i:1;a:5:{s:4:"name";s:17:"Nicolas Perriault";s:5:"email";s:37:"nicolas.perriault@symfony-project.com";s:6:"active";s:3:"yes";s:6:"handle";s:7:"nicolas";s:4:"role";s:9:"developer";}i:2;a:5:{s:4:"name";s:14:"Kris Wallsmith";s:5:"email";s:34:"kris.wallsmith@symfony-project.com";s:6:"active";s:3:"yes";s:6:"handle";s:14:"Kris.Wallsmith";s:4:"role";s:9:"developer";}i:3;a:5:{s:4:"name";s:10:"Hugo Hamon";s:5:"email";s:21:"hugo.hamon@sensio.com";s:6:"active";s:3:"yes";s:6:"handle";s:10:"hugo.hamon";s:4:"role";s:9:"developer";}}}s:10:"xsdversion";s:3:"2.0";s:13:"_lastmodified";i:1337219155;}
\ No newline at end of file
diff --git a/plugins/sfFormExtraPlugin/LICENSE b/plugins/sfFormExtraPlugin/LICENSE
new file mode 100644 (file)
index 0000000..c5e65ea
--- /dev/null
@@ -0,0 +1,19 @@
+Copyright (c) 2008 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/plugins/sfFormExtraPlugin/README b/plugins/sfFormExtraPlugin/README
new file mode 100644 (file)
index 0000000..7f15203
--- /dev/null
@@ -0,0 +1,79 @@
+sfFormExtraPlugin
+=================
+
+The `sfFormExtraPlugin` packages useful validators, widgets, and forms.
+
+This collection holds validators, widgets, and forms which we don't want to
+include with the main symfony package because they are too specific or have
+external dependencies.
+
+As no third party libraries is bundled in the plugin, you need to install and
+load the required dependencies like JQuery, JQuery UI, or TinyMCE by yourself.
+
+Installation
+------------
+
+  * Install the plugin
+
+        $ symfony plugin:install sfFormExtraPlugin
+
+  * Clear the cache
+
+        $ symfony cache:clear
+
+Documentation
+-------------
+
+All classes have full API and usage documentation. The best way to learn each widget or validator
+is to read the API.
+
+You will also find some articles on the symfony blog about this plugin:
+
+  * [Play with the user language](http://www.symfony-project.org/blog/2008/10/16/play-with-the-user-language)
+  * [Make your Choice!](http://www.symfony-project.org/blog/2008/10/14/new-in-symfony-1-2-make-your-choice)
+  * [Spice up your forms with some nice widgets and validators](http://www.symfony-project.org/blog/2008/10/18/spice-up-your-forms-with-some-nice-widgets-and-validators)
+
+Forms
+-----
+
+  * sfFormLanguage: A form to change the symfony user culture
+
+Validators
+----------
+
+  * sfValidatorDoctrineNestedSetLevel: Checks wether or not the max level of a nested set object (nestedSet behavior) is achieved
+  * sfValidatorReCaptcha: Validates a ReCaptcha (see sfWidgetFormReCaptcha)
+  * sfValidatorBlacklist: Validates that a value is not one of the configured forbidden ones
+  * sfValidatorSchemaTimeInterval: Validates a time interval between two dates provided by a widget schema
+  * sfValidatorDefault: Returns a default value rather than throwing an error
+
+Widgets
+-------
+
+  * sfWidgetFormReCaptcha: Displays a ReCaptcha widget (see sfValidatorReCaptcha)
+  * sfWidgetFormSelectDoubleList: Displays a double list widget
+  * sfWidgetFormJQueryDate: Displays a date using JQuery UI
+  * sfWidgetFormJQueryAutocompleter: Displays an input tag with autocomplete support using JQuery
+  * sfWidgetFormPropelChoiceGrouped: Displays a grouped set of choices tied to a Propel model
+  * sfWidgetFormPropelJQueryAutocompleter: Displays an autocomplete widget tied to a Propel model
+  * sfWidgetFormTextareaTinyMCE: A rich textarea rendered with TinyMCE
+  * sfWidgetFormSelectUSState: A select menu of US states
+
+As no third party libraries is bundled in the plugin, you need to install and load the required
+dependencies like JQuery, JQuery UI, or TinyMCE by yourself.
+
+How to contribute.
+------------------
+
+If you want to contribute a validator, a widget, or a form, follow these steps:
+
+  * Check the prerequisites
+    * The license must be MIT
+    * You must have a unit test suite (100% coverage)
+    * You must have PHPdoc for all classes and methods with a documentation usage
+    * You must follow symfony coding standards
+    * The contribution must not be too specific
+    * You must be sure you will be able to maintain your contribution
+  * Create a ticket and attach a patch
+    * Choose `sfFormExtraPlugin` as the component
+    * Change the qualification to `Ready for core team`
diff --git a/plugins/sfFormExtraPlugin/bin/prove.php b/plugins/sfFormExtraPlugin/bin/prove.php
new file mode 100644 (file)
index 0000000..a508bbf
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+
+/*
+ * 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.
+ */
+
+require_once dirname(__FILE__).'/../test/bootstrap.php';
+
+$h = new lime_harness(new lime_output_color());
+$h->base_dir = realpath(dirname(__FILE__).'/../test');
+$h->register(sfFinder::type('file')->name('*Test.php')->in($h->base_dir));
+$h->run();
diff --git a/plugins/sfFormExtraPlugin/lib/form/sfFormLanguage.class.php b/plugins/sfFormExtraPlugin/lib/form/sfFormLanguage.class.php
new file mode 100644 (file)
index 0000000..3e9cfd0
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * sfFormLanguage is a form to change the symfony user culture.
+ *
+ * Usage:
+ *
+ * class mainActions extends sfActions
+ * {
+ *   public function executeChangeLanguage($request)
+ *   {
+ *     $this->form = new sfFormLanguage($this->getUser(), array('languages' => array('en', 'fr')));
+ *     if ($this->form->process($request))
+ *     {
+ *       // culture has changed
+ *       return $this->redirect('@homepage');
+ *     }
+ *
+ *     // the form is not valid (can't happen... but you never know)
+ *     return $this->redirect('@homepage');
+ *   }
+ * }
+ *
+ * @package    symfony
+ * @subpackage form
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id: sfFormLanguage.class.php 30759 2010-08-25 11:48:01Z fabien $
+ */
+class sfFormLanguage extends sfForm
+{
+  protected
+    $user = null;
+
+  /**
+   * Constructor.
+   *
+   * @param sfUser A sfUser instance
+   * @param array  An array of options
+   * @param string A CSRF secret (false to disable CSRF protection, null to use the global CSRF secret)
+   *
+   * @see sfForm
+   */
+  public function __construct(sfUser $user, $options = array(), $CSRFSecret = null)
+  {
+    $this->user = $user;
+
+    if (!isset($options['languages']))
+    {
+      throw new RuntimeException(sprintf('%s requires a "languages" option.', get_class($this)));
+    }
+
+    parent::__construct(array('language' => $user->getCulture()), $options, $CSRFSecret);
+  }
+
+  /**
+   * Changes the current user culture.
+   */
+  public function save()
+  {
+    $this->user->setCulture($this->getValue('language'));
+  }
+
+  /**
+   * Processes the current request.
+   *
+   * @param  sfRequest A sfRequest instance
+   *
+   * @return Boolean   true if the form is valid, false otherwise
+   */
+  public function process(sfRequest $request)
+  {
+    $data = array('language' => $request->getParameter('language'));
+    if ($request->hasParameter(self::$CSRFFieldName))
+    {
+      $data[self::$CSRFFieldName] = $request->getParameter(self::$CSRFFieldName);
+    }
+
+    $this->bind($data);
+
+    if ($isValid = $this->isValid())
+    {
+      $this->save();
+    }
+
+    return $isValid;
+  }
+
+  /**
+   * @see sfForm
+   */
+  public function configure()
+  {
+    $this->setValidators(array(
+      'language' => new sfValidatorI18nChoiceLanguage(array('languages' => $this->options['languages'])),
+    ));
+
+    $this->setWidgets(array(
+      'language' => new sfWidgetFormI18nChoiceLanguage(array('culture' => $this->user->getCulture(), 'languages' => $this->options['languages'])),
+    ));
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/lib/validator/doctrine/sfValidatorDoctrineNestedSetLevel.class.php b/plugins/sfFormExtraPlugin/lib/validator/doctrine/sfValidatorDoctrineNestedSetLevel.class.php
new file mode 100644 (file)
index 0000000..b4fe010
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * sfValidatorDoctrineNestedSetLevel is a class that validates the max allowed  
+ * depth level for Doctrine nested set objects.
+ *
+ * @package    symfony
+ * @subpackage validator
+ * @author     Hugo Hamon <hugo.hamon@sensio.com>
+ */
+class sfValidatorDoctrineNestedSetLevel extends sfValidatorBase 
+{
+  /**
+   * Configures the current validator.
+   *
+   * Available options:
+   *
+   *  * max_level:    The max depth to test (required integer)
+   *  * model:        The model class (required)
+   *  * alias:        The alias of the root component used in the query
+   *  * query:        A query to use when retrieving objects
+   *  * column:       The column name for the where clause statement (use primary key by default)
+   *  * level_column: The level column name (use level by default)
+   *  * connection:   The Doctrine connection to use (null by default)
+   *
+   * Available error messages:
+   * 
+   *  * invalid:        The related object has already the max level value
+   *  * invalid_record: Unable to find the related object
+   * 
+   * @see sfValidatorBase
+   */
+  protected function configure($options = array(), $messages = array())
+  {
+    $this->addRequiredOption('model');
+    $this->addRequiredOption('max_level');
+    $this->addOption('alias', 'a');
+    $this->addOption('query', null);
+    $this->addOption('column', null);
+    $this->addOption('level_column', 'level');
+    $this->addOption('connection', null);
+    
+    $this->addMessage('invalid_record', 'Unable to find the related record');
+  }
+  
+  /**
+   * @see sfValidatorBase
+   * 
+   * @throws sfValidatorError
+   */
+  protected function doClean($value)
+  {
+    $level = $this->getObjectLevelValue($value);
+
+    if ($level >= (int) $this->getOption('max_level'))
+    {
+      throw new sfValidatorError($this, 'invalid', array('value' => $level));
+    }
+
+    return $value;
+  }
+  
+  /**
+   * Returns the level column to use for comparison.
+   *
+   * The primary key is used by default.
+   *
+   * @return string The column name
+   */
+  protected function getWhereColumn()
+  {
+    $table = $this->getDoctrineTable();
+    
+    if ($this->getOption('column'))
+    {
+      return $table->getColumnName($this->getOption('column'));
+    }
+
+    $identifier = (array) $table->getIdentifier();
+    $columnName = current($identifier);
+
+    return $table->getColumnName($columnName);
+  }
+  
+  /**
+   * Returns the level column name
+   *
+   * @return string The column name
+   */
+  protected function getLevelColumn()
+  {
+    $table = $this->getDoctrineTable();
+    
+    return $table->getColumnName($this->getOption('level_column'));
+  }
+  
+  /**
+   * Returns the Doctrine table object
+   *
+   * @return Doctrine_Table
+   */
+  protected function getDoctrineTable()
+  {
+    return Doctrine::getTable($this->getOption('model'));
+  }
+  
+  /**
+   * Returns the object level's value
+   *
+   * @param mixed $value The value of the where column to retrieve the object
+   * 
+   * @return int The level value of the found object
+   * 
+   * @throws sfValidatorError
+   */
+  protected function getObjectLevelValue($value)
+  {
+    $a = $this->getOption('alias');
+    $q = is_null($this->getOption('query')) ? Doctrine_Query::create()->from($this->getOption('model') . ' ' . $a) : $this->getOption('query');
+    $q->select($a . '.' . $this->getLevelColumn());
+    $q->addWhere($a . '.' . $this->getWhereColumn() . ' = ?', $value);
+
+    $result = $q->fetchOne(array(), Doctrine::HYDRATE_ARRAY);
+    
+    if (!$result)
+    {
+      throw new sfValidatorError($this, 'invalid_record', array('value' => $value)); 
+    }
+    
+    return (int) $result[ $this->getLevelColumn() ];
+  }
+}
\ No newline at end of file
diff --git a/plugins/sfFormExtraPlugin/lib/validator/sfValidatorBlacklist.class.php b/plugins/sfFormExtraPlugin/lib/validator/sfValidatorBlacklist.class.php
new file mode 100644 (file)
index 0000000..a994763
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * sfValidatorBlacklist validates than the value is not one of the configured
+ * forbidden values. This is a kind of opposite of the sfValidatorChoice
+ * validator.
+ *
+ * @package    symfony
+ * @subpackage validator
+ * @author     Nicolas Perriault <nicolas.perriault@symfony-project.com>
+ * @version    SVN: $Id: sfValidatorChoice.class.php 9048 2008-05-19 09:11:23Z FabianLange $
+ */
+class sfValidatorBlacklist extends sfValidatorBase
+{
+  /**
+   * Configures the current validator.
+   *
+   * Available options:
+   *
+   *  * forbidden_values: An array of forbidden values (required)
+   *  * case_sensitive:   Case sensitive comparison (default true)
+   *
+   * @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('forbidden_values');
+    $this->addOption('case_sensitive', true);
+    $this->addMessage('forbidden', 'Value %value% is forbidden');
+  }
+
+  /**
+   * @see sfValidatorBase
+   */
+  protected function doClean($value)
+  {
+    $forbiddenValues = $this->getOption('forbidden_values');
+    if ($forbiddenValues instanceof sfCallable)
+    {
+      $forbiddenValues = $forbiddenValues->call();
+    }
+
+    $checkValue = $value;
+
+    if (false === $this->getOption('case_sensitive'))
+    {
+      $checkValue = strtolower($checkValue);
+      $forbiddenValues = array_map('strtolower', $forbiddenValues);
+    }
+
+    if (in_array($checkValue, $forbiddenValues))
+    {
+      throw new sfValidatorError($this, 'forbidden', array('value' => $value));
+    }
+
+    return $value;
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/lib/validator/sfValidatorDefault.class.php b/plugins/sfFormExtraPlugin/lib/validator/sfValidatorDefault.class.php
new file mode 100644 (file)
index 0000000..5a3a096
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * Returns a default value instead of throwing an error on validation failure.
+ *
+ *     $this->validatorSchema['sort'] = new sfValidatorDefault(array(
+ *       'validator' => new sfValidatorChoice(array('choices' => array('up', 'down'))),
+ *       'default'   => 'up',
+ *     ));
+ *
+ * If no default option is provided, the supplied validator's empty value will
+ * be returned on error.
+ *
+ * @package    sfFormExtraPlugin
+ * @subpackage validator
+ * @author     Kris Wallsmith <kris.wallsmith@symfony-project.com>
+ * @version    SVN: $Id: sfValidatorDefault.class.php 27625 2010-02-06 22:07:40Z Kris.Wallsmith $
+ */
+class sfValidatorDefault extends sfValidatorBase
+{
+  /**
+   * Configures the current validator.
+   *
+   * Available options:
+   *
+   *  * validator: The validator to use
+   *  * default:   The value to return if the validator fails
+   *
+   * @see sfValidatorBase
+   */
+  protected function configure($options = array(), $messages = array())
+  {
+    $this->addRequiredOption('validator');
+    $this->addOption('default', null);
+  }
+
+  /**
+   * @see sfValidatorBase
+   */
+  protected function isEmpty($value)
+  {
+    return false;
+  }
+
+  /**
+   * @see sfValidatorBase
+   *
+   * @throws InvalidArgumentException If the validator option is not a validator object
+   */
+  protected function doClean($value)
+  {
+    $validator = $this->getOption('validator');
+
+    if (!$validator instanceof sfValidatorBase)
+    {
+      throw new InvalidArgumentException('The "validator" option must be an instance of sfValidatorBase.');
+    }
+
+    try
+    {
+      return $validator->clean($value);
+    }
+    catch (sfValidatorError $error)
+    {
+      return null === $this->getOption('default') ? $validator->getEmptyValue() : $this->getOption('default');
+    }
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/lib/validator/sfValidatorReCaptcha.class.php b/plugins/sfFormExtraPlugin/lib/validator/sfValidatorReCaptcha.class.php
new file mode 100644 (file)
index 0000000..f46845c
--- /dev/null
@@ -0,0 +1,134 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * sfValidatorReCaptcha validates a ReCaptcha.
+ *
+ * This validator uses ReCaptcha: http://recaptcha.net/
+ *
+ * The ReCaptcha API documentation can be found at http://recaptcha.net/apidocs/captcha/
+ *
+ * To be able to use this validator, you need an API key: http://recaptcha.net/api/getkey
+ *
+ * To create a captcha validator:
+ *
+ *    $captcha = new sfValidatorReCaptcha(array('private_key' => RECAPTCHA_PRIVATE_KEY));
+ *
+ * where RECAPTCHA_PRIVATE_KEY is the ReCaptcha private key.
+ *
+ * @package    symfony
+ * @subpackage validator
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id: sfValidatorReCaptcha.class.php 7903 2008-03-15 13:17:41Z fabien $
+ */
+class sfValidatorReCaptcha extends sfValidatorBase
+{
+  /**
+   * Configures the current validator.
+   *
+   * Available options:
+   *
+   *  * private_key:    The ReCaptcha private key (required)
+   *  * remote_addr:    The remote address of the user
+   *  * server_host:    The ReCaptcha server host
+   *  * server_port:    The ReCaptcha server port
+   *  * server_path:    The ReCatpcha server path
+   *  * server_timeout: The timeout to use when contacting the ReCaptcha server
+   *
+   * Available error codes:
+   *
+   *  * captcha
+   *  * server_problem
+   *
+   * @see sfValidatorBase
+   */
+  protected function configure($options = array(), $messages = array())
+  {
+    $this->addRequiredOption('private_key');
+
+    $this->addOption('remote_addr');
+    $this->addOption('server_host', 'api-verify.recaptcha.net');
+    $this->addOption('server_port', 80);
+    $this->addOption('server_path', '/verify');
+    $this->addOption('server_timeout', 10);
+
+    $this->addMessage('captcha', 'The captcha is not valid (%error%).');
+    $this->addMessage('server_problem', 'Unable to check the captcha from the server (%error%).');
+  }
+
+  /**
+   * Cleans the input value.
+   *
+   * The input value must be an array with 2 required keys: recaptcha_challenge_field and recaptcha_response_field.
+   *
+   * It always returns null.
+   *
+   * @see sfValidatorBase
+   */
+  protected function doClean($value)
+  {
+    $challenge = isset($value['recaptcha_challenge_field']) ? $value['recaptcha_challenge_field'] : null;
+    $response = isset($value['recaptcha_response_field']) ? $value['recaptcha_response_field'] : null;
+    if (empty($challenge) || empty($response))
+    {
+      throw new sfValidatorError($this, 'captcha', array('error' => 'invalid captcha'));
+    }
+
+    if (true !== ($answer = $this->check(array(
+      'privatekey' => $this->getOption('private_key'),
+      'remoteip'   => $this->getOption('remote_addr') ? $this->getOption('remote_addr') : $_SERVER['REMOTE_ADDR'],
+      'challenge'  => $challenge,
+      'response'   => $response,
+    ))))
+    {
+      throw new sfValidatorError($this, 'captcha', array('error' => $answer));
+    }
+
+    return null;
+  }
+
+  /**
+   * Returns true if the captcha is valid.
+   *
+   * @param  array   An array of request parameters
+   *
+   * @return Boolean true if the captcha is valid, false otherwise
+   */
+  protected function check($parameters)
+  {
+    if (false === ($fs = @fsockopen($this->getOption('server_host'), $this->getOption('server_port'), $errno, $errstr, $this->getOption('server_timeout'))))
+    {
+      throw new sfValidatorError($this, 'server_problem', array('error' => $errstr));
+    }
+
+    $query = http_build_query($parameters, null, '&');
+    fwrite($fs, sprintf(
+                  "POST %s HTTP/1.0\r\n".
+                  "Host: %s\r\n".
+                  "Content-Type: application/x-www-form-urlencoded\r\n".
+                  "Content-Length: %d\r\n".
+                  "User-Agent: reCAPTCHA/PHP/symfony\r\n".
+                  "\r\n%s",
+                $this->getOption('server_path'), $this->getOption('server_host'), strlen($query), $query)
+    );
+
+    $response = '';
+    while (!feof($fs))
+    {
+      $response .= fgets($fs, 1160);
+    }
+    fclose($fs);
+
+    $response = explode("\r\n\r\n", $response, 2);
+    $answers = explode("\n", $response[1]);
+
+    return 'true' == trim($answers[0]) ? true : $answers[1];
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/lib/validator/sfValidatorSchemaTimeInterval.class.php b/plugins/sfFormExtraPlugin/lib/validator/sfValidatorSchemaTimeInterval.class.php
new file mode 100644 (file)
index 0000000..c042dd1
--- /dev/null
@@ -0,0 +1,180 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * This schema validator validates a time interval between two dates, provided
+ * by two fields in the schema.
+ *
+ * Several options are available:
+ *
+ *  - date_start_field:      The start date field name
+ *  - date_end_field:        The end date field name
+ *  - min_duration:          The minimum duration of interval between the two dates, in seconds (optional)
+ *  - max_duration:          The maximum duration of interval between the two dates, in seconds (optional)
+ *  - disallow_future_dates: Disallows dates in the future (defaults to false)
+ *  - disallow_past_dates:   Disallows dates in the past (defaults to false)
+ *  - throw_global_error:    Throws a global error (defaults to false)
+ *
+ * These error codes are available:
+ *
+ *  - future_date:           A date is in the future
+ *  - past_date:             A date is in the past
+ *  - start_not_prior:       The start date is not prior to end date
+ *  - too_short:             The duration is too short
+ *  - too_long:              The maximum duration has been exceeded
+ *
+ * Error messages available arguments:
+ *
+ *  - date_start:            The submitted start date
+ *  - date_end:              The submitted end date
+ *
+ * @package    symfony
+ * @subpackage validator
+ * @author     Nicolas Perriault <nicolas.perriault@symfony-project.com>
+ */
+class sfValidatorSchemaTimeInterval extends sfValidatorSchema
+{
+  protected
+    $dateStart = null,
+    $dateEnd   = null;
+
+  /**
+   * Public constructor
+   *
+   * @param  string  $dateStartField  The name of the start date field
+   * @param  string  $dateEndField    The name of the end date field
+   * @param  array   $options         Options array
+   * @param  array   $messages        Error messages array
+   */
+  public function __construct($dateStartField, $dateEndField, $options = array(), $messages = array())
+  {
+    // Validator options
+    $this->addOption('date_start_field', $dateStartField);
+    $this->addOption('date_end_field', $dateEndField);
+    $this->addOption('min_duration', null);
+    $this->addOption('max_duration', null);
+    $this->addOption('disallow_future_dates', false);
+    $this->addOption('disallow_past_dates', false);
+    $this->addOption('throw_global_error', false);
+
+    // Validation error messages
+    $this->addMessage('future_date', 'The date cannot be in the future');
+    $this->addMessage('past_date', 'The date cannot be in the past');
+    $this->addMessage('too_short', 'The time interval between the two dates is too short');
+    $this->addMessage('too_long', 'The time interval between the two dates is too long');
+    $this->addMessage('start_not_prior', 'The start date must be prior to the end date');
+
+    // Parent constructor call
+    parent::__construct(null, $options, $messages);
+  }
+
+  /**
+   * Cleans the schema values
+   *
+   * @see sfValidatorSchema
+   *
+   * @param  array  $values  Values to validate
+   * @return array
+   * @throws InvalidArgumentException if $values is not an array
+   * @throws sfValidatorError
+   * @throws sfValidatorErrorSchema
+   */
+  protected function doClean($values)
+  {
+    if (is_null($values))
+    {
+      $values = array();
+    }
+
+    if (!is_array($values))
+    {
+      throw new InvalidArgumentException('You must pass an array parameter to the clean() method');
+    }
+
+    $this->dateStart = isset($values[$this->getOption('date_start_field')]) ? strtotime($values[$this->getOption('date_start_field')]) : null;
+    $this->dateEnd   = isset($values[$this->getOption('date_end_field')]) ? strtotime($values[$this->getOption('date_end_field')]) : null;
+
+    $errorCode = null;
+    $errorField = null;
+
+    if ($this->getOption('disallow_future_dates'))
+    {
+      if (!is_null($this->dateStart) && $this->dateStart > time())
+      {
+        $this->throwError('future_date', $this->getOption('date_start_field'));
+      }
+      else if (!is_null($this->dateEnd) && $this->dateEnd > time())
+      {
+        $this->throwError('future_date', $this->getOption('date_end_field'));
+      }
+    }
+
+    if ($this->getOption('disallow_past_dates'))
+    {
+      if (!is_null($this->dateStart) && $this->dateStart < time())
+      {
+        $this->throwError('past_date', $this->getOption('date_start_field'));
+      }
+      else if (!is_null($this->dateEnd) && $this->dateEnd < time())
+      {
+        $this->throwError('past_date', $this->getOption('date_end_field'));
+      }
+    }
+
+    // At this point, if either the start or end date is not set we can return values
+    if (is_null($this->dateStart) or is_null($this->dateEnd))
+    {
+      return $values;
+    }
+
+    // Duration
+    $duration = $this->dateEnd - $this->dateStart;
+
+    if ($this->hasOption('min_duration') && $duration < $this->getOption('min_duration'))
+    {
+      $this->throwError('too_short', $this->getOption('date_end_field'));
+    }
+
+    if ($this->hasOption('max_duration') && $duration > $this->getOption('max_duration'))
+    {
+      $this->throwError('too_long', $this->getOption('date_end_field'));
+    }
+
+    if ($this->dateStart > $this->dateEnd)
+    {
+      $this->throwError('start_not_prior', $this->getOption('date_start_field'));
+    }
+
+    return $values;
+  }
+
+  /**
+   * Throws a validation error
+   *
+   * @param  string  $code   The error code
+   * @param  string  $field  The field related to the error
+   * @throws sfValidatorError
+   * @throws sfValidatorErrorSchema
+   */
+  protected function throwError($code, $field)
+  {
+    $error = new sfValidatorError($this, $code, array(
+      'date_start' => date('Y-m-d', $this->dateStart),
+      'date_end'   => date('Y-m-d', $this->dateEnd),
+    ));
+
+    if ($this->getOption('throw_global_error'))
+    {
+      throw $error;
+    }
+
+    throw new sfValidatorErrorSchema($this, array($field => $error));
+  }
+}
\ No newline at end of file
diff --git a/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormDoctrineChoiceGrouped.class.php b/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormDoctrineChoiceGrouped.class.php
new file mode 100644 (file)
index 0000000..02ffc49
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * A widget of grouped choices.
+ * 
+ * @package     sfFormExtraPlugin
+ * @subpackage  widget
+ * @author      Kris Wallsmith <kris.wallsmith@symfony-project.com>
+ * @version     SVN: $Id: sfWidgetFormDoctrineChoiceGrouped.class.php 16265 2009-03-12 15:23:41Z Kris.Wallsmith $
+ */
+class sfWidgetFormDoctrineChoiceGrouped extends sfWidgetFormDoctrineChoice
+{
+  /**
+   * Available options:
+   * 
+   *  * group_by: The name of the relation to use for grouping
+   * 
+   * @see sfWidget
+   */
+  protected function configure($options = array(), $attributes = array())
+  {
+    $this->addRequiredOption('group_by');
+
+    parent::configure($options, $attributes);
+  }
+
+  /**
+   * @see sfWidgetFormDoctrineChoice
+   */
+  public function getChoices()
+  {
+    if (is_null($this->getOption('table_method')))
+    {
+      $query = is_null($this->getOption('query')) ? Doctrine::getTable($this->getOption('model'))->createQuery() : $this->getOption('query');
+
+      if ($order = $this->getOption('order_by'))
+      {
+        $query->addOrderBy($order[0].' '.$order[1]);
+      }
+
+      $objects = $query->execute();
+    }
+    else
+    {
+      $tableMethod = $this->getOption('table_method');
+      $results = Doctrine::getTable($this->getOption('model'))->$tableMethod();
+
+      if ($results instanceof Doctrine_Query)
+      {
+        $objects = $results->execute();
+      }
+      else if ($results instanceof Doctrine_Collection)
+      {
+        $objects = $results;
+      }
+      else if ($results instanceof Doctrine_Record)
+      {
+        $objects = new Doctrine_Collection($this->getOption('model'));
+        $objects[] = $results;
+      }
+      else
+      {
+        $objects = array();
+      }
+    }
+
+    $choices = array();
+    if (false !== $this->getOption('add_empty'))
+    {
+      $choices[''] = true === $this->getOption('add_empty') ? '' : $this->getOption('add_empty');
+    }
+
+    $method = $this->getOption('method');
+    $keyMethod = $this->getOption('key_method');
+    $groupBy = $this->getOption('group_by');
+
+    foreach ($objects as $object)
+    {
+      $parent = (string) $object->$groupBy;
+
+      if (!isset($choices[$parent]))
+      {
+        $choices[$parent] = array();
+      }
+
+      $choices[$parent][$object->$keyMethod()] = $object->$method();
+    }
+
+    return $choices;
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormDoctrineJQueryAutocompleter.class.php b/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormDoctrineJQueryAutocompleter.class.php
new file mode 100644 (file)
index 0000000..c05854e
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * sfWidgetFormDoctrineJQueryAutocompleter represents an autocompleter input widget rendered by JQuery
+ * optimized for foreign key lookup.
+ *
+ * This implementation is based on sfWidgetFormPropelJQueryAutocompleter.
+ *
+ * @package    symfony
+ * @subpackage widget
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @author     Roland Jungwirth <roland@top-node.com>
+ * @version    SVN: $Id: sfWidgetFormPropelJQueryAutocompleter.class.php 12130 2008-10-10 14:51:07Z fabien $
+ */
+class sfWidgetFormDoctrineJQueryAutocompleter extends sfWidgetFormJQueryAutocompleter
+{
+  /**
+   * @see sfWidget
+   */
+  public function __construct($options = array(), $attributes = array())
+  {
+    $options['value_callback'] = array($this, 'toString');
+
+    parent::__construct($options, $attributes);
+  }
+
+  /**
+   * Configures the current widget.
+   *
+   * @param array $options     An array of options
+   * @param array $attributes  An array of default HTML attributes
+   *
+   * @see sfWidgetFormJQueryAutocompleter
+   */
+  protected function configure($options = array(), $attributes = array())
+  {
+    $this->addRequiredOption('model');
+    $this->addOption('method_for_query', 'findOneById');
+    $this->addOption('method', '__toString');
+
+    parent::configure($options, $attributes);
+  }
+
+  /**
+   * Returns the text representation of a foreign key.
+   *
+   * @param string $value The primary key
+   */
+  protected function toString($value)
+  {
+    $object = null;
+    if ($value != null)
+    {
+      $class = Doctrine::getTable($this->getOption('model'));
+      $method = $this->getOption('method_for_query');
+
+      $object = call_user_func(array($class, $method), $value);
+    }
+
+    $method = $this->getOption('method');
+
+    if (!method_exists($this->getOption('model'), $method))
+    {
+      throw new RuntimeException(sprintf('Class "%s" must implement a "%s" method to be rendered in a "%s" widget', $this->getOption('model'), $method, __CLASS__));
+    }
+
+    return !is_null($object) ? $object->$method() : '';
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormJQueryAutocompleter.class.php b/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormJQueryAutocompleter.class.php
new file mode 100644 (file)
index 0000000..d66b09b
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * sfWidgetFormJQueryAutocompleter represents an autocompleter input widget rendered by JQuery.
+ *
+ * This widget needs JQuery to work.
+ *
+ * You also need to include the JavaScripts and stylesheets files returned by the getJavaScripts()
+ * and getStylesheets() methods.
+ *
+ * If you use symfony 1.2, it can be done automatically for you.
+ *
+ * @package    symfony
+ * @subpackage widget
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id: sfWidgetFormJQueryAutocompleter.class.php 15839 2009-02-27 05:40:57Z fabien $
+ */
+class sfWidgetFormJQueryAutocompleter extends sfWidgetFormInput
+{
+  /**
+   * Configures the current widget.
+   *
+   * Available options:
+   *
+   *  * url:            The URL to call to get the choices to use (required)
+   *  * config:         A JavaScript array that configures the JQuery autocompleter widget
+   *  * value_callback: A callback that converts the value before it is displayed
+   *
+   * @param array $options     An array of options
+   * @param array $attributes  An array of default HTML attributes
+   *
+   * @see sfWidgetForm
+   */
+  protected function configure($options = array(), $attributes = array())
+  {
+    $this->addRequiredOption('url');
+    $this->addOption('value_callback');
+    $this->addOption('config', '{ }');
+
+    // this is required as it can be used as a renderer class for sfWidgetFormChoice
+    $this->addOption('choices');
+
+    parent::configure($options, $attributes);
+  }
+
+  /**
+   * @param  string $name        The element name
+   * @param  string $value       The date displayed in this widget
+   * @param  array  $attributes  An array of HTML attributes to be merged with the default HTML attributes
+   * @param  array  $errors      An array of errors for the field
+   *
+   * @return string An HTML tag string
+   *
+   * @see sfWidgetForm
+   */
+  public function render($name, $value = null, $attributes = array(), $errors = array())
+  {
+    $visibleValue = $this->getOption('value_callback') ? call_user_func($this->getOption('value_callback'), $value) : $value;
+
+    return $this->renderTag('input', array('type' => 'hidden', 'name' => $name, 'value' => $value)).
+           parent::render('autocomplete_'.$name, $visibleValue, $attributes, $errors).
+           sprintf(<<<EOF
+<script type="text/javascript">
+  jQuery(document).ready(function() {
+    jQuery("#%s")
+    .autocomplete('%s', jQuery.extend({}, {
+      dataType: 'json',
+      parse:    function(data) {
+        var parsed = [];
+        for (key in data) {
+          parsed[parsed.length] = { data: [ data[key], key ], value: data[key], result: data[key] };
+        }
+        return parsed;
+      }
+    }, %s))
+    .result(function(event, data) { jQuery("#%s").val(data[1]); });
+  });
+</script>
+EOF
+      ,
+      $this->generateId('autocomplete_'.$name),
+      $this->getOption('url'),
+      $this->getOption('config'),
+      $this->generateId($name)
+    );
+  }
+
+  /**
+   * Gets the stylesheet paths associated with the widget.
+   *
+   * @return array An array of stylesheet paths
+   */
+  public function getStylesheets()
+  {
+    return array('/sfFormExtraPlugin/css/jquery.autocompleter.css' => 'all');
+  }
+
+  /**
+   * Gets the JavaScript paths associated with the widget.
+   *
+   * @return array An array of JavaScript paths
+   */
+  public function getJavascripts()
+  {
+    return array('/sfFormExtraPlugin/js/jquery.autocompleter.js');
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormJQueryDate.class.php b/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormJQueryDate.class.php
new file mode 100644 (file)
index 0000000..f73bb8b
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * sfWidgetFormJQueryDate represents a date widget rendered by JQuery UI.
+ *
+ * This widget needs JQuery and JQuery UI to work.
+ *
+ * @package    symfony
+ * @subpackage widget
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id: sfWidgetFormJQueryDate.class.php 30755 2010-08-25 11:14:33Z fabien $
+ */
+class sfWidgetFormJQueryDate extends sfWidgetForm
+{
+  /**
+   * Configures the current widget.
+   *
+   * Available options:
+   *
+   *  * image:       The image path to represent the widget (false by default)
+   *  * config:      A JavaScript array that configures the JQuery date widget
+   *  * culture:     The user culture
+   *  * date_widget: The date widget instance to use as a "base" class
+   *
+   * @param array $options     An array of options
+   * @param array $attributes  An array of default HTML attributes
+   *
+   * @see sfWidgetForm
+   */
+  protected function configure($options = array(), $attributes = array())
+  {
+    $this->addOption('image', false);
+    $this->addOption('config', '{}');
+    $this->addOption('culture', '');
+    $this->addOption('date_widget', new sfWidgetFormDate());
+
+    parent::configure($options, $attributes);
+
+    if ('en' == $this->getOption('culture'))
+    {
+      $this->setOption('culture', 'en');
+    }
+  }
+
+  /**
+   * @param  string $name        The element name
+   * @param  string $value       The date displayed in this widget
+   * @param  array  $attributes  An array of HTML attributes to be merged with the default HTML attributes
+   * @param  array  $errors      An array of errors for the field
+   *
+   * @return string An HTML tag string
+   *
+   * @see sfWidgetForm
+   */
+  public function render($name, $value = null, $attributes = array(), $errors = array())
+  {
+    $prefix = str_replace('-', '_', $this->generateId($name));
+
+    $image = '';
+    if (false !== $this->getOption('image'))
+    {
+      $image = sprintf(', buttonImage: "%s", buttonImageOnly: true', $this->getOption('image'));
+    }
+
+    if ($this->getOption('date_widget') instanceof sfWidgetFormDateTime)
+    {
+      $years = $this->getOption('date_widget')->getDateWidget()->getOption('years');
+    }
+    else
+    {
+      $years = $this->getOption('date_widget')->getOption('years');
+    }
+
+    return $this->getOption('date_widget')->render($name, $value, $attributes, $errors).
+           $this->renderTag('input', array('type' => 'hidden', 'size' => 10, 'id' => $id = $this->generateId($name).'_jquery_control', 'disabled' => 'disabled')).
+           sprintf(<<<EOF
+<script type="text/javascript">
+  function wfd_%s_read_linked()
+  {
+    jQuery("#%s").val(jQuery("#%s").val() + "-" + jQuery("#%s").val() + "-" + jQuery("#%s").val());
+
+    return {};
+  }
+
+  function wfd_%s_update_linked(date)
+  {
+    jQuery("#%s").val(parseInt(date.substring(0, 4), 10));
+    jQuery("#%s").val(parseInt(date.substring(5, 7), 10));
+    jQuery("#%s").val(parseInt(date.substring(8), 10));
+
+    wfd_%s_check_linked_days();
+  }
+
+  function wfd_%s_check_linked_days()
+  {
+    var daysInMonth = 32 - new Date(jQuery("#%s").val(), jQuery("#%s").val() - 1, 32).getDate();
+
+    jQuery("#%s option").attr("disabled", "");
+    jQuery("#%s option:gt(" + (%s) +")").attr("disabled", "disabled");
+
+    if (jQuery("#%s").val() > daysInMonth)
+    {
+      jQuery("#%s").val(daysInMonth);
+    }
+  }
+
+  jQuery(document).ready(function() {
+    jQuery("#%s").datepicker(jQuery.extend({}, {
+      minDate:    new Date(%s, 1 - 1, 1),
+      maxDate:    new Date(%s, 12 - 1, 31),
+      beforeShow: wfd_%s_read_linked,
+      onSelect:   wfd_%s_update_linked,
+      showOn:     "button"
+      %s
+    }, jQuery.datepicker.regional["%s"], %s, {dateFormat: "yy-mm-dd"}));
+    wfd_%s_check_linked_days();
+  });
+
+  jQuery("#%s, #%s, #%s").change(wfd_%s_check_linked_days);
+</script>
+EOF
+      ,
+      $prefix, $id,
+      $this->generateId($name.'[year]'), $this->generateId($name.'[month]'), $this->generateId($name.'[day]'),
+      $prefix,
+      $this->generateId($name.'[year]'), $this->generateId($name.'[month]'), $this->generateId($name.'[day]'),
+      $prefix, $prefix,
+      $this->generateId($name.'[year]'), $this->generateId($name.'[month]'),
+      $this->generateId($name.'[day]'), $this->generateId($name.'[day]'),
+      ($this->getOption('date_widget')->getOption('can_be_empty') ? 'daysInMonth' : 'daysInMonth - 1'),
+      $this->generateId($name.'[day]'), $this->generateId($name.'[day]'),
+      $id,
+      min($years), max($years),
+      $prefix, $prefix, $image, $this->getOption('culture'), $this->getOption('config'),
+      $prefix,
+      $this->generateId($name.'[day]'), $this->generateId($name.'[month]'), $this->generateId($name.'[year]'),
+      $prefix
+    );
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormPropelChoiceGrouped.class.php b/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormPropelChoiceGrouped.class.php
new file mode 100644 (file)
index 0000000..644ba29
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * A widget of grouped choices.
+ * 
+ * @package     sfFormExtraPlugin
+ * @subpackage  widget
+ * @author      Kris Wallsmith <kris.wallsmith@symfony-project.com>
+ * @version     SVN: $Id: sfWidgetFormPropelChoiceGrouped.class.php 16067 2009-03-06 16:57:46Z Kris.Wallsmith $
+ */
+class sfWidgetFormPropelChoiceGrouped extends sfWidgetFormPropelChoice
+{
+  /**
+   * Available options:
+   * 
+   *  * group_by_method:  A method on the current model that will return the
+   *                      object the widget is grouped by (i.e. getAuthor)
+   * 
+   * @see sfWidget
+   */
+  protected function configure($options = array(), $attributes = array())
+  {
+    $this->addRequiredOption('group_by_method');
+
+    parent::configure($options, $attributes);
+  }
+
+  /**
+   * @see sfWidgetFormPropelChoice
+   */
+  public function getChoices()
+  {
+    $choices = array();
+    if (!$this->getOption('expanded') && false !== $this->getOption('add_empty'))
+    {
+      $choices[''] = true === $this->getOption('add_empty') ? '' : $this->getOption('add_empty');
+    }
+
+    $methodKey = $this->getOption('key_method');
+    if (!method_exists($this->getOption('model'), $methodKey))
+    {
+      throw new RuntimeException(sprintf('Class "%s" must implement a "%s" method to be rendered in a "%s" widget', $this->getOption('model'), $methodKey, __CLASS__));
+    }
+
+    $methodValue = $this->getOption('method');
+    if (!method_exists($this->getOption('model'), $methodValue))
+    {
+      throw new RuntimeException(sprintf('Class "%s" must implement a "%s" method to be rendered in a "%s" widget', $this->getOption('model'), $methodValue, __CLASS__));
+    }
+
+    $methodParent = $this->getOption('group_by_method');
+    if (!method_exists($this->getOption('model'), $methodParent))
+    {
+      throw new RuntimeException(sprintf('Class "%s" must implement a "%s" method to be rendered in a "%s" widget', $this->getOption('model'), $methodParent, __CLASS__));
+    }
+
+    $class = constant($this->getOption('model').'::PEER');
+
+    $criteria = is_null($this->getOption('criteria')) ? new Criteria() : clone $this->getOption('criteria');
+    if ($order = $this->getOption('order_by'))
+    {
+      $method = sprintf('add%sOrderByColumn', 0 === strpos(strtoupper($order[1]), 'ASC') ? 'Ascending' : 'Descending');
+      $criteria->$method(call_user_func(array($class, 'translateFieldName'), $order[0], BasePeer::TYPE_PHPNAME, BasePeer::TYPE_COLNAME));
+    }
+
+    if ('doSelect' == $peerMethod = $this->getOption('peer_method'))
+    {
+      // apply join method
+      $peerMethod = 'doSelectJoin'.substr($this->getOption('group_by_method'), 3);
+    }
+    $objects = call_user_func(array($class, $peerMethod), $criteria, $this->getOption('connection'));
+
+    foreach ($objects as $object)
+    {
+      $parent = $object->$methodParent($this->getOption('connection'));
+      if (!method_exists($parent, '__toString'))
+      {
+        throw new RuntimeException(sprintf('Class "%s" must implement a "__toString" method to be rendered in a "%s" widget', get_class($parent), __CLASS__));
+      }
+
+      $parentValue = (string) $parent;
+      if (!isset($choices[$parentValue]))
+      {
+        $choices[$parentValue] = array();
+      }
+
+      $choices[$parentValue][$object->$methodKey()] = $object->$methodValue();
+    }
+
+    return $choices;
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormPropelJQueryAutocompleter.class.php b/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormPropelJQueryAutocompleter.class.php
new file mode 100644 (file)
index 0000000..14a88d7
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * sfWidgetFormPropelJQueryAutocompleter represents an autocompleter input widget rendered by JQuery
+ * optimized for foreign key lookup.
+ *
+ * @package    symfony
+ * @subpackage widget
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id: sfWidgetFormPropelJQueryAutocompleter.class.php 12130 2008-10-10 14:51:07Z fabien $
+ */
+class sfWidgetFormPropelJQueryAutocompleter extends sfWidgetFormJQueryAutocompleter
+{
+  /**
+   * @see sfWidget
+   */
+  public function __construct($options = array(), $attributes = array())
+  {
+    $options['value_callback'] = array($this, 'toString');
+
+    parent::__construct($options, $attributes);
+  }
+
+  /**
+   * Configures the current widget.
+   *
+   * @param array $options     An array of options
+   * @param array $attributes  An array of default HTML attributes
+   *
+   * @see sfWidgetFormJQueryAutocompleter
+   */
+  protected function configure($options = array(), $attributes = array())
+  {
+    $this->addRequiredOption('model');
+    $this->addOption('method', '__toString');
+
+    parent::configure($options, $attributes);
+  }
+
+  /**
+   * Returns the text representation of a foreign key.
+   *
+   * @param string $value The primary key
+   */
+  protected function toString($value)
+  {
+    $class = constant($this->getOption('model').'::PEER');
+
+    $criteria = new Criteria();
+    $object = call_user_func(array($class, 'retrieveByPK'), $value);
+
+    $method = $this->getOption('method');
+
+    if (!method_exists($this->getOption('model'), $method))
+    {
+      throw new RuntimeException(sprintf('Class "%s" must implement a "%s" method to be rendered in a "%s" widget', $this->getOption('model'), $method, __CLASS__));
+    }
+
+    return !is_null($object) ? $object->$method() : '';
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormReCaptcha.class.php b/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormReCaptcha.class.php
new file mode 100644 (file)
index 0000000..adc51ef
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * sfWidgetFormReCaptcha renders a ReCaptcha widget.
+ *
+ * This widget uses ReCaptcha: http://recaptcha.net/
+ *
+ * The ReCaptcha API documentation can be found at http://recaptcha.net/apidocs/captcha/
+ *
+ * To be able to use this widget, you need an API key: http://recaptcha.net/api/getkey
+ *
+ * As it's not possible to change the name of ReCaptcha fields, you will have to add them manually
+ * when binding a form from an HTTP request.
+ *
+ * Here's a typical usage when embedding a captcha in a form with a contact[%s] name format:
+ *
+ *    $captcha = array(
+ *      'recaptcha_challenge_field' => $request->getParameter('recaptcha_challenge_field'),
+ *      'recaptcha_response_field'  => $request->getParameter('recaptcha_response_field'),
+ *    );
+ *    $this->form->bind(array_merge($request->getParameter('contact'), array('captcha' => $captcha)));
+ *
+ * @package    symfony
+ * @subpackage widget
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id: sfWidgetFormReCaptcha.class.php 30758 2010-08-25 11:39:13Z fabien $
+ */
+class sfWidgetFormReCaptcha extends sfWidgetForm
+{
+  /**
+   * Configures the current widget.
+   *
+   * Available options:
+   *
+   *  * public_key:     The ReCaptcha public key
+   *  * use_ssl:        Whether to use SSL or not (false by default)
+   *  * server_url:     The URL for the HTTP API
+   *  * server_url_ssl: The URL for the HTTPS API (when use_ssl is true)
+   *  * theme:          The ReCaptcha theme
+   *  * culture:        The ReCaptcha language
+   *
+   * @see sfWidgetForm
+   */
+  public function configure($options = array(), $messages = array())
+  {
+    $this->addRequiredOption('public_key');
+
+    $this->addOption('use_ssl', false);
+    $this->addOption('server_url', 'http://api.recaptcha.net');
+    $this->addOption('server_url_ssl', 'https://api-secure.recaptcha.net');
+    $this->addOption('theme', 'clean');
+    $this->addOption('culture', 'en');
+  }
+
+  /**
+   * @see sfWidgetForm
+   */
+  public function render($name, $value = null, $attributes = array(), $errors = array())
+  {
+    $server = $this->getServerUrl();
+    $key = $this->getOption('public_key');
+    $theme = $this->getOption('theme');
+    $culture = $this->getOption('culture');
+
+    return sprintf('
+    <script type="text/javascript">
+    var RecaptchaOptions = {
+    theme : \'%s\',
+    lang : \'%s\'
+    };
+    </script>
+    <script type="text/javascript" src="%s/challenge?k=%s"></script>
+    <noscript>
+      <iframe src="%s/noscript?k=%s" height="300" width="500" frameborder="0"></iframe><br />
+      <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
+      <input type="hidden" name="recaptcha_response_field" value="manual_challenge" />
+    </noscript>
+    ', $theme, $culture, $server, $key, $server, $key);
+  }
+
+  protected function getServerUrl()
+  {
+    return $this->getOption('use_ssl') ? $this->getOption('server_url_ssl') : $this->getOption('server_url');
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormSelectDoubleList.class.php b/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormSelectDoubleList.class.php
new file mode 100644 (file)
index 0000000..24c6c64
--- /dev/null
@@ -0,0 +1,185 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * sfWidgetFormSelectDoubleList represents a multiple select displayed as a double list.
+ *
+ * This widget needs some JavaScript to work. So, you need to include the JavaScripts
+ * files returned by the getJavaScripts() method.
+ *
+ * If you use symfony 1.2, it can be done automatically for you.
+ *
+ * @package    symfony
+ * @subpackage widget
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id: sfWidgetFormSelectDoubleList.class.php 30760 2010-08-25 11:50:26Z fabien $
+ */
+class sfWidgetFormSelectDoubleList extends sfWidgetForm
+{
+  /**
+   * Constructor.
+   *
+   * Available options:
+   *
+   *  * choices:            An array of possible choices (required)
+   *  * class:              The main class of the widget
+   *  * class_select:       The class for the two select tags
+   *  * label_unassociated: The label for unassociated
+   *  * label_associated:   The label for associated
+   *  * unassociate:        The HTML for the unassociate link
+   *  * associate:          The HTML for the associate link
+   *  * associated_first:   Whether the associated list if first (true by default)
+   *  * template:           The HTML template to use to render this widget
+   *                        The available placeholders are:
+   *                          * label_associated
+   *                          * label_unassociated
+   *                          * associate
+   *                          * unassociate
+   *                          * associated
+   *                          * unassociated
+   *                          * class
+   *
+   * @param array $options     An array of options
+   * @param array $attributes  An array of default HTML attributes
+   *
+   * @see sfWidgetForm
+   */
+  protected function configure($options = array(), $attributes = array())
+  {
+    $this->addRequiredOption('choices');
+
+    $this->addOption('class', 'double_list');
+    $this->addOption('class_select', 'double_list_select');
+    $this->addOption('associated_first', true);
+    $this->addOption('label_unassociated', 'Unassociated');
+    $this->addOption('label_associated', 'Associated');
+    $associated_first = isset($options['associated_first']) ? $options['associated_first'] : true;
+
+    if ($associated_first)
+    {
+      $associate_image = 'previous.png';
+      $unassociate_image = 'next.png';
+      $float = 'left';
+    }
+    else
+    {
+      $associate_image = 'next.png';
+      $unassociate_image = 'previous.png';
+      $float = 'right';
+    }
+
+    $this->addOption('unassociate', '<img src="/sfFormExtraPlugin/images/'.$unassociate_image.'" alt="unassociate" />');
+    $this->addOption('associate', '<img src="/sfFormExtraPlugin/images/'.$associate_image.'" alt="associate" />');
+    $this->addOption('template', <<<EOF
+<div class="%class%">
+  <div style="float: left">
+    <div style="float: $float">
+      <div class="double_list_label">%label_associated%</div>
+      %associated%
+    </div>
+    <div style="float: $float; margin-top: 2em">
+      %associate%
+      <br />
+      %unassociate%
+    </div>
+    <div style="float: $float">
+      <div class="double_list_label">%label_unassociated%</div>
+      %unassociated%
+    </div>
+  </div>
+  <br style="clear: both" />
+  <script type="text/javascript">
+    sfDoubleList.init(document.getElementById('%id%'), '%class_select%');
+  </script>
+</div>
+EOF
+);
+  }
+
+  /**
+   * Renders the widget.
+   *
+   * @param  string $name        The element name
+   * @param  string $value       The value selected in this widget
+   * @param  array  $attributes  An array of HTML attributes to be merged with the default HTML attributes
+   * @param  array  $errors      An array of errors for the field
+   *
+   * @return string An HTML tag string
+   *
+   * @see sfWidgetForm
+   */
+  public function render($name, $value = null, $attributes = array(), $errors = array())
+  {
+    if (is_null($value))
+    {
+      $value = array();
+    }
+
+    $choices = $this->getOption('choices');
+    if ($choices instanceof sfCallable)
+    {
+      $choices = $choices->call();
+    }
+
+    $associated = array();
+    $unassociated = array();
+    foreach ($choices as $key => $option)
+    {
+      if (in_array(strval($key), $value))
+      {
+        $associated[$key] = $option;
+      }
+      else
+      {
+        $unassociated[$key] = $option;
+      }
+    }
+
+    $size = isset($attributes['size']) ? $attributes['size'] : (isset($this->attributes['size']) ? $this->attributes['size'] : 10);
+
+    $associatedWidget = new sfWidgetFormSelect(array('multiple' => true, 'choices' => $associated), array('size' => $size, 'class' => $this->getOption('class_select').'-selected'));
+    $unassociatedWidget = new sfWidgetFormSelect(array('multiple' => true, 'choices' => $unassociated), array('size' => $size, 'class' => $this->getOption('class_select')));
+
+    return strtr($this->getOption('template'), array(
+      '%class%'              => $this->getOption('class'),
+      '%class_select%'       => $this->getOption('class_select'),
+      '%id%'                 => $this->generateId($name),
+      '%label_associated%'   => $this->getOption('label_associated'),
+      '%label_unassociated%' => $this->getOption('label_unassociated'),
+      '%associate%'          => sprintf('<a href="#" onclick="%s">%s</a>', 'sfDoubleList.move(\'unassociated_'.$this->generateId($name).'\', \''.$this->generateId($name).'\'); return false;', $this->getOption('associate')),
+      '%unassociate%'        => sprintf('<a href="#" onclick="%s">%s</a>', 'sfDoubleList.move(\''.$this->generateId($name).'\', \'unassociated_'.$this->generateId($name).'\'); return false;', $this->getOption('unassociate')),
+      '%associated%'         => $associatedWidget->render($name),
+      '%unassociated%'       => $unassociatedWidget->render('unassociated_'.$name),
+    ));
+  }
+
+  /**
+   * Gets the JavaScript paths associated with the widget.
+   *
+   * @return array An array of JavaScript paths
+   */
+  public function getJavascripts()
+  {
+    return array('/sfFormExtraPlugin/js/double_list.js');
+  }
+
+  public function __clone()
+  {
+    if ($this->getOption('choices') instanceof sfCallable)
+    {
+      $callable = $this->getOption('choices')->getCallable();
+      if (is_array($callable))
+      {
+        $callable[0] = $this;
+        $this->setOption('choices', new sfCallable($callable));
+      }
+    }
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormSelectUSState.class.php b/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormSelectUSState.class.php
new file mode 100644 (file)
index 0000000..9be3f82
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * Select menu of US states.
+ * 
+ * Here's an example usage:
+ * 
+ *    $this->setWidgets(array(
+ *      'state' => new sfWidgetFormSelectUSState(array('add_empty' => 'Select a state...')),
+ *    ));
+ * 
+ *    $this->setValidators(array(
+ *      'state' => sfValidatorChoice(array('choices' => sfWidgetFormSelectUSState::getStateAbbreviations())),
+ *    ));
+ * 
+ * @package    sfFormExtraPlugin
+ * @subpackage widget
+ * @author     Kris Wallsmith <kris.wallsmith@symfony-project.com>
+ * @version    SVN: $Id: sfWidgetFormSelectUSState.class.php 15371 2009-02-09 19:19:20Z Kris.Wallsmith $
+ */
+class sfWidgetFormSelectUSState extends sfWidgetFormSelect
+{
+  /**
+   * @see sfWidget
+   */
+  public function __construct($options = array(), $attributes = array())
+  {
+    $options['choices'] = new sfCallable(array($this, 'getChoices'));
+
+    parent::__construct($options, $attributes);
+  }
+
+  /**
+   * @see sfWidget
+   */
+  protected function configure($options = array(), $attributes = array())
+  {
+    $this->addOption('add_empty', false);
+
+    parent::configure($options, $attributes);
+  }
+
+  /**
+   * Returns choices for the current widget.
+   * 
+   * @return array
+   */
+  public function getChoices()
+  {
+    $choices = array();
+    if (false !== $this->getOption('add_empty'))
+    {
+      $choices[''] = true === $this->getOption('add_empty') ? '' : $this->getOption('add_empty');
+    }
+
+    $choices = array_merge($choices, self::getStates());
+
+    return $choices;
+  }
+
+  /**
+   * Returns an associative array of US states.
+   * 
+   * @return array
+   */
+  static public function getStates()
+  {
+    return self::$states;
+  }
+
+  /**
+   * Sets the array of states.
+   * 
+   * @param array $states
+   */
+  static public function setStates(array $states)
+  {
+    self::$states = $states;
+  }
+
+  /**
+   * Returns an array of state abbreviations.
+   * 
+   * @return array
+   */
+  static public function getStateAbbreviations()
+  {
+    return array_keys(self::$states);
+  }
+
+  static protected $states = array(
+    'AL' => 'Alabama',
+    'AK' => 'Alaska',
+    'AZ' => 'Arizona',
+    'AR' => 'Arkansas',
+    'CA' => 'California',
+    'CO' => 'Colorado',
+    'CT' => 'Connecticut',
+    'DE' => 'Delaware',
+    'DC' => 'District Of Columbia',
+    'FL' => 'Florida',
+    'GA' => 'Georgia',
+    'HI' => 'Hawaii',
+    'ID' => 'Idaho',
+    'IL' => 'Illinois',
+    'IN' => 'Indiana',
+    'IA' => 'Iowa',
+    'KS' => 'Kansas',
+    'KY' => 'Kentucky',
+    'LA' => 'Louisiana',
+    'ME' => 'Maine',
+    'MD' => 'Maryland',
+    'MA' => 'Massachusetts',
+    'MI' => 'Michigan',
+    'MN' => 'Minnesota',
+    'MS' => 'Mississippi',
+    'MO' => 'Missouri',
+    'MT' => 'Montana',
+    'NE' => 'Nebraska',
+    'NV' => 'Nevada',
+    'NH' => 'New Hampshire',
+    'NJ' => 'New Jersey',
+    'NM' => 'New Mexico',
+    'NY' => 'New York',
+    'NC' => 'North Carolina',
+    'ND' => 'North Dakota',
+    'OH' => 'Ohio',
+    'OK' => 'Oklahoma',
+    'OR' => 'Oregon',
+    'PA' => 'Pennsylvania',
+    'RI' => 'Rhode Island',
+    'SC' => 'South Carolina',
+    'SD' => 'South Dakota',
+    'TN' => 'Tennessee',
+    'TX' => 'Texas',
+    'UT' => 'Utah',
+    'VT' => 'Vermont',
+    'VA' => 'Virginia',
+    'WA' => 'Washington',
+    'WV' => 'West Virginia',
+    'WI' => 'Wisconsin',
+    'WY' => 'Wyoming',
+  );
+}
diff --git a/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormTextareaTinyMCE.class.php b/plugins/sfFormExtraPlugin/lib/widget/sfWidgetFormTextareaTinyMCE.class.php
new file mode 100644 (file)
index 0000000..a3c67be
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+
+/*
+ * 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.
+ */
+
+/**
+ * sfWidgetFormTextareaTinyMCE represents a Tiny MCE widget.
+ *
+ * You must include the Tiny MCE JavaScript file by yourself.
+ *
+ * @package    symfony
+ * @subpackage widget
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version    SVN: $Id: sfWidgetFormTextareaTinyMCE.class.php 17192 2009-04-10 07:58:29Z fabien $
+ */
+class sfWidgetFormTextareaTinyMCE extends sfWidgetFormTextarea
+{
+  /**
+   * Constructor.
+   *
+   * Available options:
+   *
+   *  * theme:  The Tiny MCE theme
+   *  * width:  Width
+   *  * height: Height
+   *  * config: The javascript configuration
+   *
+   * @param array $options     An array of options
+   * @param array $attributes  An array of default HTML attributes
+   *
+   * @see sfWidgetForm
+   */
+  protected function configure($options = array(), $attributes = array())
+  {
+    $this->addOption('theme', 'advanced');
+    $this->addOption('width');
+    $this->addOption('height');
+    $this->addOption('config', '');
+  }
+
+  /**
+   * @param  string $name        The element name
+   * @param  string $value       The value selected in this widget
+   * @param  array  $attributes  An array of HTML attributes to be merged with the default HTML attributes
+   * @param  array  $errors      An array of errors for the field
+   *
+   * @return string An HTML tag string
+   *
+   * @see sfWidgetForm
+   */
+  public function render($name, $value = null, $attributes = array(), $errors = array())
+  {
+    $textarea = parent::render($name, $value, $attributes, $errors);
+
+    $js = sprintf(<<<EOF
+<script type="text/javascript">
+  tinyMCE.init({
+    mode:                              "exact",
+    elements:                          "%s",
+    theme:                             "%s",
+    %s
+    %s
+    theme_advanced_toolbar_location:   "top",
+    theme_advanced_toolbar_align:      "left",
+    theme_advanced_statusbar_location: "bottom",
+    theme_advanced_resizing:           true
+    %s
+  });
+</script>
+EOF
+    ,
+      $this->generateId($name),
+      $this->getOption('theme'),
+      $this->getOption('width')  ? sprintf('width:                             "%spx",', $this->getOption('width')) : '',
+      $this->getOption('height') ? sprintf('height:                            "%spx",', $this->getOption('height')) : '',
+      $this->getOption('config') ? ",\n".$this->getOption('config') : ''
+    );
+
+    return $textarea.$js;
+  }
+}
diff --git a/plugins/sfFormExtraPlugin/test/bootstrap.php b/plugins/sfFormExtraPlugin/test/bootstrap.php
new file mode 100644 (file)
index 0000000..a8f1542
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+
+/*
+ * 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.
+ */
+
+if (!isset($_SERVER['SYMFONY']))
+{
+  die("You must set the \"SYMFONY\" environment variable to the symfony lib dir (export SYMFONY=/path/to/symfony/lib/).\n");
+}
+
+require_once $_SERVER['SYMFONY'].'/vendor/lime/lime.php';
+require_once $_SERVER['SYMFONY'].'/autoload/sfCoreAutoload.class.php';
+sfCoreAutoload::register();
diff --git a/plugins/sfFormExtraPlugin/test/form/sfFormLanguageTest.php b/plugins/sfFormExtraPlugin/test/form/sfFormLanguageTest.php
new file mode 100644 (file)
index 0000000..f3c3f4f
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * 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.
+ */
+
+require_once dirname(__FILE__).'/../bootstrap.php';
+require_once dirname(__FILE__).'/../../lib/form/sfFormLanguage.class.php';
+
+$t = new lime_test(9, new lime_output_color());
+
+// initialize objects
+$dispatcher = new sfEventDispatcher();
+
+$sessionPath = sys_get_temp_dir().'/sessions_'.rand(11111, 99999);
+$storage = new sfSessionTestStorage(array('session_path' => $sessionPath));
+$user = new sfUser($dispatcher, $storage);
+$user->setCulture('en');
+
+$request = new sfWebRequest($dispatcher);
+
+// __construct()
+$t->diag('__construct()');
+try
+{
+  new sfFormLanguage($user);
+  $t->fail('__construct() throws a RuntimeException if you don\'t pass a "languages" option');
+}
+catch (RuntimeException $e)
+{
+  $t->pass('__construct() throws a RuntimeException if you don\'t pass a "languages" option');
+}
+$form = new sfFormLanguage($user, array('languages' => array('en', 'fr')));
+$t->is($form->getDefault('language'), 'en', '__construct() sets the default language value to the user language');
+$w = $form->getWidgetSchema();
+$t->is($w['language']->getOption('languages'), array('en', 'fr'), '__construct() uses the "languages" option for the select form widget');
+$v = $form->getValidatorSchema();
+$t->is($v['language']->getOption('languages'), array('en', 'fr'), '__construct() uses the "languages" option for the validator');
+
+// ->process()
+$t->diag('->process()');
+
+// with CSRF disabled
+$t->diag('with CSRF disabled');
+sfForm::disableCSRFProtection();
+
+$form = new sfFormLanguage($user, array('languages' => array('en', 'fr')));
+$request->setParameter('language', 'fr');
+$t->is($form->process($request), true, '->process() returns true if the form is valid');
+$t->is($user->getCulture(), 'fr', '->process() changes the user culture');
+
+$request->setParameter('language', 'es');
+$t->is($form->process($request), false, '->process() returns true if the form is not valid');
+$t->is($form['language']->getError()->getCode(), 'invalid', '->process() throws an error if the language is not in the languages option');
+
+sfToolkit::clearDirectory($sessionPath);
+
+// with CSRF enabled
+$t->diag('with CSRF enabled');
+sfForm::enableCSRFProtection('secret');
+
+$form = new sfFormLanguage($user, array('languages' => array('en', 'fr')));
+$request->setParameter('language', 'fr');
+$request->setParameter('_csrf_token', $form->getCSRFToken('secret'));
+$t->is($form->process($request), true, '->process() returns true if the form is valid');
diff --git a/plugins/sfFormExtraPlugin/test/validator/sfValidatorBlacklistTest.php b/plugins/sfFormExtraPlugin/test/validator/sfValidatorBlacklistTest.php
new file mode 100644 (file)
index 0000000..f95dd7c
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * 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.
+ */
+
+require_once dirname(__FILE__).'/../bootstrap.php';
+require_once dirname(__FILE__).'/../../lib/validator/sfValidatorBlacklist.class.php';
+
+$t = new lime_test(6, new lime_output_color());
+
+// __construct()
+$t->diag('__construct()');
+try
+{
+  new sfValidatorBlacklist();
+  $t->fail('__construct() expects a "forbidden_values" option');
+}
+catch (RuntimeException $e)
+{
+  $t->pass('__construct() expects a "forbidden_values" option');
+}
+
+$v = new sfValidatorBlacklist(array('forbidden_values' => array('foo', 'bar', 'baz')));
+
+// ->clean()
+$t->diag('->clean()');
+try
+{
+  $v->clean('foo'); // "foo" is a forbidden value
+  $t->fail('->clean() throws a sfValidatorError when the submitted value is invalid');
+  $t->skip();
+}
+catch (sfValidatorError $e)
+{
+  $t->pass('->clean() throws a sfValidatorError when the submitted value is invalid');
+  $t->is($e->getCode(), 'forbidden', '->clean() throws an "forbidden" error');
+}
+
+try
+{
+  $v->clean('gonzagues'); // "gonzagues" is not a forbidden value
+  $t->pass('->clean() does not throw a sfValidatorError when the submitted value is valid');
+}
+catch (sfValidatorError $e)
+{
+  $t->fail('->clean() does not throw a sfValidatorError when the submitted value is valid');
+}
+
+$v = new sfValidatorBlacklist(array(
+  'forbidden_values' => array('Foo', 'bAr', 'baZ'),
+  'case_sensitive'   => false,
+));
+
+try
+{
+  $v->clean('baz'); // "baz" is a forbidden value in a case sensitive context
+  $t->fail('->clean() throws a sfValidatorError when the submitted value is invalid in a case-insensitive context');
+}
+catch (sfValidatorError $e)
+{
+  $t->pass('->clean() throws a sfValidatorError when the submitted value is invalid in a case-insensitive context');
+}
+
+$t->is($v->clean('FOOBAR'), 'FOOBAR', '->clean() does not change the value\'s case');
diff --git a/plugins/sfFormExtraPlugin/test/validator/sfValidatorDefaultTest.php b/plugins/sfFormExtraPlugin/test/validator/sfValidatorDefaultTest.php
new file mode 100644 (file)
index 0000000..cf0e88c
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * 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.
+ */
+
+require_once dirname(__FILE__).'/../bootstrap.php';
+require_once dirname(__FILE__).'/../../lib/validator/sfValidatorDefault.class.php';
+
+$t = new lime_test(4);
+
+// ->clean()
+$t->diag('->clean()');
+
+$validator = new sfValidatorDefault(array(
+  'validator' => new sfValidatorString(),
+  'default'   => '==DEFAULT==',
+));
+$t->is($validator->clean('foo'), 'foo', '->clean() returns cleaned values');
+$t->is($validator->clean(null), '==DEFAULT==', '->clean() returns the default on validation failure');
+
+$validator = new sfValidatorDefault(array(
+  'validator' => new sfValidatorString(array('empty_value' => '==EMPTY==')),
+));
+$t->is($validator->clean(null), '==EMPTY==', '->clean() returns the validator empty value if no default value is set');
+
+$validator = new sfValidatorDefault(array(
+  'validator' => new sfValidatorString(array('empty_value' => '==EMPTY==')),
+  'default'   => '==DEFAULT==',
+));
+$t->is($validator->clean(null), '==DEFAULT==', '->clean() returns the default if both default and the embedded empty value are set');
diff --git a/plugins/sfFormExtraPlugin/test/validator/sfValidatorReCaptchaTest.php b/plugins/sfFormExtraPlugin/test/validator/sfValidatorReCaptchaTest.php
new file mode 100644 (file)
index 0000000..d4cd07e
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * 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.
+ */
+
+require_once dirname(__FILE__).'/../bootstrap.php';
+require_once dirname(__FILE__).'/../../lib/validator/sfValidatorReCaptcha.class.php';
+
+$t = new lime_test(3, new lime_output_color());
+
+$REMOTE_ADDR = 'symfony.example.com';
+$PUBLIC_KEY  = '6Lf2DQEAAAAAALB9pGaVdcjMiv4CAuOVkfCSVvGh';
+$PRIVATE_KEY = '6Lf2DQEAAAAAALnEL0iEogIxZNYMlG7pmNhwEXjk';
+
+// __construct()
+$t->diag('__construct()');
+try
+{
+  new sfValidatorReCaptcha();
+}
+catch (RuntimeException $e)
+{
+  $t->pass('__construct() expects a "private_key" option');
+}
+
+// ->clean()
+$t->diag('->clean()');
+$v = new sfValidatorReCaptcha(array('private_key' => $PRIVATE_KEY, 'remote_addr' => $REMOTE_ADDR));
+try
+{
+  $v->clean(array(
+    'recaptcha_challenge_field' => null,
+    'recaptcha_response_field'  => null,
+    'captcha'                   => null,
+  ));
+  $t->fail('->clean() throws a sfValidatorError when the captcha is invalid');
+  $t->skip();
+}
+catch (sfValidatorError $e)
+{
+  $t->pass('->clean() throws a sfValidatorError when the captcha is invalid');
+  $t->is($e->getCode(), 'captcha', '->clean() throws a captcha code');
+}
diff --git a/plugins/sfFormExtraPlugin/test/validator/sfValidatorSchemaTimeIntervalTest.php b/plugins/sfFormExtraPlugin/test/validator/sfValidatorSchemaTimeIntervalTest.php
new file mode 100644 (file)
index 0000000..1b7016c
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+
+/*
+ * 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.
+ */
+
+require_once dirname(__FILE__).'/../bootstrap.php';
+require_once dirname(__FILE__).'/../../lib/validator/sfValidatorSchemaTimeInterval.class.php';
+
+$t = new lime_test(14, new lime_output_color());
+
+// __construct()
+$t->diag('__construct()');
+$v = new sfValidatorSchemaTimeInterval('start', 'end');
+$t->is($v->getOption('date_start_field'), 'start', '__construct() assigns date start field option');
+$t->is($v->getOption('date_end_field'), 'end', '__construct() assigns date end field option');
+
+// clean()
+$t->diag('clean()');
+$v = new sfValidatorSchemaTimeInterval('start', 'end', array('disallow_future_dates' => false, 'throw_global_error' => false));
+try
+{
+  $v->clean(array('start' => null, 'end' => null));
+  $t->pass('clean() validates if no dates are set');
+}
+catch (sfValidatorErrorSchema $e)
+{
+  $t->fail('clean() validates if no dates are set');
+}
+
+$v = new sfValidatorSchemaTimeInterval('start', 'end', array('disallow_future_dates' => false, 'throw_global_error' => false));
+try
+{
+  $v->clean(array('start' => '2008-01-01', 'end' => '2007-01-01'));
+  $t->fail('clean() throws a sfValidatorError');
+  $t->fail('clean() detects start date greater than end date');
+}
+catch (sfValidatorErrorSchema $e)
+{
+  $t->isa_ok($e, 'sfValidatorErrorSchema', 'clean() throws a sfValidatorErrorSchema');
+  $t->is($e->getCode(), 'start [start_not_prior]', 'clean() detects start date greater than end date');
+}
+
+$v = new sfValidatorSchemaTimeInterval('start', 'end', array('disallow_future_dates' => true, 'throw_global_error' => false));
+try
+{
+  $v->clean(array('start' => date('Y-m-d', time() + (365*24*3600)), 'end' => date('Y-m-d', time() + (366*24*3600))));
+  $t->fail('clean() detects future date');
+}
+catch (sfValidatorErrorSchema $e)
+{
+  $t->is($e->getCode(), 'start [future_date]', 'clean() detects future date');
+}
+
+$v = new sfValidatorSchemaTimeInterval('start', 'end', array('disallow_past_dates' => true, 'throw_global_error' => false));
+try
+{
+  $v->clean(array('start' => date('Y-m-d', time() - (365*24*3600)), 'end' => date('Y-m-d', time() - (366*24*3600))));
+  $t->fail('clean() detects past date');
+}
+catch (sfValidatorErrorSchema $e)
+{
+  $t->is($e->getCode(), 'start [past_date]', 'clean() detects past date');
+}
+
+$v = new sfValidatorSchemaTimeInterval('start', 'end', array('disallow_future_dates' => false, 'throw_global_error' => false));
+try
+{
+  $v->clean(array('start' => date('Y-m-d', time() + (365*24*3600)), 'end' => date('Y-m-d', time() + (366*24*3600))));
+  $t->pass('clean() validates future dates if option allows it');
+}
+catch (sfValidatorErrorSchema $e)
+{
+  $t->fail('clean() validates future dates if option allows it');
+}
+
+$v = new sfValidatorSchemaTimeInterval('start', 'end', array('disallow_future_dates' => false, 'throw_global_error' => true));
+try
+{
+  $v->clean(array('start' => '2008-01-01', 'end' => '2007-01-01'));
+  $t->fail('clean() throws a gobal error if option is set');
+}
+catch (sfValidatorError $e)
+{
+  $t->isa_ok($e, 'sfValidatorError', 'clean() throws a gobal error if option is set');
+}
+
+$v = new sfValidatorSchemaTimeInterval('start', 'end', array('disallow_future_dates' => false, 'throw_global_error' => true));
+try
+{
+  $v->clean(array('start' => '2008-01-01', 'end' => null));
+  $t->pass('clean() validates if end date is not set');
+}
+catch (sfValidatorError $e)
+{
+  $t->fail('clean() validates if end date is not set');
+}
+
+$v = new sfValidatorSchemaTimeInterval('start', 'end', array('min_duration' => 86400 * 10, 'disallow_future_dates' => false, 'throw_global_error' => false));
+try
+{
+  $v->clean(array('start' => '2008-01-01', 'end' => '2008-01-09'));
+  $t->fail('clean() detects a too short duration');
+}
+catch (sfValidatorError $e)
+{
+  $t->is($e->getCode(), 'end [too_short]', 'clean() detects a too short duration');
+}
+
+$v = new sfValidatorSchemaTimeInterval('start', 'end', array('max_duration' => 86400 * 10, 'disallow_future_dates' => false, 'throw_global_error' => false));
+try
+{
+  $v->clean(array('start' => '2008-01-01', 'end' => '2008-01-12'));
+  $t->fail('clean() detects an exceeded duration');
+}
+catch (sfValidatorError $e)
+{
+  $t->is($e->getCode(), 'end [too_long]', 'clean() detects an exceeded duration');
+}
+
+$v = new sfValidatorSchemaTimeInterval('start', 'end', array('disallow_future_dates' => true, 'throw_global_error' => false));
+try
+{
+  $v->clean(array('start' => null, 'end' => date('Y-m-d', time() + (10*86400))));
+  $t->fail('clean() detects an unique future date');
+}
+catch (sfValidatorError $e)
+{
+  $t->pass('clean() detects an unique future date');
+}
+
+$v = new sfValidatorSchemaTimeInterval('start', 'end', array('disallow_past_dates' => true, 'throw_global_error' => false));
+try
+{
+  $v->clean(array('start' => null, 'end' => date('Y-m-d', time() - (10*86400))));
+  $t->fail('clean() detects an unique past date');
+}
+catch (sfValidatorError $e)
+{
+  $t->pass('clean() detects an unique past date');
+}
\ No newline at end of file
diff --git a/plugins/sfFormExtraPlugin/test/widget/sfWidgetFormReCaptchaTest.php b/plugins/sfFormExtraPlugin/test/widget/sfWidgetFormReCaptchaTest.php
new file mode 100644 (file)
index 0000000..3cd5bd5
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+/*
+ * 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.
+ */
+
+require_once dirname(__FILE__).'/../bootstrap.php';
+require_once dirname(__FILE__).'/../../lib/widget/sfWidgetFormReCaptcha.class.php';
+require_once $_SERVER['SYMFONY'].'/util/sfDomCssSelector.class.php';
+
+$t = new lime_test(7, new lime_output_color());
+
+$REMOTE_ADDR = 'symfony.example.com';
+$PUBLIC_KEY  = '6Lf2DQEAAAAAALB9pGaVdcjMiv4CAuOVkfCSVvGh';
+$PRIVATE_KEY = '6Lf2DQEAAAAAALnEL0iEogIxZNYMlG7pmNhwEXjk';
+
+// __construct()
+$t->diag('__construct()');
+try
+{
+  new sfWidgetFormReCaptcha();
+}
+catch (RuntimeException $e)
+{
+  $t->pass('__construct() expects a "public_key" option');
+}
+
+// ->render()
+$t->diag('->render()');
+$w = new sfWidgetFormReCaptcha(array('public_key' => $PUBLIC_KEY));
+$dom = new DomDocument('1.0', 'utf-8');
+$dom->loadHTML($w->render('captcha'));
+$c = new sfDomCssSelector($dom);
+$t->is(count($c->matchSingle('script[src^="http://"]')->getNodes()), 1, '->render() uses the HTTP ReCaptcha URL by default');
+$t->is(count($c->matchSingle(sprintf('script[src$="%s"]', $PUBLIC_KEY))->getNodes()), 1, '->render() embeds the ReCatpcha public key in the URL');
+$t->is(count($c->matchSingle('iframe[src^="http://"]')->getNodes()), 1, '->render() uses the HTTP ReCaptcha URL by default');
+$t->is(count($c->matchSingle(sprintf('iframe[src$="%s"]', $PUBLIC_KEY))->getNodes()), 1, '->render() embeds the ReCatpcha public key in the URL');
+
+$w = new sfWidgetFormReCaptcha(array('public_key' => $PUBLIC_KEY, 'use_ssl' => true));
+$dom = new DomDocument('1.0', 'utf-8');
+$dom->loadHTML($w->render('captcha'));
+$c = new sfDomCssSelector($dom);
+$t->is(count($c->matchSingle('script[src^="https://"]')->getNodes()), 1, '->render() uses the HTTPS ReCaptcha URL is use_ssl is true');
+$t->is(count($c->matchSingle('iframe[src^="https://"]')->getNodes()), 1, '->render() uses the HTTPS ReCaptcha URL is use_ssl is true');
diff --git a/plugins/sfFormExtraPlugin/web/css/jquery.autocompleter.css b/plugins/sfFormExtraPlugin/web/css/jquery.autocompleter.css
new file mode 100644 (file)
index 0000000..4e6b634
--- /dev/null
@@ -0,0 +1,48 @@
+.ac_results {
+       padding: 0px;
+       border: 1px solid black;
+       background-color: white;
+       overflow: hidden;
+       z-index: 99999;
+}
+
+.ac_results ul {
+       width: 100%;
+       list-style-position: outside;
+       list-style: none;
+       padding: 0;
+       margin: 0;
+}
+
+.ac_results li {
+       margin: 0px;
+       padding: 2px 5px;
+       cursor: default;
+       display: block;
+       /* 
+       if width will be 100% horizontal scrollbar will apear 
+       when scroll mode will be used
+       */
+       /*width: 100%;*/
+       font: menu;
+       font-size: 12px;
+       /* 
+       it is very important, if line-height not setted or setted 
+       in relative units scroll will be broken in firefox
+       */
+       line-height: 16px;
+       overflow: hidden;
+}
+
+.ac_loading {
+       background: white url('../images/indicator.gif') right center no-repeat;
+}
+
+.ac_odd {
+       background-color: #eee;
+}
+
+.ac_over {
+       background-color: #0A246A;
+       color: white;
+}
diff --git a/plugins/sfFormExtraPlugin/web/images/indicator.gif b/plugins/sfFormExtraPlugin/web/images/indicator.gif
new file mode 100644 (file)
index 0000000..085ccae
Binary files /dev/null and b/plugins/sfFormExtraPlugin/web/images/indicator.gif differ
diff --git a/plugins/sfFormExtraPlugin/web/images/next.png b/plugins/sfFormExtraPlugin/web/images/next.png
new file mode 100644 (file)
index 0000000..a1dafb9
Binary files /dev/null and b/plugins/sfFormExtraPlugin/web/images/next.png differ
diff --git a/plugins/sfFormExtraPlugin/web/images/previous.png b/plugins/sfFormExtraPlugin/web/images/previous.png
new file mode 100644 (file)
index 0000000..2a8890e
Binary files /dev/null and b/plugins/sfFormExtraPlugin/web/images/previous.png differ
diff --git a/plugins/sfFormExtraPlugin/web/js/double_list.js b/plugins/sfFormExtraPlugin/web/js/double_list.js
new file mode 100644 (file)
index 0000000..7537a53
--- /dev/null
@@ -0,0 +1,63 @@
+var sfDoubleList =
+{
+  init: function(id, className)
+  {
+    form = sfDoubleList.get_current_form(id);
+
+    callback = function() { sfDoubleList.submit(form, className) };
+
+    if (form.addEventListener)
+    {
+      form.addEventListener("submit", callback, false);
+    }
+    else if (form.attachEvent)
+    {
+      var r = form.attachEvent("onsubmit", callback);
+    }
+  },
+
+  move: function(srcId, destId)
+  {
+    var src = document.getElementById(srcId);
+    var dest = document.getElementById(destId);
+    for (var i = 0; i < src.options.length; i++)
+    {
+      if (src.options[i].selected)
+      {
+        dest.options[dest.length] = new Option(src.options[i].text, src.options[i].value);
+        src.options[i] = null;
+        --i;
+      }
+    }
+  },
+
+  submit: function(form, className)
+  {
+    var element;
+
+    for (var i = 0; i < form.elements.length; i++)
+    {
+      element = form.elements[i];
+      if (element.type == 'select-multiple')
+      {
+        if (element.className == className + '-selected')
+        {
+          for (var j = 0; j < element.options.length; j++)
+          {
+            element.options[j].selected = true;
+          }
+        }
+      }
+    }
+  },
+
+  get_current_form: function(el)
+  {
+    if ("form" != el.tagName.toLowerCase())
+    {
+      return sfDoubleList.get_current_form(el.parentNode);
+    }
+
+    return el;
+  }
+};
diff --git a/plugins/sfFormExtraPlugin/web/js/jquery.autocompleter.js b/plugins/sfFormExtraPlugin/web/js/jquery.autocompleter.js
new file mode 100644 (file)
index 0000000..8387ba1
--- /dev/null
@@ -0,0 +1,808 @@
+/*
+ * jQuery Autocomplete plugin 1.1
+ *
+ * Copyright (c) 2009 Jörn Zaefferer
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
+ */
+
+;(function($) {
+       
+$.fn.extend({
+       autocomplete: function(urlOrData, options) {
+               var isUrl = typeof urlOrData == "string";
+               options = $.extend({}, $.Autocompleter.defaults, {
+                       url: isUrl ? urlOrData : null,
+                       data: isUrl ? null : urlOrData,
+                       delay: isUrl ? $.Autocompleter.defaults.delay : 10,
+                       max: options && !options.scroll ? 10 : 150
+               }, options);
+               
+               // if highlight is set to false, replace it with a do-nothing function
+               options.highlight = options.highlight || function(value) { return value; };
+               
+               // if the formatMatch option is not specified, then use formatItem for backwards compatibility
+               options.formatMatch = options.formatMatch || options.formatItem;
+               
+               return this.each(function() {
+                       new $.Autocompleter(this, options);
+               });
+       },
+       result: function(handler) {
+               return this.bind("result", handler);
+       },
+       search: function(handler) {
+               return this.trigger("search", [handler]);
+       },
+       flushCache: function() {
+               return this.trigger("flushCache");
+       },
+       setOptions: function(options){
+               return this.trigger("setOptions", [options]);
+       },
+       unautocomplete: function() {
+               return this.trigger("unautocomplete");
+       }
+});
+
+$.Autocompleter = function(input, options) {
+
+       var KEY = {
+               UP: 38,
+               DOWN: 40,
+               DEL: 46,
+               TAB: 9,
+               RETURN: 13,
+               ESC: 27,
+               COMMA: 188,
+               PAGEUP: 33,
+               PAGEDOWN: 34,
+               BACKSPACE: 8
+       };
+
+       // Create $ object for input element
+       var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
+
+       var timeout;
+       var previousValue = "";
+       var cache = $.Autocompleter.Cache(options);
+       var hasFocus = 0;
+       var lastKeyPressCode;
+       var config = {
+               mouseDownOnSelect: false
+       };
+       var select = $.Autocompleter.Select(options, input, selectCurrent, config);
+       
+       var blockSubmit;
+       
+       // prevent form submit in opera when selecting with return key
+       $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
+               if (blockSubmit) {
+                       blockSubmit = false;
+                       return false;
+               }
+       });
+       
+       // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
+       $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
+               // a keypress means the input has focus
+               // avoids issue where input had focus before the autocomplete was applied
+               hasFocus = 1;
+               // track last key pressed
+               lastKeyPressCode = event.keyCode;
+               switch(event.keyCode) {
+               
+                       case KEY.UP:
+                               event.preventDefault();
+                               if ( select.visible() ) {
+                                       select.prev();
+                               } else {
+                                       onChange(0, true);
+                               }
+                               break;
+                               
+                       case KEY.DOWN:
+                               event.preventDefault();
+                               if ( select.visible() ) {
+                                       select.next();
+                               } else {
+                                       onChange(0, true);
+                               }
+                               break;
+                               
+                       case KEY.PAGEUP:
+                               event.preventDefault();
+                               if ( select.visible() ) {
+                                       select.pageUp();
+                               } else {
+                                       onChange(0, true);
+                               }
+                               break;
+                               
+                       case KEY.PAGEDOWN:
+                               event.preventDefault();
+                               if ( select.visible() ) {
+                                       select.pageDown();
+                               } else {
+                                       onChange(0, true);
+                               }
+                               break;
+                       
+                       // matches also semicolon
+                       case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
+                       case KEY.TAB:
+                       case KEY.RETURN:
+                               if( selectCurrent() ) {
+                                       // stop default to prevent a form submit, Opera needs special handling
+                                       event.preventDefault();
+                                       blockSubmit = true;
+                                       return false;
+                               }
+                               break;
+                               
+                       case KEY.ESC:
+                               select.hide();
+                               break;
+                               
+                       default:
+                               clearTimeout(timeout);
+                               timeout = setTimeout(onChange, options.delay);
+                               break;
+               }
+       }).focus(function(){
+               // track whether the field has focus, we shouldn't process any
+               // results if the field no longer has focus
+               hasFocus++;
+       }).blur(function() {
+               hasFocus = 0;
+               if (!config.mouseDownOnSelect) {
+                       hideResults();
+               }
+       }).click(function() {
+               // show select when clicking in a focused field
+               if ( hasFocus++ > 1 && !select.visible() ) {
+                       onChange(0, true);
+               }
+       }).bind("search", function() {
+               // TODO why not just specifying both arguments?
+               var fn = (arguments.length > 1) ? arguments[1] : null;
+               function findValueCallback(q, data) {
+                       var result;
+                       if( data && data.length ) {
+                               for (var i=0; i < data.length; i++) {
+                                       if( data[i].result.toLowerCase() == q.toLowerCase() ) {
+                                               result = data[i];
+                                               break;
+                                       }
+                               }
+                       }
+                       if( typeof fn == "function" ) fn(result);
+                       else $input.trigger("result", result && [result.data, result.value]);
+               }
+               $.each(trimWords($input.val()), function(i, value) {
+                       request(value, findValueCallback, findValueCallback);
+               });
+       }).bind("flushCache", function() {
+               cache.flush();
+       }).bind("setOptions", function() {
+               $.extend(options, arguments[1]);
+               // if we've updated the data, repopulate
+               if ( "data" in arguments[1] )
+                       cache.populate();
+       }).bind("unautocomplete", function() {
+               select.unbind();
+               $input.unbind();
+               $(input.form).unbind(".autocomplete");
+       });
+       
+       
+       function selectCurrent() {
+               var selected = select.selected();
+               if( !selected )
+                       return false;
+               
+               var v = selected.result;
+               previousValue = v;
+               
+               if ( options.multiple ) {
+                       var words = trimWords($input.val());
+                       if ( words.length > 1 ) {
+                               var seperator = options.multipleSeparator.length;
+                               var cursorAt = $(input).selection().start;
+                               var wordAt, progress = 0;
+                               $.each(words, function(i, word) {
+                                       progress += word.length;
+                                       if (cursorAt <= progress) {
+                                               wordAt = i;
+                                               return false;
+                                       }
+                                       progress += seperator;
+                               });
+                               words[wordAt] = v;
+                               // TODO this should set the cursor to the right position, but it gets overriden somewhere
+                               //$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
+                               v = words.join( options.multipleSeparator );
+                       }
+                       v += options.multipleSeparator;
+               }
+               
+               $input.val(v);
+               hideResultsNow();
+               $input.trigger("result", [selected.data, selected.value]);
+               return true;
+       }
+       
+       function onChange(crap, skipPrevCheck) {
+               if( lastKeyPressCode == KEY.DEL ) {
+                       select.hide();
+                       return;
+               }
+               
+               var currentValue = $input.val();
+               
+               if ( !skipPrevCheck && currentValue == previousValue )
+                       return;
+               
+               previousValue = currentValue;
+               
+               currentValue = lastWord(currentValue);
+               if ( currentValue.length >= options.minChars) {
+                       $input.addClass(options.loadingClass);
+                       if (!options.matchCase)
+                               currentValue = currentValue.toLowerCase();
+                       request(currentValue, receiveData, hideResultsNow);
+               } else {
+                       stopLoading();
+                       select.hide();
+               }
+       };
+       
+       function trimWords(value) {
+               if (!value)
+                       return [""];
+               if (!options.multiple)
+                       return [$.trim(value)];
+               return $.map(value.split(options.multipleSeparator), function(word) {
+                       return $.trim(value).length ? $.trim(word) : null;
+               });
+       }
+       
+       function lastWord(value) {
+               if ( !options.multiple )
+                       return value;
+               var words = trimWords(value);
+               if (words.length == 1) 
+                       return words[0];
+               var cursorAt = $(input).selection().start;
+               if (cursorAt == value.length) {
+                       words = trimWords(value)
+               } else {
+                       words = trimWords(value.replace(value.substring(cursorAt), ""));
+               }
+               return words[words.length - 1];
+       }
+       
+       // fills in the input box w/the first match (assumed to be the best match)
+       // q: the term entered
+       // sValue: the first matching result
+       function autoFill(q, sValue){
+               // autofill in the complete box w/the first match as long as the user hasn't entered in more data
+               // if the last user key pressed was backspace, don't autofill
+               if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
+                       // fill in the value (keep the case the user has typed)
+                       $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
+                       // select the portion of the value not typed by the user (so the next character will erase)
+                       $(input).selection(previousValue.length, previousValue.length + sValue.length);
+               }
+       };
+
+       function hideResults() {
+               clearTimeout(timeout);
+               timeout = setTimeout(hideResultsNow, 200);
+       };
+
+       function hideResultsNow() {
+               var wasVisible = select.visible();
+               select.hide();
+               clearTimeout(timeout);
+               stopLoading();
+               if (options.mustMatch) {
+                       // call search and run callback
+                       $input.search(
+                               function (result){
+                                       // if no value found, clear the input box
+                                       if( !result ) {
+                                               if (options.multiple) {
+                                                       var words = trimWords($input.val()).slice(0, -1);
+                                                       $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
+                                               }
+                                               else {
+                                                       $input.val( "" );
+                                                       $input.trigger("result", null);
+                                               }
+                                       }
+                               }
+                       );
+               }
+       };
+
+       function receiveData(q, data) {
+               if ( data && data.length && hasFocus ) {
+                       stopLoading();
+                       select.display(data, q);
+                       autoFill(q, data[0].value);
+                       select.show();
+               } else {
+                       hideResultsNow();
+               }
+       };
+
+       function request(term, success, failure) {
+               if (!options.matchCase)
+                       term = term.toLowerCase();
+               var data = cache.load(term);
+               // recieve the cached data
+               if (data && data.length) {
+                       success(term, data);
+               // if an AJAX url has been supplied, try loading the data now
+               } else if( (typeof options.url == "string") && (options.url.length > 0) ){
+                       
+                       var extraParams = {
+                               timestamp: +new Date()
+                       };
+                       $.each(options.extraParams, function(key, param) {
+                               extraParams[key] = typeof param == "function" ? param() : param;
+                       });
+                       
+                       $.ajax({
+                               // try to leverage ajaxQueue plugin to abort previous requests
+                               mode: "abort",
+                               // limit abortion to this input
+                               port: "autocomplete" + input.name,
+                               dataType: options.dataType,
+                               url: options.url,
+                               data: $.extend({
+                                       q: lastWord(term),
+                                       limit: options.max
+                               }, extraParams),
+                               success: function(data) {
+                                       var parsed = options.parse && options.parse(data) || parse(data);
+                                       cache.add(term, parsed);
+                                       success(term, parsed);
+                               }
+                       });
+               } else {
+                       // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
+                       select.emptyList();
+                       failure(term);
+               }
+       };
+       
+       function parse(data) {
+               var parsed = [];
+               var rows = data.split("\n");
+               for (var i=0; i < rows.length; i++) {
+                       var row = $.trim(rows[i]);
+                       if (row) {
+                               row = row.split("|");
+                               parsed[parsed.length] = {
+                                       data: row,
+                                       value: row[0],
+                                       result: options.formatResult && options.formatResult(row, row[0]) || row[0]
+                               };
+                       }
+               }
+               return parsed;
+       };
+
+       function stopLoading() {
+               $input.removeClass(options.loadingClass);
+       };
+
+};
+
+$.Autocompleter.defaults = {
+       inputClass: "ac_input",
+       resultsClass: "ac_results",
+       loadingClass: "ac_loading",
+       minChars: 1,
+       delay: 400,
+       matchCase: false,
+       matchSubset: true,
+       matchContains: false,
+       cacheLength: 10,
+       max: 100,
+       mustMatch: false,
+       extraParams: {},
+       selectFirst: true,
+       formatItem: function(row) { return row[0]; },
+       formatMatch: null,
+       autoFill: false,
+       width: 0,
+       multiple: false,
+       multipleSeparator: ", ",
+       highlight: function(value, term) {
+               return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
+       },
+    scroll: true,
+    scrollHeight: 180
+};
+
+$.Autocompleter.Cache = function(options) {
+
+       var data = {};
+       var length = 0;
+       
+       function matchSubset(s, sub) {
+               if (!options.matchCase) 
+                       s = s.toLowerCase();
+               var i = s.indexOf(sub);
+               if (options.matchContains == "word"){
+                       i = s.toLowerCase().search("\\b" + sub.toLowerCase());
+               }
+               if (i == -1) return false;
+               return i == 0 || options.matchContains;
+       };
+       
+       function add(q, value) {
+               if (length > options.cacheLength){
+                       flush();
+               }
+               if (!data[q]){ 
+                       length++;
+               }
+               data[q] = value;
+       }
+       
+       function populate(){
+               if( !options.data ) return false;
+               // track the matches
+               var stMatchSets = {},
+                       nullData = 0;
+
+               // no url was specified, we need to adjust the cache length to make sure it fits the local data store
+               if( !options.url ) options.cacheLength = 1;
+               
+               // track all options for minChars = 0
+               stMatchSets[""] = [];
+               
+               // loop through the array and create a lookup structure
+               for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
+                       var rawValue = options.data[i];
+                       // if rawValue is a string, make an array otherwise just reference the array
+                       rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
+                       
+                       var value = options.formatMatch(rawValue, i+1, options.data.length);
+                       if ( value === false )
+                               continue;
+                               
+                       var firstChar = value.charAt(0).toLowerCase();
+                       // if no lookup array for this character exists, look it up now
+                       if( !stMatchSets[firstChar] ) 
+                               stMatchSets[firstChar] = [];
+
+                       // if the match is a string
+                       var row = {
+                               value: value,
+                               data: rawValue,
+                               result: options.formatResult && options.formatResult(rawValue) || value
+                       };
+                       
+                       // push the current match into the set list
+                       stMatchSets[firstChar].push(row);
+
+                       // keep track of minChars zero items
+                       if ( nullData++ < options.max ) {
+                               stMatchSets[""].push(row);
+                       }
+               };
+
+               // add the data items to the cache
+               $.each(stMatchSets, function(i, value) {
+                       // increase the cache size
+                       options.cacheLength++;
+                       // add to the cache
+                       add(i, value);
+               });
+       }
+       
+       // populate any existing data
+       setTimeout(populate, 25);
+       
+       function flush(){
+               data = {};
+               length = 0;
+       }
+       
+       return {
+               flush: flush,
+               add: add,
+               populate: populate,
+               load: function(q) {
+                       if (!options.cacheLength || !length)
+                               return null;
+                       /* 
+                        * if dealing w/local data and matchContains than we must make sure
+                        * to loop through all the data collections looking for matches
+                        */
+                       if( !options.url && options.matchContains ){
+                               // track all matches
+                               var csub = [];
+                               // loop through all the data grids for matches
+                               for( var k in data ){
+                                       // don't search through the stMatchSets[""] (minChars: 0) cache
+                                       // this prevents duplicates
+                                       if( k.length > 0 ){
+                                               var c = data[k];
+                                               $.each(c, function(i, x) {
+                                                       // if we've got a match, add it to the array
+                                                       if (matchSubset(x.value, q)) {
+                                                               csub.push(x);
+                                                       }
+                                               });
+                                       }
+                               }                               
+                               return csub;
+                       } else 
+                       // if the exact item exists, use it
+                       if (data[q]){
+                               return data[q];
+                       } else
+                       if (options.matchSubset) {
+                               for (var i = q.length - 1; i >= options.minChars; i--) {
+                                       var c = data[q.substr(0, i)];
+                                       if (c) {
+                                               var csub = [];
+                                               $.each(c, function(i, x) {
+                                                       if (matchSubset(x.value, q)) {
+                                                               csub[csub.length] = x;
+                                                       }
+                                               });
+                                               return csub;
+                                       }
+                               }
+                       }
+                       return null;
+               }
+       };
+};
+
+$.Autocompleter.Select = function (options, input, select, config) {
+       var CLASSES = {
+               ACTIVE: "ac_over"
+       };
+       
+       var listItems,
+               active = -1,
+               data,
+               term = "",
+               needsInit = true,
+               element,
+               list;
+       
+       // Create results
+       function init() {
+               if (!needsInit)
+                       return;
+               element = $("<div/>")
+               .hide()
+               .addClass(options.resultsClass)
+               .css("position", "absolute")
+               .appendTo(document.body);
+       
+               list = $("<ul/>").appendTo(element).mouseover( function(event) {
+                       if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
+                   active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
+                           $(target(event)).addClass(CLASSES.ACTIVE);            
+               }
+               }).click(function(event) {
+                       $(target(event)).addClass(CLASSES.ACTIVE);
+                       select();
+                       // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
+                       input.focus();
+                       return false;
+               }).mousedown(function() {
+                       config.mouseDownOnSelect = true;
+               }).mouseup(function() {
+                       config.mouseDownOnSelect = false;
+               });
+               
+               if( options.width > 0 )
+                       element.css("width", options.width);
+                       
+               needsInit = false;
+       } 
+       
+       function target(event) {
+               var element = event.target;
+               while(element && element.tagName != "LI")
+                       element = element.parentNode;
+               // more fun with IE, sometimes event.target is empty, just ignore it then
+               if(!element)
+                       return [];
+               return element;
+       }
+
+       function moveSelect(step) {
+               listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
+               movePosition(step);
+        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
+        if(options.scroll) {
+            var offset = 0;
+            listItems.slice(0, active).each(function() {
+                               offset += this.offsetHeight;
+                       });
+            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
+                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
+            } else if(offset < list.scrollTop()) {
+                list.scrollTop(offset);
+            }
+        }
+       };
+       
+       function movePosition(step) {
+               active += step;
+               if (active < 0) {
+                       active = listItems.size() - 1;
+               } else if (active >= listItems.size()) {
+                       active = 0;
+               }
+       }
+       
+       function limitNumberOfItems(available) {
+               return options.max && options.max < available
+                       ? options.max
+                       : available;
+       }
+       
+       function fillList() {
+               list.empty();
+               var max = limitNumberOfItems(data.length);
+               for (var i=0; i < max; i++) {
+                       if (!data[i])
+                               continue;
+                       var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
+                       if ( formatted === false )
+                               continue;
+                       var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
+                       $.data(li, "ac_data", data[i]);
+               }
+               listItems = list.find("li");
+               if ( options.selectFirst ) {
+                       listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
+                       active = 0;
+               }
+               // apply bgiframe if available
+               if ( $.fn.bgiframe )
+                       list.bgiframe();
+       }
+       
+       return {
+               display: function(d, q) {
+                       init();
+                       data = d;
+                       term = q;
+                       fillList();
+               },
+               next: function() {
+                       moveSelect(1);
+               },
+               prev: function() {
+                       moveSelect(-1);
+               },
+               pageUp: function() {
+                       if (active != 0 && active - 8 < 0) {
+                               moveSelect( -active );
+                       } else {
+                               moveSelect(-8);
+                       }
+               },
+               pageDown: function() {
+                       if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
+                               moveSelect( listItems.size() - 1 - active );
+                       } else {
+                               moveSelect(8);
+                       }
+               },
+               hide: function() {
+                       element && element.hide();
+                       listItems && listItems.removeClass(CLASSES.ACTIVE);
+                       active = -1;
+               },
+               visible : function() {
+                       return element && element.is(":visible");
+               },
+               current: function() {
+                       return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
+               },
+               show: function() {
+                       var offset = $(input).offset();
+                       element.css({
+                               width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
+                               top: offset.top + input.offsetHeight,
+                               left: offset.left
+                       }).show();
+            if(options.scroll) {
+                list.scrollTop(0);
+                list.css({
+                                       maxHeight: options.scrollHeight,
+                                       overflow: 'auto'
+                               });
+                               
+                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
+                                       var listHeight = 0;
+                                       listItems.each(function() {
+                                               listHeight += this.offsetHeight;
+                                       });
+                                       var scrollbarsVisible = listHeight > options.scrollHeight;
+                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
+                                       if (!scrollbarsVisible) {
+                                               // IE doesn't recalculate width when scrollbar disappears
+                                               listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
+                                       }
+                }
+                
+            }
+               },
+               selected: function() {
+                       var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
+                       return selected && selected.length && $.data(selected[0], "ac_data");
+               },
+               emptyList: function (){
+                       list && list.empty();
+               },
+               unbind: function() {
+                       element && element.remove();
+               }
+       };
+};
+
+$.fn.selection = function(start, end) {
+       if (start !== undefined) {
+               return this.each(function() {
+                       if( this.createTextRange ){
+                               var selRange = this.createTextRange();
+                               if (end === undefined || start == end) {
+                                       selRange.move("character", start);
+                                       selRange.select();
+                               } else {
+                                       selRange.collapse(true);
+                                       selRange.moveStart("character", start);
+                                       selRange.moveEnd("character", end);
+                                       selRange.select();
+                               }
+                       } else if( this.setSelectionRange ){
+                               this.setSelectionRange(start, end);
+                       } else if( this.selectionStart ){
+                               this.selectionStart = start;
+                               this.selectionEnd = end;
+                       }
+               });
+       }
+       var field = this[0];
+       if ( field.createTextRange ) {
+               var range = document.selection.createRange(),
+                       orig = field.value,
+                       teststring = "<->",
+                       textLength = range.text.length;
+               range.text = teststring;
+               var caretAt = field.value.indexOf(teststring);
+               field.value = orig;
+               this.selection(caretAt, caretAt + textLength);
+               return {
+                       start: caretAt,
+                       end: caretAt + textLength
+               }
+       } else if( field.selectionStart !== undefined ){
+               return {
+                       start: field.selectionStart,
+                       end: field.selectionEnd
+               }
+       }
+};
+
+})(jQuery);