Cakephp - Conditional Save - php

My form contains a model object that contains five child objects that are related using hasMany. When I save the form, I notice that all fields, regardless if they are empty, are saved into the database. Is it possible to set a condition in the beforeSave() callback method to prevent child items that have no values from being saved? I have tried to unset the key in the array containing empty values but the row is still being added into the database.
Here is the code for my 'Mtd' model. The Mtd model contains many Flowratereatments. On my form, I have a checkbox that says 'This is a Flow Rate Based Treatment'. So, if a user clicks on that, then the user can fill it in the fields. However, if the user does not fill it in, I want to prevent a new row from being added with just the foreign key of the Mtd table.
<?php
class Mtd extends AppModel {
public $name = 'Mtd';
public $hasOne = array('Treatmentdesign', 'Volumetreatment');
public $hasMany = 'Flowratetreatment';
function beforeSave() {
if($this->data['Mtd']['is_settling'] != 1){
unset($this->data['Flowratetreatment'][0]);
}
return true;
}
}
?>

Did you tried something like:
class User extends AppModel {
function validates() {
$this->setAction();
#always validate presence of username
$this->validates_presence_of('username');
#validate uniqueness of username when creating a new user
$this->validates_uniqueness_of('username',array('on'=>'create'));
#validate length of username (minimum)
$this->validates_length_of('username',array('min'=>3));
#validate length of username (maximum)
$this->validates_length_of('username',array('max'=>50));
#validate presence of password
$this->validates_presence_of('password');
#validate presence of email
$this->validates_presence_of('email');
#validate uniqueness of email when creating a new user
$this->validates_uniqueness_of('email',array('on'=>'create'));
#validate format of email
$this->validates_format_of('email',VALID_EMAIL);
#if there were errors, return false
$errors = $this->invalidFields();
return (count($errors) == 0);
}
}
?>
in your model

