-
Notifications
You must be signed in to change notification settings - Fork 63
Fix filter compatibility's #236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
ecf3c51
68ff565
5f9dd01
3175b1b
7c5d4c3
d6ca7fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -151,20 +151,65 @@ public static function set($key, $value) | |||||||
| * Flatten a multidimensional array into a single array. Does not maintain keys. | ||||||||
| * | ||||||||
| * @param array $array | ||||||||
| * @param bool $skipHash If true, associative arrays (hashes) are preserved without flattening. | ||||||||
| * This mimics Ruby's Array#flatten behavior which preserves Hash objects. | ||||||||
| * | ||||||||
| * @return array | ||||||||
| */ | ||||||||
| public static function arrayFlatten($array) | ||||||||
| public static function arrayFlatten($array, $skipHash = false) | ||||||||
| { | ||||||||
| $return = []; | ||||||||
|
|
||||||||
| foreach ($array as $element) { | ||||||||
| if (is_array($element)) { | ||||||||
| $return = array_merge($return, self::arrayFlatten($element)); | ||||||||
| if ($skipHash && self::isHash($element)) { | ||||||||
| $return[] = $element; | ||||||||
| } else { | ||||||||
| $return = array_merge($return, self::arrayFlatten($element, $skipHash)); | ||||||||
| } | ||||||||
| } else { | ||||||||
| $return[] = $element; | ||||||||
| } | ||||||||
| } | ||||||||
| return $return; | ||||||||
| } | ||||||||
|
|
||||||||
| /** | ||||||||
| * Determine if an array is a hash (associative array). | ||||||||
| * This is a polyfill for !array_is_list() (PHP 8.1+). | ||||||||
| * | ||||||||
| * @param array $array | ||||||||
| * | ||||||||
| * @return bool | ||||||||
| * | ||||||||
| * @see https://www.php.net/manual/en/function.array-is-list.php | ||||||||
| */ | ||||||||
| public static function isHash(array $array): bool | ||||||||
| { | ||||||||
| if (empty($array)) { | ||||||||
| return false; | ||||||||
| } | ||||||||
| return array_keys($array) !== range(0, count($array) - 1); | ||||||||
| } | ||||||||
|
|
||||||||
| /** | ||||||||
| * Determine if a value represents an integer. | ||||||||
| * Returns true for int type or string integers (e.g., "20", "-5"). | ||||||||
| * Returns false for floats (e.g., 20.0) to preserve float division behavior. | ||||||||
| * | ||||||||
| * @param mixed $value | ||||||||
| * | ||||||||
| * @return bool | ||||||||
| */ | ||||||||
| public static function isInteger($value): bool | ||||||||
| { | ||||||||
| if (is_int($value)) { | ||||||||
| return true; | ||||||||
| } | ||||||||
| if (is_string($value)) { | ||||||||
| $trimmed = ltrim($value, '-'); | ||||||||
| return $trimmed !== '' && ctype_digit($trimmed); | ||||||||
|
Comment on lines
+210
to
+211
|
||||||||
| $trimmed = ltrim($value, '-'); | |
| return $trimmed !== '' && ctype_digit($trimmed); | |
| return preg_match('/^-?\d+$/', $value) === 1; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -34,18 +34,17 @@ public static function append($input, $string) | |||||
|
|
||||||
|
|
||||||
| /** | ||||||
| * Capitalize words in the input sentence | ||||||
| * Capitalize the first character and downcase the rest | ||||||
| * | ||||||
| * @param string $input | ||||||
| * | ||||||
| * @return string | ||||||
| */ | ||||||
| public static function capitalize($input) | ||||||
| { | ||||||
| return preg_replace_callback("/(^|[^\p{L}'])([\p{Ll}])/u", function ($matches) { | ||||||
| $first_char = mb_substr($matches[2], 0, 1); | ||||||
| return $matches[1] . mb_strtoupper($first_char) . mb_substr($matches[2], 1); | ||||||
| }, ucwords($input)); | ||||||
| $firstChar = mb_strtoupper(mb_substr($input, 0, 1, 'UTF-8'), 'UTF-8'); | ||||||
| $rest = mb_strtolower(mb_substr($input, 1, null, 'UTF-8'), 'UTF-8'); | ||||||
| return $firstChar . $rest; | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
|
|
@@ -111,16 +110,19 @@ public static function _default($input, $default_value) | |||||
|
|
||||||
|
|
||||||
| /** | ||||||
| * division | ||||||
| * Division | ||||||
| * | ||||||
| * @param float $input | ||||||
| * @param float $operand | ||||||
| * @param int|float|string $input | ||||||
| * @param int|float|string $operand | ||||||
| * | ||||||
| * @return float | ||||||
| * @return int|float | ||||||
| */ | ||||||
| public static function divided_by($input, $operand) | ||||||
| { | ||||||
| return (float)$input / (float)$operand; | ||||||
| if (Liquid::isInteger($input) && Liquid::isInteger($operand)) { | ||||||
| return (int) floor($input / $operand); | ||||||
|
||||||
| return (int) floor($input / $operand); | |
| return intdiv($input, $operand); |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No division by zero handling in divided_by filter. When $operand is 0 (or evaluates to 0), this will result in a PHP warning/error and potentially INF or NAN. Consider adding a check and returning a sensible default (like 0 or null) or throwing a more descriptive exception, depending on how Ruby's Liquid handles this case.
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
map now flattens nested arrays via Liquid::arrayFlatten($input, true) before iterating, which increases the chance that elements evaluated below as callables (is_callable($elem)) will be surfaced and executed. An attacker controlling input can place nested PHP callables (e.g., "phpinfo", [ClassName, "method"], or a closure) so they are flattened and then invoked, enabling arbitrary function execution from templates. Remove callable invocation or strictly whitelist safe callables; e.g., delete the is_callable branch or change it to only allow vetted closures you create, and avoid flattening untrusted arrays before iteration:
// Either remove callable execution entirely
return array_map(function ($elem) use ($property) {
// no is_callable branch here
// ... only property extraction logic
}, $input);
// Or, if callable support is strictly needed, restrict:
if ($elem instanceof \Closure && $this->isTrustedClosure($elem)) {
return $elem();
}
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No modulo by zero handling in modulo filter. When $operand is 0 (or evaluates to 0), this will result in a PHP warning "Division by zero" for the integer path (line 379) or undefined behavior for fmod(). Consider adding a check and handling this edge case appropriately, consistent with Ruby's Liquid behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Using
!==for array comparison may be inefficient for large arrays. While this works correctly,array_keys($array) !== range(0, count($array) - 1)creates and compares two potentially large arrays. For PHP 8.1+, consider using the native!array_is_list($array)function with a version check fallback. Alternatively, iterate through keys and return early on the first non-sequential key for better performance.