![]() |
|
Snippets |
|
Symfony 1.1 comes with a complete new form system. It works completely according to the MVC draft:
Make sure you have a running Symfony 1.1 based project and application and modules. In this example I build the form inside the myModule module and myLogin action.
My form makes use of i18n, which is in my case autoloaded in settings.yml.
This tutorial uses Symfony 1.1 beta4 and RC1. There are a little important changes with respect to beta3, which I don't cover.
I also expect you to have practical knowledge and a little bit experience with Symfony as system. I will not explain how you i18n implements or modules shield with security.yml.
The form gets two import fields: username and password. Furthermore is there a hidden field in which the URI comes that the user requested, but got redirected to the loginform (through Symfony’s security.yml and settings.yml). This will be used to go to that URI again after a successful login.
Both imput fields are required I will build a custom validation for a correct username/password check. Also we want to make use of a little new protection feature: CSRF.
The form takes uses i18n for multilinguity, I will use English primarily, but the view has been prepared for other languages.
We will begin with the action which contains the primary control.
/** * Executes myLogin action * * Login functionality. * * @param void * @return void * @access public */ public function executeMyLogin() { // Erase auth data $this->getUser()->clearCredentials(); $this->getUser()->setAuthenticated(FALSE); // Build login form $oForm = new inloggenForm(); if ($this->getRequest()->isMethod('post')) { // When called through POST (form submit) $oForm->bind( array('username' => $this->getRequest()->getParameter('username'), 'password' => $this->getRequest()->getParameter('password'), 'referrer' => $this->getRequest()->getParameter('referrer'), ) ); // Save orginal requested location in referrer field $oForm->setDefault('referrer', $this->getRequest()->getParameter('referrer')); if ($oForm->isValid()) { // When validations OK $aValues = $oForm->getValues(); sfContext::getInstance()->getLogger()->debug($aValues['username']); // Authentification $this->getUser()->setAuthenticated(TRUE); // To requested page $this->redirect($this->getRequest()->getParameter('referrer')); } } else { // Save original requested uri in form $oForm->setDefault('referrer', sfContext::getInstance()->getRequest()->getUri()); } $this->oForm = $oForm; // form to view }
Logic of authentification is set after POST further. Through the sfForm::bind() method couples the input of the user coupled with the form controller.
I made this in myModule/lib/form/inloggenForm.class.php. In the form is defined and coupled with the validations and the formatting.
<?php class inloggenForm extends sfForm { /** * Prepare validators */ private function _setValidators() { $this->oValGebruikersnaam = new sfValidatorAnd( array( new sfValidatorString(array('min_length' => 3, 'max_length' => 50), array('min_length' => 'The username should be at least three characters long', 'max_length' => 'The username should be fifty characters long at most', ) ), new sfValidatorCallback(array('callback' => array('cmsLoginValidator', 'execute'), 'arguments' => array() ), array('invalid' => 'This username/password combination is unknown') ), ), array('required' => TRUE), array('required' => 'The username is mandatory') ); $this->oValWachtwoord = new sfValidatorString(array('required' => TRUE), array('required' => 'The password is mandatory') ); $this->oValReferrer = new sfValidatorString(array('required' => FALSE)); } /** * Prepare widgets */ private function _setWidgets() { $this->oWidGebruikersnaam = new sfWidgetFormInput(array(), array('id' => 'username', 'size' => 25)); $this->oWidWachtwoord = new sfWidgetFormInputPassword(array(), array('id' => 'password', 'size' => 10)); $this->oWidReferrer = new sfWidgetFormInputHidden(array(), array('id' => 'referrer')); } /** * Configure form */ public function configure() { $this->_setValidators(); $this->_setWidgets(); /* * Set validators */ $this->setValidators(array('username' => $this->oValGebruikersnaam, 'password' => $this->oValWachtwoord, 'referrer' => $this->oValReferrer, ) ); /* * Set widgets */ $this->setWidgets(array('username' => $this->oWidGebruikersnaam, 'password' => $this->oWidWachtwoord, 'referrer' => $this->oWidReferrer, ) ); /* * Set decorator */ $oDecorator = new sfWidgetFormSchemaFormatterDiv($this->getWidgetSchema()); $this->getWidgetSchema()->addFormFormatter('div', $oDecorator); $this->getWidgetSchema()->setFormFormatterName('div'); $this->getWidgetSchema()->setHelps(array('username' => 'Please enter your username', 'password' => 'Please enter your password' ) ); } /** * Bind override */ public function bind(array $taintedValues = null, array $taintedFiles = array()) { $request = sfContext::getInstance()->getRequest(); if ($request->hasParameter(self::$CSRFFieldName)) { $taintedValues[self::$CSRFFieldName] = $request->getParameter(self::$CSRFFieldName); } parent::bind($taintedValues, $taintedFiles); } }
There are two private methods:
I overloaded the earlier mentioned bind() method to process the CSRF token internally. This fiels is regulated by sfForm internally, you to do need to worry about it much, only with the actual functionality.
The configure() method contains the functionality. The validators are created and linked to the corresponding fields and also are the widgets. Also, the decorator is defined with I will go into later on.
In _setValidators() sfValidatorBase objects are made for each field.
The password field check is the simplest, only the required input is checked. The object is of the type sfValidatorString (extend sfValidatorBase) without extra controls, only the required attribute with errortext is specified. In principle, you can use each validator as -empty- container.
The username is a combination; it is required, there are restrictions to the the stringlength and a correct login is checked. The stringcontrole/requirement is checked through sfValidatorString, which is quite simple.
The check for a correct login is done through a special validator, that does a callback to an custom function: sfValidatorCallback. This is explained later.
Since these two validators check the same field , the sfValidatorAnd validator is used that combines several validators. All validators must be satisfied. Of course Symfony also offers sfValidatorOr that checks that at least one underlying validator satisfies.
As you can see the callback validator calls to a custom class/method: myLoginValidator::execute().
/** * Fiel with myLoginValidator klasse * * @package - */ /** * Validation correct username/password * * @author Jordi Backx (Snowkrash) * @copyright Copyright © 2008, Jordi Backx (Snowkrash) * @package - */ class myLoginValidator { /** * execute validator * * @param sfValidatorBase Validator instance that calls this method * @param string Value of field that sfValidatorCallback checks * @param array Arguments for correct working * * @return value field when OK. Nothing if error (sfValidatorError exception) */ public static function execute ($oValidator, $sValue, $aArguments) { if ( /* check OK */ ) { // Return waarde veld indien controle OK return $sValue; } // Throw exception when not OK throw new sfValidatorError($oValidator, 'invalid', array('value' => $sValue, 'invalid' => $oValidator->getOption('invalid'))); } }
I set this here with no further logic, that is application specific, thus that you 'll have to do yourself. The base structure can be used. The three parameters must be defined, otherwise the whole application crashes.
In _setWidget() sfWidget objects are made for each field.
The widgets are the form elements: finally <input>, <select> etc tags in combination with labels and errortexts.
Each widget can have HTML attributes, which will be printed inside the form elements.
Finally the form must be printed to the screen through a view template.
<p><?php echo __('You need to log in to be able to use the Content Management System.') ?></p> <div id="formContainer"> <?php if ($oForm->getErrorSchema()->getErrors()) { ?> <div id="formulierFouten"> <ul> <?php foreach ($oForm->getErrorSchema() as $sError) { ?> <li><?php echo __($sError) ?></li> <?php } ?> </ul> </div> <?php } ?> <form action="<?php echo url_for('myModule/myLogin') ?>" method="post"> <?php echo $oForm['username']->renderLabel(__($oForm['username']->renderLabelName())); echo $oForm['username']->renderRow(__($oForm->getWidgetSchema()->getHelp('username'))); ?> <?php echo $oForm['password']->renderLabel(__($oForm['password']->renderLabelName())); echo $oForm['password']->renderRow(__($oForm->getWidgetSchema()->getHelp('password'))); ?> <?php echo $oForm['referrer']->render(array('value' => $oForm->getDefault('referrer'))) ?> <?php echo $oForm['_csrf_token'] ?> <label for="inloggen"> </label><input type="submit" value="Inloggen" id="inloggen" class="aanmeldenSubmit" /> </form> </div>
You can see all the i18n code (__() helper) and some non-Symfony 1.0 form building. Errorlists are built through the errorSchema which is available within the form object, the texts themself can be translated as you can see.
Also the labels and help texts are squeezed through i18n. The field names are in English, because the labels are based on these and must go through i18n. This way everything can be translated.
You can print the whole form with an echo of $oForm (goes through __toString()), but you have more control over the layout when you use specific widgetrender functions, like I do with renderRow(). This method takes the helptext as an argument, with is also translated.
The submit button is no widget, so we place it ourselves the old-fashioned way ... no helper, that is so Symfony 1.0.
That one is new. It is there, but we never defined it. It is created within sfForm and only since beta4 when indicated in settings.yml:
#Form security secret (CSRF protection)
csrf_secret: hierjeeigenc0d3 # Unique secret to enable CSRF protection or false to disable
You can choose your own code, on which the hash inside the CSRF value is based.
The form functionally is ready, but we want more control over the layout. I am a supporter of the tableless HTML design and the standard formatter of sfForm uses ... tables. Well, we can do better.
The form controller showed the coupling with my own formatter:
/* * Set decorator */ $oDecorator = new sfWidgetFormSchemaFormatterDiv($this->getWidgetSchema()); $this->getWidgetSchema()->addFormFormatter('div', $oDecorator); $this->getWidgetSchema()->setFormFormatterName('div');
I will now go into this part.
I have a class sfWidgetFormSchemaFormatterDiv in sfWidgetFormSchemaFormatterDiv.class.php made in the application-level lib/ directory so that all modules of can use it.
This takes care of the HTML layout of the form elements.
class sfWidgetFormSchemaFormatterDiv extends sfWidgetFormSchemaFormatter { protected $rowFormat = '%error%%field%<br />%help%<br />', $helpFormat = '<span class="help">%help%</span>', $errorRowFormat = '<div>%errors%</div>', $errorListFormatInARow = '%errors%', $errorRowFormatInARow = '<div class="formError">↓ %error% ↓</div>', $namedErrorRowFormatInARow = '%name%: %error%<br />', $decoratorFormat = '<div id="formContainer">%content%</div>'; }
A good article is available that describes this system.
For people that wonder why the label (%label% placeholder) is not used: $rowFormat sets the layout of the renderRow() method and since I want to render the label separately (i18n), it must not be rendered a second time by renderRow().
Hopefully the above can be a good help for your own form in Symfony 1.1. The documentation is quite scarce at the moment, so each bit of help will be welcome.
If the English is somewhat bad, I did a automatic translation of my original Dutch version of the article and tuned that a bit. The reason? I am lazy. ;-)
If you find errors in the above, it is because of copying my code probably. Please mention it in the comments.
Good luck!
Symfony 1.1 comes with a complete new form system. It works completely according to the MVC draft:
Make sure you have a running Symfony 1.1 based project and application and modules. In this example I build the form inside the myModule module and myLogin action.
My form makes use of i18n, which is in my case autoloaded in settings.yml.
This tutorial uses Symfony 1.1 beta4 and RC1. There are a little important changes with respect to beta3, which I don't cover.
I also expect you to have practical knowledge and a little bit experience with Symfony as system. I will not explain how you i18n implements or modules shield with security.yml.
The form gets two import fields: username and password. Furthermore is there a hidden field in which the URI comes that the user requested, but got redirected to the loginform (through Symfony’s security.yml and settings.yml). This will be used to go to that URI again after a successful login.
Both imput fields are required I will build a custom validation for a correct username/password check. Also we want to make use of a little new protection feature: CSRF.
The form takes uses i18n for multilinguity, I will use English primarily, but the view has been prepared for other languages.
We will begin with the action which contains the primary control.
/** * Executes myLogin action * * Login functionality. * * @param void * @return void * @access public */ public function executeMyLogin() { // Erase auth data $this->getUser()->clearCredentials(); $this->getUser()->setAuthenticated(FALSE); // Build login form $oForm = new inloggenForm(); if ($this->getRequest()->isMethod('post')) { // When called through POST (form submit) $oForm->bind( array('username' => $this->getRequest()->getParameter('username'), 'password' => $this->getRequest()->getParameter('password'), 'referrer' => $this->getRequest()->getParameter('referrer'), ) ); // Save orginal requested location in referrer field $oForm->setDefault('referrer', $this->getRequest()->getParameter('referrer')); if ($oForm->isValid()) { // When validations OK $aValues = $oForm->getValues(); sfContext::getInstance()->getLogger()->debug($aValues['username']); // Authentification $this->getUser()->setAuthenticated(TRUE); // To requested page $this->redirect($this->getRequest()->getParameter('referrer')); } } else { // Save original requested uri in form $oForm->setDefault('referrer', sfContext::getInstance()->getRequest()->getUri()); } $this->oForm = $oForm; // form to view }
Logic of authentification is set after POST further. Through the sfForm::bind() method couples the input of the user coupled with the form controller.
I made this in myModule/lib/form/inloggenForm.class.php. In the form is defined and coupled with the validations and the formatting.
<?php class inloggenForm extends sfForm { /** * Prepare validators */ private function _setValidators() { $this->oValGebruikersnaam = new sfValidatorAnd( array( new sfValidatorString(array('min_length' => 3, 'max_length' => 50), array('min_length' => 'The username should be at least three characters long', 'max_length' => 'The username should be fifty characters long at most', ) ), new sfValidatorCallback(array('callback' => array('cmsLoginValidator', 'execute'), 'arguments' => array() ), array('invalid' => 'This username/password combination is unknown') ), ), array('required' => TRUE), array('required' => 'The username is mandatory') ); $this->oValWachtwoord = new sfValidatorString(array('required' => TRUE), array('required' => 'The password is mandatory') ); $this->oValReferrer = new sfValidatorString(array('required' => FALSE)); } /** * Prepare widgets */ private function _setWidgets() { $this->oWidGebruikersnaam = new sfWidgetFormInput(array(), array('id' => 'username', 'size' => 25)); $this->oWidWachtwoord = new sfWidgetFormInputPassword(array(), array('id' => 'password', 'size' => 10)); $this->oWidReferrer = new sfWidgetFormInputHidden(array(), array('id' => 'referrer')); } /** * Configure form */ public function configure() { $this->_setValidators(); $this->_setWidgets(); /* * Set validators */ $this->setValidators(array('username' => $this->oValGebruikersnaam, 'password' => $this->oValWachtwoord, 'referrer' => $this->oValReferrer, ) ); /* * Set widgets */ $this->setWidgets(array('username' => $this->oWidGebruikersnaam, 'password' => $this->oWidWachtwoord, 'referrer' => $this->oWidReferrer, ) ); /* * Set decorator */ $oDecorator = new sfWidgetFormSchemaFormatterDiv($this->getWidgetSchema()); $this->getWidgetSchema()->addFormFormatter('div', $oDecorator); $this->getWidgetSchema()->setFormFormatterName('div'); $this->getWidgetSchema()->setHelps(array('username' => 'Please enter your username', 'password' => 'Please enter your password' ) ); } /** * Bind override */ public function bind(array $taintedValues = null, array $taintedFiles = array()) { $request = sfContext::getInstance()->getRequest(); if ($request->hasParameter(self::$CSRFFieldName)) { $taintedValues[self::$CSRFFieldName] = $request->getParameter(self::$CSRFFieldName); } parent::bind($taintedValues, $taintedFiles); } }
There are two private methods:
I overloaded the earlier mentioned bind() method to process the CSRF token internally. This field is regulated by sfForm internally, you to do need to worry about it much, only with the actual functionality.
The configure() method contains the functionality. The validators are created and linked to the corresponding fields and also are the widgets. Also, the decorator is defined with I will go into later on.
In _setValidators() sfValidatorBase objects are made for each field.
The password field check is the simplest, only the required input is checked. The object is of the type sfValidatorString (extend sfValidatorBase) without extra controls, only the required attribute with errortext is specified. In principle, you can use each validator as -empty- container.
The username is a combination; it is required, there are restrictions to the the stringlength and a correct login is checked. The stringcontrole/requirement is checked through sfValidatorString, which is quite simple.
The check for a correct login is done through a special validator, that does a callback to an custom function: sfValidatorCallback. This is explained later.
Since these two validators check the same field , the sfValidatorAnd validator is used that combines several validators. All validators must be satisfied. Of course Symfony also offers sfValidatorOr that checks that at least one underlying validator satisfies.
As you can see the callback validator calls to a custom class/method: myLoginValidator::execute().
/** * Fiel with myLoginValidator klasse * * @package - */ /** * Validation correct username/password * * @author Jordi Backx (Snowkrash) * @copyright Copyright © 2008, Jordi Backx (Snowkrash) * @package - */ class myLoginValidator { /** * execute validator * * @param sfValidatorBase Validator instance that calls this method * @param string Value of field that sfValidatorCallback checks * @param array Arguments for correct working * * @return value field when OK. Nothing if error (sfValidatorError exception) */ public static function execute ($oValidator, $sValue, $aArguments) { if ( /* check OK */ ) { // Return waarde veld indien controle OK return $sValue; } // Throw exception when not OK throw new sfValidatorError($oValidator, 'invalid', array('value' => $sValue, 'invalid' => $oValidator->getOption('invalid'))); } }
I set this here with no further logic, that is application specific, thus that you 'll have to do yourself. The base structure can be used. The three parameters must be defined, otherwise the whole application crashes.
In _setWidget() sfWidget objects are made for each field.
The widgets are the form elements: finally <input>, <select> etc tags in combination with labels and errortexts.
Each widget can have HTML attributes, which will be printed inside the form elements.
Finally the form must be printed to the screen through a view template.
<p><?php echo __('You need to log in to be able to use the Content Management System.') ?></p> <div id="formContainer"> <?php if ($oForm->getErrorSchema()->getErrors()) { ?> <div id="formulierFouten"> <ul> <?php foreach ($oForm->getErrorSchema() as $sError) { ?> <li><?php echo __($sError) ?></li> <?php } ?> </ul> </div> <?php } ?> <form action="<?php echo url_for('myModule/myLogin') ?>" method="post"> <?php echo $oForm['username']->renderLabel(__($oForm['username']->renderLabelName())); echo $oForm['username']->renderRow(__($oForm->getWidgetSchema()->getHelp('username'))); ?> <?php echo $oForm['password']->renderLabel(__($oForm['password']->renderLabelName())); echo $oForm['password']->renderRow(__($oForm->getWidgetSchema()->getHelp('password'))); ?> <?php echo $oForm['referrer']->render(array('value' => $oForm->getDefault('referrer'))) ?> <?php echo $oForm['_csrf_token'] ?> <label for="inloggen"> </label><input type="submit" value="Inloggen" id="inloggen" class="aanmeldenSubmit" /> </form> </div>
You can see all the i18n code (__() helper) and some non-Symfony 1.0 form building. Errorlists are built through the errorSchema which is available within the form object, the texts themself can be translated as you can see.
Also the labels and help texts are squeezed through i18n. The field names are in English, because the labels are based on these and must go through i18n. This way everything can be translated.
You can print the whole form with an echo of $oForm (goes through __toString()), but you have more control over the layout when you use specific widgetrender functions, like I do with renderRow(). This method takes the helptext as an argument, with is also translated.
The submit button is no widget, so we place it ourselves the old-fashioned way ... no helper, that is so Symfony 1.0.
That one is new. It is there, but we never defined it. It is created within sfForm gedefinieerd and only (since beta4) when indicated in settings.yml:
#Form security secret (CSRF protection)
csrf_secret: hierjeeigenc0d3 # Unique secret to enable CSRF protection or false to disable
You can choose your own code, on which the hash inside the CSRF value is based.
The form functionally ready, but we want more control over the layout. I am a supporter of the tableless HTML design and the standard formatter of sfForm uses ... tables. Well, we can do better.
The form controller showed the coupling with my own formatter:
/* * Set decorator */ $oDecorator = new sfWidgetFormSchemaFormatterDiv($this->getWidgetSchema()); $this->getWidgetSchema()->addFormFormatter('div', $oDecorator); $this->getWidgetSchema()->setFormFormatterName('div');
I will now go into this part.
I have a class sfWidgetFormSchemaFormatterDiv in sfWidgetFormSchemaFormatterDiv.class.php made in the application-level lib/ directory so that all modules of can use it.
This takes care of the HTML layout of the form elements.
class sfWidgetFormSchemaFormatterDiv extends sfWidgetFormSchemaFormatter { protected $rowFormat = '%error%%field%<br />%help%<br />', $helpFormat = '<span class="help">%help%</span>', $errorRowFormat = '<div>%errors%</div>', $errorListFormatInARow = '%errors%', $errorRowFormatInARow = '<div class="formError">↓ %error% ↓</div>', $namedErrorRowFormatInARow = '%name%: %error%<br />', $decoratorFormat = '<div id="formContainer">%content%</div>'; }
A good article is available that describes this system.
For people that wonder why the label (%label% placeholder) is not used: $rowFormat sets the layout of the renderRow() method and since I want to render the label separately (i18n), it must not be rendered a second time by renderRow().
Hopefully the above can be a good help for your own form in Symfony 1.1. The documentation is quite scarce at the moment, so each bit of help will be welcome.
If the English is somewhat bad, I did a automatic translation of my original Dutch version of the article and tuned that a bit. The reason? I am lazy. ;-)
If you find errors in the above, it is because of copying my code probably. Please mention it in the comments.
Goog luck!
For ergonomic and graphism reasons, I sometimes prefer to use link rather than buttons and/or button rather than links...
To do so, I created this small function :
<?php # File : JsHelper.php // use of "normal" Javascript Helper use_helper('Javascript'); /*! * Create a link that submit the closest parent form * * @param $string string : text to display * @param $options mixed : options to be pass to the tag * @return string : HTML code for the link */ function link_to_submit( $string, $options = null ) { $func = ";var a=this.ancestors();for(var i in a){if(a[i].tagName=='FORM'){try{a[i].onsubmit();} catch(err){a[i].submit();}break;}}"; return link_to_function( $string, $func, $options ); } /*! * Create a button that act as a link * * @param $string string : text to display in the button * @param $url string : URL of the link * @param $options mixed : options to be pass to the tag * @return string : HTML code for the link */ function button_to_link( $string, $url, $options = null ) { $url = url_for($url); $func = ";document.location = '$url';return false;"; if ( is_string($options) ) $options.= " onclick=$func"; else $options['onclick'] = $func; return tag('button', $options ).htmlspecialchars($string).'</button>'; }
Then, you simply use it as a normal link_to function :
<? use_helper('Js') ?> <?=form_tag('module/action')?> <?=input_tag('text')?> <?=link_to_submit('Validate')?> <?=link_to('Cancel','module/index')?> </form>
Or :
<? use_helper('Js') ?> <?=form_tag('module/action')?> <?=input_tag('text')?> <?=submit_tag('Validate')?> <?=button_to_link('Cancel','module/index')?> </form>
In these two examples, links/buttons of the form are graphicly consistents. For a better user experience ;)
PS : works as well with AJAX forms form_remote_tag()
PPS : Don't forget that thanks to CSS, you can make a button looking like a link (et vice-versa).
PPPS : Don't forget that buttons and links doesn't have the same meaning for search engine bots.
PPPPS : Don't forget that this rely on the fact that user is using javascript!!!
There are two big open source rich text editors:
Some people prefer the first, some other the second, but there’s a problem for who likes to use TinyMCE: it lacks of a free file manager/uploader.
What we actually need is to have a TinyMCE installation with a free file manager/uploader, thus we take it from FCKEditor.
This is TinyFCK: a TinyMCE with the FCKEditor’s file manager/uploader, nothing less, nothing more.
Installation of TinyFCK rich text editing is described here. You need to download the editor from the project website (http://p4a.crealabsfoundation.org/tinyfck) and unpack it in a temporary folder. Copy the tiny_fck/ directory into your project web/js/ directory, and define the path to the library and new rich editor class in settings.yml.
all:
.settings:
rich_text_editor_class: TinyFCK
rich_text_js_dir: js/tiny_fck
After that create new text editor class file sfRichTextEditorTinyFCK.class.php in your project or application lib/ folder.
<?php /* * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com> * (c) 2007 Eugene Krasichkov <megazlo@megazlo.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * sfRichTextEditorTinyFCK implements the TinyFCK rich text editor. * * <b>Options:</b> * - css - Path to the TinyFCK editor stylesheet * * <b>Css example:</b> * <code> * / * user: foo * / => without spaces. 'foo' is the name in the select box * .foobar * { * color: #f00; * } * </code> * */ class sfRichTextEditorTinyFCK extends sfRichTextEditor { /** * Returns the rich text editor as HTML. * * @return string Rich text editor HTML representation */ public function toHTML() { $options = $this->options; // we need to know the id for things the rich text editor // in advance of building the tag $id = _get_option($options, 'id', $this->name); // use tinymce's gzipped js? $tinymce_file = _get_option($options, 'tinymce_gzip') ? '/tiny_mce_gzip.php' : '/tiny_mce.js'; // tinymce installed? $js_path = sfConfig::get('sf_rich_text_js_dir') ? '/'.sfConfig::get('sf_rich_text_js_dir').$tinymce_file : '/sf/tinymce/js'.$tinymce_file; if (!is_readable(sfConfig::get('sf_web_dir').$js_path)) { throw new sfConfigurationException('You must install TinyFCK to use this helper (see rich_text_js_dir settings).'); } sfContext::getInstance()->getResponse()->addJavascript($js_path); use_helper('Javascript'); $tinymce_options = ''; $style_selector = ''; // custom CSS file? if ($css_file = _get_option($options, 'css')) { $css_path = stylesheet_path($css_file); sfContext::getInstance()->getResponse()->addStylesheet($css_path); $css = file_get_contents(sfConfig::get('sf_web_dir').DIRECTORY_SEPARATOR.$css_path); $styles = array(); preg_match_all('#^/\*\s*user:\s*(.+?)\s*\*/\s*\015?\012\s*\.([^\s]+)#Smi', $css, $matches, PREG_SET_ORDER); foreach ($matches as $match) { $styles[] = $match[1].'='.$match[2]; } $tinymce_options .= ' content_css: "'.$css_path.'",'."\n"; $tinymce_options .= ' theme_advanced_styles: "'.implode(';', $styles).'"'."\n"; $style_selector = 'styleselect,separator,'; } $culture = sfContext::getInstance()->getUser()->getCulture(); $tinymce_js = ' tinyMCE.init({ mode: "exact", language: "'.strtolower(substr($culture, 0, 2)).'", elements: "'.$id.'", plugins : "table,save,advhr,advimage,advlink,emotions,iespell,insertdatetime,preview,zoom,flash,searchreplace,print,paste,directionality,fullscreen,noneditable,contextmenu", theme: "advanced", theme_advanced_buttons1_add_before : "save,newdocument,separator", theme_advanced_buttons1_add : "fontselect,fontsizeselect", theme_advanced_buttons2_add : "separator,insertdate,inserttime,preview,zoom,separator,forecolor,backcolor,liststyle", theme_advanced_buttons2_add_before: "cut,copy,paste,pastetext,pasteword,separator,search,replace,separator", theme_advanced_buttons3_add_before : "tablecontrols,separator", theme_advanced_buttons3_add : "emotions,iespell,flash,advhr,separator,print,separator,ltr,rtl,separator,fullscreen", theme_advanced_toolbar_location : "top", theme_advanced_toolbar_align : "left", theme_advanced_statusbar_location : "bottom", theme_advanced_resizing : true, theme_advanced_resize_horizontal : false, plugin_insertdate_dateFormat : "%Y-%m-%d", plugin_insertdate_timeFormat : "%H:%M:%S", file_browser_callback : "tinyFCKFileBrowserCallBack", paste_use_dialog : false, extended_valid_elements: "img[class|src|border=0|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name]", apply_source_formatting : true, relative_urls: false, debug: false '.($tinymce_options ? ','.$tinymce_options : '').' '.(isset($options['tinymce_options']) ? ','.$options['tinymce_options'] : '').' }); function tinyFCKFileBrowserCallBack(field_name, url, type, win) { var connector = "../../filemanager/browser.html?Connector=connectors/php/connector.php"; var enableAutoTypeSelection = true; var cType; tinyfck_field = field_name; tinyfck = win; switch (type) { case "image": cType = "Image"; break; case "flash": cType = "Flash"; break; case "file": cType = "File"; break; } if (enableAutoTypeSelection && cType) { connector += "&Type=" + cType; } window.open(connector, "tinyfck", "modal,width=600,height=400"); }'; if (isset($options['tinymce_options'])) { unset($options['tinymce_options']); } return content_tag('script', javascript_cdata_section($tinymce_js), array('type' => 'text/javascript')). content_tag('textarea', $this->content, array_merge(array('name' => $this->name, 'id' => get_id_from_name($id, null)), _convert_options($options))); } }
Then clear cache:
symfony cc
Once this is done, toggle the use of rich text editing in text areas by adding the rich=true option. You can also specify custom options for the TinyFCK editor using the tinymce_options option.
<?php echo textarea_tag('name', 'default content', 'rich=true size=10x20')) ?> => a rich text edit zone powered by TinyFCK <?php echo textarea_tag('name', 'default content', 'rich=true size=10x20tinymce_options=language:"fr",theme_advanced_buttons2:"separator"')) ?> => a rich text edit zone powered by TinyFCK with custom parameters
Say you want the "create" form to have default values in some of the fields.
Unfortunately, if you hardcode values like this in the edit section of generator.yml, it will break the edit form for existing objects.
edit:
fields:
thing_field: {params:value=foo} # WRONG
The solution is to override the getThingOrCreate method the admin generator generates in its actions.class.php file.
If you're dealing with a class/module named 'post', you can copy and paste the generated function out of cache/frontend/dev/modules/autoPost/templates/_edit_form.php into apps/frontend/modules/post/actions/actions.class.php and add the defaults like this:
protected function getPostOrCreate($id = 'id') { if (!$this->getRequestParameter($id)) { $post = new Post(); // add some default values $post->setThing('foo'); // default publish 24 hours from now $post->setPublishTime(date('Y-n-j G:i', time() + 60*60*24 )); } else { $post = PostPeer::retrieveByPk($this->getRequestParameter($id)); $this->forward404Unless($post); } return $post; }
When using fillin filter, after validation success (all form fields are ok) if you want display page without form, fillin filter is still enabled and throws exception "Exception - No form found in this page". Use this snippet to prevent it.
in sfFillInFormFilter.class.php file
public function execute($filterChain) { // execute next filter $filterChain->execute(); $context = $this->getContext(); $response = $context->getResponse(); $request = $context->getRequest(); // add these two lines if(! $request->hasErrors()) return;
By default, we can't display primary keys in the admin generator edit view.
But I wanted to do just that.
Here is an example for the module/table "article", and the primary key column "id" :
Create a partial "_input_id.php" in the "templates" directory of your module :
<?php echo input_tag('inputId', $article->getId(), array('disabled' => true, 'size'=>10)); ?>
In "config/generator.yml", declare your partial field in the "fields" section to set back the original name of the field, and in the "edit/display" parameter :
generator: class: sfPropelAdminGenerator param: model_class: Article theme: default fields: input_id: {name: id} list: title: Article List edit: display: [_inputId, name]
Beware of the tricky spelling of the partial name in the generator.yml file if, like me, you like underscores...
I needed the object_admin_double_list helper of the admin generator for my regular projects (without using admin generator). So I ported the admin_double_list helper a bit and now I want to publish this for you.
The admin_double_list helper is for selecting multiple items from a pool of items using ManyToMany relationships. For example to associate a user to multiple groups.
The helper itself has two parts: - two helper functions - two javascript functions
After that I have an example how the three controller, view and model parts handle this helper.
/** * two multiline select tags with associated and unassociated items * * @return string * @param object object * @param string method of object * @param array options * @param array html options of select tags **/ function double_list($object, $method, $options = array(), $html_options = array()) { $options = _parse_attributes($options); // get the lists of objects list($all_objects, $objects_associated, $associated_ids) = _get_object_list($object, $method, _get_option($options, 'through_class')); // options $html_options['multiple'] = _get_option($html_options, 'multiple', true); $html_options['size'] = _get_option($html_options, 'size', 10); $html_options['class'] = 'double_list'; $label_assoc = _get_option($options, 'associated_label', 'Zugehörige Gruppen'); $label_all = _get_option($options, 'unassociated_label', 'Gruppenliste'); $name1 = _get_option($options, 'associated', 'associated'); $name2 = _get_option($options, 'unassociated', 'unassociated'); $form = _get_option($options, 'form_id', 'editForm'); // unassociated objects $objects_unassociated = array(); foreach ($all_objects as $object) { if (!in_array($object->getPrimaryKey(), $associated_ids)) $objects_unassociated[] = $object; } // select tags $select1 = select_tag($name1, options_for_select(_get_options_from_objects($objects_associated), '', $options), $html_options); unset($html_options['class']); $select2 = select_tag($name2, options_for_select(_get_options_from_objects($objects_unassociated), '', $options), $html_options); // output skeloton $html = '<div style="float:left; padding-right: 20px;"> <label for="%s">%s</label> %s </div> <div class="float:left; padding-right: 20px; padding-top: 20px">%s<br />%s</div> <div class="float:left;"> <label for="%s">%s</label> %s </div> <div style="clear:both"></div>'; // include js library $response = sfContext::getInstance()->getResponse(); $response->addJavascript('/js/double_list.js', 'last'); return sprintf($html, $name1, $label_assoc, $select1, link_to_function(image_tag('resultset_previous'), "double_list_move(\$('{$name2}'), \$('{$name1}'))"), link_to_function(image_tag('resultset_next'), "double_list_move(\$('{$name1}'), \$('{$name2}'))", 'style=display:block'), $name2, $label_all, $select2, $form ); } /** * retrieve object list via propel * * @return array * @param object root object * @param string retrieving method * @param string name of satellite class **/ function _get_object_list($object, $method, $middleClass) { // get object $object = $object instanceof sfOutputEscaper ? $object->getRawValue() : $object; // get all objects $objects = sfPropelManyToMany::getAllObjects($object, $middleClass); // get related objects $objects_associated = sfPropelManyToMany::getRelatedObjects($object, $middleClass); // get ids $ids = array_map(create_function('$o', 'return $o->getPrimaryKey();'), $objects_associated); return array($objects, $objects_associated, $ids); }
You probably want to modify the look of the list using css (like I do). So you can change the $js variable like you want f.e. adding class names. And you perhaps also want to change the image paths (I used two icons of the famfamfam icon library).
Put the code below in a file called double_list.js in your js directory. (For individual path and file name, modify the double_list helper, search for $response)
function double_list_move(src, dest)
{
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;
}
}
}
function double_list_submit()
{
// get all selects with double list class
selects = $$('select.double_list');
selects.each(function(element){
for (var i = 0; i < element.options.length; i++)
element.options[i].selected = true;
});
return true;
}
I would like to show an example how to handle this helper in the three patterns.
The example is easy. Assigning an user to many groups.
We have to build a table which handles the ManyToMany relationship.
user_group:
_attributes:
phpName: UserGroup
group_id:
type: integer
primaryKey: true
foreignTable:groups
foreignReference:id
onDelete: cascade
user_id:
type: integer
primaryKey: true
foreignTable:users
foreignReference:id
onDelete: cascade
Don't forget to rebuild all db stuff and to clear the cache.
Now we display the double_list:
<?php echo double_list($user, 'getUserGroups', 'through_class=UserGroup associated=groups unassociated=not_groups associated_label=Associated Groups unassociated_label=Group list') ?>
The first parameter is the user object, than the method of the object retrieving the UserGroup records. I think the options are clear.
At last we have to save the selection of the user. Before doing this we have to delete all UserGroup objects of the user, because we would assign it twice, if the item was selected before.
// clear group data to save it again
<