Adding a userfield with calculated data for the shop owner

Started by brandiware, May 23, 2017, 19:24:47 PM

Previous topic - Next topic

brandiware

Hi all,

on request from Max I post this development issue here for anyone to peruse and maybe even provide a helping hand.

Problem:

Based on the products chosen by the customer and placed in the cart, a sum of field values of specific products must be calculated. I.e. some products come with a addon feature which belongs
to the product and is dependent on the Qty. In particular, products which can be wall mounted are sold with suitable mortar to provide for a solid mount on walls.
The mortar qty is calculated based on the type and number of products added to the cart.
At the end of the process, when looking at the order, the customer gets an info on the total amount of mortar that will be part of the delivery.
Also, the shop owner wants to know this amount in order to properly fill the order.

Solution so far:

The products have a hidden customfield where the shop owner enters the amount of mortar for the particular products.
I have also created a userfield to be able to display the total amount of mortar to the customer and to post the value to the shop owner with the order.
Then I created a sublayout as instructed by Max. I tried to go alongside of the tos.php which comes with VM.

I now struggle with a solution to populate the userfield with my mortar amount such that it makes it into the VM Back-End. Also, It does not appear in the front end
when the user reviews the order.

The code I wrote to write the userfield value to the database (well, an attempt rather):

First to reach into the right objects:

$cart = null;
$customFieldsModel = null;
$userFields = null;

$app = JFactory::getApplication();
if($app->isSite()){
   //vmJsApi::popup('#full-tos','#terms-of-service');
   if (!class_exists('VirtueMartCart')) require(VMPATH_SITE . DS . 'helpers' . DS . 'cart.php');
   $cart = VirtuemartCart::getCart();
   /*$cart->prepareVendor();
   if(empty($tos) and !VmConfig::get ('agree_to_tos_onorder', true)){
      if(is_array($cart->BT) and !empty($cart->BT['tos'])){
         $tos = $cart->BT['tos'];
      }
   }*/
   
if (!class_exists( 'VmModel' )) require(JPATH_ADMINISTRATOR . DS . 'components' . DS . 'com_virtuemart'.DS.'helpers'.DS.'vmmodel.php');
$customFieldsModel = VmModel::getModel('Customfields');
if (!class_exists( 'VmModel' )) require(JPATH_ADMINISTRATOR . DS . 'components' . DS . 'com_virtuemart'.DS.'helpers'.DS.'vmmodel.php');
$userFields = VmModel::getModel('Userfields');
}

....some calculation for the total amount of mortar...


$renderText = "";

if( $mengeGrosseTube > 0 && $mengeKleineTube > 0)
   $renderText = ' '.$mengeGrosseTube.' Stk. Mörteltube Gross und '.$mengeKleineTube.' Stk. Mörteltube Klein werden mitgeliefert.';
else if ($mengeGrosseTube > 0 && $mengeKleineTube == 0)
   $renderText = ' '.$mengeGrosseTube.' Stk. Mörteltube Gross werden mitgeliefert.';
else if ($mengeGrosseTube == 0 && $mengeKleineTube > 0)
   $renderText = ' '.$mengeKleineTube.' Stk. Mörteltube Klein werden mitgeliefert.';
else
   $renderText =  ' -Keine-';

// this actually works
$fieldTobraNotes = $userFields->getUserfield('53','');


$data = array('tobra_notes' => '', 'virtuemart_userfield_id' => '53');
$data['tobra_notes'] = $renderText;

// this call is currently not of use, I assume it should be used to sanitize and secure the value to be written to the DB
$value = $userFields->prepareFieldDataSave($fieldTobraNotes, $data);
vmdebug('hm',$value);


// Here is the problem: $data must be a POST structure with a number of keys filled, hence this code fails miserable as I only filled some of the fields
//$status = $userFields->store($data);


I guess a better solution would be to use a means of form post instead, such as

echo VmHtml::checkbox ($_prefix.$field['name'], $tos, 1, 0, 'class="'.$class.'"', 'tos');

in tos.php

something like VmHtml::input ??

I am lost here, not sure how the posting mechanism for these userfields works.

Any idea is greatly appreciated.

Thanks
Stefan








Milbo

the checkbox thing is just a shortcut for

<input name="tos" value="0" type="hidden">
<input class="terms-of-service required" id="tos" name="tos" value="1" type="checkbox">

the userfields just takes the user input data. So you can add the info with a simple hidden input

<input name="tos" value="MÖRTELAmount" type="hidden">

But you should know, that it is possible to manipulate it by the user. Better is a plugin, so you can check the result
Should I fix your bug, please support the VirtueMart project and become a member
______________________________________
Extensions approved by the core team: http://extensions.virtuemart.net/

brandiware

o.k. the problems are now

a.) that the field value does not get stored in the userfield_values table. At least I cannot find it anywhere.

b.) Moreover, the field is displayed on the Front-End as well as on the back-end (when checking the order).
But there, the cart object is not present, meaning I can't do the calculation in the tobra_notes.php (aka the userfield).

I could do the calculation on the FE and pass it to the tobra_notes.php (aka the userfield), but what then ?

