1515use ipl \Web \Control \SearchBar \Terms ;
1616use ipl \Web \Control \SearchBar \ValidatedColumn ;
1717use ipl \Web \Control \SearchBar \ValidatedOperator ;
18+ use ipl \Web \Control \SearchBar \ValidatedTerm ;
1819use ipl \Web \Control \SearchBar \ValidatedValue ;
1920use ipl \Web \Filter \ParseException ;
21+ use ipl \Web \Filter \Parser ;
2022use ipl \Web \Filter \QueryString ;
2123use ipl \Web \Url ;
2224use ipl \Web \Widget \Icon ;
@@ -53,6 +55,9 @@ class SearchBar extends Form
5355 /** @var string */
5456 protected $ searchParameter ;
5557
58+ /** @var string[] */
59+ protected array $ searchColumns = [];
60+
5661 /** @var Url */
5762 protected $ suggestionUrl ;
5863
@@ -135,6 +140,30 @@ public function getSearchParameter()
135140 return $ this ->searchParameter ?: 'q ' ;
136141 }
137142
143+ /**
144+ * Set the search columns to use
145+ *
146+ * @param string[] $columns
147+ *
148+ * @return $this
149+ */
150+ public function setSearchColumns (array $ columns ): static
151+ {
152+ $ this ->searchColumns = $ columns ;
153+
154+ return $ this ;
155+ }
156+
157+ /**
158+ * Get the search columns in use
159+ *
160+ * @return string[]
161+ */
162+ public function getSearchColumns (): array
163+ {
164+ return $ this ->searchColumns ;
165+ }
166+
138167 /**
139168 * Set the suggestion url
140169 *
@@ -405,7 +434,7 @@ protected function assemble()
405434 if (isset ($ this ->changes [1 ][$ columnIndex ])) {
406435 $ change = $ this ->changes [1 ][$ columnIndex ];
407436 $ condition ->setColumn ($ change ['search ' ]);
408- } elseif ( empty ( $ this -> changes )) {
437+ } else {
409438 $ column = ValidatedColumn::fromFilterCondition ($ condition );
410439 $ operator = ValidatedOperator::fromFilterCondition ($ condition );
411440 $ value = ValidatedValue::fromFilterCondition ($ condition );
@@ -449,25 +478,80 @@ protected function assemble()
449478 try {
450479 $ filter = $ parser ->parse ();
451480 } catch (ParseException $ e ) {
452- $ charAt = $ e ->getCharPos () - 1 ;
481+ $ charAt = $ e ->getCharPos ();
453482 $ char = $ e ->getChar ();
454483
484+ if ($ char === Parser::EOL ) {
485+ $ value = $ q ;
486+ $ title = t ('Unexpected end of input ' );
487+ $ pattern = sprintf (
488+ ValidatedTerm::DEFAULT_PATTERN ,
489+ ValidatedTerm::escapeForHTMLPattern ($ q )
490+ );
491+ } else {
492+ $ value = substr ($ q , $ charAt );
493+ $ title = sprintf (t ('Unexpected %s at start of input ' ), $ char );
494+ $ pattern = sprintf ('^(?!%s).* ' , ValidatedTerm::escapeForHTMLPattern ($ char ));
495+
496+ if ($ charAt > 0 ) {
497+ try {
498+ $ this ->setFilter (QueryString::parse (substr ($ q , 0 , $ charAt )));
499+ } catch (ParseException ) {
500+ $ value = $ q ;
501+ $ title = sprintf (t ('Unexpected %s at position %d ' ), $ char , $ charAt + 1 );
502+ $ pattern = sprintf (
503+ ValidatedTerm::DEFAULT_PATTERN ,
504+ ValidatedTerm::escapeForHTMLPattern ($ q )
505+ );
506+ }
507+ }
508+ }
509+
455510 $ this ->getElement ($ this ->getSearchParameter ())
456511 ->addAttributes ([
457- 'title ' => sprintf ( t ( ' Unexpected %s at start of input ' ), $ char ) ,
458- 'pattern ' => sprintf ( ' ^(?!%s).* ' , $ char === ' ) ' ? ' \) ' : $ char ) ,
512+ 'title ' => $ title ,
513+ 'pattern ' => $ pattern ,
459514 'data-has-syntax-error ' => true
460515 ])
461516 ->getAttributes ()
462- ->registerAttributeCallback ('value ' , function () use ($ q , $ charAt ) {
463- return substr ( $ q , $ charAt ) ;
517+ ->registerAttributeCallback ('value ' , function () use ($ value ) {
518+ return $ value ;
464519 });
465520
466- $ probablyValidQueryString = substr ($ q , 0 , $ charAt );
467- $ this ->setFilter (QueryString::parse ($ probablyValidQueryString ));
468521 return false ;
469522 }
470523
524+ if (
525+ $ this ->getSearchColumns ()
526+ && $ filter instanceof Filter \Condition
527+ // The parser yields a boolean but the validation may cast this to a string -.-
528+ && ($ filter ->getValue () === '1 ' || $ filter ->getValue () === true )
529+ && $ filter ->metaData ()->has ('invalidColumnMessage ' )
530+ ) {
531+ // A single expression that's invalid and has only a truthy value can be safely
532+ // be transformed to a quick search
533+ $ changes = [];
534+ $ change = 0 ;
535+ $ filter = Filter::any ();
536+ foreach ($ this ->getSearchColumns () as $ column ) {
537+ $ condition = Filter::like ($ column , "* $ q* " );
538+ $ column = ValidatedColumn::fromFilterCondition ($ condition );
539+ $ operator = ValidatedOperator::fromFilterCondition ($ condition );
540+ $ value = ValidatedValue::fromFilterCondition ($ condition );
541+ $ this ->emit (self ::ON_ADD , [$ column , $ operator , $ value ]);
542+
543+ $ condition ->setColumn ($ column ->getSearchValue ());
544+ $ condition ->setValue ($ value ->getSearchValue ());
545+ $ filter ->add ($ condition );
546+
547+ $ changes [$ change ++] = $ column ->toTermData ();
548+ $ changes [$ change ++] = $ operator ->toTermData ();
549+ $ changes [$ change ++] = $ value ->toTermData ();
550+ }
551+
552+ $ invalid = false ;
553+ }
554+
471555 $ this ->getElement ($ this ->getSearchParameter ())
472556 ->getAttributes ()
473557 ->registerAttributeCallback ('value ' , function () {
@@ -476,7 +560,11 @@ protected function assemble()
476560 $ this ->setFilter ($ filter );
477561
478562 if (! empty ($ changes )) {
479- $ this ->changes = ['# ' . $ searchInputId , $ changes ];
563+ if (empty ($ this ->changes )) {
564+ $ this ->changes = ['# ' . $ searchInputId , $ changes ];
565+ } else {
566+ $ this ->changes [1 ] += $ changes ;
567+ }
480568 }
481569
482570 return ! $ invalid ;
0 commit comments