I'm facing validation problems integrating my custom module in zfcAdmin and BjyAuthorize.
My form class:
...
$formOptions = $this->settings->getFormSettings();
foreach ($formOptions as $field){
if (isset($field['field']))
$this->add($field['field']);
}
...
My filter class:
$formOptions = $this->settings->getFormSettings();
foreach ($formOptions as $filter){
if (isset($filter['filter']))
$this->add($filter['filter']);
}
...
Fields, filters and other options are retrieved from config file.
Basically everything works fine: form data can be added, edited or deleted from db.
Also after the zfcAdmin module installation no problem rose. Everything works fine using both 'site/mymodule' route and 'site/admin/mymodule' route: i can still add, edit and delete items from db.
Here the problem: I need some form elements (a Select in this particular case) editable/viewable only by administrator. (I can write a new controller/entity class 'ad hoc' for admin but i would like to use the same code for the whole site.)
I installed and configured bjyoungblood/BjyAuthorize module: it allowed me to display some form elements/fields only to admin but when i'm in edit mode a form validation error is displayed: "Value is required and can't be empty"
Here the code:
//view/mymodule/mymodule/update.phtml
<div id="page" style="margin-top: 50px;">
<?php if (isset($this->messages) && count($this->messages) > 0 ): ?>
<?php foreach ($this->messages as $msg): ?>
<div class="alert alert-<?php echo $this->escapeHtmlAttr($msg['type']); ?>">
<?php if (isset($msg['icon'])) echo '<i class="'.$this->escapeHtmlAttr($msg['icon']).'"></i> '; ?><?php echo $this->escapeHtml($msg['message']); ?>
</div>
<?php endforeach; ?>
<?php endif; ?>
<?php
$title = 'Edit Item';
$this->headTitle($title);
?>
<h1><?php echo $this->escapeHtml($title); ?></h1>
<?php
$form = $this->form;
$form->setAttribute('action', $this->url($this->route . 'mymodule/update', array('action' => 'update', 'id' => $this->id )));
$form->prepare();
$form->setAttribute('method', 'post');
$input = $form->getInputFilter();
?>
<?php echo $this->form()->openTag($form) ?>
<dl class="zend_form">
<?php foreach ($form as $element): ?>
<?php
//CHECK USER PRIVILEDGES
$elName = $element->getName();
$elResource = isset($this->form_options[$elName]['auth']) ? $this->form_options[$elName]['auth']['resource'] : "userresource";
$elPrivilege = isset($this->form_options[$elName]['auth']) ? $this->form_options[$elName]['auth']['privilege'] : "view";
//SHOW THE ELEMENT IF ALLOWED
if($this->isAllowed($elResource, $elPrivilege)):
?>
<?php if ($element->getLabel() != null): ?>
<dt><?php echo $this->formLabel($element) ?></dt>
<?php endif ?>
<?php if ($element instanceof Zend\Form\Element\Button): ?>
<dd><?php echo $this->formButton($element) ?></dd>
<?php elseif ($element instanceof Zend\Form\Element\Select): ?>
<dd><?php echo $this->formSelect($element) . $this->formElementErrors($element) ?></dd>
<?php else: ?>
<dd><?php echo $this->formInput($element) . $this->formElementErrors($element) ?></dd>
<?php endif ?>
<?php else: ?>
<?php
?>
<?php endif ?>
<?php endforeach ?>
</dl>
<?php echo $this->form()->closeTag() ?>
</div>
<div class="clear-both"></div>
My controller action
//controller
public function updateAction(){
$messages = array();
$id = (int)$this->getEvent()->getRouteMatch()->getParam('id');
$form = $this->getServiceLocator()->get('FormItemService');
$itemMapper = $this->getItemMapper();
$item = $itemMapper->findById($id);
$form->bind($item);
$request = $this->getRequest();
if($request->isPost()){
$form->setData($request->getPost());
if ($form->isValid()) {
die('c');//never here
$service = $this->getServiceLocator()->get('mymodule\Service\Item');
if ( $service->save($form->getData()) )
{
$messages[] = array(
'type' => 'success',
'icon' => 'icon-ok-sign',
'message' => 'Your profile has been updated successfully!',
);
}
else
{
$messages[] = array(
'type' => 'error',
'icon' => 'icon-remove-sign',
'message' => 'Profile update failed! See error messages below for more details.',
);
}
}else{
var_dump($form->getMessages());//Value is required and can't be empty
}
}
return array(
'messages' => $messages,
'form' => $form,
'id' => $id,
'form_options' => $this->getServiceLocator()->get('mymodule_module_options')->getFormSettings(),
'route' => $this->checkRoute($this->getEvent()->getRouteMatch()->getmatchedRouteName())
);
}
If user is not allowed to view the resource, the element is not echoed. So $request->getPost() has no value for that form element and an error is returned by isValid().
Has anyone solved a similar problem or can anyone point me to the right direction?
Thanks
The problem is that you don't do any security check in your FormFilter class, where you define your required fields.
The $form->isValid() function checks the posted data against those filter elements. So it's not enough to prevent the 'echo field' in your view, you still need to apply the security check to the filter element.
One other approach would be to make two forms one for the front end and one for the admin. Since the one for the admin will have the same fields plus one extra select field you can make the admin form extends the front end one. E.g.
class myForm
{
public function __construct(...)
{
// add fields and set validators
}
}
and the admin form could be:
class MyAdminForm extends myForm
{
public function __construct(...)
{
parent::__construct(...);
// add the extra field and extra validator
}
}
In that way even if you edit the front end form (or validators) the back end will always be up to date.
Hope this helps :),
Stoyan
Related
Hopefully all this info helps out.
So what I am doing is i have a forum page that I have set up to were you can select a category, from that category you can insert a post into that category. What I need help with is getting the id of that category for the database so that post will show up when I echo it out. In other words linking ids to the pages upon insert.
ok so i know that its inserting the username message title ect but what its not doing is getting the 1 from the url and inserting that 1 into the database under category_id
Here is my url I left out the main http to shorten this up but the rest of it were the number 1 is what I am wanting to get and insert cause that its going to change depending on the category you choose. index.php/forum/create_post/1
This is what my category table has
ID title
1 community
This is what my post table has were all the main info comes from and were I am wanting to connect the category_id to the main category table.
id, title message date user id category_id flaged username
This is the first view that will insert the new post
View:
<div id="container">
<div class="module">
<?php echo form_open('forum/create_post'); ?>
<div>
<?php
echo form_label('Title', 'title');
echo form_input('title', '');
?>
</div>
<div>
<?php
echo form_label('Message', 'message');
echo form_input('message', '');
?>
</div>
<div>
<?php echo form_submit('create', 'create new post'); ?>
</div>
<?php echo form_close(); ?>
</div>
</div>
Controller: here is the section in the controller were I am passing all my input if no to
public function create_post() {
if( !$this->session->userdata('username') ) {
redirect('/'); // please login to continue
}
if( $this->input->post('create') ) {
$this->forum->createpost(array(
// $id= $this->uri->segment(3),
'title' => $this->input->post('title'),
'message' => $this->input->post('message'),
'user_id'=> $this->session->userdata('id'),
'username'=>$this->session->userdata('username'),
));
}
$this->load->view('templates/header');
$this->load->view('forum/create_post');
$this->load->view('templates/footer');
}
Here is the model that i'am inserting the data to
public function createpost($data){
$this->db->insert('post',$data);
}
As per your URL index.php/forum/create_post/1, You controller function should be as below to meet CI Standard.
public function create_post($category_id) {
So you can access $category_id directly. No need to get url segment.
$res = $this->forum->createpost(array(
$id= $category_id,
'title' => $this->input->post('title'),
'message' => $this->input->post('message'),
'user_id'=> $this->session->userdata('id'),
'username'=>$this->session->userdata('username'),
));
if($res)
{
// show thanks msg
}
else
{
// show error msg
}
In your model:
You can check that data is inserted
public function createpost($data)
{
$this->db->insert('post',$data);
if($this->db->affected_rows()>0)
return true;
else
return false;
}
You are not sending /1 to the page again. Your form must send that /1 from original url.
<?php echo form_open('forum/create_post'); ?>
create_post method wont receive a 3rd segment, because it doesn't exists. Store category id in a input hidden and access it by $this->input->post('category_id'):
Controller
public function create_post() {
if( !$this->session->userdata('username') ) {
redirect('/'); // please login to continue
}
if( $this->input->post('create') ) {
$this->forum->createpost(array(
'category_id' => $this->input->post('category_id'),
'title' => $this->input->post('title'),
'message' => $this->input->post('message'),
'user_id' => $this->session->userdata('id'),
'username'=>$this->session->userdata('username')
));
}
$data['category_id'] = $this->uri->segment(3);
$this->load->view('templates/header');
$this->load->view('forum/create_post', $data);
$this->load->view('templates/footer');
}
View
<div id="container">
<div class="module">
<?php echo form_open('forum/create_post'); ?>
<div>
<?php
echo form_label('Title', 'title');
echo form_input('title', '');
?>
</div>
<div>
<?php
echo form_label('Message', 'message');
echo form_input('message', '');
?>
</div>
<div>
<?php echo form_submit('create', 'create new post'); ?>
</div>
<input type="hidden" name="category_id" value="<?php echo $category_id; ?>">
<?php echo form_close(); ?>
</div>
</div>
Also, ask yourself if there is a need to storage user ID and username. Isn't username attached to an ID? Couldn't it be retrieved by just selecting userID from a post?
I found out what i needed to do make this all work i had to insert the uri segment into a hidden input forum to get it to insert into the right id box and now iam able to connect the right categorys
I am trying to setup a new application using the Yii framework that requires a dependent dropdown, so that when a user selects the jobSkillArea, the options for the next dropdown, jobSkillSpecialty, gets loaded using the built-in jQuery methods. I have copied and modified the code from things I found here and the Yii forums, but I am getting nothing, not even in Chrome's javascript console. Can anyone look at this and see where I've gone wrong? Thanks.
Here is the code in my view for the two dropdowns:
<div class="row">
<?php echo $form->labelEx($model,'jobSkillArea'); ?>
<?php
$list = array();
$list = CHtml::listData(validJobSkillAreas::model()->findAll(), 'JobSkillArea', 'JobSkillArea');
echo $form->dropDownList($model, 'jobSkillArea', $list,
array('prompt'=>'--Select Skill Area--'),
array(
'ajax'=>array(
'type'=>'POST',
'data'=>array('jobSkillArea'=>'js:this.value'),
'url'=>CController::createUrl('NewConsFormController/getSkillSpecialty'),
'update'=>'#'.CHtml::activeId($model,'jobSkillSpecialty')
)
)
);
?>
<?php echo $form->error($model,'jobSkillArea'); ?>
</div>
<div class="row">
<?php echo $form->labelEx($model,'jobSkillSpecialty'); ?>
<?php
$list = array();
$list = CHtml::listData(validJobSkillSpecialties::model()->findAll(),'jobSkillSpecialty','jobSkillSpecialty');
echo $form->dropDownList($model, 'jobSkillSpecialty', array(), array('prompt'=>'--Select Skill Specialty--'));
?>
<?php echo $form->error($model,'jobSkillSpecialty'); ?>
</div>
Then below is the code called by the first dropdown from my controller. The first find is to get the ID that links the parent with the child since I am not storing the KeyValue in the end product. The rest is as it came from the forums.
public function actionGetSkillSpecialty() {
$areaID = ValidJobSkillAreas::model()->find('JobSkillArea=:SkillArea',
array(':SkillArea'=>'$_POST[$jobSkillArea]'));
$data=ValidJobSkillSpecialties::model()->findAll('SkillAreaId=:SkillAreaId',
array(':SkillAreaId'=>$areaID->ID));
$list=array();
$list=CHtml::listData($data,'jobSkillSpecialty','jobSkillSpecialty');
echo "<option value=''>--Select Skill Specialty--</option>";
foreach($list as $value=>$jobSkillSpecialty) {
echo CHtml::tag('option',
array('value'=>$value),CHtml::encode($jobSkillSpecialty),true);
}
}
The view is a partial render within the _form view because that was the only way I could get the accordion widget to work with the fields I have. This is the accordion code that calls the jobDetails section that contains the two dropdown selection boxes.
<div id="accordion">
<?php
$this->widget('zii.widgets.jui.CJuiAccordion', array(
'panels'=>array(
'Job Details'=>$this->renderPartial('_partial_jobdetails',array('model'=>$model,'this'=>$this,'form'=>$form),true,false),
'Consultant Details'=>$this->renderPartial('_partial_consdetails',array('model'=>$model,'this'=>$this,'form'=>$form),true,false),
'Client Details'=>$this->renderPartial('_partial_clientdetails',array('model'=>$model,'this'=>$this,'form'=>$form),true,false),
'Internal Info'=>$this->renderPartial('_partial_internaldetails',array('model'=>$model,'this'=>$this,'form'=>$form),true,false),
'Form Requirements'=>$this->renderPartial('_partial_formsdetails',array('model'=>$model,'this'=>$this,'form'=>$form),true,false),
'JPMC Details'=>$this->renderPartial('_partial_jpmcdetails',array('model'=>$model,'this'=>$this,'form'=>$form),true,false),
),
// additional javascript options for the accordion plugin
'options'=>array(
'collapsible'=>true,
'active'=>false,
'autoHeight'=>false,
'heightStyle'=>'content',
),
'htmlOptions'=>array(
// HTML options you may need
),
));
?>
</div>
Please try the following code
View
<?php
echo CHtml::dropDownList('region_id','',
CHtml::listData($courses, 'course_id', 'course_name'),
array(
'prompt'=>'Select Region',
'ajax' => array(
'type'=>'POST',
'url'=>CController::createUrl('loadcities'),
'update'=>'#city_name',
'data'=>array('region'=>'js:this.value'),
)));
echo CHtml::dropDownList('city_name','', array(), array('prompt'=>'Select City'),
);
?>
=================================================
controller function
public function actionLoadcities()
{
$data=City::model()->findAll('course='.$_POST['region'],
array(':region'=>(int) $_POST['region']));
$data=CHtml::listData($data,'city_id','city_name');
echo "<option value=''>Select City</option>";
foreach($data as $value=>$city_name)
echo CHtml::tag('option', array('value'=>$value),CHtml::encode($city_name),true);
}
There is two actions in the controller:
CProfile:
public function actionCProfile()
{
$model=$this->loadModel(Yii::app()->user->id);
$model->scenario = 'updateProfile';
$this->performAjaxValidation($model);
if(isset($_POST['User'])){
$model->attributes=$_POST['User'];
if($model->validate())
if($model->save()){
if(Yii::app()->request->isAjaxRequest)
Yii::app()->end('saved');
else{
Yii::app()->user->setFlash('status','saved');
}
}
if(Yii::app()->request->isAjaxRequest)
if(!$model->validate())
Yii::app()->end('!validate');
}
if(Yii::app()->request->isAjaxRequest)
$this->renderPartial('_cprofile',array('model'=>$model));
else
$this->render('update',array('model'=>$model,'form'=>'_cprofile'));
}
And CPass:
public function actionCPass()
{
$model = $this->loadModel(Yii::app()->user->id);
$model->scenario = 'CPass';
$this->performAjaxValidation($model);
if(isset($_POST['User'])){
$model->attributes = $_POST['User'];
if($model->validate()){
if($model->verifyPassword($model->currentPass)){
$model->changePassword($model->newPass);
Yii::app()->end('changed');
}
}
}
if(Yii::app()->request->isAjaxRequest)
$this->renderPartial('_cpass',array('model'=>$model));
else
$this->render('update',array('model'=>$model,'form'=>'_cpass'));
}
And three view files:
update.php:
<?php
$cs = Yii::app()->clientScript;
$cs->registerCssFile('/x/css/myDetailview.css');
$cs->registerCssFile('/x/css/upanel/user-profile.css');
$url = Yii::app()->getBaseUrl().'/js/upanel.js';
$cs->registerScriptFile($url,CClientScript::POS_HEAD);
?>
<div id='user-profile-menu'>
<ul>
<li><?php echo CHtml::link('profile',Yii::app()->createUrl('/upanel/user/CProfile'),array('id'=>'profile-change-link')) ?></li>
<li><?php echo CHtml::link('change email',Yii::app()->createUrl('/upanel/user/CPass'),array('id'=>'pass-change-link')) ?></li>
</ul>
</div>
<div id='container'>
<?php $this->renderPartial($form,array('model'=>$model)); ?>
</div>
_cprofile.php:
<div class="form" >
<?php
$form = $this->beginWidget('CActiveForm',array(
'id'=>'change-profile-form',
'enableAjaxValidation'=>true,
'enableClientValidation'=>true,
'action' => 'index.php?r=upanel/user/cprofile',
'method' => 'post',
'clientOptions'=>array(
'validateOnSubmit'=>true,
'validateOnChange'=>true,
'validateOnType'=>false,
),
));
?>
.
.
.
<div class="row">
<?php
$url=Yii::app()->createUrl('upanel/user/CProfile');
echo CHtml::ajaxSubmitButton('update',$url,
array(
'type'=>'POST',
'data'=>"js:$('#change-profile-form').serialize()",
'success'=>'callback',
'beforeSend'=>'before',
),
array(
'id'=>'update-button',
'class'=>'submit-button',
)
);
?>
</div>
<?php $this->endWidget() ?>
<?php echo $form->errorSummary($model,'resolve following errors: ') ?>
_cpass.php:
<div class="form">
<?php
$form = $this->beginWidget('CActiveForm',array(
'id'=>'change-pass-form',
'enableAjaxValidation' => 'true',
'action' => Yii::app()->createUrl('upanel/user/CPass'),
'method' => 'POST',
'clientOptions' => array(
'validateOnSubmit' => true,
'validateOnChange' => true,
'validateOnType' => false,
)
));
?>
.
.
.
<div class="row">
<?php
$url=Yii::app()->createUrl('upanel/user/CPass');
echo CHtml::ajaxSubmitButton('update',$url,
array(
'type'=>'POST',
'data'=>"js:$('#change-pass-form').serialize()",
'success'=>'callback',
'beforeSend'=>'before',
),
array(
'id'=>'update-button',
'class'=>'submit-button',
)
);
?>
</div>
<?php $this->endWidget() ?>
</div> <!-- End Password Form -->
<?php echo $form->errorSummary($model) ?>
*Edit #1: *
See, i have a view page named update.php that in this page there is two links:
<div id='user-profile-menu'>
<ul>
<li><?php echo CHtml::link('profile',Yii::app()->createUrl('/upanel/user/CProfile'),array('id'=>'profile-change-link')) ?></li>
<li><?php echo CHtml::link('Change Password',Yii::app()->createUrl('/upanel/user/CPass'),array('id'=>'pass-change-link')) ?></li>
</ul>
The following JQuery code runs exsist actions in controller, that result is a renderpartial() one of these view files: _cpass.php or _profile.php
$('document').ready(function(){
$('#profile-change-link').click(function(){
var link = $(this).attr('href');
$('#container').load(link);
return false;
})
$('#pass-change-link').click(function(){
var link = $(this).attr('href');
$('#container').load(link);
return false;
})
});
In the controller CProfile action defined as default action. with this action there is no any problem in which of POST and Ajax methods.
But main problem: after that _cpass.php view replaced with _profile and have send the exist form in the _profile to CPass action for processing encountered with problem.
The problem is this:
Output of following code as expected is 1:
public function actionCPass()
{
$model = $this->loadModel(Yii::app()->user->id);
$model->scenario = 'CPass';
$this->performAjaxValidation($model);
if (1==1)
Yii::app()->end('1');
}
But output of following code is not that thing we expected. Returned value is a renderpartial() of _profile view. while there is no any related code about _cprofile view in CPass action code!
public function actionCPass()
{
$model = $this->loadModel(Yii::app()->user->id);
$model->scenario = 'CPass';
$this->performAjaxValidation($model);
if(isset($_POST['User'])){
$model->attributes = $_POST['User'];
if($model->validate()){
if($model->verifyPassword($model->currentPass)){
$model->changePassword($model->newPass);
Yii::app()->end('changed');
}
}
}
if(Yii::app()->request->isAjaxRequest)
$this->renderPartial('_cpass',array('model'=>$model));
else
$this->render('update',array('model'=>$model,'form'=>'_cpass'));
}
Output of above code:
<div class="form" >
<form id="change-profile-form" action="index.php?r=upanel/user/cprofile" method="post">
.
.
.
<div class="row">
<input id="update-button" class="submit-button" type="submit" name="yt0" value="update" /> </div>
</form>
</div> <!-- End The Profile Form -->
<div id="change-profile-form_es_" class="errorSummary" style="display:none">resole following errors:
<ul><li>dummy</li></ul></div>
Now understand what is the issue? The problem there is only with Ajax request and when disabled the javascript in the browser the CPass action acts truly.
Excuseme if firstly explained very bad.
Thanks friends
*Edit #2: *
When debugging with Firebug i noticed that snippet of JQuery code didn't change.
When firstly in default action of controller this code:
$this->renderPartial('_cprofile',array('model'=>$model));
be run following JQuery code will create: (As you know following code is related to CHtml::ajaxSubmitButton)
$('body').on('click','#update-button',function(){jQuery.ajax({'type':'POST','data':$('#change-profile-form').serialize(),'success':callback,'beforeSend':before,'url':'/x/index.php?r=upanel/user/CProfile','cache':false});return false;});
So when _cpass view renders with renderpartial() method in CPass action, the above JQuery code must be changed but will not change. I guess it must be the following code:
$('body').on('click','#update-button',function(){jQuery.ajax({'type':'POST','data':$('#change-cpass-form').serialize(),'success':callback,'beforeSend':before,'url':'/x/index.php?r=upanel/user/CPass','cache':false});return false;});
So my problem is with JQuery code generated for CHtml::ajaxSubmitButton. Can anyone help me?
Solved
With applying uniqueId for ajaxSubmitButton and setting CController::processOutput() to True in Controller Action solved.
Summary Code:
actionCPass:
public function actionCPass()
{
$model = $this->loadModel(Yii::app()->user->id);
$model->scenario = 'CPass';
$this->performAjaxValidation($model);
if(isset($_POST['User'])){
$model->attributes = $_POST['User'];
if($model->validate()){
if($model->verifyPassword($model->currentPass)){
$model->changePassword($model->newPass);
Yii::app()->end('changed');
}
}
}
if(Yii::app()->request->isAjaxRequest)
$this->renderPartial('_cpass',array('model'=>$model),false,true);
else
$this->render('update',array('model'=>$model,'form'=>'_cpass'));
}
_cpass.php:
.
.
.
<?php
echo CHtml::ajaxSubmitButton(
'update',
Yii::app()->createUrl('upanel/user/CPass'),
array(
'type'=>'POST',
'data'=>"js:$('#change-pass-form').serialize()",
'success'=>'callback',
'beforeSend'=>'before',
),
array(
'id'=>'update-button'.uniqid(),
'class'=>'submit-button',
)
);
.
.
.
?>
Rename one/both of your button ids. This means the event handlers will be attached to different buttons.
Use renderPartial('view.path',array(parameter_array),false,true); when returning a view via Ajax. The last parameter of CController::renderPartial() calls CController::processOutput() which inserts the required client scripts at appropriate places.
I think I understand your problem, you are wondering why the page does not have the surrounding <div id='profile-container'> and </div>.
I think this may have something to do with the render functions only being able to run once per view called (so it only renders the inside view), you can test this by removing the renderPartial from the update page and see if it prints the blank div.
One way you could get around this is to instead of doing the two renders, always call each page (even in the ajax requests) but pass an extra attribute if it was an ajax request. Then on each of the pages begin and end a custom widget that contains the surrounding template you want on those pages (or just write it in manually).
Example:
controller
public function actionCProfile()
{
// ... same as your example
if(Yii::app()->request->isAjaxRequest){
$this->renderPartial('_cprofile',array('model'=>$model, 'wasAjax'=>false));
} else {
$this->renderPartial('_cprofile',array('model'=>$model, 'wasAjax'=>true));
}
}
then in your view just check if wasAjax is true, if it is then print the surrounding div (or extra elements). If this is big or could change later on, put it in a custom widget.
I'm adding dinamically textfields to a property in the model called names, but I like to set validation rules to every name in the array names in the view, Is this possible to do it unsing validation rules in the CForm Model?, for example I like to validate that every name will be required after clicking the Button.
This is the code of the view with a CActiveForm
<?php $form=$this->beginWidget('CActiveForm', array(
'id'=>'test',
'enableClientValidation'=>true,
'clientOptions'=>array(
'validateOnSubmit'=>true,
),
)); ?>
<p class="note">Campos <span class="required">*</span> son obligatorios.</p>
<div class="row">
<?php echo $form->labelEx($model,'Age'); ?>
<?php echo $form->textField($model,'age'); ?>
<?php echo $form->error($model,'age'); ?>
</div>
<div class="row">
<?php echo $form->labelEx($model,'Name'); ?>
<?php echo $form->passwordField($model,'names[0]'); ?>
<?php echo $form->error($model,'names[0]'); ?>
</div>
<div class="row">
<?php echo $form->labelEx($model,'Name'); ?>
<?php echo $form->passwordField($model,'names[1]'); ?>
<?php echo $form->error($model,'names[1]'); ?>
</div>
<div class="row buttons">
<?php echo CHtml::submitButton('test'); ?>
</div>
<?php $this->endWidget(); ?>
You need to write a custom validator that assumes the attribute being validated is an array and applies the result of another validator to each of its elements.
I used this validator that does exactly the above as a starting point; after slightly cleaning up and simplifying as much as possible, I reached this code for server-side validation:
protected function validateAttribute($object, $attribute)
{
$value = $object->$attribute;
if (!is_array($value) ) {
$this->addError($object, $attribute, "TODO: error message");
return;
}
else if ($this->isEmpty($value)) {
if (!$this->allowEmpty) {
$this->addError($object, $attribute, "TODO: error message");
}
return;
}
// $this->validator and $this->parameters are supposed to be
// attributes of your custom validator class that you set from
// inside rules().
$validator = self::createValidator(
$this->validator,
$object,
array($attribute),
$this->parameters);
$errors = array();
// Iterate over $value, validating each item in turn.
// Since $validator may be a filtering validator, we need to make
// sure that any changes it makes to the validated item stick, so
// we iterate by reference and do a bit of back and forth shuffling.
foreach($value as $key => &$item) {
$object->$attribute = $item; // the only way
$validator->validateAttribute($object, $attribute);
$item = $object->$attribute; // make any changes stick
if ($object->hasErrors($attribute)) {
$errors[$key] = $object->gerErrors($attribute);
$object->clearErrors($attribute);
}
}
unset($item); // always a good idea after foreach by reference
$object->$attribute = $value; // undo the damage
// And now decide what to do with $errors, which is per-item.
// This should be good:
foreach ($errors as $key => $error) {
$object->addError("$attribute[$key]", $error);
}
}
OK, but what about client-side validation? It seems that this should be possible, but I have not tested it:
public function clientValidateAttribute($object,$attribute)
{
// Since this is copy/pasted from above, it's an obvious candidate
// for refactoring into a private method. I 'm keeping it simple.
$validator = self::createValidator(
$this->validator,
$object,
array($attribute),
$this->parameters);
$js = '';
// No need for filtering support here (I think...)
foreach($value as $key => $item) {
$object->$attribute = $item;
$js .= $validator->clientValidateAttribute($object, $attribute);
}
return $js;
}
First you need to specify the relation within the model.Like:
'user' => array(self::BELONGS_TO, 'User', 'user_id'),
Then in the search function use that relation like:
$criteria->with=array('user');
And use that in your view like:
array(
'name'=>'username_search',
'value'=>'$data->user->username',
'header'=>'Posted By',
),
I have a Post model and Tag model which are both set up correctly with a HABTM relationship. I also have a table in my database called posts_tags which is named correctly as per the naming conventions.
However, I can't for the life of me work out how to save tags along with a post. On my "Add Post" page I have this:
<?php echo $this->Form->create('Post'); ?>
<?php echo $this->Form->input('Post.title'); ?>
<?php echo $this->Form->input('Post.body'); ?>
<?php echo $this->Form->input('Tag.name'); ?>
<?php echo $this->Form->input('Post.slug'); ?>
<?php echo $this->Form->end('Publish'); ?>
which calls this action on submit:
public function add() {
if($this->request->is('post')) {
if($this->Post->saveAll($this->request->data)) {
$this->Session->setFlash('Post published!');
$this->redirect('/');
} else {
$this->Session->setFlash('Post unable to be saved!');
}
}
$this->set('pageTitle', 'Compose new blog post');
}
The post saves but not the tag or the relationship between the post and tag, and Cake gives me no error or warning. What do I have to do to get Cake to save the tag along with the post and make the required relationship?
Edit: here's a dump of what's in $this->request->data which is being saved to the database using saveAll():
array(
'Post' => array(
'title' => 'Blog post',
'body' => 'Testing this post',
'slug' => 'Blog-post'
),
'Tag' => array(
'name' => 'test-tag'
)
)
If your relationship for Tag to Post is hasMany, try changing:
<?php echo $this->Form->input('Tag.name'); ?>
to
<?php echo $this->Form->input('Tag.0.name'); ?>