Required Rule on specific condition - yii

I need to apply rule on a form. I have a country and dependent drop down of state.
I need to apply required rule on state field.
But if i choose India from drop down then required rule should be remove from the form.
I have enabled clientsidevalidation true in cactiveform.
View form:
<?php $form = $this->beginWidget('CActiveForm', array(
'id' => 'cart',
'enableAjaxValidation' => false,
'enableClientValidation'=>true,
'clientOptions'=>array('validateOnSubmit'=>true),
// we need the next one for transmission of files in the form.
'htmlOptions' => array('enctype' => 'multipart/form-data'),
));
echo $form->dropDownList($modelUser, 'country', Countries::getcountrylistwithcode(),
array('options' => array($currentCountry=>array('selected'=>true)),
'empty'=>'Select Country',
'class'=>'form-control input-lg',
));
echo $form->error($modelUser,'country');
echo $form->labelEx($modelUser,'state',array('class'=>"col-md-30"));
echo $form->dropDownList($modelUser, 'state', $stateList,array('class'=>"form-control input-lg",'prompt'=>'Select State'));
echo $form->error($modelUser,'state');
$this->endWidget();
?>
And model rules are like this:
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('city,country,state,address_line_one,postcode', 'required'),

You can achieve this by adding a custom rule to your model.
Change your rules function to something like this
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('city,country,address_line_one,postcode', 'required'),
array('state', 'validateState'),
)
}
Next up would be to create the custom validation rule. This would look something like this
public function validateState ($attribute, $params)
{
$aCountriesWithState = array('USA');
if (in_array($this->$attribute, $aCountriesWithState) && empty($this->$attribute))
{
$this->addError($attribute, 'State is missing');
}
}

Related

How to check if an item belongs to a hasMany association before updating in CakePHP 3.2