Here is the complete code now:

<?php
/**
* field tobra_notes
*
*/


$i = 1;
$moertelSumme = 0;
$kleineKartuscheID = '2707';
$grosseKartuscheID = '2709';
           
$mengeKleineTube=0;
$mengeGrosseTube=0;           
$grosseTube = 150;
$kleineTube = 70;
$action = "";


defined('_JEXEC') or die('Restricted access');
$_prefix = $viewData['prefix'];
$field = $viewData['field'];
//$tobra_notes = $field['value'];

$cart = null;

$app = JFactory::getApplication();
if($app->isSite()){
   //vmJsApi::popup('#full-tos','#terms-of-service');
   if (!class_exists('VirtueMartCart')) require(VMPATH_SITE . DS . 'helpers' . DS . 'cart.php');
   $cart = VirtuemartCart::getCart();
   /*$cart->prepareVendor();
   if(empty($tos) and !VmConfig::get ('agree_to_tos_onorder', true)){
      if(is_array($cart->BT) and !empty($cart->BT['tos'])){
         $tos = $cart->BT['tos'];
      }
   }*/
   
}

// die Mörtelmengen zusammenzählen
foreach ($cart->products as $pkey => $prow) {
   // der folgende Call bringt html zurück und alle Custom Fields von einem bestimmten Typ. Möglicherweise zu ändern falls weitere Felder dazukommen
  $moertelFeld =  $customFieldsModel->CustomsFieldCartDisplay ($prow);

  $dom = new DOMDocument();
  $dom->loadHTML($moertelFeld); 

  $moertel = $dom->getElementsByTagName('span');
  if($moertel[0] != null)
    $moertelSumme = $moertelSumme + intval(substr($moertel[0]->nodeValue,strpos($moertel[0]->nodeValue," ")+1)) * intval($prow->quantity);
 
}
 
if($moertelSumme>0)   {       
   $moertelProductIDs = array();
 
  if($moertelSumme <= $kleineTube) {
      $moertelProductIDs[0] = $kleineKartuscheID;
      $mengeKleineTube = 1;
      $mengeGrosseTube = 0;
  }
  else {
    if($moertelSumme <= $grosseTube) {
      $moertelProductIDs[0] = $grosseKartuscheID;
      $mengeGrosseTube = 1;
      $mengeKleineTube = 0;
    }
    else {
      if($moertelSumme % $grosseTube <= $kleineTube and $moertelSumme % $grosseTube > 0) {
        $mengeGrosseTube = floor($moertelSumme / $grosseTube);
        $mengeKleineTube = 1;
          $moertelProductIDs[0] = $grosseKartuscheID;
          $moertelProductIDs[1] = $kleineKartuscheID;
      }
      elseif($moertelSumme % $grosseTube <= $kleineTube and $moertelSumme % $grosseTube == 0) {
        $mengeGrosseTube = $moertelSumme / $grosseTube;
        $mengeKleineTube = 0;
          $moertelProductIDs[0] = $grosseKartuscheID;
          $moertelProductIDs[1] = $kleineKartuscheID;
      }
      else {
        $mengeGrosseTube = ceil($moertelSumme / $grosseTube);
        $mengeKleineTube = 0;
          $moertelProductIDs[0] = $grosseKartuscheID;
      }
    }
  }
}

$renderText = "";

if( $mengeGrosseTube > 0 && $mengeKleineTube > 0)
   $renderText = ' '.$mengeGrosseTube.' Stk. Mörteltube Gross und '.$mengeKleineTube.' Stk. Mörteltube Klein werden mitgeliefert.';
else if ($mengeGrosseTube > 0 && $mengeKleineTube == 0)
   $renderText = ' '.$mengeGrosseTube.' Stk. Mörteltube Gross werden mitgeliefert.';
else if ($mengeGrosseTube == 0 && $mengeKleineTube > 0)
   $renderText = ' '.$mengeKleineTube.' Stk. Mörteltube Klein werden mitgeliefert.';
else
   $renderText =  ' -Keine-';

echo '<input name="tobra_notes" value="'.$renderText.'" type="hidden">';
echo '<span>'.' '.$renderText.'</span>';

?>


brandiware

I forgot:

PHP-Version   5.6.30
Joomla!-Version   Joomla! 3.7.2
VirtueMart 3.2.2

Milbo

It should be stored in the order_userinfo table, per cartform... the result of the calculation

and the order shows the result, it should not start to calculate again.
the table "userfield_values" keeps data like Mr and Mrs and stuff like that. It is most time almost empty
Should I fix your bug, please support the VirtueMart project and become a member
______________________________________
Extensions approved by the core team: http://extensions.virtuemart.net/

brandiware

Yes, Milbo. Of course you are right. And the back-end problem is solved as well: the user field view receives the field correctly filled

$field = $viewData['field'];

when it is called on the back-end. Now the code works even when the clerk views the order on the back end.

I agree that this is probably not the finest way of achieving the solution, as Milbo put it, a plugin would be the correct way.
For the time being, here's the code that allows to calculate user field values on the front-end, with persisting data on the back-end:

