Disabling custom fields have bugs, show []d checkbox and show d fields in BE

Started by Kuubs, May 24, 2024, 08:35:00 AM

Previous topic - Next topic

Kuubs

When you remove a custom field, it also removes them from database and thus from the already made orders itself. This makes it so custom fields where the customer paid for is not in the order itself anymore which is not good for order management.

This bug is already a long time here, and I think it's time to smash this one. I think I have the solution at hand.

- Enable the []d checkbox that is also on child products to disable custom fields for the parent
- Make sure to keep including them in the admin backend to not let virtuemart completely remove the custom field

You can set the disabler from the custom field to 1 in the database, this will disable the field but not remove them. BUT, the custom fields also won't display in the backend, which is a huge problem because if you save the parent product (which doens't display the disabled field) it will remove the custom field from the dataabase. My fixes would be to enable the []d checkbox, but also keep showing all the fields in the backend, even if it's disabled.

@Milbo

Milbo

yes, that sounds like a plan. But I have no fast answer how todo 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/

Kuubs

Quote from: Milbo on May 24, 2024, 08:55:02 AMyes, that sounds like a plan. But I have no fast answer how todo it.
Ah that's a shame. I will try to find if I can somehow fix it myself by checking all the files, I hoped you could point me to the files itself.. I will just chekc the view files first, then the controller files and then the model files. Thanks for the reply.

EDIT:

So I checked the model first that removes the disabled custom fields from the product. I changed some code that does this:


public function storeProductCustomfields($table, $datas, $id) {

vRequest::vmCheckToken('Invalid token in storeProductCustomfields');
//Sanitize id
$id = (int)$id;

//Table whitelist
$tableWhiteList = array('product','category','manufacturer');
if(!in_array($table,$tableWhiteList)) return false;

// Get old IDS
$db = JFactory::getDBO();
$db->setQuery( 'SELECT * FROM `#__virtuemart_'.$table.'_customfields` as `PC` WHERE `PC`.virtuemart_'.$table.'_id ='.$id );

$oldCustomfields = $db->loadAssocList('virtuemart_customfield_id');
$old_customfield_ids = array_keys($oldCustomfields);

if (!empty( $datas['field'])) {

foreach($datas['field'] as $key => $fields){

if(!empty($datas['field'][$key]['virtuemart_product_id']) and (int)$datas['field'][$key]['virtuemart_product_id']!=$id){
//vmdebug('The field is from the parent',$fields);
$fields['override'] = !empty($fields['override'])?(int)$fields['override']:0;
$fields['disabler'] = !empty($fields['disabler'])?(int)$fields['disabler']:0;

if($fields['override']!=0 or $fields['disabler']!=0){
if($fields['override']!=0){
$fields['override'] = $fields['virtuemart_customfield_id'];
}
if($fields['disabler']!=0){
$fields['disabler'] = $fields['virtuemart_customfield_id'];
}

if(!empty($fields['virtuemart_customfield_id']) and empty($oldCustomfields[$fields['virtuemart_customfield_id']]['virtuemart_customfield_id'])){
//vmdebug('It is set now as override, store it as clone, therefore set the virtuemart_customfield_id = 0');
$fields['virtuemart_customfield_id'] = 0;
}
}
else {
//vmdebug('there is no override/disabler set',$fields,$oldCustomfields[$fields['virtuemart_customfield_id']]);
//we do not store customfields inherited by the parent, therefore
$key = array_search($fields['virtuemart_customfield_id'], $old_customfield_ids );
if ($key !== false ){
unset( $old_customfield_ids[ $key ] );
}
continue;
}
}
else {
//vmdebug('The field is from the current product',$fields);
// if(empty($fields['override']) and empty($fields['disabler']) and !empty($fields['virtuemart_customfield_id']) and (!empty($oldCustomfields[$fields['virtuemart_customfield_id']]['disabler']) or !empty($oldCustomfields[$fields['virtuemart_customfield_id']]['override']) )){
// //vmdebug('Remove customfield override/disabler',$fields['virtuemart_customfield_id']);
// $old_customfield_ids[] = $fields['virtuemart_customfield_id'];
// }

//DONT REMOVE DISABLED FROM THE PRODUCT < CHANGES KUUBS
if(empty($fields['override']) and !empty($fields['virtuemart_customfield_id']) or !empty($oldCustomfields[$fields['virtuemart_customfield_id']]['override'])){
//vmdebug('Remove customfield override/disabler',$fields['virtuemart_customfield_id']);
$old_customfield_ids[] = $fields['virtuemart_customfield_id'];
}

}

if(!empty($fields['field_type']) and $fields['field_type']=='C' and !isset($datas['clone']) ){

$cM = VmModel::getModel('custom');
$c = $cM->getCustom($fields['virtuemart_custom_id'],'');

if(!empty($fields['set_matrix'])){

$productModel = VmModel::getModel ('product');
$avail = $productModel->getProductChildIds($id);

foreach($fields['selectoptions'] as $kv => $selectoptions){
if(!empty($selectoptions['values'])){
$values[$kv] = preg_split('/\r\n|\r|\n/', $selectoptions['values'],5);
}
}

vmdebug('my values',$values,$avail);

$parentCombo = $fields['options'][$id];
$myMatrix = array();
$size = 1;
//$parentMatrix = array();
//Yes, also this may get better written, but I am just happy that it works this way.
foreach ($values as $variantKey => $optArray) {
$size = $size * sizeof($optArray);
//$level++;
foreach ($optArray as $option) {
//vmdebug('myMatrix $k=>$option',$option);
if($variantKey==0){
$myMatrix[$option] = null;
//$parentMatrix[$parentCombo[0]] = null;
} else if($variantKey == 1){

$myMatrix = self::writeValuesToKeysAddValueArray($myMatrix,$option);

//$parentMatrix[$parentCombo[0]][$parentCombo[1]]= null;
} else if($variantKey == 2){
foreach($myMatrix as $k1 => &$option1){
$option1 = self::writeValuesToKeysAddValueArray($option1,$option);
}
//$parentMatrix[$parentCombo[0]][$parentCombo[1]][$parentCombo[2]]= null;
} else if($variantKey == 3){
foreach($myMatrix as $k1 => &$option1){
foreach($option1 as $k2 => &$option2){
$option2 = self::writeValuesToKeysAddValueArray($option2,$option);
}
}
//$parentMatrix[$parentCombo[0]][$parentCombo[1]][$parentCombo[2]][$parentCombo[3]]= null;
} else if($variantKey == 4){
foreach($myMatrix as $k1 => &$option1){
foreach($option1 as $k2 => &$option2){
foreach($option2 as $k3 => &$option3){
$option3 = self::writeValuesToKeysAddValueArray($option3,$option);
}
}
}
//$parentMatrix[$parentCombo[0]][$parentCombo[1]][$parentCombo[2]][$parentCombo[3]][$parentCombo[4]]= null;
}
}
}
vmdebug('myMatrix 3',$size,$myMatrix);

vmdebug('myMatrix orig $fields',$fields['options']);
$parentComboSerialized = serialize($parentCombo);
//reset($avail);
for((int)$i=0;$i<$size;$i++){
if(empty($avail)){
$childId = $productModel->createChild($id);
} else {
$childId = $avail[$i];
unset($avail[$i]);
}
vmdebug('$childId after unset'.$i,$childId,$avail);
$combo = self::writeCombos($myMatrix);

if(serialize($combo) == $parentComboSerialized){
$combo = self::writeCombos($myMatrix);
//vmdebug('myMatrix $combo equals $parentCombo',$combo, $parentCombo);
$size--;
} else {
//vmdebug('myMatrix $combo NOT equal',serialize($combo), $parentComboSerialized);
}
$fields['options'][$childId] = $combo;
}
vmdebug('myMatrix $fields',$fields['options'],$avail);

}
//The idea was here to store the images directly. Maybe just the ids.
/*if(!empty($c->withImage)){
$mediaM = VmModel::getModel('media');
$tablePM = $mediaM->getTable('product_medias');
foreach($fields['options'] as $prodId => $lvalue){
$images = $tablePM->load($prodId);
if(isset($images[0])){
$media = $mediaM->createMediaByIds($images[0]);
$fields['images'][$prodId] = $media[0]->getFileUrlThumb();
}

}
}*/

//Set tags on extra customfield
if(!empty($c->sCustomId)){

$sCustId = $c->sCustomId;
$labels = array();
foreach($fields['selectoptions'] as $k => $option){
if (is_object($option)) {
$option = Joomla\Utilities\ArrayHelper::fromObject($option);
}
if($option['voption'] == 'clabels' and !empty($option['clabel'])){
$labels[$k] = $option['clabel'];
}
}

foreach($fields['options'] as $prodId => $lvalue){

if($prodId == $id) continue;
$db->setQuery( 'SELECT `virtuemart_customfield_id` FROM `#__virtuemart_'.$table.'_customfields` as `PC` WHERE `PC`.virtuemart_'.$table.'_id ="'.$prodId.'" AND `virtuemart_custom_id`="'.(int)$sCustId.'" '  );
$strIds = $db->loadColumn();
$i=0;
foreach($lvalue as $k=>$value) {

if(!empty($labels[$k])) {
$ts = array();
$ts['field_type'] = 'S';
$ts['virtuemart_product_id'] = (int)$prodId;
$ts['virtuemart_custom_id'] = (int)$sCustId;
if(isset($strIds[$i])){
$ts['virtuemart_customfield_id'] = (int)$strIds[$i];
unset( $strIds[$i++] );
}
$ts['customfield_value'] = $value;

$tableCustomfields = $this->getTable($table.'_customfields');
$tableCustomfields->bindChecknStore($ts);
}
}

if(count($strIds)>0){
// delete old unused Customfields
$db->setQuery( 'DELETE FROM `#__virtuemart_'.$table.'_customfields` WHERE `virtuemart_customfield_id` in ("'.implode('","', $strIds ).'") ');
$db->execute();
}
}
}
//vmdebug('Executing',$id,$fields);
}

if (!empty($datas['customfield_params'][$key]) and !isset($datas['clone']) ) {
if (array_key_exists( $key,$datas['customfield_params'])) {
$fields = array_merge ((array)$fields, (array)$datas['customfield_params'][$key]);
}
}
$fields['virtuemart_'.$table.'_id'] = $id;

if(!empty($fields['field_type']) and ( $fields['field_type']=='RC' or $fields['field_type']=='R' ) and !isset($datas['clone']) ){
if(is_array($fields['customfield_value'])){
$fields['customfield_value'] = implode(',',$fields['customfield_value']);
}
}

$this->storeProductCustomfield('product', $fields);

$key = array_search($fields['virtuemart_customfield_id'], $old_customfield_ids );
if ($key !== false ) unset( $old_customfield_ids[ $key ] );

}
} else {
//vmdebug('storeProductCustomfields nothing to store');
}

vDispatcher::importVMPlugins('vmcustom');

//vmdebug('Delete $old_customfield_ids',$old_customfield_ids);

if ( count($old_customfield_ids) ) {
// call the plugins to delete their records
foreach ($old_customfield_ids as $old_customfield_id) {
vDispatcher::trigger('plgVmOnCustomfieldRemove', array($oldCustomfields[$old_customfield_id]));
}
// delete old unused Customfields
$db->setQuery( 'DELETE FROM `#__virtuemart_'.$table.'_customfields` WHERE `virtuemart_customfield_id` in ("'.implode('","', $old_customfield_ids ).'") ');
$db->execute();
//vmdebug('Deleted $old_customfield_ids',$old_customfield_ids);
}



if (isset($datas['customfield_params']) and is_array($datas['customfield_params'])) {
foreach ($datas['field'] as $key => $pfield ) {
if($pfield['field_type']=="E" and !empty($pfield['custom_element'])){
vDispatcher::directTrigger( 'vmcustom',$pfield['custom_element'], 'plgVmOnStoreProduct', array($datas, $datas['customfield_params'][$key], $old_customfield_ids, $key ));
}
}
}

}


