Saving only changed fields in a CakePHP 2.0 Form - forms

I'm creating a CakePHP application that can have multiple users editing the same information at the same time. To prevent users from overwriting over each other's changes, I want to make it where when a user saves their form, it only saves the one or two fields that the user has changed.
I can't just compare the fields to the database values because those database values may actually be newer ones from other users.
Does CakePHP offer any way to only send the updated fields via POST?

I don't believe there is a built-in solution. I came up with some code that works, but you would definitely want to put it into its own function so that it can be used by multiple controller actions. Also, it's not perfect. For example, it would fail on a date field since CakePHP has date fields rendered as selects in forms.
This is for an edit action. Original code:
public function edit($id = null) {
$this->User->id = $id;
if (!$this->User->exists()) {
throw new NotFoundException(__('Invalid user'));
}
if ($this->request->is('post') || $this->request->is('put')) {
if ($this->User->save($this->request->data)) {
$this->Session->setFlash(__('The user has been saved'));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The user could not be saved. Please, try again.'));
}
} else {
$this->request->data = $this->User->read(null, $id);
}
}
Modified code:
public function edit($id = null) {
$this->User->id = $id;
if (!$this->User->exists()) {
throw new NotFoundException(__('Invalid user'));
}
if ($this->request->is('post') || $this->request->is('put')) {
$originalData = unserialize(base64_decode($this->request->data['Extra']['original_data']));
$save = $this->request->data;
unset($save['Extra']);
foreach ($save as $model => $modelFields) {
if (!array_key_exists($model, $originalData)) {
continue;
}
foreach ($modelFields as $field => $value) {
if (!array_key_exists($field, $originalData[$model])) {
continue;
}
if ($save[$model][$field] === $originalData[$model][$field]) {
unset($save[$model][$field]);
}
}
}
$this->User->set($save);
if ($this->User->validates() && $this->User->save($save, false)) {
$this->Session->setFlash(__('The user has been saved'));
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The user could not be saved. Please, try again.'));
}
} else {
$this->request->data = $this->User->read(null, $id);
$this->request->data['Extra']['original_data'] = base64_encode(serialize($this->request->data));
}
}
Also, add this somewhere in the form in edit.ctp:
echo $this->Form->hidden('Extra.original_data');

Related

set user id based on pk when logged in through facebook