<?php
/**
* field tobra_notes
*
*/


$i = 1;
$moertelSumme = 0;
$kleineKartuscheID = '2707';
$grosseKartuscheID = '2709';
           
$mengeKleineTube=0;
$mengeGrosseTube=0;           
$grosseTube = 150;
$kleineTube = 70;
$action = "";


defined('_JEXEC') or die('Restricted access');
$_prefix = $viewData['prefix'];
$field = $viewData['field'];
$tobra_notes = $field['value'];


$cart = null;
$customFieldsModel = null;

$app = JFactory::getApplication();
if($app->isSite()){
   //vmJsApi::popup('#full-tos','#terms-of-service');
   if (!class_exists('VirtueMartCart')) require(VMPATH_SITE . DS . 'helpers' . DS . 'cart.php');
   $cart = VirtuemartCart::getCart();
   /*$cart->prepareVendor();
   if(empty($tos) and !VmConfig::get ('agree_to_tos_onorder', true)){
      if(is_array($cart->BT) and !empty($cart->BT['tos'])){
         $tos = $cart->BT['tos'];
      }
   }*/
   
   if (!class_exists( 'VmModel' )) require(JPATH_ADMINISTRATOR . DS . 'components' . DS . 'com_virtuemart'.DS.'helpers'.DS.'vmmodel.php');
    $customFieldsModel = VmModel::getModel('Customfields');
}

if($app->isSite()){

// die Mörtelmengen zusammenzählen
foreach ($cart->products as $pkey => $prow) {
   // der folgende Call bringt html zurück und alle Custom Fields von einem bestimmten Typ. Möglicherweise zu ändern falls weitere Felder dazukommen
  $moertelFeld =  $customFieldsModel->CustomsFieldCartDisplay ($prow);

  $dom = new DOMDocument();
  $dom->loadHTML($moertelFeld); 

  $moertel = $dom->getElementsByTagName('span');
  if($moertel[0] != null)
    $moertelSumme = $moertelSumme + intval(substr($moertel[0]->nodeValue,strpos($moertel[0]->nodeValue," ")+1)) * intval($prow->quantity);
 
}
 
if($moertelSumme>0)   {       
   $moertelProductIDs = array();
 
  if($moertelSumme <= $kleineTube) {
      $moertelProductIDs[0] = $kleineKartuscheID;
      $mengeKleineTube = 1;
      $mengeGrosseTube = 0;
  }
  else {
    if($moertelSumme <= $grosseTube) {
      $moertelProductIDs[0] = $grosseKartuscheID;
      $mengeGrosseTube = 1;
      $mengeKleineTube = 0;
    }
    else {
      if($moertelSumme % $grosseTube <= $kleineTube and $moertelSumme % $grosseTube > 0) {
        $mengeGrosseTube = floor($moertelSumme / $grosseTube);
        $mengeKleineTube = 1;
          $moertelProductIDs[0] = $grosseKartuscheID;
          $moertelProductIDs[1] = $kleineKartuscheID;
      }
      elseif($moertelSumme % $grosseTube <= $kleineTube and $moertelSumme % $grosseTube == 0) {
        $mengeGrosseTube = $moertelSumme / $grosseTube;
        $mengeKleineTube = 0;
          $moertelProductIDs[0] = $grosseKartuscheID;
          $moertelProductIDs[1] = $kleineKartuscheID;
      }
      else {
        $mengeGrosseTube = ceil($moertelSumme / $grosseTube);
        $mengeKleineTube = 0;
          $moertelProductIDs[0] = $grosseKartuscheID;
      }
    }
  }
}

$renderText = "";

if( $mengeGrosseTube > 0 && $mengeKleineTube > 0)
   $renderText = ' '.$mengeGrosseTube.' Stk. Mörteltube Gross und '.$mengeKleineTube.' Stk. Mörteltube Klein werden mitgeliefert.';
else if ($mengeGrosseTube > 0 && $mengeKleineTube == 0)
   $renderText = ' '.$mengeGrosseTube.' Stk. Mörteltube Gross werden mitgeliefert.';
else if ($mengeGrosseTube == 0 && $mengeKleineTube > 0)
   $renderText = ' '.$mengeKleineTube.' Stk. Mörteltube Klein werden mitgeliefert.';
else
   $renderText =  ' -Keine-';
}
else
  $renderText = $tobra_notes;
 
echo '<input name="tobra_notes" value="'.$renderText.'" type="hidden">';
echo '<span>'.' '.$renderText.'</span>';

?>


Milbo

Nice done. Thanks for adding your final code here, so the community can learn from it.
Should I fix your bug, please support the VirtueMart project and become a member
______________________________________
Extensions approved by the core team: http://extensions.virtuemart.net/

brandiware

thanks. One thing which should be changed is the way I currently read the custom product field:

$moertelFeld =  $customFieldsModel->CustomsFieldCartDisplay ($prow);

  $dom = new DOMDocument();
  $dom->loadHTML($moertelFeld); 

  $moertel = $dom->getElementsByTagName('span');


Is there a method in $customFieldsModel which retrieves the field e.g. by name ?