Add is_required column to pp_shop_products_custom_fields table
This commit introduces a new column `is_required` to the `pp_shop_products_custom_fields` table. The column is of type TINYINT, cannot be null, and has a default value of 1. This change is intended to enhance the product custom fields by allowing the specification of whether a field is mandatory.
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2228,4 +2228,20 @@ textarea.form-control {
|
||||
width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
width: auto;
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.additional_fields {
|
||||
input[type="text"] {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
@@ -663,12 +663,19 @@ ob_start();
|
||||
<div>
|
||||
<a href="#" class="btn btn-success" id="add_custom_field"><i class="fa fa-plus"></i> dodaj niestandardowe pole</a>
|
||||
<div class="additional_fields">
|
||||
<? if ( is_array( $this -> product['custom_fields'] ) ) : foreach ( $this -> product['custom_fields'] as $field ):?>
|
||||
<div class="form-group row">
|
||||
<? if ( is_array( $this->product['custom_fields'] ) ) : foreach ( $this->product['custom_fields'] as $field ):?>
|
||||
<? $isRequired = !empty($field['is_required']); ?>
|
||||
<div class="form-group row custom-field-row">
|
||||
<label class="col-lg-4 control-label">Nazwa pola:</label>
|
||||
<div class="col-lg-8">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="custom_field_name[]" value="<?= $field['name']; ?>">
|
||||
<input type="text" class="form-control" name="custom_field_name[]" value="<?= htmlspecialchars($field['name']); ?>">
|
||||
<span class="input-group-addon">
|
||||
<label style="margin:0; font-weight:normal;">
|
||||
<input type="checkbox" class="custom-field-required" <?= $isRequired ? 'checked' : '' ?> name="custom_field_required[]" />
|
||||
wymagane
|
||||
</label>
|
||||
</span>
|
||||
<span class="input-group-addon btn btn-info" onclick="remove_custom_filed( $( this ) );">usuń</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -740,17 +747,23 @@ echo $grid->draw();
|
||||
|
||||
$(function() {
|
||||
|
||||
$( 'body' ).on( 'click', '#add_custom_field', function() {
|
||||
$('body').on('click', '#add_custom_field', function() {
|
||||
var html = '';
|
||||
html += '<div class="form-group row">';
|
||||
html += '<div class="form-group row custom-field-row">';
|
||||
html += '<label class="col-lg-4 control-label">Nazwa pola:</label>';
|
||||
html += '<div class="col-lg-8">';
|
||||
html +='<div class="input-group">';
|
||||
html += '<div class="input-group">';
|
||||
html += '<input type="text" class="form-control" name="custom_field_name[]" value="">';
|
||||
html += '<span class="input-group-addon">';
|
||||
html += '<label style="margin:0; font-weight:normal;">';
|
||||
html += '<input type="checkbox" class="custom-field-required" name="custom_field_required[]"> wymagane';
|
||||
html += '</label>';
|
||||
html += '</span>';
|
||||
html += '<span class="input-group-addon btn btn-info" onclick="remove_custom_filed( $( this ) );">usuń</span>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
$( '.additional_fields' ).append( html );
|
||||
$('.additional_fields').append(html);
|
||||
});
|
||||
|
||||
$('body').on('click', '#product-preview', function() {
|
||||
|
||||
BIN
autoload/.DS_Store
vendored
Normal file
BIN
autoload/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -186,7 +186,7 @@ class ShopProduct
|
||||
$values['id'], $values['name'], $values['short_description'], $values['description'], $values['status'], $values['meta_description'], $values['meta_keywords'], $values['seo_link'],
|
||||
$values['copy_from'], $values['categories'], $values['price_netto'], $values['price_brutto'], $values['vat'], $values['promoted'], $values['warehouse_message_zero'], $values['warehouse_message_nonzero'], $values['tab_name_1'],
|
||||
$values['tab_description_1'], $values['tab_name_2'], $values['tab_description_2'], $values['layout_id'], $values['products_related'], (int) $values['set'], $values['price_netto_promo'], $values['price_brutto_promo'],
|
||||
$values['new_to_date'], $values['stock_0_buy'], $values['wp'], $values['custom_label_0'], $values['custom_label_1'], $values['custom_label_2'], $values['custom_label_3'], $values['custom_label_4'], $values['additional_message'], (int)$values['quantity'], $values['additional_message_text'], $values['additional_message_required'] == 'on' ? 1 : 0, $values['canonical'], $values['meta_title'], $values['producer_id'], $values['sku'], $values['ean'], $values['product_unit'], $values['weight'], $values['xml_name'], $values['custom_field_name']
|
||||
$values['new_to_date'], $values['stock_0_buy'], $values['wp'], $values['custom_label_0'], $values['custom_label_1'], $values['custom_label_2'], $values['custom_label_3'], $values['custom_label_4'], $values['additional_message'], (int)$values['quantity'], $values['additional_message_text'], $values['additional_message_required'] == 'on' ? 1 : 0, $values['canonical'], $values['meta_title'], $values['producer_id'], $values['sku'], $values['ean'], $values['product_unit'], $values['weight'], $values['xml_name'], $values['custom_field_name'], $values['custom_field_required']
|
||||
) ) {
|
||||
$response = [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.', 'id' => $id ];
|
||||
}
|
||||
|
||||
@@ -761,7 +761,7 @@ class ShopProduct
|
||||
}
|
||||
|
||||
public static function save(
|
||||
$product_id, $name, $short_description, $description, $status, $meta_description, $meta_keywords, $seo_link, $copy_from, $categories, $price_netto, $price_brutto, $vat, $promoted, $warehouse_message_zero, $warehouse_message_nonzero, $tab_name_1, $tab_description_1, $tab_name_2, $tab_description_2, $layout_id, $products_related, int $set_id, $price_netto_promo, $price_brutto_promo, $new_to_date, $stock_0_buy, $wp, $custom_label_0, $custom_label_1, $custom_label_2, $custom_label_3, $custom_label_4, $additional_message, int $quantity, $additional_message_text, int $additional_message_required, $canonical, $meta_title, $producer_id, $sku, $ean, $product_unit, $weight, $xml_name, $custom_field_name
|
||||
$product_id, $name, $short_description, $description, $status, $meta_description, $meta_keywords, $seo_link, $copy_from, $categories, $price_netto, $price_brutto, $vat, $promoted, $warehouse_message_zero, $warehouse_message_nonzero, $tab_name_1, $tab_description_1, $tab_name_2, $tab_description_2, $layout_id, $products_related, int $set_id, $price_netto_promo, $price_brutto_promo, $new_to_date, $stock_0_buy, $wp, $custom_label_0, $custom_label_1, $custom_label_2, $custom_label_3, $custom_label_4, $additional_message, int $quantity, $additional_message_text, int $additional_message_required, $canonical, $meta_title, $producer_id, $sku, $ean, $product_unit, $weight, $xml_name, $custom_field_name, $custom_field_required
|
||||
)
|
||||
{
|
||||
global $mdb, $user;
|
||||
@@ -941,13 +941,17 @@ class ShopProduct
|
||||
}
|
||||
|
||||
// dodatkowe pola
|
||||
foreach ( $custom_field_name as $custom_field )
|
||||
for ( $i = 0; $i < count( $custom_field_name ); ++$i )
|
||||
{
|
||||
if ( !empty( $custom_field ) )
|
||||
if ( !empty( $custom_field_name[$i] ) )
|
||||
{
|
||||
$custom_field = $custom_field_name[$i];
|
||||
$custom_field_required = isset( $custom_field_required[$i] ) ? 1 : 0;
|
||||
|
||||
$mdb -> insert( 'pp_shop_products_custom_fields', [
|
||||
'id_product' => (int) $id,
|
||||
'name' => $custom_field,
|
||||
'is_required' => $custom_field_required,
|
||||
] );
|
||||
}
|
||||
}
|
||||
@@ -1268,13 +1272,20 @@ class ShopProduct
|
||||
|
||||
$mdb -> delete( 'pp_shop_products_custom_fields', [ 'AND' => [ 'id_product' => $product_id, 'id_additional_field[!]' => $exits_custom_ids ] ] );
|
||||
|
||||
foreach ( $custom_field_name as $custom_field )
|
||||
// $custom_field_name i $custom_field_required
|
||||
foreach ( $custom_field_name as $i => $custom_field )
|
||||
{
|
||||
if ( !empty( $custom_field ) )
|
||||
{
|
||||
$is_required = !empty( $custom_field_required[$i] ) ? 1 : 0;
|
||||
|
||||
if ( !$mdb -> count( 'pp_shop_products_custom_fields', [ 'AND' => [ 'id_product' => $product_id, 'name' => $custom_field ] ] ) )
|
||||
{
|
||||
$mdb -> insert( 'pp_shop_products_custom_fields', [ 'id_product' => $product_id, 'name' => $custom_field ] );
|
||||
$mdb -> insert( 'pp_shop_products_custom_fields', [ 'id_product' => $product_id, 'name' => $custom_field, 'is_required' => $is_required ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb -> update( 'pp_shop_products_custom_fields', [ 'is_required' => $is_required ], [ 'AND' => [ 'id_product' => $product_id, 'name' => $custom_field ] ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,17 +322,42 @@ class S
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function normalize_decimal( $val, $precision = 2 )
|
||||
static public function normalize_decimal($val, int $precision = 2)
|
||||
{
|
||||
$input = str_replace( ' ', '', $val );
|
||||
$number = str_replace( ',', '.', $input );
|
||||
if ( strpos( $number, '.' ) )
|
||||
{
|
||||
$groups = explode( '.', str_replace( ',', '.', $number ) );
|
||||
$lastGroup = array_pop( $groups );
|
||||
$number = implode( '', $groups ) . '.' . $lastGroup;
|
||||
if ($val === null || $val === '') {
|
||||
return number_format(0, $precision, '.', '');
|
||||
}
|
||||
return bcadd( round( $number, $precision ), 0, $precision );
|
||||
|
||||
// 1) wstępne czyszczenie
|
||||
$s = (string)$val;
|
||||
$s = str_replace(["\xC2\xA0", ' ', '’', "'"], '', $s); // spacje (w tym NBSP) i apostrofy
|
||||
$s = preg_replace('/[^0-9.,\-]/', '', $s); // zostaw tylko cyfry, . , i -
|
||||
|
||||
// 2) ustalenie separatora dziesiętnego
|
||||
$lastDot = strrpos($s, '.');
|
||||
$lastComma = strrpos($s, ',');
|
||||
|
||||
if ($lastDot !== false && $lastComma !== false) {
|
||||
// oba występują – prawy znak traktujemy jako dziesiętny
|
||||
if ($lastDot > $lastComma) {
|
||||
$s = str_replace(',', '', $s); // , = tysiące
|
||||
} else {
|
||||
$s = str_replace('.', '', $s); // . = tysiące
|
||||
$s = str_replace(',', '.', $s); // , = dziesiętny
|
||||
}
|
||||
} elseif ($lastComma !== false) {
|
||||
// tylko przecinek – traktuj jako dziesiętny
|
||||
$s = str_replace(',', '.', $s);
|
||||
} elseif (substr_count($s, '.') > 1) {
|
||||
// wiele kropek – ostatnia dziesiętna, pozostałe tysiące
|
||||
$pos = strrpos($s, '.');
|
||||
$s = str_replace('.', '', substr($s, 0, $pos)) . '.' . substr($s, $pos + 1);
|
||||
}
|
||||
// na tym etapie mamy opcjonalny '-' i co najwyżej jedną kropkę dziesiętną
|
||||
|
||||
// 3) zaokrąglenie i normalizacja
|
||||
$rounded = round((float)$s, $precision);
|
||||
return number_format($rounded, $precision, '.', '');
|
||||
}
|
||||
|
||||
public static function decimal( $val, $precision = 2, $dec_point = ',', $thousands_sep = ' ' )
|
||||
|
||||
BIN
autoload/front/.DS_Store
vendored
Normal file
BIN
autoload/front/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -485,6 +485,8 @@ jQuery( 'body' ).on( 'click', '#g-cancel', function() {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
jQuery( 'body' ).on( 'click', '#g-save, #g-edit-save', function()
|
||||
{
|
||||
var back_url = jQuery( this ).attr( 'back_url' );
|
||||
@@ -534,6 +536,44 @@ jQuery( 'body' ).on( 'click', '#g-save, #g-edit-save', function()
|
||||
}
|
||||
});
|
||||
|
||||
/* === UNIWERSALNA NORMALIZACJA CHECKBOXÓW TABLICOWYCH ===
|
||||
Dla wszystkich input[type=checkbox] z name="coś[]":
|
||||
- jeśli wartości są typu boolean ('', '1', 'on', 'true', 'yes') → zbuduj pełną tablicę 1/0 w kolejności DOM,
|
||||
tak aby indeksy były ciągłe [0..N-1] nawet gdy część jest odznaczona.
|
||||
- jeśli to lista wyboru (np. categories[] z różnymi ID) → pozostaw jak z serializeArray (tylko zaznaczone).
|
||||
*/
|
||||
(function normalizeCheckboxArrays() {
|
||||
var $form = jQuery('#fg-' + gtable);
|
||||
if (!$form.length) return;
|
||||
|
||||
// zgrupuj checkboxy po pełnej nazwie (z [] na końcu)
|
||||
var groups = {};
|
||||
$form.find('input[type="checkbox"][name$="[]"]').each(function () {
|
||||
var n = this.name; // np. "required[]", "visible[]", "categories[]"
|
||||
(groups[n] = groups[n] || []).push(this);
|
||||
});
|
||||
|
||||
Object.keys(groups).forEach(function (nameWithBrackets) {
|
||||
var inputs = groups[nameWithBrackets];
|
||||
if (!inputs.length) return;
|
||||
|
||||
// sprawdź, czy wszystkie wartości wyglądają „booleanowo”
|
||||
var uniqVals = Array.from(new Set(inputs.map(function (el) {
|
||||
return (el.getAttribute('value') || '').toLowerCase();
|
||||
})));
|
||||
var boolSet = new Set(['', '1', 'on', 'true', 'yes']);
|
||||
var isBooleanLike = uniqVals.every(function (v) { return boolSet.has(v); });
|
||||
|
||||
if (isBooleanLike) {
|
||||
var baseKey = nameWithBrackets.replace(/\[\]$/, ''); // usuń [] → "required", "visible"
|
||||
// pełna tablica 1/0 w kolejności w formularzu
|
||||
formattedValues[baseKey] = inputs.map(function (el) { return el.checked ? '1' : '0'; });
|
||||
}
|
||||
// else: zostawiamy formattedValues tak jak już zbudowane z serializeArray()
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
var url = jQuery( this ).attr( 'url' );
|
||||
|
||||
if ( url !== '' )
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<? if ( is_array( $this -> custom_fields ) ): foreach ( $this -> custom_fields as $custom_field ):?>
|
||||
<div class="custom-field">
|
||||
<div class="_name">
|
||||
<?= $custom_field['name'];?>:
|
||||
<?= $custom_field['name'];?><? if ( !empty( $custom_field['is_required'] ) ): ?>*<? endif; ?>:
|
||||
</div>
|
||||
<div class="_input">
|
||||
<input type="text" class="form-control" name="custom_field[<?= $custom_field['id_additional_field'];?>]" field_name="<?= $custom_field['name'];?>" value="">
|
||||
<input type="text" class="form-control" name="custom_field[<?= $custom_field['id_additional_field'];?>]" field_name="<?= $custom_field['name'];?>" value="" <? if ( !empty( $custom_field['is_required'] ) ): ?>required<? endif; ?>>
|
||||
</div>
|
||||
</div>
|
||||
<? endforeach; endif;?>
|
||||
@@ -526,7 +526,7 @@
|
||||
}
|
||||
|
||||
// dodatkowe pola muszą być uzupełnione
|
||||
$( '.custom-field input' ).each( function( index, element )
|
||||
$( '.custom-field input[required]' ).each( function( index, element )
|
||||
{
|
||||
if ( $.trim( $( element ).val() ) == '' )
|
||||
{
|
||||
|
||||
BIN
updates/0.20/ver_0.229.zip
Normal file
BIN
updates/0.20/ver_0.229.zip
Normal file
Binary file not shown.
1
updates/0.20/ver_0.229_sql.txt
Normal file
1
updates/0.20/ver_0.229_sql.txt
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `pp_shop_products_custom_fields` ADD COLUMN `is_required` TINYINT NOT NULL DEFAULT 1 AFTER `name`;
|
||||
@@ -1,3 +1,6 @@
|
||||
<b>ver. 0.229</b><br />
|
||||
- NEW - pola dodatkowe z opcją wymagane/niewymagane
|
||||
<hr>
|
||||
<b>ver. 0.228</b><br />
|
||||
- NEW - cron do wysyłania zamówień do trustmate.io
|
||||
<hr>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?
|
||||
$current_ver = 228;
|
||||
$current_ver = 229;
|
||||
|
||||
for ($i = 1; $i <= $current_ver; $i++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user