diff --git a/Core/DbQuery.php b/Core/DbQuery.php index cf751ee40a..33c597891a 100644 --- a/Core/DbQuery.php +++ b/Core/DbQuery.php @@ -297,7 +297,7 @@ public function orderBy(string $field, string $order = 'ASC'): self // permitimos LOWER(), UPPER(), CAST() y COALESCE() if (preg_match('/^(LOWER|UPPER)\([a-zA-Z0-9_.]+\)$/i', $field) || preg_match('/^CAST\([a-zA-Z0-9_.]+ AS [a-zA-Z0-9_ ]+\)$/i', $field) || - preg_match('/^COALESCE\([a-zA-Z0-9_., ]+\)$/i', $field)) { + preg_match("/^COALESCE\([a-zA-Z0-9_.]+\s*,\s*(?:'[^']*'|-?\d+(?:\.\d+)?)\)$/i", $field)) { $this->orderBy[] = $field . ' ' . $order; return $this; } diff --git a/Core/Lib/ReceiptGenerator.php b/Core/Lib/ReceiptGenerator.php index 2d71eb0b5b..89a259d399 100644 --- a/Core/Lib/ReceiptGenerator.php +++ b/Core/Lib/ReceiptGenerator.php @@ -265,16 +265,17 @@ protected function updateCustomerReceipts($invoice): bool // calculate outstanding amount $amount = $this->getOutstandingAmount($receipts, $invoice->total); - if (empty($amount)) { - return true; - } // calculate new receipt number $newNum = 1; foreach ($receipts as $receipt) { // try to update open receipts if ($receipt->pagado === false) { + if (empty($amount) && $receipt->coddivisa === $invoice->coddivisa) { + continue; + } $receipt->importe += $amount; + $receipt->coddivisa = $invoice->coddivisa; return $receipt->save(); } @@ -299,16 +300,17 @@ protected function updateSupplierReceipts($invoice): bool // calculate outstanding amount $amount = $this->getOutstandingAmount($receipts, $invoice->total); - if (empty($amount)) { - return true; - } // calculate new receipt number $newNum = 1; foreach ($receipts as $receipt) { // try to update open receipts if ($receipt->pagado === false) { + if (empty($amount) && $receipt->coddivisa === $invoice->coddivisa) { + continue; + } $receipt->importe += $amount; + $receipt->coddivisa = $invoice->coddivisa; return $receipt->save(); } diff --git a/Core/Model/CodeModel.php b/Core/Model/CodeModel.php index 8cfe54ea28..6db3a7f8d0 100644 --- a/Core/Model/CodeModel.php +++ b/Core/Model/CodeModel.php @@ -272,7 +272,10 @@ public static function setLimit(int $newLimit): void /** * Valída que un nombre de campo sea seguro para usar en consultas SQL. * Solo permite letras, números, guiones bajos y puntos (para campos con alias de tabla). - * También permite el uso de las funciones lower() y upper(). + * También permite el uso de algunas funciones SQL concretamente: + * - lower() y upper() + * - substring() con sintaxis substring(campo, start, length) donde start y length son números enteros positivos + * - concat() con sintaxis concat(arg1, arg2, ...) donde arg es un identificador o un string literal simple (entre comillas simples, sin comillas internas) * * @param string $fieldName * @@ -280,18 +283,40 @@ public static function setLimit(int $newLimit): void */ protected static function isValidFieldName(string $fieldName): bool { - // permite campos vacíos (se usan valores por defecto en algunos casos) - if (empty($fieldName)) { + // permite campos vacíos (valores por defecto) + if ($fieldName === '') { return true; } - // permite lower() y upper() con un campo válido dentro - if (preg_match('/^(lower|upper)\(([a-zA-Z0-9_\.]+)\)$/i', $fieldName, $matches)) { + // Identificador: campo o tabla.campo (sin espacios, sin comillas) + $fieldName = trim($fieldName); + $ident = '[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?'; + + // Campo directo + if (preg_match('/^' . $ident . '$/', $fieldName)) { + return true; + } + + // lower(field) / upper(field) + if (preg_match('/^(lower|upper)\((' . $ident . ')\)$/i', $fieldName)) { + return true; + } + + // substring(field, start, len) con números + if (preg_match('/^substring\((' . $ident . '),\s*(\d+)\s*,\s*(\d+)\s*\)$/i', $fieldName, $m)) { + $start = (int)$m[2]; + $len = (int)$m[3]; + // límites razonables (ajusta a tu caso) + return $start >= 1 && $len >= 1 && $len <= 1000; + } + + // concat(arg1, arg2, ...) donde arg es un identificador o literal simple '...'(sin comillas internas o escapadas) + $arg = "(?:$ident|'[^']*')"; + if (preg_match('/^concat\(\s*' . $arg . '(?:\s*,\s*' . $arg . ')+\s*\)$/i', $fieldName)) { return true; } - // permite letras, números, guiones bajos y puntos (para tabla.campo) - return preg_match('/^[a-zA-Z0-9_\.]+$/', $fieldName) === 1; + return false; } protected static function db(): DataBase