I have used this code:
public function beforeSave() {
if(isset($this->data[$this->alias]['profile_picture'])) {
if($this->data[$this->alias]['profile_picture']['error']==4) {
unset($this->data[$this->alias]['profile_picture']);
}
}
return true;
}
in a previous app, to remove a key from $this->data if the user had not uploaded a file, to prevent the old value being overwritten.
this should work for you (you'll need to adapt it; based on what $this->data contains at this point.
public function beforeSave() {
if(empty($this->data[$this->alias]['the_key'])) {
unset($this->data[$this->alias]['the_key']);
}
//debug($this->data); exit; // this is what will be saved
return true;
}
you mention you tried this already? post your code in your original post.

Related

Save attribute of model in database in PHP in OctoberCMS

Hi I have problem when i tried to save attribute of model to database. I write in OctoberCMS and i have this function:
public function findActualNewsletter()
{
$actualNewsletter = Newsletter::where('status_id', '=', NewsletterStatus::getSentNowStatus())->first();
if (!$actualNewsletter) {
$actualNewsletter = Newsletter::where('send_at', '<=', date('Y-m-d'))->where('status_id', NewsletterStatus::getUnsentStatus())->first();
$actualNewsletter->status_id = NewsletterStatus::getSentNowStatus();
dd($actualNewsletter);
}
return $actualNewsletter;
}
getSentNowStatus()=2;
getUnsentStatus()=1;
dd($actualNewsletter) in my if statement show that status_id = 2 But in database i still have 1. I used this function in afterSave() so i dont need:
$actualNewsletter->status_id = NewsletterStatus::getSentNowStatus();
$actualNewsletter->save();
becosue i have error then i use save in save.
Of course i filled table $fillable =['status_id']. And now i dont know why its not save in database when it go to my if. Maybe someone see my mistake?
If you are trying to modify the model based on some custom logic and then save it, the best place to put it is in the beforeSave() method of the model. To access the current model being saved, just use $this. Below is an example of the beforeSave() method being used to modify the attributes of a model before it gets saved to the database:
public function beforeSave() {
$user = BackendAuth::getUser();
$this->backend_user_id = $user->id;
// Handle archiving
if ($this->is_archived && !$this->archived_at) {
$this->archived_at = Carbon\Carbon::now()->toDateTimeString();
}
// Handle publishing
if ($this->is_published && !$this->published_at) {
$this->published_at = Carbon\Carbon::now()->toDateTimeString();
}
// Handle unarchiving
if ($this->archived_at && !$this->is_archived) {
$this->archived_at = null;
}
// Handle unpublishing, only allowed when no responses have been recorded against the form
if ($this->published_at && !$this->is_published) {
if (is_null($this->responses) || $this->responses->isEmpty()) {
$this->published_at = null;
}
}
}
You don't have to run $this->save() or anything like that. Simply modifying the model's attributes in the beforeSave() method will accomplish what you desire.

YII scenario: how to?

What If I have an input field for example:
UserEmail:
UserPhoneNumber:
UserOldPasword:
UserNewPassword:
UserRetypePassword:
And on my action update I will only require UserNewPassword if UserOldPassword is not empty else the only thing that needs to be updated is either UserEmail or UserPhoneNumber. How will I implement this rule?only on update? Or should I create a custom validator on my model?
so on my afterFind() I have this code to avoid the output of the hashed password:
public function afterFind()
{
//reset the password to null because we don't want the hash to be shown.
$this->old_password = $this->password;
$this->password = null;
parent::afterFind();
}
and I created a custom validation. it does validates, the problem is even if I submitted the form with empty UserNewPassword I still get an error and $this->password field is now returning a hashed value from my database
Please see my code and correct my mistakes:
public function checkForEmpty($attribute,$params){
if(!empty($this->$attribute)){
if(empty($params['oldPassword'])){
$this->addError('oldPassword','We require your old password to proceed changing password');
}
if(empty($params['retypePassword'])){
$this->addError('retype_password','To assure that you type the right password please retype your password');
}
if(!empty($params['oldPassword']) && !empty($params['retypePassword'])){
$this->compareOldPass($params['oldPassword'],$this->old_password);
}
}
}
Thanks in advance...
you can do a beforeSave() in your model. I think it's the best solution because of your complicated logic, and not being a global issue of your app.
UPDATE:
public function beforeSave()
{
if(parent::beforeSave())
{
//implement you logic here
//or check it is a new record
if($this->isNewRecord)
{
// do stuff here if new
}
//or you can return false if it doesn't meet your logic
return true;
}
else
return false;
}

MySQL error 1054 when I try more than one model call from a controller function in CodeIgniter

I want to write a function which will take a series of fields and input different values into a different databases. Right now it is only two separate database entries but I hope to implement more later. I want to input a new Saint into one table and then, if the user fills in the 'ofRegion' field, I want that to be stored in a different table. My problem comes about when the model tries to input the information for 'ofRegion.' I get a MySQL error ( 1054 ) stating there is an unknown column. I can see by the MySQL error that it is trying to input all the information from the previous entry as well as the new information. How do I clear the old information? Can I even do this or will I need multiple models for each table I want to enter information into?
Model Functions
public function input_saint($newSaintID)
{
//grab values from post stream
$this->saintID = $newSaintID;
$this->active = 1;
$this->nameFirst = $this->input->post('nameFirst');
$this->nameLast = $this->input->post('nameLast');
$this->gender = $this->input->post('gender');
$this->martyr = $this->input->post('martyr');
$this->nationality = $this->input->post('nationality');
$this->feastMonth = $this->input->post('feastMonth');
$this->feastDay = $this->input->post('feastDay');
$this->about = $this->input->post('about');
//insert information into the saint table
$this->db->insert('saint_table', $this);
}
public function set_of_region($newSaintID)
{
$this->saintID = $newSaintID;
$this->ofRegion = $this->input->post('ofRegion');
$this->db->insert('saint_of_region', $this);
}
Controller Function
public function saint_input()
{
//Check if user is logged in, if they are not, send them to the login screen
if($this->session->userdata('logged_in') == FALSE)
{
redirect('base/');
}
$this->load->library('form_validation');
//load Saint model and get the nation list
$this->load->model('saint_model');
//Load the nation list
$data['nationList'] = $this->saint_model->get_nations();
if($this->form_validation->run('saint_input')==FALSE)
{
$this->load->view('std/top');
$this->load->view('forms/saint_input', $data);
$this->load->view('std/bottom');
}
else
{
//generate saintID
$newSaintID = $this->saint_model->get_largest_saintID();
$newSaintID++;
$this->saint_model->input_saint($newSaintID);
//if specified, record the ofRegion
if($this->input->post('ofRegion') != NULL)
{
$this->saint_model->set_of_region($newSaintID);
}
//Send the user to this saint's single view page for review
redirect('base/display_one_saint/'.$newSaintID);
}
}
Thank you very much for your time and work!
That's because you're using $this as an array to store data before inserting it. $this is a reference to the object as a whole and any properties that you set on it will persist until they are unset. One solution is to change to an array for the insert() function as below:
public function set_of_region($newSaintID)
{
$ins_arr['saintID'] = $newSaintID;
$ins_arr['ofRegion'] = $this->input->post('ofRegion');
$this->db->insert('saint_of_region', $ins_arr);
}

PHP security: 'Nonce' or 'unique form key' problem

I use this class (taken from a blog tutorial) to generate unique keys to validate a form:
class formKey {
//Here we store the generated form key
private $formKey;
//Here we store the old form key
private $old_formKey;
//The constructor stores the form key (if one excists) in our class variable
function __construct() {
//We need the previous key so we store it
if(isset($_SESSION['form_key'])) {
$this->old_formKey = $_SESSION['form_key'];
}
}
//Function to generate the form key
private function generateKey() {
//Get the IP-address of the user
$ip = $_SERVER['REMOTE_ADDR'];
//We use mt_rand() instead of rand() because it is better for generating random numbers.
//We use 'true' to get a longer string.
$uniqid = uniqid(mt_rand(), true);
//Return the hash
return md5($ip . $uniqid);
}
//Function to output the form key
public function outputKey() {
//Generate the key and store it inside the class
$this->formKey = $this->generateKey();
//Store the form key in the session
$_SESSION['form_key'] = $this->formKey;
//Output the form key
// echo "<input type='hidden' name='form_key' id='form_key' value='".$this->formKey."' />";
return $this->formKey;
}
//Function that validated the form key POST data
public function validate() {
//We use the old formKey and not the new generated version
if($_POST['form_key'] == $this->old_formKey) {
//The key is valid, return true.
return true;
}
else {
//The key is invalid, return false.
return false;
}
}
}
Everything in my website goes through index.php first, so I put this in index.php: $formKey = new formKey();
Then, in every form I put this: <?php $formKey->outputKey(); ?>
That generates this: <input type="hidden" name="form_key" id="form_key" value="7bd8496ea1518e1850c24cf2de8ded23" />
Then I can simply check for if(!isset($_POST['form_key']) || !$formKey->validate())
I have two problems. First: I cant use more than one form per page becouse only the last key generated will validate.
Second: Because everything goes through index.php first, if I use ajax to validate the form, the first time will validate but the second time not, because index.php generates a new key but the pages containing the form does't refresh so the form key is not updated..
I have tried several things but I cant get it to work.. Maybe YOU can update/modify the code/class to get it to work?? Thanks!!!
You could put this into a class, but this is needless complexity. Simple security systems are best because they are easier to audit.
//Put this in a header file
session_start();
if(!$_SESSION['xsrf_token']){
//Not the best but this should be enough entropy
$_SESSION['xsrf_token']=uniqid(mt_rand(),true);
}
//$_REQUEST is used because you might need it for a GET or POST request.
function validate_xsrf(){
return $_SESSION['xsrf_token']==$_REQUEST['xsrf_token'] && $_SESSION['xsrf_token'];
}
//End of header file.
The extra && $_SESSION['xsrf_token'] makes sure this variable is populated. Its there to make sure the implementation fails securely. (Like if you forgot the header file doah! ;)
This following html/php goes in any file you want to protect from XSRF, make sure you have the code above in a header file.
if(validate_xsrf()){
//do somthing with $_POST
}
This is all you need to print out the form, again make sure you call session_start(); before you do anything, it doesn't matter if you call it multiple times.
<input type="hidden" name="xsrf_token" id="form_key" value="<?=$_SESSION['xsrf_token']?>" />
Not tested, but it should work.
class formKey {
//Here we store the generated form key
private $formKey;
//Here we store the old form key
private $old_formKey;
//The constructor stores the form key (if one excists) in our class variable
function __construct() {
//We need the previous key so we store it
if(isset($_SESSION['form_key'])) {
$this->old_formKey = $_SESSION['form_key'];
$this->formKey = $this->generateKey();
$_SESSION['form_key'] = $this->formKey;
}
}
//Function to generate the form key
private function generateKey() {
//Get the IP-address of the user
$ip = $_SERVER['REMOTE_ADDR'];
//We use mt_rand() instead of rand() because it is better for generating random numbers.
//We use 'true' to get a longer string.
$uniqid = uniqid(mt_rand(), true);
//Return the hash
return md5($ip . $uniqid);
}
//Function to output the form key
public function outputKey() {
return $this->formKey;
}
//Function that validated the form key POST data
public function validate() {
//We use the old formKey and not the new generated version
if($_POST['form_key'] == $this->old_formKey) {
//The key is valid, return true.
return true;
}
else {
//The key is invalid, return false.
return false;
}
}
}
Edit: changed back to single key. Just call outputkey() to when needed. Don't create more than one instance of this class.

Best ways to handle Record Form in Zend Framework

Once you're OK with basic record form built after example from Tutorial, you realize you want more professionally designed Record Form. E.g. I don't want to duplicate record form for the same table in User and Admin areas.
1) Does anyone use some mechanism, possibly inheritance, to reduce duplication of almost similar admin and user forms? Is that burdensome or sometimes you better just do with copy-pasting?
2) Has anyone considered it to be a good idea to build some basic Record class
that can determine that among several record forms on this page, the current post is addressed specifically to this record form
that can distinguish between Edit or Delete buttons clicks in some organized fashion.
3) My current practice includes putting all form config code (decorators, validations, initial values) into constructor and form submit handling is put into a separate ProcessSubmit() method to free controller of needless code.
All the above addresses to some expected Record Form functionality and I wonder if there is any guideline, good sample app for such slightly more advanced record handling or people are still reinveting the wheel. Wondering how far you should go and where you should stop with such impovements...
Couple of suggestions:
First of all - Use the init() function instead of constructors to add your elements when you are subclassing the form. The init() function happens after the parameters you pass to the class are set.
Second - Instead of subclassing your form - you can just set an "option" to enable the admin stuff:
class My_Record_Form extends Zend_Form {
protected $_record = null;
public function setRecord($record) {
$this->_record = $record;
}
public function getRecord() {
if ($this->_record === null || (!$this->_record instanceOf My_Record)) {
throw new Exception("Record not set - or not the right type");
}
return $this->_record;
}
protected $_admin = false;
public function setAdmin($admin) {
$this->_admin = $admin;
}
public function getAdmin() { return $this->_admin; }
public function init() {
$record = $this->getRecord();
$this->addElement(......);
$this->addElement(......);
$this->addElement(......);
if ($this->getAdmin()) {
$this->addElement(.....);
}
$this->setDefaults($record->toArray());
}
public function process(array $data) {
if ($this->isValid($data)) {
$record = $this->getRecord();
if (isset($this->delete) && $this->delete->getValue()) {
// delete button was clicked
$record->delete();
return true;
}
$record->setFromArray($this->getValues());
$record->save();
return true;
}
}
}
Then in your controller you can do something like:
$form = new My_Record_Form(array(
'record'=>$record,
'admin'=>My_Auth::getInstance()->hasPermission($record, 'admin')
));
There is nothing "wrong" with making a My_Record_Admin_Form that handles the admin stuff as well - but I found this method keeps all the "record form" code in one single place, and a bit easier to maintain.
To answer section 2: The edit forms in my code are returned from a function of the model: $record->getEditForm() The controller code ends up looking a little like this:
protected $_domain = null;
protected function _getDomain($allowNew = false)
{
if ($this->_domain)
{
return $this->view->domain = $this->_domain;
} else {
$id = $this->_request->getParam('id');
if (($id == 'new' || $id=='') && $allowNew)
{
MW_Auth::getInstance()->requirePrivilege($this->_table, 'create');
$domain = $this->_table->createRow();
} else {
$domain = $this->_table->find($id)->current();
if (!$domain) throw new MW_Controller_404Exception('Domain not found');
}
return $this->view->domain = $this->_domain = $domain;
}
}
public function editAction()
{
$domain = $this->_getDomain(true);
MW_Auth::getInstance()->requirePrivilege($domain,'edit');
$form = $domain->getEditForm();
if ($this->_request->isPost() && $form->process($this->_request->getPost()))
{
if ($form->delete && $form->delete->getValue())
{
return $this->_redirect($this->view->url(array(
'controller'=>'domain',
'action'=>'index',
), null, true));
} else {
return $this->_redirect($this->view->url(array(
'controller'=>'domain',
'action'=>'view',
'id'=>$form->getDomain()->id,
), null, true));
}
}
$this->view->form = $form;
}
So - the actual id of the record is passed in the URI /domain/edit/id/10 for instance. If you were to put multiple of these forms on a page - you should make sure to set the "action" attribute of the form to point to an action specific to that form.
I created a SimpleTable extends Zend_Db_Table and SimpleForm extends Zend_Db_Form classes. Both of these assume that your table has an auto-incrementing ID column.
SimpleTable has a saveForm(SimpleForm $form) function which uses the dynamic binding to match form element names to the columns of the record. I also included an overridable saveFormCustom($form) for any special handling.
The SimpleForm has an abstract setup() which must be overridden to setup the form. I use the init() to do the initial setup (such as adding the hidden ID field).
However, to be honest, I really don't like using the Zend_Form object, I feel like that should be handled in the View, not the Model or Controller.

Categories