I am able to login/logout on my web app using facebook. But the problem is when the user needs to update his profile on my application, he can't. It would say that he is restricted to view his own page. The only way for this to happen is that user identity is probably not set. But I am not sure how to correct this. When I tried Yii::app()->user->user_id the Id is actually correct id, which is the pk in user model. So how is it that he cannot get to update his own page?
in my facebookUserIdentity:
public function authenticate()
{
if($this->getIsAccessTokenValid() && $this->setFBUser())
{
$this->_user = $this->getUserFromDatabase();
if($this->_user === false)
return false;
$this->setState('isUser', false);
$this->setState('isAdmin', false);
$this->setState('isShop', false);
$this->setState('isSuper', false);
$this->setState('user',$this->_user);
$this->_id = $this->_FBUser['id'];
//I've tried doing something like $this->_id = Yii::app()->user->user_id; or like $this->_user->user_id; ?
$this->_name = $this->_FBUser['name'];
return true;
}
else {
return false;
}
}
getting user from db:
protected function getUserFromDatabase()
{
$FBUser = $this->_FBUser;
if($FBUser)
{
$user = User::model()->findByAttributes (array('oauth_uid'=>$FBUser['id']));
if(!$user)
{
$user = new User;
$user->oauth_uid = $FBUser['id'];
$user->username = $FBUser['id'];
$user->first_name = $FBUser['first_name'];
//other info etc
$user->image = "https://graph.facebook.com/" . $FBUser['id'] . "/picture?type=large";
}
else
{
if ($user->oauth_permission == 'n')
{
$this->errorMessage = self::ERROR_LOGIN_DISABLE;
return false;
}
$user->last_login_date = date('Y-m-d H:i:s');
}
if($user->save())
{
return $user;
}
else
$this->errorMessage = CJSON::encode ( $user->getErrors());
return false;
}
else {
$this->errorMessage = "Failed getting facebook user data";
return false;
}
}
and lastly, the controller page rules:
public function accessRules()
{
$params=array();
$id=Yii::app()->request->getParam('id');
$params['model']=$this->loadModel($id);
return array(
//stuff
array('allow', // allow authenticated user to perform 'create' and 'update' actions
'actions'=>array('create','update','RemoveImage'),
'roles'=>array('admin','super','owner'=>$params),
),
//stuff

Silverstripe 3.1.x getting values from Parent

I would like to get a generic function working for getting Data in Template of a Page and if the property is not set, getting it from the Parent or Parents Parent and so on. With generic I mean independent of Relations like db, HasOne, HasMany, ManyMany. Let's say I have this for ManyMany but would like to detect if it's a Object, HasManyList or ManyManyList or a value. Is anything like this built in or how would you go about?
function ManyManyUpUntilHit($ComponentName){
$Component = $this->getManyManyComponents($ComponentName);
if($Component && $Component->exists())
return $Component;
$Parent = $this->Parent();
if(is_object($Parent) && $Parent->ID != 0){
return $Parent->ManyManyUpUntilHit($ComponentName);
} else {
return null;
}
}
in template:
$ManyManyUpUntilHit(Teaser)
There is no in built method to do this in Silverstripe. You will need to write your own function to do this.
Here is an example to get a page's has_one, has_many or many_many resource by parameter, or go up the site tree until a resource is found, or we hit a root page:
function getComponentRecursive($componentName) {
// has_one
if ($this->has_one($componentName)) {
$component = $this->getComponent($componentName);
if (isset($component) && $component->ID)
{
return $component;
}
}
// has_many
if ($this->has_many($componentName)) {
$components = $this->getComponents($componentName);
if (isset($components) && $components->count())
{
return $components;
}
}
// many_many
if ($this->many_many($componentName)) {
$components = $this->getManyManyComponents($componentName);
if (isset($components) && $components->count())
{
return $components;
}
}
if ($this->ParentID != 0)
{
return $this->Parent()->getComponentRecursive($componentName);
}
return false;
}

Zend form validation multiple custom validation message for same field at the same time

I have a Zend form password custom validation.
I have set the addValidator functions IInd argument to false since I need to get all errors at once. Both of my validation classes have set the self::INVLID to corresponding error messages.
But when I look in the Zend controller even though both validations fail I'm getting only one (the last) error message.
I need all the error messages at once.
/* Form validation*/
$passwordSpecialValidator = new Passwordspecialvalidationvalidator();
$passwordHistoryValidator = new Passwordhistorylvalidationvalidator(new model(), $username, $newpass);
$this->newpass->addValidator($passwordSpecialValidator, false)
->addValidator($passwordHistoryValidator, false);
/* One validation class */
class Passwordhistorylvalidationvalidator extends Zend_Validate_Abstract {
const INVLID = '';
protected $_messageTemplates = array(
self::INVLID => 'Your password doesnt meet the history requirements'
);
public function __construct($model, $username, $password) {
$this->_model = $model;
$this->_username = $username;
$this->_password = $password;
}
public function isValid($value, $context = null) {
if ($this->_username == "") {
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
$arrayResult = $auth->getIdentity();
if (isset($arrayResult->username)) {
$this->_username = $arrayResult->username;
}
}
}
$passwordExists = false;
$oldPasswords = $this->_model->getHistoryPasswords($this->_username, $this->_password);
if (count($oldPasswords) > 0) {
$passwordExists = true;
}
if ($passwordExists == false) {
return true;
} else {
$this->_setValue($value);
$this->_error(self::INVLID);
return false;
}
}
}
/* Getting error messages */
foreach ($objForm->getMessages() as $messages) {
foreach ($messages as $message) {
$errMessages[] = $message;
}
}
But the array $errMessages has only the last validation message (without index) even though both special validation and history validation fails. How would I get both error messages in an array if both validations fail?

how to save a collection form in symfony 1.4

I have created a collection form in symfony 1.4 and Propel 1.5 and everything displays properly but I cannot get the form to save to the database.
The form is used to edit multiple users at once.
I found this question and I implemented the suggestion of extending my collectionForm class with sfFormPropel, but when I do that I run out of memory. I cannot find what is being pulled from the database that would fill up the processes memory.
In my new save function I am not even doing anything.
Any ideas?
class ContactCollectionForm extends sfFormPropel
{
public function getModelName()
{
return 'ContactCollectionForm';
}
public function retrieveSubObject($fieldname, $model)
{
switch($fieldname)
{
default:
break;
}
return array();
}
public function save($con = null)
{
}
public function configure()
{
$user = $this->getOption('user');
$embedded = $this->getOption('embedded');
$custom = $this->getOption('custom');
$contact_list = $this->getOption('contact_list');
$cf = $custom['form'];
if(!array_key_exists(0, $cf['fields']['field']))
$cf['fields']['field'] = array($cf['fields']['field']);
$use_fields = array();
for($i=0;$i<count($contact_list);$i++)
{
foreach($cf['fields']['field'] as $field)
{
if($field['type'] == 'object')
{
// embed object form (ala: PersonData, Coordinate etc...)
$model = $field['model'];
$model_form = $model.'Form';
$sub_object = $contact_list[$i];
$sub_form = new $model_form($sub_object, array('user' => $user, 'embedded' => true, 'custom' => $field['fields']));
$this->embedForm($field['name'], $sub_form);
array_push($use_fields, $field['name']);
} // end field type == object
else
{
// standard form field
$this->setWidget($field['name'], CustomWidgetExtender::createSfWidget($field, $user, $this));
$this->widgetSchema->setLabel($field['name'], $field['label']);
if(trim($field['default']) != '')
$this->setDefault($field['name'], $field['default']);
// add field name to use_fields array
array_push($use_fields, $field['name']);
} // end field type != object
}
}
}
}
I ended up doing a rough hack by processing each form manually rather than trying to shoehorn this type of form into the symfony 1.x form framework.

Yii form model validation- either one is required

I have two fields on the form ( forgotpassword form ) username and email Id . User should enter one of them . I mean to retrieve the password user can enter user name or the email id . Could some one point me the validation rule for this ?
Is there any inbuilt rule I can use ?
( Sorry if it is already discussed or if I missed)
Thanks for your help
Regards
Kiran
I was trying to solve same problem today. What I've got is the code below.
public function rules()
{
return array(
// array('username, email', 'required'), // Remove these fields from required!!
array('email', 'email'),
array('username, email', 'my_equired'), // do it below any validation of username and email field
);
}
public function my_required($attribute_name, $params)
{
if (empty($this->username)
&& empty($this->email)
) {
$this->addError($attribute_name, Yii::t('user', 'At least 1 of the field must be filled up properly'));
return false;
}
return true;
}
General idea is to move 'required' validation to custom my_required() method which can check if any of field is filled up.
I see this post is from 2011 however I couldn't find any other solution for it. I Hope it will work for you or other in the future.
Enjoy.
Something like this is a bit more generic and can be reused.
public function rules() {
return array(
array('username','either','other'=>'email'),
);
}
public function either($attribute_name, $params)
{
$field1 = $this->getAttributeLabel($attribute_name);
$field2 = $this->getAttributeLabel($params['other']);
if (empty($this->$attribute_name) && empty($this->$params['other'])) {
$this->addError($attribute_name, Yii::t('user', "either {$field1} or {$field2} is required."));
return false;
}
return true;
}
Yii2
namespace common\components;
use yii\validators\Validator;
class EitherValidator extends Validator
{
/**
* #inheritdoc
*/
public function validateAttributes($model, $attributes = null)
{
$labels = [];
$values = [];
$attributes = $this->attributes;
foreach($attributes as $attribute) {
$labels[] = $model->getAttributeLabel($attribute);
if(!empty($model->$attribute)) {
$values[] = $model->$attribute;
}
}
if (empty($values)) {
$labels = '«' . implode('» or «', $labels) . '»';
foreach($attributes as $attribute) {
$this->addError($model, $attribute, "Fill {$labels}.");
}
return false;
}
return true;
}
}
in model:
public function rules()
{
return [
[['attribute1', 'attribute2', 'attribute3', ...], EitherValidator::className()],
];
}
I don't think there is a predefined rule that would work in that case, but it would be easy enough to define your own where for username and password fields the rule was "if empty($username . $password) { return error }" - you might want to check for a min length or other field-level requirements as well.
This works for me:
['clientGroupId', 'required', 'when' => function($model) {
return empty($model->clientId);
}, 'message' => 'Client group or client selection is required'],
You can use private property inside model class for preventing displays errors two times (do not assign error to model's attribute, but only add to model without specifying it):
class CustomModel extends CFormModel
{
public $username;
public $email;
private $_addOtherOneOfTwoValidationError = true;
public function rules()
{
return array(
array('username, email', 'requiredOneOfTwo'),
);
}
public function requiredOneOfTwo($attribute, $params)
{
if(empty($this->username) && empty($this->email))
{
// if error is not already added to model, add it!
if($this->_addOtherOneOfTwoValidationError)
{
$this->addErrors(array('Please enter your username or emailId.'));
// after first error adding, make error addition impossible
$this->_addOtherOneOfTwoValidationError = false;
}
return false;
}
return true;
}
}
don't forget "skipOnEmpty" attr. It cost me some hours.
protected function customRules()
{
return [
[['name', 'surname', 'phone'], 'compositeRequired', 'skipOnEmpty' => false,],
];
}
public function compositeRequired($attribute_name, $params)
{
if (empty($this->name)
&& empty($this->surname)
&& empty($this->phone)
) {
$this->addError($attribute_name, Yii::t('error', 'At least 1 of the field must be filled up properly'));
return false;
}
return true;
}
Yii 1
It can be optimized of course but may help someone
class OneOfThemRequiredValidator extends \CValidator
{
public function validateAttribute($object, $attribute)
{
$all_empty = true;
foreach($this->attributes as $_attribute) {
if (!$this->isEmpty($object->{$_attribute})) {
$all_empty = false;
break;
}
}
if ($all_empty) {
$message = "Either of the following attributes are required: ";
$attributes_labels = array_map(function($a) use ($object) {
return $object->getAttributeLabel($a);
}, $this->attributes);
$this->addError($object, $_attribute, $message . implode(',',
$attributes_labels));
}
}
}
yii1
public function rules(): array
{
return [
[
'id', // attribute for error
'requiredOneOf', // validator func
'id', // to params array
'name', // to params array
],
];
}
public function requiredOneOf($attribute, $params): void
{
$arr = array_filter($params, function ($key) {
return isset($this->$key);
});
if (empty($arr)) {
$this->addError(
$attribute,
Yii::t('yii', 'Required one of: [{attributes}]', [
'{attributes}' => implode(', ', $params),
])
);
}
}