Validating third variable on difference between 2 other variables in cakephp - validation

I can't get my head around this one. I want to validate whether a third variable on a cakephp form is less than or equal to the difference between two other variables on the form. Has anyone tackled this or similar?
Paul
here is some code to show what I have done:
public function lessThanEqualTo($check, $otherfield) {
$value = array_values($check);
$compareTo = $this->data[$this->name][$otherfield];
if (!Validation::comparison($value[0], 'lessorequal', $compareTo)) {
return false;
}
else {
return true;
}
}
public function numDifference($startnumber, $usednumber) {
$sn = int($this->data[$this->name][$startnumber]);
$un = int($this->data[$this->name][$usednumber]);
return ($sn - $un);
}
Model contains validation: but the second rule is plain wrong, I have tried a number of things, but I am just coming up with rubbish:
public $validate = array(
'ag1_compl_dist_num'=>array(
'rule' => array('lessThanEqualTo','ag1_compl_start_number'),
'message' => 'Value must be less than starting number',
'allowEmpty' => true,
'required' => false
),
'ag1_compl_remain' => array(
'rule' => array('lessThanEqualTo','numDifference'),
'message' => 'Value must be less than difference between numbers',
'allowEmpty' => true,
'required' => false
)
);
Clearly, the call to numDifference should have something that identifies which two numbers to work out the difference between..

When you do:
'rule' => array('lessThanEqualTo','numDifference')
And then in your lessThanEqualTo function $compareTo = $this->data[$this->name][$otherfield];, you will look for numDifference field in your data object, which does not exist.
One way would be to create a lessThanEqualToDiff method like this:
public function lessThanEqualToDiff ($check, $startfield, $usedfield) {
$value = array_values($check);
$sn = intval($this->data[$this->name][$startfield]);
$un = intval($this->data[$this->name][$usedfield]);
return Validation::comparison($value[0], 'lessorequal', $sn - $un) ;
}
Then your rule:
public $validate = array(
'ag1_compl_remain' => array(
'rule' => array('lessThanEqualToDiff', 'ag1_compl_start_number', 'ag1_compl_used_number'),
'message' => 'Value must be less than difference between numbers',
'allowEmpty' => true,
'required' => false
)
);

So, the validation method should be like this:
public function lessThanEqualToDiff ($check, $startfield, $usedfield) {
$value = array_values($check);
$sn = (int)$this->data[$this->name][$startfield];
$un = (int)$this->data[$this->name][$usedfield]
return Validation::comparison($value[0], 'lessorequal', $sn - $un) ;
}
to use the php int feature correctly. Thanks to Holt and marian0 for their help.

Related

Returning user input after AJAX call in Drupal 7 hook_form_FORM_ID_alter

