44
55use Illuminate \Contracts \Database \Query \Expression ;
66use Illuminate \Support \Facades \DB ;
7- use Illuminate \Support \Str ;
8-
97trait InteractsWithRawFilter
108{
9+ private const MAX_INTERVAL = 365 ;
10+
1111 private bool $ usesRawValue = false ;
1212
1313 /**
1414 * Operators allowed to be used with raw()
1515 *
1616 * @var array
1717 */
18- private array $ validRawFilterOperators = ['= ' , '!= ' , '> ' , '< ' , '>= ' , '<= ' ];
18+ private array $ validRawFilterOperators = ['= ' , '!= ' , '> ' , '< ' , '>= ' , '<= ' , ' between ' ];
1919
2020 /**
2121 * Unwrap the raw() and retrieve the string value passed
@@ -24,14 +24,30 @@ trait InteractsWithRawFilter
2424 */
2525 public function getRawValue (): Expression
2626 {
27- // Get the string equivalent of the raw() filter value
28- $ value = $ this ->containsRawValue ($ this ->getValue ()) ? $ this ->getValue () : '' ;
27+ $ value = $ this ->getValue ();
28+ $ matches = [];
29+ $ pattern = '/^\s*raw\(\s*NOW\(\)\s*(?:(\+|-)\s*INTERVAL\s+([1-9]\d*)\s+ '
30+ . '(SECOND|MINUTE|HOUR|DAY|WEEK|MONTH))?\s*\)\s*$/iD ' ;
31+
32+ if (!is_string ($ value ) || preg_match ($ pattern , $ value , $ matches ) !== 1 ) {
33+ abort (422 , 'Invalid raw filter expression. ' );
34+ }
35+
36+ if (isset ($ matches [2 ]) && (int ) $ matches [2 ] > self ::MAX_INTERVAL ) {
37+ abort (422 , 'Raw filter interval exceeds the maximum allowed value. ' );
38+ }
2939
30- // Remove the actual row( and ) from the string
31- $ unwrappedRawValue = $ this ->unwrapRawValue ($ value );
40+ $ expression = 'NOW() ' ;
41+ if (isset ($ matches [1 ])) {
42+ $ expression .= sprintf (
43+ ' %s INTERVAL %d %s ' ,
44+ $ matches [1 ],
45+ (int ) $ matches [2 ],
46+ strtoupper ($ matches [3 ])
47+ );
48+ }
3249
33- // Wrap it in a DB expression and return it
34- return DB ::raw ($ unwrappedRawValue );
50+ return DB ::raw ($ expression );
3551 }
3652
3753 /**
@@ -43,8 +59,7 @@ public function getRawValue(): Expression
4359 */
4460 public function containsRawValue (string $ value ): bool
4561 {
46- return Str::contains ($ value , 'raw( ' )
47- && Str::endsWith ($ value , ') ' );
62+ return preg_match ('/^\s*raw\s*\(/i ' , $ value ) === 1 ;
4863 }
4964
5065 /**
@@ -54,18 +69,19 @@ public function containsRawValue(string $value): bool
5469 */
5570 protected function detectRawValue (): void
5671 {
57- $ value = $ this ->getValue ();
58-
59- // Sometimes, the value is an array, which likely means
60- // this filter is set to the use the "between" operator
61- $ value = is_string ($ value ) ? $ value : '' ;
72+ $ values = is_array ($ this ->getValue ()) ? $ this ->getValue () : [$ this ->getValue ()];
73+ $ this ->usesRawValue = false ;
6274
63- // Detect if this particular filter includes a raw() value
64- $ this ->usesRawValue = $ this ->containsRawValue ($ value );
75+ if ($ this ->operator === 'between ' && (!is_array ($ this ->getValue ()) || count ($ values ) !== 2 )) {
76+ abort (422 , 'The between operator requires exactly two values. ' );
77+ }
6578
66- // If so, validate it is being used with a compatible operator
67- if ($ this ->usesRawValue ) {
68- $ this ->validateOperator ();
79+ foreach ($ values as $ value ) {
80+ if (is_string ($ value ) && $ this ->containsRawValue ($ value )) {
81+ $ this ->usesRawValue = true ;
82+ $ this ->validateOperator ();
83+ $ this ->validateRawValue ($ value );
84+ }
6985 }
7086 }
7187
@@ -78,9 +94,7 @@ protected function detectRawValue(): void
7894 */
7995 protected function unwrapRawValue (string $ value ): string
8096 {
81- $ stripped = Str::after ($ value , 'raw( ' );
82-
83- return Str::beforeLast ($ stripped , ') ' );
97+ return substr ($ value , 4 , -1 );
8498 }
8599
86100 /**
@@ -103,6 +117,43 @@ protected function filteringWithRawValue(): bool
103117 return $ this ->usesRawValue === true ;
104118 }
105119
120+ protected function valueWithRawExpressions (mixed $ value ): mixed
121+ {
122+ if (is_array ($ value )) {
123+ return array_map (fn ($ item ) => $ this ->valueWithRawExpressions ($ item ), $ value );
124+ }
125+
126+ if (is_string ($ value ) && $ this ->containsRawValue ($ value )) {
127+ return $ this ->getRawValueFor ($ value );
128+ }
129+
130+ return $ value ;
131+ }
132+
133+ private function getRawValueFor (string $ value ): Expression
134+ {
135+ $ originalValue = $ this ->value ;
136+ $ this ->value = $ value ;
137+
138+ try {
139+ return $ this ->getRawValue ();
140+ } finally {
141+ $ this ->value = $ originalValue ;
142+ }
143+ }
144+
145+ private function validateRawValue (string $ value ): void
146+ {
147+ $ originalValue = $ this ->value ;
148+ $ this ->value = $ value ;
149+
150+ try {
151+ $ this ->getRawValue ();
152+ } finally {
153+ $ this ->value = $ originalValue ;
154+ }
155+ }
156+
106157 /**
107158 * Validate the operator for this raw() filter
108159 *
0 commit comments