What I am trying to do:
I have Estimates and Estimates have items "EstimateItems". When updating a Estimate the EstimateItems changed should update. (using patchEntity)
This is working with my current code, my only problem is that other users can edit the Estimate Items of other users when changing the primary key of a EstimateItem in the edit form, because when patching the existing EstimateItems CakePHP only looks at the primary key of the EstimateItem and doesn't take the association in consideration. Also it's still possible to edit the estimate_id of a EstimateItem while $protected estimate_id is set to false.
So what I need is CakePHP to validate that this EstimateItem belongs to the current association before updating or while trying to update.
I hope some one can tell me what I am doing wrong or what I am missing.
Current Query
UPDATE
estimate_items
SET
data = 'Test Query 1',
amount = 123456789,
tax_id = 3
WHERE
id = 3
Expected Query
UPDATE
estimate_items
SET
data = 'Test Query 1',
amount = 123456789,
tax_id = 3
WHERE
id = 3 AND estimate_id = 1
Current code:
Estimates -> Edit.ctp
<?php $this->Form->templates($formTemplates['default']); ?>
<?= $this->Form->create($estimate, ['enctype' => 'multipart/form-data']) ?>
<fieldset>
<legend><?= __('Offerte') ?></legend>
<?= $this->Form->input('reference', ['label' => __('#Referentie'), 'autocomplete' => 'off']) ?>
<?= $this->Form->input('client_id',
[
'type' => 'select',
'empty' => true,
'label' => __('Klant'),
'options' => $clients
]
)
?>
<?php
foreach($estimate->estimate_items as $key => $item){
?>
<div class="item">
<legend>Item</legend>
<?= $this->Form->hidden('estimate_items.'. $key .'.id') ?>
<?= $this->Form->input('estimate_items.'. $key .'.data', ['type' => 'text', 'label' => __('Beschrijving')]) ?>
<?= $this->Form->input('estimate_items.'. $key .'.amount', ['type' => 'text', 'label' => __('Bedrag'), 'class' => 'input-date']) ?>
<?= $this->Form->input('estimate_items.'. $key .'.tax_id',
[
'type' => 'select',
'empty' => true,
'label' => __('Belasting type'),
'options' => $taxes
]
)
?>
</div>
<?php
}
?>
<legend>Informatie</legend>
<?= $this->Form->input('date', ['type' => 'text', 'label' => __('Offerte datum'), 'autocomplete' => 'off']) ?>
<?= $this->Form->input('expiration', ['type' => 'text', 'label' => __('Verloop datum'), 'autocomplete' => 'off']) ?>
</fieldset>
<?= $this->Form->button(__('Save')); ?>
<?= $this->Form->end() ?>
Estimates Controller
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\Event;
use Cake\ORM\TableRegistry;
class EstimatesController extends AppController
{
public function edit($id){
$associated = ['EstimateItems'];
$estimate = $this->Estimates->get($id, ['contain' => $associated]);
$this->log($estimate);
if($this->request->is(['patch', 'post', 'put'])) {
$estimate = $this->Estimates->patchEntity($estimate, $this->request->data, [
'associated' => $associated
]);
$estimate->total = '0';
$this->log($estimate);
$this->log($this->request->data);
if($this->Estimates->save($estimate, ['associated' => $associated])){
$this->Flash->success(__('De offerte is bijgewerkt'));
return $this->redirect(['action' => 'index']);
}
}
$this->set('taxes', $this->Estimates->Taxes->find('list', [ 'keyField' => 'id', 'valueField' => 'tax_name' ]));
$this->set('clients', $this->Estimates->Clients->find('list', [ 'keyField' => 'id', 'valueField' => 'companyname' ]));
$this->set('estimate', $estimate);
}
}
EstimatesTable
<?php
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\RulesChecker;
use Cake\ORM\Rule\IsUnique;
class EstimatesTable extends Table
{
public function initialize(array $config)
{
$this->addAssociations([
'hasOne' => ['Taxes'],
'belongsTo' => ['Companies', 'Clients'],
'hasMany' => ['EstimateItems' => [
'foreignKey' => 'estimate_id'
]]
]);
}
public function buildRules(RulesChecker $rules){
// A Node however should in addition also always reference a Site.
$rules->add($rules->existsIn(['estimate_id'], 'EstimateItems'));
return $rules;
}
}
EstimateItem Entity
<?php
namespace App\Model\Entity;
use Cake\ORM\Entity;
class EstimateItem extends Entity
{
protected $_accessible = [
'*' => false,
'data' => true,
'amount' => true,
'tax_id' => true,
'unit_id' => true
];
}
EstimateItemsTable
<?php
namespace App\Model\Table;
use Cake\ORM\Entity;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\RulesChecker;
use Cake\ORM\Rule\IsUnique;
use Cake\ORM\Query;
class EstimateItemsTable extends Table
{
public function initialize(array $config)
{
$this->addAssociations([
'belongsTo' => ['Estimates' => ['foreignKey' => 'estimate_id']],
'hasOne' => ['Taxes' => ['foreignKey' => 'tax_id']]
]);
}
Estimate Entity
<?php
namespace App\Model\Entity;
use Cake\ORM\Entity;
class Estimate extends Entity
{
/**
* Fields that can be mass assigned using newEntity() or patchEntity().
*
* Note that when '*' is set to true, this allows all unspecified fields to
* be mass assigned. For security purposes, it is advised to set '*' to false
* (or remove it), and explicitly make individual fields accessible as needed.
*
* #var array
*/
protected $_accessible = [
'*' => false,
'id' => false,
];
}
Looks like you need to implement the check via a beforeSave callback function if you don't trust that setting fields hidden / not editable is not enough.
In the callBack you can check if the relation was already there before before you overwrite them with a wrongly edited value.
Markstory Replied to me on github with a solution credits to him:
https://github.com/cakephp/cakephp/issues/9527
In Model/Table/EstimateItemsTable.php
<?php
namespace App\Model\Table;
use Cake\ORM\RulesChecker;
....
class EstimateItemsTable extends Table
{
....
public function buildRules(RulesChecker $rules){
$rules->addUpdate(function($entity) {
if (!$entity->dirty('estimate_id')) {
return true;
}
return $entity->estimate_id == $entity->getOriginal('estimate_id');
}, 'ownership', ['errorField' => 'estimate_id']);
return $rules;
}
}

how to update a record with file uploaded already?

I want to UPDATE record without losing the File that i have uploaded. Below is my create action.
public function actionCreate()
{
$model = new Page;
if (isset($_POST['Page']))
{
$model->attributes = $_POST['Page'];
$model->filename = CUploadedFile::getInstance($model, 'filename');
if ($model->save())
{
if ($model->filename !== null)
{
$dest = Yii::getPathOfAlias('application.uploads');
$model->filename->saveAs($dest . '/' . $model->filename->name);
$model->save();
}
$this->redirect(array('view', 'id' => $model->id));
}
}
$this->render('create', array(
'model' => $model,
));
}
In yii, update method is designed to update the values that you are posting from your form. It will not update all Model properties.
For suppose, your Page model has 4 model properties, assume all are mandatory.
title
description
keywords
image
Since you don't want to update image field, you can make it non mandatory field by setting scenario for image field in your Page model rules.
class Page extends CActiveRecord
{
/*
Coding
*/
public function rules()
{
return array(
array('title, description,keywords', 'required'),
array('image', 'file', 'types'=>'jpg, png'),
array('image', 'required', 'on' => 'insert'),
// => You need image field to be entered on Create, not on Update
.................
}
}
So, Show file input in your form on Create, Hide on Update action so that image field won't submit.
//Your page form
<?php if($model->isNewRecord):?>
echo $form->labelEx($model, 'image');
echo $form->fileField($model, 'image');
echo $form->error($model, 'image');
<?php endif;?>
Since your are using Yii-2 you can use skippOnEmpty validator for the same.

passing value from Yii CController class to CForm (Form Builder) config array

I'm new to Yii, and I'm trying to do my initial project the "right" way. I've created a CFormModel class that needs three fields to query for some data, a CForm config to construct the form, and a CController to tie it together (all given below).
The data request needs an account, and this can come from a couple of different places. I think retrieving it should be in the controller. However, I don't know how to get it into the form's hidden "account" field from the controller, so that it makes it to the arguments assigned to the CFormModel after submission. More generally, I know how to pass from CController to view script, but not to CForm. Is the registry (Yii::app()->params[]) my best bet?
I suppose I can just leave it out of the form (and required fields) and wait to populate it in the submit action (actionSummaries). Does that break the intention of CForm? Is there a best practice? Even taking this solution, can someone address the first issue, in case it comes up again?
Any other, gentle critique is welcome.
models/SummariesForm.php
class SummariesForm extends CFormModel
{
public $account;
public $userToken;
public $year;
public function rules () {...}
public function fetchSummary () {...}
static public function getYearOptions () {...}
}
views/account/select.php
<?php
$this->pageTitle=Yii::app()->name;
?>
<div class="form">
<?php echo $form->render(); ?>
</div>
controllers/AccountController.php
class AccountController extends CController
{
public $layout = 'extranet';
public function actionSelect ()
{
$model = new SummariesForm();
// retrieve account
require_once 'AccountCookie.php';
/*
*
* Here, I insert the account directly into the
* model used to build the form, but $model isn't
* available to selectForm.php. So, it doesn't
* become part of the form, and this $model doesn't
* persist to actionSummaries().
*
*/
$model->account = AccountCookie::decrypt();
if ($model->account === false) {
throw new Exception('Unable to retrieve account.');
}
$form = new CForm('application.views.account.selectForm', $model);
$this->render('select', array(
'form' => $form,
'account' => $model->account,
));
}
public function actionSummaries ()
{
$model = new SummariesForm();
if (isset($_POST['SummariesForm'])) {
$model->attributes = $_POST['SummariesForm'];
/*
*
* Should I just omit "account" from the form altogether
* and fetch it here? Does that break the "model"?
*
*/
if ($model->validate() === true) {
try {
$summaries = $model->fetchSummary();
} catch (Exception $e) {
...
CApplication::end();
}
if (count($summaries) === 0) {
$this->render('nodata');
CApplication::end();
}
$this->render('summaries', array('model' => $model, 'summaries' => $summaries));
} else {
throw new Exception('Invalid year.');
}
}
}
}
views/account/selectForm.php
<?php
return array(
'title' => 'Select year',
'action' => Yii::app()->createUrl('Account/Summaries'),
'method' => 'post',
'elements' => array(
'account' => array(
'type' => 'hidden',
'value' => $account,
),
'userToken' => array(
'type' => 'hidden',
'value' => /* get token */,
),
'year' => array(
'type' => 'dropdownlist',
'items' => SummariesForm::getYearOptions(),
),
),
'buttons' => array(
'view' => array(
'type' => 'submit',
'label' => 'View summaries',
),
),
);
The answer is NO to do what you asked. You can see $form variable which acted almost like array when it was passed from controller to view. The solution is you add more property $account into selectForm model and treat it like other elements. I don't think leaving the new field outside the form will be properly way if you want to submit its value also.
Edited:

Undefined Variable: model

I am new to Yii and learning it now. Here i am trying to get the listing of the user from the users table of the database.
Following is my Users Controller function for view:
class UsersController extends Controller
{
public function actionIndex()
{
$this->render('index');
}
public function actionView()
{
$model = new Users;
$this->render('view',array(
'model'=>$model,
));
}
}
Following is my Users Model:
class Users extends CActiveRecord
{
public static function model($className=__CLASS__)
{
return parent::model($className);
}
/**
* #return string the associated database table name
*/
public function tableName()
{
return '{{users}}';
}
/**
* #return array validation rules for model attributes.
*/
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('fname, lname, email', 'required'),
array('fname, lname', 'length', 'max'=>50),
array('email', 'length', 'max'=>100),
// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('id, fname, lname, email', 'safe', 'on'=>'search'),
);
}
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
);
}
/**
* #return array customized attribute labels (name=>label)
*/
public function attributeLabels()
{
return array(
'id' => 'ID',
'fname' => 'First Name',
'lname' => 'Last Name',
'email' => 'Email',
);
}
/**
* Retrieves a list of models based on the current search/filter conditions.
* #return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
*/
public function search()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('id',$this->id);
$criteria->compare('fname',$this->fname,true);
$criteria->compare('lname',$this->lname,true);
$criteria->compare('email',$this->email,true);
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
}
Following is my View:
<h1>Users</h1>
<p>
Below is the list of users, here you may add user.
</p>
<?php $this->widget('zii.widgets.CDetailView', array(
'data'=>$model,
'attributes'=>array(
'id',
'fname',
'lname',
'email',
),
)); ?>
<div class="view">
<b><?php echo CHtml::encode($data->getAttributeLabel('id')); ?>:</b>
<?php echo CHtml::link(CHtml::encode($data->id), array('view', 'id'=>$data->id)); ? >
<br />
<b><?php echo CHtml::encode($data->getAttributeLabel('fname')); ?>:</b>
<?php echo CHtml::encode($data->fname); ?>
<br />
<b><?php echo CHtml::encode($data->getAttributeLabel('lname')); ?>:</b>
<?php echo CHtml::encode($data->lname); ?>
<br />
<b><?php echo CHtml::encode($data->getAttributeLabel('email')); ?>:</b>
<?php echo CHtml::encode($data->email); ?>
<br />
</div>
I am getting a PHP notice saying that undefined variable: model, please help thanks in advance.
It looks like you were pasting _view.php.
Make sure you hand your $model variable from view.php to _view.php, since in actionView() you are only handing it to 'view'.
field($model, 'VideoTitle')->textInput(['autofocus' => true, 'required' => true]) ?>
field($model, 'Description')->textInput(['autofocus' => true, 'required' => true]) ?>

Searching/filtering two concatenated columns in cgridview

cgridview code in admi.php
array(
'header'=>'Client Name',
'name'=>'client_name',
'value'=>'$data->first_name." ".$data->last_name', //or u can write also value'=>'$data->first_name.\' \'.$data->last_name',
),
model search() class in client.php
public $client_name;
$criteria->compare('CONCAT(first_name,last_name)',$this->client_name,true);
Concatenation and displaying is done but seraching the data show no results found?
In model :
class User extends CActiveRecord
{
public $fullName;
}
in search
$criteria->addSearchCondition('concat(first_name, " ", last_name)', $this->fullName);
in rules()
array('..., fullName', 'safe', 'on' => 'search'),
in model created a function
public function getFullName()
{
return $this->first_name . ' ' . $this->last_name;
}
And last put it in your view:
<?php $this->widget('zii.widgets.grid.CGridView', array(
.......
'columns' => array(
array(
'name' => 'full_name',
'value' => '$data->getFullName()',
),
...