PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0

Friday, October 21, 2022

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

 October 21, 2022     associations, cakephp, has-many, sql, validation     No comments   

Issue

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,
    ];
}

Solution

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;
    }
}


Answered By - R. Jordaan
Answer Checked By - David Marino (PHPFixing Volunteer)
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Newer Post Older Post Home

0 Comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
Comments
Atom
Comments

Copyright © PHPFixing