I am using the hook_form_FORM_ID_alter in Drupal 7 to create a custom form so that the user can enter and edit data which is then attached to a custom node type.
For a new node the default number of input boxes in a group is 10, this can then be added to in groups of 5. When the node is reloaded for editing the saved data is used to create the form with whatever number of inputs have been saved previously and also the ability to add more fields in the same manner as required.
I have managed to get both the initial version of the form and the editing version to work using the following code however when the 'add five' button is pressed and the AJAX called (in both cases), any values which have been entered without saving are removed.
<?php
/**
* Implements hook_form_FORM_ID_alter().
*/
function entries_form_form_entries_node_form_alter(&$form, &$form_state, $form_id) {
$node = $form['#node'];
// fieldset
$form["section"] = array(
'#type' => 'fieldset',
'#title'=> 'Section 1',
);
$form["section"]["termwrapper"] = array(
"#type" => 'container',
"#attributes" => array(
"id" => 'groupWrapper',
),
);
$form["section"]["termwrapper"]["terms"]["#tree"] = TRUE;
if(!isset($form_state['fired'])){
$form_state['terms'] = $node->entries_form['term'];
}
foreach ($form_state['terms'] as $key => $values) {
$form["section"]["termwrapper"]["terms"][$key] = array(
'#type' => 'textfield',
'#size' => 20,
'#attributes' => array(
'class' => array('left'),
),
'#value' => $values,
);
}
$form['section']['addFive_button'] = array(
'#type' => 'submit',
'#value' => t('+5'),
'#submit' => array('entries_form_add_five_submit'),
'#ajax' => array(
'callback' => 'entries_form_commands_add_callback',
'wrapper' => 'groupWrapper',
),
'#prefix' => "<div class='clear'></div>",
);
dpm($form_state);
}
function entries_form_commands_add_callback($form, $form_state) {
return $form["section"]["termwrapper"];
}
function entries_form_add_five_submit($form, &$form_state){
$form_state['rebuild'] = TRUE;
$form_state['fired'] = 1;
$values = $form_state['values'];
$form_state['terms'] = $values['terms'];
$numterms = count($values['terms']);
$addfivearray = array_fill($numterms,5,'');
$form_state['terms'] = array_merge($values['terms'],$addfivearray);
}
/**
* Implements hook_node_submit().
*/
function entries_form_node_submit($node, $form, &$form_state) {
$values = $form_state['values'];
$node->entries_form['term'] = $values['term'];
}
/**
* Implements hook_node_prepare().
*/
function entries_form_node_prepare($node) {
if (empty($node->entries_form)){
$node->entries_form['term'] = array_fill(0, 10, '');
}
}
/**
* Implements hook_node_load().
*/
function entries_form_node_load($nodes, $types) {
if($types[0] == 'entries'){
$result = db_query('SELECT * FROM {mytable} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes)))->fetchAllAssoc('nid');
foreach ($nodes as &$node) {
$node->entries_form['term'] = json_decode($result[$node->nid]->term);
}
}
}
/**
* Implements hook_node_insert().
*/
function entries_form_node_insert($node) {
if (isset($node->entries_form)) {
db_insert('mytable')
->fields(array(
'nid' => $node->nid,
'term' => json_encode($node->entries_form['term']),
))
->execute();
}
}
How can I keep the values which have been typed in and retain the ajax functionality?
Any help or pointers much appreciated. This is my first dip into Drupal so I'm sure there is something which hopefully is quite obvious that I'm missing.
Ok, I think I finally have the answer.
Within the foreach that builds the form input boxes I had set '#value' => $values, when it seems that '#default_value' => $values, should have been set instead.
The updated section of the code that is now working for me is as follows
foreach ($form_state['terms'] as $key => $values) {
$form["section"]["termwrapper"]["terms"][$key] = array(
'#type' => 'textfield',
'#size' => 20,
'#attributes' => array(
'class' => array('left'),
),
'#default_value' => $values,
);
}
Seems to be as simple as that. Hope this helps someone else.

drupal custom views filter

First time on stackoverflow so , i will try to be as clear as possible.
I'm using drupal 7 and views 3.
I needed to create a custom views filter that handle dates range. So i looked at example and tried to mimic the behavior and i get some trouble.
It seems that when i extend my own class from views_handler_filter , the query method is never invoked BUT if i extend my class from let's say views_handler_filter_string, it works oO...
I must forget something but i'm stuck here ...
Here is my code, if someone can take a look and advise me about what happend , i would be very grateful.
Thanks everyone !
Here is my .views.inc file :
<?php
class v3d_date_custom_filter extends views_handler_filter {
var $always_multiple = TRUE;
function value_form(&$form, &$form_state) {
//parent::value_form($form, $form_state);
$form['value']['v3d_date']['period'] = array(
'#type' => 'select',
'#title' => 'Period',
'#options' => array(
'7_days' => 'Last 7 days',
'yesterday' => 'Yesterday',
'today' => 'Today',
'custom' => 'Custom dates'),
'#default_value' => 'custom',
'#attributes' => array("onclick" => "period_click(this);"),
);
$form['value']['v3d_date']['start_date'] = array(
'#type' => 'date_popup',
'#date_format' => 'Y-m-d',
'#title' => 'Start date',
'#size' => 30);
$form['value']['v3d_date']['end_date'] = array(
'#type' => date_popup',
'#title' => 'End date',
'#date_format' => 'Y-m-d',
'#size' => 30);
}
function exposed_validate(&$form, &$form_state) {
if(is_null($form_state['values']['start_date']) &&
is_null($form_state['values']['start_date'])) {
return TRUE;
}
/*
* If we get array for start_date or end_date
* errors occured, but the date module will handle it.
*/
if(!is_string($form_state['values']['start_date']) ||
!is_string($form_state['values']['end_date'])) {
return TRUE;
}
/* Get day, month and year from start_date string */
if(!preg_match('/(\d+)-(\d+)-(\d+)/',
$form_state['values']['start_date'],
$start_date
)) {
return TRUE; }
/* Get day, month and year from end_date string */
if(!preg_match('/(\d+)-(\d+)-(\d+)/',
$form_state['values']['end_date'],
$end_date
)) {
return TRUE; }
/* Create timestamps and compare */
$start_date = mktime(0,0,0,$start_date[1],$start_date[2],$start_date[3]);
$end_date = mktime(0,0,0,$end_date[1],$end_date[2],$end_date[3]);
if($start_date >= $end_date) {
form_set_error('start_date','Start date must be anterior to end date.');
}
}
function query() {
die('fdsfds');
$this->ensure_my_table();
$field = "$this->table_alias.$this->real_field";
dsm($this);
}
}
?>
And my .module file
<?php
function custom_filters_views_api() {
return array(
'api'=>3,
'path' => drupal_get_path('module','custom_filters') . '/views',
);
}
?>
And part of my views_data that use my custom filter :
<?php
function voice_views_data() {
$data['v_tp_voice']['date_utc_agent'] = array(
'title' => t('date_utc_agent'),
'help' => 'date_utc_agent',
'field' => array('handler' => 'views_handler_field'),
'filter' => array('handler' => 'v3d_date_custom_filter'),
'sort' => array('handler' => 'views_handler_sort')
);
I am working on this same problem. My setup looks very much like yours, though I do call parent::value_form($form, $form_state) in my value_form function.
I found that in the value_form function, I had to have something like
$form['value'] = array(
'#type' => 'checkbox',
'#title' => "Filter by Date",
'#default_value' => $this->value,
);
in order to have my query() function called. Doing something like $form['value']['before'] = array( ...
didn't work.

Magento - How to create "decimal" attribute type

I've done a bit of searching online but I have not found any answers to this question yet. I have a situation where I need a product attribute that is a decimal value and it must support negative numbers as well as positive and must also be sortable. For some reason, Magento does not have a "decimal" attribute type. The only type that uses decimal values is Price, but that doesn't support negative numbers. If I use "text" as the type, it supports whatever I want, but it doesn't sort properly because it sees the values as strings rather than floating point numbers. I have been able to work around this issue, as others have in posts I've found, by manually editing the eav_attribute table and changing 'frontend_input' from 'price' to 'text', but leaving the 'backend_type' as 'decimal'. This works great...until someone edits the attribute in the admin panel. Once you save the attribute, Magento notices that the frontend_input is 'text' and changes the 'backend_type' to 'varchar'. The only way around this that I can think of is by creating a custom attribute type, but I'm not sure where to start and I can't find any details online for this.
Has anyone else experienced this problem? If so, what have you done to correct it? If I need to create a custom attribute type, do you have any tips or can you point me at any tutorials out there for doing this?
Thanks!
What you want to do is create a custom attribute type.
This can be done by first creating a installer script (this updates the database).
startSetup();
$installer->addAttribute('catalog_product', 'product_type', array(
'group' => 'Product Options',
'label' => 'Product Type',
'note' => '',
'type' => 'dec', //backend_type
'input' => 'select', //frontend_input
'frontend_class' => '',
'source' => 'sourcetype/attribute_source_type',
'backend' => '',
'frontend' => '',
'global' => Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_WEBSITE,
'required' => true,
'visible_on_front' => false,
'apply_to' => 'simple',
'is_configurable' => false,
'used_in_product_listing' => false,
'sort_order' => 5,
));
$installer->endSetup();
After that you need to create a custom php class named:
Whatever_Sourcetype_Model_Attribute_Source_Type
And in there paste this in:
class Whatever_Sourcetype_Model_Attribute_Source_Type extends Mage_Eav_Model_Entity_Attribute_Source_Abstract
{
const MAIN = 1;
const OTHER = 2;
public function getAllOptions()
{
if (is_null($this->_options)) {
$this->_options = array(
array(
'label' => Mage::helper('sourcetype')->__('Main Product'),
'value' => self::MAIN
),
array(
'label' => Mage::helper('sourcetype')->__('Other Product'),
'value' => self::OTHER
),
);
}
return $this->_options;
}
public function toOptionArray()
{
return $this->getAllOptions();
}
public function addValueSortToCollection($collection, $dir = 'asc')
{
$adminStore = Mage_Core_Model_App::ADMIN_STORE_ID;
$valueTable1 = $this->getAttribute()->getAttributeCode() . '_t1';
$valueTable2 = $this->getAttribute()->getAttributeCode() . '_t2';
$collection->getSelect()->joinLeft(
array($valueTable1 => $this->getAttribute()->getBackend()->getTable()),
"`e`.`entity_id`=`{$valueTable1}`.`entity_id`"
. " AND `{$valueTable1}`.`attribute_id`='{$this->getAttribute()->getId()}'"
. " AND `{$valueTable1}`.`store_id`='{$adminStore}'",
array()
);
if ($collection->getStoreId() != $adminStore) {
$collection->getSelect()->joinLeft(
array($valueTable2 => $this->getAttribute()->getBackend()->getTable()),
"`e`.`entity_id`=`{$valueTable2}`.`entity_id`"
. " AND `{$valueTable2}`.`attribute_id`='{$this->getAttribute()->getId()}'"
. " AND `{$valueTable2}`.`store_id`='{$collection->getStoreId()}'",
array()
);
$valueExpr = new Zend_Db_Expr("IF(`{$valueTable2}`.`value_id`>0, `{$valueTable2}`.`value`, `{$valueTable1}`.`value`)");
} else {
$valueExpr = new Zend_Db_Expr("`{$valueTable1}`.`value`");
}
$collection->getSelect()
->order($valueExpr, $dir);
return $this;
}
public function getFlatColums()
{
$columns = array(
$this->getAttribute()->getAttributeCode() => array(
'type' => 'int',
'unsigned' => false,
'is_null' => true,
'default' => null,
'extra' => null
)
);
return $columns;
}
public function getFlatUpdateSelect($store)
{
return Mage::getResourceModel('eav/entity_attribute')
->getFlatUpdateSelect($this->getAttribute(), $store);
}
}
Hope this helps.
For further info see here.

cakephp custom validation does not display error message in the nested rule

im doing a custom validation but it does not display error message when invalidated.
do you know where is the problem? I think the problem might be in the invalidate function. do you know how to set it up for the nested validation like this one?
var $validate = array(
'receiver' => array(
'maxMsg' => array(
'rule' => array('maxMsgSend'),
//'message' => ''
),
'notEmpty' => array(
'rule' => array('notEmpty'),
'message' => 'field must not be left empty'
))......
custom validation method in the model:
function maxMsgSend ( $data )
{
$id = User::$auth['User']['id'];
$count_contacts = (int)$this->Contact->find( 'count', array( 'conditions' =>array( 'and' =>array( 'Contact.contact_status_id' => '2',
'Contact.user_id' => $id))));
$current_credit = (int)$this->field( '3_credit_counter', array( 'id' => $id));
$max_allowed_messages = ($count_contacts >= $current_credit)? $current_credit: $count_contacts ;
if ($data>$max_allowed_messages)
{
$this->invalidate('maxMsg', "you can send maximum of {$max_allowed_messages} text messages.");
}
}
UPDATE: how is solved it.
i moved the the guts of the function to beforeValidate() in the model.
function beforeValidate($data) {
if (isset($this->data['User']['receiver']))
{
$id = User::$auth['User']['id'];
$count_contacts = (int)$this->Contact->find( 'count', array( 'conditions' =>array( 'and' =>array( 'Contact.contact_status_id' => '2',
'Contact.user_id' => $id))));
$current_credit = (int)$this->field( '3_credit_counter', array( 'id' => $id));
$max_allowed_messages = ($count_contacts >= $current_credit)? $current_credit: $count_contacts ;
if ($data>$max_allowed_messages)
{
$this->invalidate('receiver', "you can send maximum of {$max_allowed_messages} text messages.");
return false;
}
}
return true;
}
I think your maxMsgSend function still needs to return false if validation fails.
I think the problem is in your Model::maxMsgSend function. As written in the bakery, (http://bakery.cakephp.org/articles/view/using-equalto-validation-to-compare-two-form-fields), to build a custom validation rule (they want to compare two fields, but the concepts are the same), they write:
I return a false if the values don't match, and a true if they do.
Check out their code for the Model class, about half way down. In short, you don't need to call invalidate from within the custom validation method; you simply return true if it passes validation, and false if it doesn't pass validation.

CakePHP model validation with array

I want to use CakePHP's core validation for lists in my model:
var $validate = array(
'selectBox' => array(
'allowedChoice' => array(
'rule' => array('inList', $listToCheck),
'message' => 'Enter something in listToCheck.'
)
)
);
However, the $listToCheck array is the same array that's used in the view, to populate a selectbox. Where do I put this function?
public function getList() {
return array('hi'=>'Hello','bi'=>'Goodbye','si'=>'Salutations');
}
Already in my controller, in one of the actions I'm setting it for the view, like:
public function actionForForm() {
$options = $this->getList();
$this->set('options', $options);
}
So, I don't want to have to copy the getList() function...where can I put it so the Model can call it to populate its $listToCheck array?
Thanks for your help.
Considering that it's data, you should store the list of valid choices in the model.
class MyModel extends AppModel {
var $fieldAbcChoices = array('a' => 'The A', 'b' => 'The B', 'c' => 'The C');
}
You can get that variable in the Controller simply like this:
$this->set('fieldAbcs', $this->MyModel->fieldAbcChoices);
Unfortunately you can't simply use that variable in the rule declaration for the inList rule, since rules are declared as instance variables and those can only be initialized statically (no variables allowed). The best way around that is to set the variable in the Constructor:
var $validate = array(
'fieldAbc' => array(
'allowedChoice' => array(
'rule' => array('inList', array()),
'message' => 'Enter something in listToCheck.'
)
)
);
function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
$this->validate['fieldAbc']['allowedChoice']['rule'][1] = array_keys($this->fieldAbcChoices);
}
If you're not comfortable overriding the Constructor, you could also do this in a beforeValidate() callback.
Also note that you shouldn't name your field 'selectBox'. :)

Resources