I removed the part where it adds the product custom field to the old_custom_fields so it doesn't remove them in the query below. I tested it and it seems that it works correctly. But i'm not sure if this is correct. This doesn't show the []d checkbox, but at least it doesn't remove it from the product when you save the product with the disabled checkbox activated (still needs to use direct database to change the disabler value)

@Milbo, do you see anything wrong with this that will break other parts of Virtuemart?

Kuubs

I tried many times to implement this myself, but I cannot make it work correctly. @Milbo is it possible for you to take a look at it? I can buy some hours if you'd like

Milbo

You cannot solve it that way. You need to use the stored customfield ids and values which are in the order_items table in the column "product_attribute".
So we can say, if it is stored there, load it regardless the disabled state. New order items wont have the ids, because it is disabled.
Should I fix your bug, please support the VirtueMart project and become a member
______________________________________
Extensions approved by the core team: http://extensions.virtuemart.net/

Milbo

Btw, I think the real solution is to clone the product and sell the cloned product, with the changed customfields
Should I fix your bug, please support the VirtueMart project and become a member
______________________________________
Extensions approved by the core team: http://extensions.virtuemart.net/

Kuubs

Quote from: Milbo on August 07, 2024, 13:40:44 PMYou cannot solve it that way. You need to use the stored customfield ids and values which are in the order_items table in the column "product_attribute".
So we can say, if it is stored there, load it regardless the disabled state. New order items wont have the ids, because it is disabled.

The problem is is that when you disable a custom field, it gets deleted from the product_customfields table. Because the product_attribute saves the custom_id it cannot get the value, because it's deleted.

Quote from: Milbo on August 07, 2024, 15:42:48 PMBtw, I think the real solution is to clone the product and sell the cloned product, with the changed customfields

Isn't this a bit cumbersome? I still think having the option to actually disable the field (like you can with child products) is the best way to do this. The only thing is is that it should never remove it from the database, only when it's actually removed in the backed with the X icon. In essence the functionality is there, only it should also work on main products without any children, and not only on child products.

--

There are 2 problems right now:

- You cannot temporarily disable a field, for example when a custom field is temporary sold out (well, you can but this involves setting the disabled parameter in the product_customfields AND you cannot save the product in the GUI of Virtuemart, because then it doesn't load the disabled field and then it will be removed from the product AND database

- Disabled fields are not being loaded in the backend with orders. This should be a fairly easy fix, as you described. But this post is mainly for the first problem i described above.