1818use Icinga \Web \Session ;
1919use ipl \Html \Attributes ;
2020use ipl \Html \DeferredText ;
21+ use ipl \Html \FormDecoration \DescriptionDecorator ;
2122use ipl \Html \FormElement \FieldsetElement ;
2223use ipl \Html \HtmlDocument ;
2324use ipl \Html \HtmlElement ;
3031use ipl \Validator \GreaterThanValidator ;
3132use ipl \Web \Common \CsrfCounterMeasure ;
3233use ipl \Web \Compat \CompatForm ;
34+ use ipl \Web \FormDecorator \IcingaFormDecorator ;
3335use ipl \Web \FormElement \TermInput ;
3436use ipl \Web \Url ;
3537use LogicException ;
@@ -190,6 +192,8 @@ public function __construct(int $scheduleId, Connection $db)
190192 {
191193 $ this ->db = $ db ;
192194 $ this ->scheduleId = $ scheduleId ;
195+
196+ $ this ->applyDefaultElementDecorators ();
193197 }
194198
195199 /**
@@ -599,7 +603,9 @@ protected function assembleModeSelection(): string
599603 '24-7 ' => $ this ->translate ('24/7 ' )
600604 ];
601605
602- $ modeList = new HtmlElement ('ul ' );
606+ $ modeList = new HtmlElement ('ul ' , Attributes::create ([
607+ 'class ' => ['rotation-mode ' , $ this ->disableModeSelection ? 'disabled ' : '' ]
608+ ]));
603609 foreach ($ modes as $ mode => $ label ) {
604610 $ radio = $ this ->createElement ('input ' , 'mode ' , [
605611 'type ' => 'radio ' ,
@@ -681,8 +687,14 @@ protected function assembleModeSelection(): string
681687
682688 $ this ->addHtml (new HtmlElement (
683689 'div ' ,
684- Attributes::create (['class ' => ['rotation-mode ' , $ this ->disableModeSelection ? 'disabled ' : '' ]]),
685- new HtmlElement ('h2 ' , null , Text::create ($ this ->translate ('Mode ' ))),
690+ Attributes::create ([
691+ 'class ' => ['control-group ' ]
692+ ]),
693+ new HtmlElement (
694+ 'div ' ,
695+ Attributes::create (['class ' => 'control-label-group ' ]),
696+ Text::create ($ this ->translate ('Rotation Mode ' ))
697+ ),
686698 $ modeList
687699 ));
688700
@@ -701,12 +713,15 @@ protected function assembleTwentyFourSevenOptions(FieldsetElement $options): Dat
701713 $ options ->addElement ('number ' , 'interval ' , [
702714 'required ' => true ,
703715 'label ' => $ this ->translate ('Handoff every ' ),
716+ 'description ' => $ this ->translate ('Have multiple rotation members take turns after this interval. ' ),
704717 'step ' => 1 ,
705718 'min ' => 1 ,
706719 'value ' => 1 ,
707720 'validators ' => [new GreaterThanValidator ()]
708721 ]);
709722 $ interval = $ options ->getElement ('interval ' );
723+ $ interval ->getDecorators ()
724+ ->replaceDecorator ('Description ' , DescriptionDecorator::class, ['class ' => 'description ' ]);
710725
711726 $ frequency = $ options ->createElement ('select ' , 'frequency ' , [
712727 'required ' => true ,
@@ -793,11 +808,15 @@ protected function assemblePartialDayOptions(FieldsetElement $options): DateTime
793808 $ options ->addElement ('number ' , 'interval ' , [
794809 'required ' => true ,
795810 'label ' => $ this ->translate ('Handoff every ' ),
811+ 'description ' => $ this ->translate ('Have multiple rotation members take turns after this interval. ' ),
796812 'step ' => 1 ,
797813 'min ' => 1 ,
798814 'value ' => 1 ,
799815 'validators ' => [new GreaterThanValidator ()]
800816 ]);
817+ $ interval = $ options ->getElement ('interval ' );
818+ $ interval ->getDecorators ()
819+ ->replaceDecorator ('Description ' , DescriptionDecorator::class, ['class ' => 'description ' ]);
801820
802821 $ selectedFromTime = $ from ->getValue ();
803822 foreach ($ timeOptions as $ key => $ value ) {
@@ -827,7 +846,6 @@ protected function assemblePartialDayOptions(FieldsetElement $options): DateTime
827846 )
828847 );
829848
830- $ interval = $ options ->getElement ('interval ' );
831849 $ interval ->prependWrapper (
832850 (new HtmlDocument ())->addHtml (
833851 $ interval ,
@@ -909,8 +927,12 @@ protected function assembleMultiDayOptions(FieldsetElement $options): DateTime
909927 'step ' => 1 ,
910928 'min ' => 1 ,
911929 'value ' => 1 ,
912- 'label ' => $ this ->translate ('Handoff every ' )
930+ 'label ' => $ this ->translate ('Handoff every ' ),
931+ 'description ' => $ this ->translate ('Have multiple rotation members take turns after this interval. ' )
913932 ]);
933+ $ interval = $ options ->getElement ('interval ' );
934+ $ interval ->getDecorators ()
935+ ->replaceDecorator ('Description ' , DescriptionDecorator::class, ['class ' => 'description ' ]);
914936
915937 $ timeOptions = $ this ->getTimeOptions ();
916938 $ fromAt = $ options ->createElement ('select ' , 'from_at ' , [
@@ -985,7 +1007,6 @@ protected function assembleMultiDayOptions(FieldsetElement $options): DateTime
9851007 )
9861008 );
9871009
988- $ interval = $ options ->getElement ('interval ' );
9891010 $ interval ->prependWrapper (
9901011 (new HtmlDocument ())->addHtml (
9911012 $ interval ,
@@ -1026,17 +1047,9 @@ protected function assemble()
10261047
10271048 $ this ->addElement ('hidden ' , 'priority ' , ['ignore ' => true ]);
10281049
1029- $ mode = $ this ->assembleModeSelection ();
1030-
1031- $ autoSubmittedBy = $ this ->getRequest ()->getHeader ('X-Icinga-Autosubmittedby ' )[0 ] ?? '' ;
1032- if ($ autoSubmittedBy === 'mode ' ) {
1033- $ this ->clearPopulatedValue ('options ' );
1034- $ this ->clearPopulatedValue ('first_handoff ' );
1035- }
1036-
10371050 $ this ->addElement ('text ' , 'name ' , [
10381051 'required ' => true ,
1039- 'label ' => $ this ->translate ('Title ' ),
1052+ 'label ' => $ this ->translate ('Rotation Name ' ),
10401053 'validators ' => [
10411054 new CallbackValidator (function ($ value , $ validator ) {
10421055 $ rotations = Rotation::on ($ this ->db )
@@ -1048,7 +1061,7 @@ protected function assemble()
10481061 }
10491062
10501063 if ($ rotations ->first () !== null ) {
1051- $ validator ->addMessage ($ this ->translate ('A rotation with this title already exists ' ));
1064+ $ validator ->addMessage ($ this ->translate ('A rotation with this name already exists ' ));
10521065
10531066 return false ;
10541067 }
@@ -1058,8 +1071,76 @@ protected function assemble()
10581071 ]
10591072 ]);
10601073
1061- $ options = new FieldsetElement ('options ' );
1062- $ this ->addElement ($ options );
1074+ $ termValidator = function (array $ terms ) {
1075+ $ contactTerms = [];
1076+ $ groupTerms = [];
1077+ foreach ($ terms as $ term ) {
1078+ /** @var TermInput\Term $term */
1079+ if (strpos ($ term ->getSearchValue (), ': ' ) === false ) {
1080+ // TODO: Auto-correct this to a valid type:id pair, if possible
1081+ $ term ->setMessage ($ this ->translate ('Is not a contact nor a group of contacts ' ));
1082+ continue ;
1083+ }
1084+
1085+ list ($ type , $ id ) = explode (': ' , $ term ->getSearchValue (), 2 );
1086+ if ($ type === 'contact ' ) {
1087+ $ contactTerms [$ id ] = $ term ;
1088+ } elseif ($ type === 'group ' ) {
1089+ $ groupTerms [$ id ] = $ term ;
1090+ }
1091+ }
1092+
1093+ if (! empty ($ contactTerms )) {
1094+ $ contacts = (Contact::on (Database::get ()))
1095+ ->filter (Filter::equal ('id ' , array_keys ($ contactTerms )));
1096+ foreach ($ contacts as $ contact ) {
1097+ $ contactTerms [$ contact ->id ]
1098+ ->setLabel ($ contact ->full_name )
1099+ ->setClass ('contact ' );
1100+ }
1101+ }
1102+
1103+ if (! empty ($ groupTerms )) {
1104+ $ groups = (Contactgroup::on (Database::get ()))
1105+ ->filter (Filter::equal ('id ' , array_keys ($ groupTerms )));
1106+ foreach ($ groups as $ group ) {
1107+ $ groupTerms [$ group ->id ]
1108+ ->setLabel ($ group ->name )
1109+ ->setClass ('group ' );
1110+ }
1111+ }
1112+ };
1113+
1114+ $ members = (new TermInput ('members ' ))
1115+ ->setIgnored ()
1116+ ->setRequired ()
1117+ ->setOrdered ()
1118+ ->setReadOnly ()
1119+ ->setVerticalTermDirection ()
1120+ ->setLabel ($ this ->translate ('Rotation Members ' ))
1121+ ->setSuggestionUrl ($ this ->suggestionUrl ->with (['showCompact ' => true , '_disableLayout ' => 1 ]))
1122+ ->on (TermInput::ON_ENRICH , $ termValidator )
1123+ ->on (TermInput::ON_ADD , $ termValidator )
1124+ ->on (TermInput::ON_SAVE , $ termValidator )
1125+ ->on (TermInput::ON_PASTE , $ termValidator );
1126+ $ this ->addElement ($ members );
1127+
1128+ // TODO: TermInput is not compatible with the new decorators yet: https://github.com/Icinga/ipl-web/pull/317
1129+ $ legacyDecorator = new IcingaFormDecorator ();
1130+ $ members ->setDefaultElementDecorator ($ legacyDecorator );
1131+ $ legacyDecorator ->decorate ($ members );
1132+
1133+ $ mode = $ this ->assembleModeSelection ();
1134+
1135+ $ autoSubmittedBy = $ this ->getRequest ()->getHeader ('X-Icinga-Autosubmittedby ' )[0 ] ?? '' ;
1136+ if ($ autoSubmittedBy === 'mode ' ) {
1137+ $ this ->clearPopulatedValue ('options ' );
1138+ $ this ->clearPopulatedValue ('first_handoff ' );
1139+ }
1140+
1141+ $ this ->addElement ('fieldset ' , 'options ' );
1142+ /** @var FieldsetElement $options */
1143+ $ options = $ this ->getElement ('options ' );
10631144
10641145 if ($ mode === '24-7 ' ) {
10651146 $ firstHandoff = $ this ->assembleTwentyFourSevenOptions ($ options );
@@ -1094,7 +1175,7 @@ protected function assemble()
10941175 'aria-describedby ' => 'first-handoff-description ' ,
10951176 'min ' => $ earliestHandoff !== null ? $ earliestHandoff ->format ('Y-m-d ' ) : null ,
10961177 'max ' => $ latestHandoff ->format ('Y-m-d ' ),
1097- 'label ' => $ this ->translate ('First Handoff ' ),
1178+ 'label ' => $ this ->translate ('Rotation Start ' ),
10981179 'value ' => $ firstHandoffDefault ,
10991180 'validators ' => [
11001181 new CallbackValidator (
@@ -1106,14 +1187,14 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando
11061187 );
11071188 if ($ earliestHandoff !== null && $ chosenHandoff < $ earliestHandoff ) {
11081189 $ validator ->addMessage (sprintf (
1109- $ this ->translate ('The first handoff can only happen after %s ' ),
1190+ $ this ->translate ('The rotation can only start after %s ' ),
11101191 $ earliestHandoff ->format ('Y-m-d ' ) // TODO: Use intl here
11111192 ));
11121193
11131194 return false ;
11141195 } elseif ($ chosenHandoff > $ latestHandoff ) {
11151196 $ validator ->addMessage (sprintf (
1116- $ this ->translate ('The first handoff can only happen before %s ' ),
1197+ $ this ->translate ('The rotation can only start before %s ' ),
11171198 $ latestHandoff ->format ('Y-m-d ' ) // TODO: Use intl here
11181199 ));
11191200
@@ -1138,10 +1219,10 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando
11381219
11391220 $ actualFirstHandoff = $ ruleGenerator ->current ()[0 ]->getStartDate ();
11401221 if ($ actualFirstHandoff < new DateTime ()) {
1141- return $ this ->translate ('The first handoff will happen immediately ' );
1222+ return $ this ->translate ('The rotation will start immediately ' );
11421223 } else {
11431224 return sprintf (
1144- $ this ->translate ('The first handoff will happen on %s ' ),
1225+ $ this ->translate ('The rotation will start on %s ' ),
11451226 (new \IntlDateFormatter (
11461227 \Locale::getDefault (),
11471228 \IntlDateFormatter::MEDIUM ,
@@ -1153,61 +1234,6 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando
11531234 ));
11541235 }
11551236
1156- $ termValidator = function (array $ terms ) {
1157- $ contactTerms = [];
1158- $ groupTerms = [];
1159- foreach ($ terms as $ term ) {
1160- /** @var TermInput\Term $term */
1161- if (strpos ($ term ->getSearchValue (), ': ' ) === false ) {
1162- // TODO: Auto-correct this to a valid type:id pair, if possible
1163- $ term ->setMessage ($ this ->translate ('Is not a contact nor a group of contacts ' ));
1164- continue ;
1165- }
1166-
1167- list ($ type , $ id ) = explode (': ' , $ term ->getSearchValue (), 2 );
1168- if ($ type === 'contact ' ) {
1169- $ contactTerms [$ id ] = $ term ;
1170- } elseif ($ type === 'group ' ) {
1171- $ groupTerms [$ id ] = $ term ;
1172- }
1173- }
1174-
1175- if (! empty ($ contactTerms )) {
1176- $ contacts = (Contact::on (Database::get ()))
1177- ->filter (Filter::equal ('id ' , array_keys ($ contactTerms )));
1178- foreach ($ contacts as $ contact ) {
1179- $ contactTerms [$ contact ->id ]
1180- ->setLabel ($ contact ->full_name )
1181- ->setClass ('contact ' );
1182- }
1183- }
1184-
1185- if (! empty ($ groupTerms )) {
1186- $ groups = (Contactgroup::on (Database::get ()))
1187- ->filter (Filter::equal ('id ' , array_keys ($ groupTerms )));
1188- foreach ($ groups as $ group ) {
1189- $ groupTerms [$ group ->id ]
1190- ->setLabel ($ group ->name )
1191- ->setClass ('group ' );
1192- }
1193- }
1194- };
1195-
1196- $ this ->addElement (
1197- (new TermInput ('members ' ))
1198- ->setIgnored ()
1199- ->setRequired ()
1200- ->setOrdered ()
1201- ->setReadOnly ()
1202- ->setVerticalTermDirection ()
1203- ->setLabel ($ this ->translate ('Members ' ))
1204- ->setSuggestionUrl ($ this ->suggestionUrl ->with (['showCompact ' => true , '_disableLayout ' => 1 ]))
1205- ->on (TermInput::ON_ENRICH , $ termValidator )
1206- ->on (TermInput::ON_ADD , $ termValidator )
1207- ->on (TermInput::ON_SAVE , $ termValidator )
1208- ->on (TermInput::ON_PASTE , $ termValidator )
1209- );
1210-
12111237 $ this ->addElement ('submit ' , 'submit ' , [
12121238 'label ' => $ this ->getSubmitLabel ()
12131239 ]);
@@ -1235,7 +1261,7 @@ function ($value, $validator) use ($earliestHandoff, $firstHandoff, $latestHando
12351261 $ this ->getElement ('submit ' )->prependWrapper ((new HtmlDocument ())->setHtmlContent (...$ removeButtons ));
12361262 }
12371263
1238- $ this ->addElement ( $ this -> createCsrfCounterMeasure ( Session::getSession ()->getId () ));
1264+ $ this ->addCsrfCounterMeasure ( Session::getSession ()->getId ());
12391265 }
12401266
12411267 /**
0 commit comments