From d4632f4b78a0e112e14f25fe90fc789c22bfcce8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 06:45:42 -0700 Subject: [PATCH 01/17] Restore "Land Revision" action to UI Summary: This was accidentally caught in the crossfire in D18150. This is stable enough to formalize instead of adding with an event hook. Test Plan: Looked at a candidate revision, saw "Land Revision" appear in UI again. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18154 --- .../DifferentialRevisionViewController.php | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index f19ff37360..ab03ee6d81 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -615,6 +615,29 @@ final class DifferentialRevisionViewController extends DifferentialController { $curtain->addAction($relationship_submenu); } + $repository = $revision->getRepository(); + if ($repository && $repository->canPerformAutomation()) { + $revision_id = $revision->getID(); + + $op = new DrydockLandRepositoryOperation(); + $barrier = $op->getBarrierToLanding($viewer, $revision); + + if ($barrier) { + $can_land = false; + } else { + $can_land = true; + } + + $action = id(new PhabricatorActionView()) + ->setName(pht('Land Revision')) + ->setIcon('fa-fighter-jet') + ->setHref("/differential/revision/operation/{$revision_id}/") + ->setWorkflow(true) + ->setDisabled(!$can_land); + + $curtain->addAction($action); + } + return $curtain; } From e4785484176c317da088485b9bdf38f7689f3839 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 27 Jun 2017 13:21:12 +0200 Subject: [PATCH 02/17] Turn off spellcheck, etc, on main search input Summary: Ref T12872, turns off all these "helpful" fields. Test Plan: Type "phab" in main search and do not get a suggestion for "phablet". Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T12872 Differential Revision: https://secure.phabricator.com/D18163 --- src/view/page/menu/PhabricatorMainMenuSearchView.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/view/page/menu/PhabricatorMainMenuSearchView.php b/src/view/page/menu/PhabricatorMainMenuSearchView.php index d21d23d0fd..0b2ca8ab10 100644 --- a/src/view/page/menu/PhabricatorMainMenuSearchView.php +++ b/src/view/page/menu/PhabricatorMainMenuSearchView.php @@ -39,6 +39,9 @@ final class PhabricatorMainMenuSearchView extends AphrontView { 'name' => 'query', 'id' => $search_id, 'autocomplete' => 'off', + 'autocorrect' => 'off', + 'autocapitalize' => 'off', + 'spellcheck' => 'false', )); $target = javelin_tag( From d6450bf7d255fcd201fe66edf9fcb5e2c3b18e60 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 27 Jun 2017 12:32:21 +0200 Subject: [PATCH 03/17] New icons for Projects Summary: Updates the builtin images, leaves the old choose... icons for now. I'd like to automate this based on icon when creating a project. Test Plan: Visit edit picture page, pick a few. Purge cache, see new default image. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18162 --- resources/builtin/project.png | Bin 411 -> 4350 bytes resources/builtin/projects/v3/briefcase.png | Bin 0 -> 4350 bytes resources/builtin/projects/v3/bug.png | Bin 0 -> 8151 bytes resources/builtin/projects/v3/calendar.png | Bin 0 -> 4330 bytes resources/builtin/projects/v3/cloud.png | Bin 0 -> 7539 bytes resources/builtin/projects/v3/creditcard.png | Bin 0 -> 2306 bytes resources/builtin/projects/v3/database.png | Bin 0 -> 11418 bytes resources/builtin/projects/v3/desktop.png | Bin 0 -> 2581 bytes .../builtin/projects/v3/experimental.png | Bin 0 -> 7579 bytes resources/builtin/projects/v3/flag.png | Bin 0 -> 6751 bytes resources/builtin/projects/v3/folder.png | Bin 0 -> 2237 bytes resources/builtin/projects/v3/lock.png | Bin 0 -> 7824 bytes resources/builtin/projects/v3/mail.png | Bin 0 -> 5777 bytes resources/builtin/projects/v3/mobile.png | Bin 0 -> 4209 bytes .../builtin/projects/v3/organization.png | Bin 0 -> 4252 bytes resources/builtin/projects/v3/people.png | Bin 0 -> 13654 bytes resources/builtin/projects/v3/servers.png | Bin 0 -> 6447 bytes resources/builtin/projects/v3/tag.png | Bin 0 -> 4161 bytes resources/builtin/projects/v3/trash.png | Bin 0 -> 9154 bytes resources/builtin/projects/v3/truck.png | Bin 0 -> 6294 bytes resources/builtin/projects/v3/umbrella.png | Bin 0 -> 9812 bytes ...habricatorProjectEditPictureController.php | 36 ++++++++++++++++-- 22 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 resources/builtin/projects/v3/briefcase.png create mode 100644 resources/builtin/projects/v3/bug.png create mode 100644 resources/builtin/projects/v3/calendar.png create mode 100644 resources/builtin/projects/v3/cloud.png create mode 100644 resources/builtin/projects/v3/creditcard.png create mode 100644 resources/builtin/projects/v3/database.png create mode 100644 resources/builtin/projects/v3/desktop.png create mode 100644 resources/builtin/projects/v3/experimental.png create mode 100644 resources/builtin/projects/v3/flag.png create mode 100644 resources/builtin/projects/v3/folder.png create mode 100644 resources/builtin/projects/v3/lock.png create mode 100644 resources/builtin/projects/v3/mail.png create mode 100644 resources/builtin/projects/v3/mobile.png create mode 100644 resources/builtin/projects/v3/organization.png create mode 100644 resources/builtin/projects/v3/people.png create mode 100644 resources/builtin/projects/v3/servers.png create mode 100644 resources/builtin/projects/v3/tag.png create mode 100644 resources/builtin/projects/v3/trash.png create mode 100644 resources/builtin/projects/v3/truck.png create mode 100644 resources/builtin/projects/v3/umbrella.png diff --git a/resources/builtin/project.png b/resources/builtin/project.png index 1e16790ea8c5764db8bfe86cd201f04dfc1f448d..c6abf5ed42461fd03eb1bbe26ba87735794c0e16 100644 GIT binary patch literal 4350 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 Woh~YmBAZJeTDUlRI5gM+r2hfTA_vR> literal 411 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 Woh~YmBAZJeTDUlRI5gM+r2hfTA_vR> literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/bug.png b/resources/builtin/projects/v3/bug.png new file mode 100644 index 0000000000000000000000000000000000000000..bb2948a93aba530003b47d34aefbbee65880e41e GIT binary patch literal 8151 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 iqiM4~&ZBSb)?NvJW`Afh;efRu!xi9!fL zq_+eFRGJ6`=}I*Oi6DfML|&rKyYrlR>&;ttt^2Q){3mDcv(G;J`}TL%O43DZb0L0d zegFUjq(AZ5}}%zD?MmKLExHkdmM6W z&*w^OPSV0nl&s0nq0>nQu%=&EwjvV|W|)At{=c5|fpQuxwK@*pfz#;GV+HJGVJ7Lu zUccy&YMV||wOmt-1CMzJHheB}V4jJT1QoqFq*e4{O{q*x3)?HkOcS5PukYC6Zmz?-Je^ygMS_!FD-tz zY7o=8>D~dWeymG9-gCXa^_h@Z>gHv2!iYOe8)-yNX+vLZBfyhuej3zsvV?&%&)>Q8 zN<^uPgRr@E5*O|NsupCI2M^RvU-5r{NZ}ZonO4M9k;*uYV>4$ zH*NZzL8pI*Rqb!O6(=Sl8#5ma`@R8>-<9s+?}T6rbT=#YSm_-!kOY-XaKDT$`p2Wr z&O;e8l8LL*WNYxn9J6emrBaIRFb@R#R7GNd9sLGkr5o{ytnhX!hz+vc>sMuWFi zxzu7zC!SxfSf(A3TktPR=B_^-wCJ3bQ^pwLf#7CjlUI}{_U;$NHV1jF*KW}73x7WG z=A+QnK+Iy?LszOEUenn=Z*f3QWJMFd#s40%n4m${>|b+^cAufqGnWHC$$%b~J?Xdl zAnc*y1F<#WVPmF+mjnp{8`=p4VP9Wf-wc(4JmSUP5Eg*qT*-(If)~qCS&l226K4p9enm%A>XHx=wJI6ng(+_TGRxs>06^#% zX>3|Y@}Q^GuEAIBt%Hx_1vPYmI*b?lbj|^8A;M?}k%Xtb4F|*FVJdLlJBW?5?FUh6 z5BgLkL1Bxx2OBj6tst>|S5x1Bg;?3`(NZ{;I*2!5%7Jn-Zs?c@a zyjC?|W;NF(!#_8d5mPVqGM(4rp{it{ypMdJn$0{L(llMI9y8+Z$e?mO;M{ zumT&BeG3=#bVY?ysLM?Xs+A$TIh&G!!ax=o$iqzk zx^Wai`dZ_CZBR?%!<=|oq*yT(BCEM^Q638+!lP?) z9)sM@l9xr;af~$@nYh|OK48|}M>-t{W!ezxtg{w8haO{*7!?Hb-u+G2=fq1R{F*}9 zh8*Z2r6vx5X(OiyT)6ElP%eTcRTKHJdBMP|>JgzKC2R@@1}-ZMp+q}oH38}rsIQRuG>inW?_uW@Rep#||u}{a|Nq0UbwP8d!odj6D$Io4R zxL*I=#<~r~fCBfq0s*mP=M%W0XVd-5Pja;O7B6FaYpll5bf$kDiV@KS)JQv02=K5n zF*d|K86jeHhdS>Wy6w(emx!?%bU7+%%#hTiSRQD(sG@evhv!5OS(r;r^W82(6sMY$ zeDy6a-6GY9(YZaSBoC=LL`q;S;_M)oN2MO{evdy*BgWMXEZmZ^8VhrO%@zOwhQEE> z`C@t@Sb_fb#pJmZ2VBZz8aJt>EFR8>HjK|UXQ_czsN&9KBG*f*#B$o)$qXTiryDj{ zTd!sFp@Vu3z4LiCXuKi&wcPqOK2Df1YGSu1n{aJQ%_6liEJ)bCQkc}{k4u}P zD)oa+ywtJ&zD6H};aLbVZd`m&^Lo4nIMsQJuFdy(rzZxB+&FNzHFm>O#dT)aUm&y+ zRlziQXB`h+?Nz-<%l=LK;*JTE$u=PCI$~Wc7uF)&b{9HIac_i@6 zxy;U+Spbh&XgrjPL&$+~06~wgh=3Pzt*5k$A2szvc=`EtcJ!0>rC}< zM;nIZ-?FQcos2CQrcBp(d3@Z-+ukr-Z78^{GtwifT^q%$gr?F1J5*P=Rq={cB39M3usghK#C zU?uQ};U%em{c=Jj?7_y>N>4Sti9y4_#D=I@c=+s2bth#r8cdW<$?lXL(9{O4j$ZIg zsUv|2uUcyp(yv^7e>vQ{UHfii=y(~Jc&XCMtQtbR(P?l*k6dhEas27Q?jO8>;pmgDi*R>cp$87r1jI2>S{{- zvQqGKilr*a)zH3YVqv2TW3gOjhcnBa9|io28k}j|Un%&Fl=5`4Oh`L;f3aT_lPFg1 zzH1c^wrl*782>4t30LIRTm3uYdxLPt^7Xq{!um0WlP;--laplOze4_x$?7b-CPNSE zP8S~p6AdC)uAEvdUp3GR_Spl||6t>rqC91Igblkd3-*&lHth4prSe5f#--ZKLymvJ zsz1j5Tb23~FaJLgj-mNnr7NnUJ)Q8R3J(dHvelU+6)6aQyS4o2xVbAO z7vekRLU@(8cmRIP^UJBseLptCkDF44>6y-p)~?D0LqKy{?v;(5TJ>p{#xKw~y7hmz zSvkASo_}jIj1XS&ckNKswD>M7?2e}(f?FMo7)a-2Wu0!5>EO*Bg`Fw2YD(*_%Ph?H zV&y`1me)drM;cXW(@|^WaNkolZY$Q2kqwQ5a^LgBI`RBpK>n%NNHu6_AI9wNh!I{@ z4K!X8RIYmkG<{t{Be$R5sdWVXO4hbX51-(u3zA52($hUP7`9rsgN1Yywn_j&udhR` z^ZNLmD#o+E`~4~Y4|4Kj1^+EvE%#8$&7IkrrK*6{@7CrZeQe-x|1xgJX52hd40=+h z`{MyZc7@E$KwcA@{Zii8MuDjWGWW)S!XSp(~~4OZ(JbP=5x11hq$=+G0AF%H`Cut`>r- z2v)x_1f|45sXCgl(J~}K9e{~wu(jAx9uyLxL|lphUE~A}WDor^aC_w2NqMpQRcR2h zn;fCV2^&cksD|Bf4aaVm1aW*S>(n7mvpv@QBqd8uid$idwLXY(YqX8}EJ2KI)9s2E z%zakq`HKa_Od;z!VO3%^*r@*5SX}@YOXRWQqEM~Ax%e0&W~JTp7k`ME#*m<6@`!zF zY!!UvUa+$UV;&vtcnv+fQC(9K_t}rR$Y{=-4L1{m;j&cxf;M)%`xR-!&F&t#&C*4w z9a(v5V%W%g26D)rm{jYoEDb5_2V^0Sc5J?)P$x7N7ltpO`$?oT^T<;`CI aeHlNIb|xw5RrE>rHWp^qrns|^8~+7Qp8QP! literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/cloud.png b/resources/builtin/projects/v3/cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..efb644001f07c851f7c1add1b5e6f003cb1d43c0 GIT binary patch literal 7539 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/creditcard.png b/resources/builtin/projects/v3/creditcard.png new file mode 100644 index 0000000000000000000000000000000000000000..d231c9437dd8e35311204ad85a8d203f1006ff46 GIT binary patch literal 2306 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 Z4# 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 Lt0U=zcg(*4DpclA literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/experimental.png b/resources/builtin/projects/v3/experimental.png new file mode 100644 index 0000000000000000000000000000000000000000..5bb05ac100f76fae3e93d4ebe10c353da910e721 GIT binary patch literal 7579 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 X=ce!Bgb2th!R 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 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/truck.png b/resources/builtin/projects/v3/truck.png new file mode 100644 index 0000000000000000000000000000000000000000..3c903ea58bfd0613a2b9d86ae79992b81f60306a GIT binary patch literal 6294 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 T)Xwa6GryMlLujQMBJ95aCNPh4 literal 0 HcmV?d00001 diff --git a/resources/builtin/projects/v3/umbrella.png b/resources/builtin/projects/v3/umbrella.png new file mode 100644 index 0000000000000000000000000000000000000000..98c7c1236545200fdb673445e3e4c055ab20d99c GIT binary patch literal 9812 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] = array( + 'uri' => $file->getBestURI(), + 'tip' => pht('Builtin Image'), + ); + } + $images[PhabricatorPHIDConstants::PHID_VOID] = array( 'uri' => $default_image->getBestURI(), - 'tip' => pht('No Picture'), + 'tip' => pht('Default Picture'), ); require_celerity_resource('people-profile-css'); @@ -200,7 +230,7 @@ final class PhabricatorProjectEditPictureController $compose_button = javelin_tag( 'button', array( - 'class' => 'grey', + 'class' => 'button-grey', 'id' => $launch_id, 'sigil' => 'icon-composer', ), @@ -227,7 +257,7 @@ final class PhabricatorProjectEditPictureController $form->appendChild( id(new AphrontFormMarkupControl()) - ->setLabel(pht('Quick Create')) + ->setLabel(pht('Custom')) ->setValue($compose_form)); $upload_form = id(new AphrontFormView()) From 03b6bdde193143d87da5ba45b41996c0a6f57221 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 08:45:31 -0700 Subject: [PATCH 04/17] Begin modularizing config options in a more modern way Summary: Ref T12845. Config options are "modular", but the modularity is very old, half-implemented, and doesn't use modern patterns. Half the types are hard-coded, while half the types are semi-modular but in a weird hacky way where you prefix the type with `custom:...`. The actual API is also weird and requires types to return a lot of `array($stuff, $thing, $other_thing, $more_stuff)` sorts of tuples. Instead: - Add a new replacement layer which uses modern modularity patterns and overrides the older stuff if available, so we can migrate things one at a time. - New layer uses a more modern API -- no `return array($thing, $other_thing, ...)`, and more modern building blocks (like AphrontHTTPParameterType). - New layer allows custom types to be deleted, which will ultimately let us deal with T12845. Then, convert the `'int'` type to use the new layer. Test Plan: - Set, edited, tried-to-change-in-an-invalid-way, and deleted an `'int'` option from the web UI. - Same from the CLI. - Edited `config.json` to have an invalid value, verified that the error was detected and config was repaired. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18155 --- src/__phutil_library_map__.php | 6 + .../PhabricatorConfigEditController.php | 69 ++++++++-- ...PhabricatorConfigManagementSetWorkflow.php | 128 +++++++++--------- .../PhabricatorApplicationConfigOptions.php | 25 ++-- .../config/option/PhabricatorConfigOption.php | 6 + .../config/type/PhabricatorConfigType.php | 115 ++++++++++++++++ .../config/type/PhabricatorIntConfigType.php | 36 +++++ .../config/type/PhabricatorTextConfigType.php | 21 +++ 8 files changed, 322 insertions(+), 84 deletions(-) create mode 100644 src/applications/config/type/PhabricatorConfigType.php create mode 100644 src/applications/config/type/PhabricatorIntConfigType.php create mode 100644 src/applications/config/type/PhabricatorTextConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9deeb338f6..6ad24c6af3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2448,6 +2448,7 @@ phutil_register_library_map(array( 'PhabricatorConfigTableSchema' => 'applications/config/schema/PhabricatorConfigTableSchema.php', 'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php', 'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php', + 'PhabricatorConfigType' => 'applications/config/type/PhabricatorConfigType.php', 'PhabricatorConfigValidationException' => 'applications/config/exception/PhabricatorConfigValidationException.php', 'PhabricatorConfigVersionController' => 'applications/config/controller/PhabricatorConfigVersionController.php', 'PhabricatorConpherenceApplication' => 'applications/conpherence/application/PhabricatorConpherenceApplication.php', @@ -2997,6 +2998,7 @@ phutil_register_library_map(array( 'PhabricatorInlineCommentPreviewController' => 'infrastructure/diff/PhabricatorInlineCommentPreviewController.php', 'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php', 'PhabricatorInstructionsEditField' => 'applications/transactions/editfield/PhabricatorInstructionsEditField.php', + 'PhabricatorIntConfigType' => 'applications/config/type/PhabricatorIntConfigType.php', 'PhabricatorInternalSetting' => 'applications/settings/setting/PhabricatorInternalSetting.php', 'PhabricatorInternationalizationManagementExtractWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php', 'PhabricatorInternationalizationManagementWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementWorkflow.php', @@ -4127,6 +4129,7 @@ phutil_register_library_map(array( 'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php', 'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php', 'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php', + 'PhabricatorTextConfigType' => 'applications/config/type/PhabricatorTextConfigType.php', 'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php', 'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php', 'PhabricatorTimeFormatSetting' => 'applications/settings/setting/PhabricatorTimeFormatSetting.php', @@ -7697,6 +7700,7 @@ phutil_register_library_map(array( 'PhabricatorConfigTableSchema' => 'PhabricatorConfigStorageSchema', 'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'PhabricatorConfigType' => 'Phobject', 'PhabricatorConfigValidationException' => 'Exception', 'PhabricatorConfigVersionController' => 'PhabricatorConfigController', 'PhabricatorConpherenceApplication' => 'PhabricatorApplication', @@ -8324,6 +8328,7 @@ phutil_register_library_map(array( 'PhabricatorInlineCommentPreviewController' => 'PhabricatorController', 'PhabricatorInlineSummaryView' => 'AphrontView', 'PhabricatorInstructionsEditField' => 'PhabricatorEditField', + 'PhabricatorIntConfigType' => 'PhabricatorTextConfigType', 'PhabricatorInternalSetting' => 'PhabricatorSetting', 'PhabricatorInternationalizationManagementExtractWorkflow' => 'PhabricatorInternationalizationManagementWorkflow', 'PhabricatorInternationalizationManagementWorkflow' => 'PhabricatorManagementWorkflow', @@ -9659,6 +9664,7 @@ phutil_register_library_map(array( 'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorTestWorker' => 'PhabricatorWorker', 'PhabricatorTextAreaEditField' => 'PhabricatorEditField', + 'PhabricatorTextConfigType' => 'PhabricatorConfigType', 'PhabricatorTextEditField' => 'PhabricatorEditField', 'PhabricatorTime' => 'Phobject', 'PhabricatorTimeFormatSetting' => 'PhabricatorSelectSetting', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 71b4499591..33e796200b 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -274,12 +274,58 @@ final class PhabricatorConfigEditController PhabricatorConfigOption $option, AphrontRequest $request) { + $type = $option->newOptionType(); + if ($type) { + $is_set = $type->isValuePresentInRequest($option, $request); + if ($is_set) { + $value = $type->readValueFromRequest($option, $request); + + $errors = array(); + try { + $canonical_value = $type->newValueFromRequestValue( + $option, + $value); + $type->validateStoredValue($canonical_value); + $xaction = $type->newTransaction($option, $canonical_value); + } catch (PhabricatorConfigValidationException $ex) { + $errors[] = $ex->getMessage(); + $xaction = null; + } + + return array( + $errors ? pht('Invalid') : null, + $errors, + $value, + $xaction, + ); + } else { + $delete_xaction = id(new PhabricatorConfigTransaction()) + ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) + ->setNewValue( + array( + 'deleted' => true, + 'value' => null, + )); + + return array( + null, + array(), + null, + $delete_xaction, + ); + } + } + + // TODO: If we missed on the new `PhabricatorConfigType` map, fall back + // to the old semi-modular, semi-hacky way of doing things. + $xaction = new PhabricatorConfigTransaction(); $xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT); $e_value = null; $errors = array(); + if ($option->isCustomType()) { $info = $option->getCustomObject()->readRequest($option, $request); list($e_value, $errors, $set_value, $value) = $info; @@ -301,14 +347,6 @@ final class PhabricatorConfigEditController $set_value = null; switch ($type) { - case 'int': - if (preg_match('/^-?[0-9]+$/', trim($value))) { - $set_value = (int)$value; - } else { - $e_value = pht('Invalid'); - $errors[] = pht('Value must be an integer.'); - } - break; case 'string': case 'enum': $set_value = (string)$value; @@ -390,6 +428,11 @@ final class PhabricatorConfigEditController PhabricatorConfigEntry $entry, $value) { + $type = $option->newOptionType(); + if ($type) { + return $type->newDisplayValue($option, $value); + } + if ($option->isCustomType()) { return $option->getCustomObject()->getDisplayValue( $option, @@ -398,7 +441,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'int': case 'string': case 'enum': case 'class': @@ -421,6 +463,14 @@ final class PhabricatorConfigEditController $display_value, $e_value) { + $type = $option->newOptionType(); + if ($type) { + return $type->newControls( + $option, + $display_value, + $e_value); + } + if ($option->isCustomType()) { $controls = $option->getCustomObject()->renderControls( $option, @@ -429,7 +479,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'int': case 'string': $control = id(new AphrontFormTextControl()); break; diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index 4511865785..823f1db9b3 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -59,63 +59,47 @@ final class PhabricatorConfigManagementSetWorkflow $option = $options[$key]; - $type = $option->getType(); - switch ($type) { - case 'string': - case 'class': - case 'enum': - $value = (string)$value; - break; - case 'int': - if (!ctype_digit($value)) { - throw new PhutilArgumentUsageException( - pht( - "Config key '%s' is of type '%s'. Specify an integer.", - $key, - $type)); - } - $value = (int)$value; - break; - case 'bool': - if ($value == 'true') { - $value = true; - } else if ($value == 'false') { - $value = false; - } else { - throw new PhutilArgumentUsageException( - pht( - "Config key '%s' is of type '%s'. Specify '%s' or '%s'.", - $key, - $type, - 'true', - 'false')); - } - break; - default: - $value = json_decode($value, true); - if (!is_array($value)) { - switch ($type) { - case 'set': - $command = csprintf( - './bin/config set %R %s', + $type = $option->newOptionType(); + if ($type) { + try { + $value = $type->newValueFromCommandLineValue( + $option, + $value); + } catch (PhabricatorConfigValidationException $ex) { + throw new PhutilArgumentUsageException($ex->getMessage()); + } + } else { + $type = $option->getType(); + switch ($type) { + case 'string': + case 'class': + case 'enum': + $value = (string)$value; + break; + case 'bool': + if ($value == 'true') { + $value = true; + } else if ($value == 'false') { + $value = false; + } else { + throw new PhutilArgumentUsageException( + pht( + "Config key '%s' is of type '%s'. Specify '%s' or '%s'.", $key, - '{"value1": true, "value2": true}'); - - $message = sprintf( - "%s\n\n %s\n", - pht( - 'Config key "%s" is of type "%s". Specify it in JSON. '. - 'For example:', - $key, - $type), - $command); - break; - default: - if (preg_match('/^listgetArg('database'); if ($option->getLocked() && $use_database) { throw new PhutilArgumentUsageException( diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 34d74e88ab..39538a2af5 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -20,13 +20,24 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { return; } + $type = $option->newOptionType(); + if ($type) { + try { + $type->validateStoredValue($option, $value); + } catch (PhabricatorConfigValidationException $ex) { + throw $ex; + } catch (Exception $ex) { + // If custom validators threw exceptions other than validation + // exceptions, convert them to validation exceptions so we repair the + // configuration and raise an error. + throw new PhabricatorConfigValidationException($ex->getMessage()); + } + } + if ($option->isCustomType()) { try { return $option->getCustomObject()->validateOption($option, $value); } catch (Exception $ex) { - // If custom validators threw exceptions, convert them to configuation - // validation exceptions so we repair the configuration and raise - // an error. throw new PhabricatorConfigValidationException($ex->getMessage()); } } @@ -41,14 +52,6 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { $option->getKey())); } break; - case 'int': - if (!is_int($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' is of type int, but value is not an integer.", - $option->getKey())); - } - break; case 'string': if (!is_string($value)) { throw new PhabricatorConfigValidationException( diff --git a/src/applications/config/option/PhabricatorConfigOption.php b/src/applications/config/option/PhabricatorConfigOption.php index d10c450192..11b8de2617 100644 --- a/src/applications/config/option/PhabricatorConfigOption.php +++ b/src/applications/config/option/PhabricatorConfigOption.php @@ -175,6 +175,12 @@ final class PhabricatorConfigOption return $this->type; } + public function newOptionType() { + $type_key = $this->getType(); + $type_map = PhabricatorConfigType::getAllTypes(); + return idx($type_map, $type_key); + } + public function isCustomType() { return !strncmp($this->getType(), 'custom:', 7); } diff --git a/src/applications/config/type/PhabricatorConfigType.php b/src/applications/config/type/PhabricatorConfigType.php new file mode 100644 index 0000000000..c467d6ceec --- /dev/null +++ b/src/applications/config/type/PhabricatorConfigType.php @@ -0,0 +1,115 @@ +getPhobjectClassConstant('TYPEKEY'); + } + + final public static function getAllTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getTypeKey') + ->execute(); + } + + public function isValuePresentInRequest( + PhabricatorConfigOption $option, + AphrontRequest $request) { + $http_type = $this->newHTTPParameterType(); + return $http_type->getExists($request, 'value'); + } + + public function readValueFromRequest( + PhabricatorConfigOption $option, + AphrontRequest $request) { + $http_type = $this->newHTTPParameterType(); + return $http_type->getValue($request, 'value'); + } + + abstract protected function newHTTPParameterType(); + + public function validateValue(PhabricatorConfigOption $option, $value) { + return array(); + } + + public function newTransaction( + PhabricatorConfigOption $option, + $value) { + + $xaction_value = $this->newTransactionValue($option, $value); + + return id(new PhabricatorConfigTransaction()) + ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) + ->setNewValue( + array( + 'deleted' => false, + 'value' => $xaction_value, + )); + } + + protected function newTransactionValue( + PhabricatorConfigOption $option, + $value) { + return $value; + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + return $value; + } + + public function newControls( + PhabricatorConfigOption $option, + $value, + $error) { + + $control = $this->newControl($option) + ->setError($error) + ->setLabel(pht('Database Value')) + ->setName('value'); + + $value = $this->newControlValue($option, $value); + $control->setValue($value); + + return array( + $control, + ); + } + + abstract protected function newControl(PhabricatorConfigOption $option); + + protected function newControlValue( + PhabricatorConfigOption $option, + $value) { + return $value; + } + + protected function newException($message) { + return new PhabricatorConfigValidationException($message); + } + + public function newValueFromRequestValue( + PhabricatorConfigOption $option, + $value) { + return $this->newCanonicalValue($option, $value); + } + + public function newValueFromCommandLineValue( + PhabricatorConfigOption $option, + $value) { + return $this->newCanonicalValue($option, $value); + } + + protected function newCanonicalValue( + PhabricatorConfigOption $option, + $value) { + return $value; + } + + abstract public function validateStoredValue( + PhabricatorConfigOption $option, + $value); + +} diff --git a/src/applications/config/type/PhabricatorIntConfigType.php b/src/applications/config/type/PhabricatorIntConfigType.php new file mode 100644 index 0000000000..807104f1a5 --- /dev/null +++ b/src/applications/config/type/PhabricatorIntConfigType.php @@ -0,0 +1,36 @@ +newException( + pht( + 'Value for option "%s" must be an integer.', + $option->getKey())); + } + + return (int)$value; + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + + if (!is_int($value)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'an integer.', + $option->getKey(), + $this->getTypeKey())); + } + } + +} diff --git a/src/applications/config/type/PhabricatorTextConfigType.php b/src/applications/config/type/PhabricatorTextConfigType.php new file mode 100644 index 0000000000..fee3977487 --- /dev/null +++ b/src/applications/config/type/PhabricatorTextConfigType.php @@ -0,0 +1,21 @@ + Date: Mon, 26 Jun 2017 09:05:26 -0700 Subject: [PATCH 05/17] Convert "enum" and "string" config options to new modular option types Summary: Ref T12845. This moves the "enum" and "string" types to the new code. Test Plan: Set, deleted, and tried to set invalid values for various enum and string config values (header color, mail prefixes, etc) from the CLI and web. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18156 --- src/__phutil_library_map__.php | 4 ++ .../PhabricatorConfigEditController.php | 20 +-------- ...PhabricatorConfigManagementSetWorkflow.php | 2 - .../PhabricatorApplicationConfigOptions.php | 8 ---- .../PhabricatorMetaMTAConfigOptions.php | 6 +-- .../option/PhabricatorUIConfigOptions.php | 12 +++--- .../config/type/PhabricatorEnumConfigType.php | 43 +++++++++++++++++++ .../type/PhabricatorStringConfigType.php | 22 ++++++++++ .../config/type/PhabricatorTextConfigType.php | 6 +++ .../PhabricatorDifferentialConfigOptions.php | 5 ++- 10 files changed, 89 insertions(+), 39 deletions(-) create mode 100644 src/applications/config/type/PhabricatorEnumConfigType.php create mode 100644 src/applications/config/type/PhabricatorStringConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6ad24c6af3..a0cfff88f3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2755,6 +2755,7 @@ phutil_register_library_map(array( 'PhabricatorEmojiRemarkupRule' => 'applications/macro/markup/PhabricatorEmojiRemarkupRule.php', 'PhabricatorEmojiTranslation' => 'infrastructure/internationalization/translation/PhabricatorEmojiTranslation.php', 'PhabricatorEmptyQueryException' => 'infrastructure/query/PhabricatorEmptyQueryException.php', + 'PhabricatorEnumConfigType' => 'applications/config/type/PhabricatorEnumConfigType.php', 'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php', 'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php', 'PhabricatorEpochEditField' => 'applications/transactions/editfield/PhabricatorEpochEditField.php', @@ -4069,6 +4070,7 @@ phutil_register_library_map(array( 'PhabricatorStoragePatch' => 'infrastructure/storage/management/PhabricatorStoragePatch.php', 'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php', 'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php', + 'PhabricatorStringConfigType' => 'applications/config/type/PhabricatorStringConfigType.php', 'PhabricatorStringListEditField' => 'applications/transactions/editfield/PhabricatorStringListEditField.php', 'PhabricatorStringSetting' => 'applications/settings/setting/PhabricatorStringSetting.php', 'PhabricatorSubmitEditField' => 'applications/transactions/editfield/PhabricatorSubmitEditField.php', @@ -8044,6 +8046,7 @@ phutil_register_library_map(array( 'PhabricatorEmojiRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorEmojiTranslation' => 'PhutilTranslation', 'PhabricatorEmptyQueryException' => 'Exception', + 'PhabricatorEnumConfigType' => 'PhabricatorTextConfigType', 'PhabricatorEnv' => 'Phobject', 'PhabricatorEnvTestCase' => 'PhabricatorTestCase', 'PhabricatorEpochEditField' => 'PhabricatorEditField', @@ -9605,6 +9608,7 @@ phutil_register_library_map(array( 'PhabricatorStoragePatch' => 'Phobject', 'PhabricatorStorageSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorStorageSetupCheck' => 'PhabricatorSetupCheck', + 'PhabricatorStringConfigType' => 'PhabricatorTextConfigType', 'PhabricatorStringListEditField' => 'PhabricatorEditField', 'PhabricatorStringSetting' => 'PhabricatorSetting', 'PhabricatorSubmitEditField' => 'PhabricatorEditField', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 33e796200b..efdd64e92a 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -285,7 +285,7 @@ final class PhabricatorConfigEditController $canonical_value = $type->newValueFromRequestValue( $option, $value); - $type->validateStoredValue($canonical_value); + $type->validateStoredValue($option, $canonical_value); $xaction = $type->newTransaction($option, $canonical_value); } catch (PhabricatorConfigValidationException $ex) { $errors[] = $ex->getMessage(); @@ -347,10 +347,6 @@ final class PhabricatorConfigEditController $set_value = null; switch ($type) { - case 'string': - case 'enum': - $set_value = (string)$value; - break; case 'list': case 'list': $set_value = phutil_split_lines( @@ -441,8 +437,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'string': - case 'enum': case 'class': return $value; case 'bool': @@ -479,9 +473,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'string': - $control = id(new AphrontFormTextControl()); - break; case 'bool': $control = id(new AphrontFormSelectControl()) ->setOptions( @@ -491,15 +482,6 @@ final class PhabricatorConfigEditController 'false' => idx($option->getBoolOptions(), 1), )); break; - case 'enum': - $options = array_mergev( - array( - array('' => pht('(Use Default)')), - $option->getEnumOptions(), - )); - $control = id(new AphrontFormSelectControl()) - ->setOptions($options); - break; case 'class': $symbols = id(new PhutilSymbolLoader()) ->setType('class') diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index 823f1db9b3..d0407291af 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -71,9 +71,7 @@ final class PhabricatorConfigManagementSetWorkflow } else { $type = $option->getType(); switch ($type) { - case 'string': case 'class': - case 'enum': $value = (string)$value; break; case 'bool': diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 39538a2af5..1742169fde 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -52,14 +52,6 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { $option->getKey())); } break; - case 'string': - if (!is_string($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' is of type string, but value is not a string.", - $option->getKey())); - } - break; case 'class': $symbols = id(new PhutilSymbolLoader()) ->setType('class') diff --git a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php index 556b1b169a..cc9732dc70 100644 --- a/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php +++ b/src/applications/config/option/PhabricatorMetaMTAConfigOptions.php @@ -297,9 +297,9 @@ EODOC $this->newOption('metamta.user-address-format', 'enum', 'full') ->setEnumOptions( array( - 'short' => 'short', - 'real' => 'real', - 'full' => 'full', + 'short' => pht('Short'), + 'real' => pht('Real'), + 'full' => pht('Full'), )) ->setSummary(pht('Control how Phabricator renders user names in mail.')) ->setDescription($address_description) diff --git a/src/applications/config/option/PhabricatorUIConfigOptions.php b/src/applications/config/option/PhabricatorUIConfigOptions.php index cef3fbd342..7b2823f6ba 100644 --- a/src/applications/config/option/PhabricatorUIConfigOptions.php +++ b/src/applications/config/option/PhabricatorUIConfigOptions.php @@ -21,12 +21,12 @@ final class PhabricatorUIConfigOptions public function getOptions() { $options = array( - 'blindigo' => 'blindigo', - 'red' => 'red', - 'blue' => 'blue', - 'green' => 'green', - 'indigo' => 'indigo', - 'dark' => 'dark', + 'blindigo' => pht('Blindigo'), + 'red' => pht('Red'), + 'blue' => pht('Blue'), + 'green' => pht('Green'), + 'indigo' => pht('Indigo'), + 'dark' => pht('Dark'), ); $example = <<newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a string.', + $option->getKey(), + $this->getTypeKey())); + } + + $map = $option->getEnumOptions(); + if (!isset($map[$value])) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the current value ("%s") is not '. + 'among the set of valid values: %s.', + $option->getKey(), + $this->getTypeKey(), + $value, + implode(', ', array_keys($map)))); + } + } + + protected function newControl(PhabricatorConfigOption $option) { + $map = array( + '' => pht('(Use Default)'), + ) + $option->getEnumOptions(); + + return id(new AphrontFormSelectControl()) + ->setOptions($map); + } + +} diff --git a/src/applications/config/type/PhabricatorStringConfigType.php b/src/applications/config/type/PhabricatorStringConfigType.php new file mode 100644 index 0000000000..da05b75780 --- /dev/null +++ b/src/applications/config/type/PhabricatorStringConfigType.php @@ -0,0 +1,22 @@ +newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a string.', + $option->getKey(), + $this->getTypeKey())); + } + } + +} diff --git a/src/applications/config/type/PhabricatorTextConfigType.php b/src/applications/config/type/PhabricatorTextConfigType.php index fee3977487..8e67d65b7a 100644 --- a/src/applications/config/type/PhabricatorTextConfigType.php +++ b/src/applications/config/type/PhabricatorTextConfigType.php @@ -10,6 +10,12 @@ abstract class PhabricatorTextConfigType return (bool)strlen($value); } + protected function newCanonicalValue( + PhabricatorConfigOption $option, + $value) { + return (string)$value; + } + protected function newHTTPParameterType() { return new AphrontStringHTTPParameterType(); } diff --git a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php index a3db26bef6..7ff886664f 100644 --- a/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php +++ b/src/applications/differential/config/PhabricatorDifferentialConfigOptions.php @@ -260,7 +260,10 @@ EOHELP ->setDescription( pht('Format for inlined or attached patches.')) ->setEnumOptions( - array('unified' => 'unified', 'git' => 'git')), + array( + 'unified' => pht('Unified'), + 'git' => pht('Git'), + )), ); } From 467be5e53fe6f574082ab7c470834103b2647eef Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 09:22:40 -0700 Subject: [PATCH 06/17] Convert the "list" and "list" Config option types Summary: Ref T12845. This updates the "list" and "list" options. Test Plan: Set, deleted, and mangled options of these types from the web UI and CLI. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18157 --- src/__phutil_library_map__.php | 6 ++ .../PhabricatorConfigEditController.php | 22 ----- ...PhabricatorConfigManagementSetWorkflow.php | 25 +---- .../PhabricatorApplicationConfigOptions.php | 54 ---------- .../type/PhabricatorRegexListConfigType.php | 24 +++++ .../type/PhabricatorStringListConfigType.php | 8 ++ .../type/PhabricatorTextListConfigType.php | 98 +++++++++++++++++++ 7 files changed, 141 insertions(+), 96 deletions(-) create mode 100644 src/applications/config/type/PhabricatorRegexListConfigType.php create mode 100644 src/applications/config/type/PhabricatorStringListConfigType.php create mode 100644 src/applications/config/type/PhabricatorTextListConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a0cfff88f3..7753782858 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3746,6 +3746,7 @@ phutil_register_library_map(array( 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php', + 'PhabricatorRegexListConfigType' => 'applications/config/type/PhabricatorRegexListConfigType.php', 'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php', 'PhabricatorReleephApplication' => 'applications/releeph/application/PhabricatorReleephApplication.php', 'PhabricatorReleephApplicationConfigOptions' => 'applications/releeph/config/PhabricatorReleephApplicationConfigOptions.php', @@ -4071,6 +4072,7 @@ phutil_register_library_map(array( 'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php', 'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php', 'PhabricatorStringConfigType' => 'applications/config/type/PhabricatorStringConfigType.php', + 'PhabricatorStringListConfigType' => 'applications/config/type/PhabricatorStringListConfigType.php', 'PhabricatorStringListEditField' => 'applications/transactions/editfield/PhabricatorStringListEditField.php', 'PhabricatorStringSetting' => 'applications/settings/setting/PhabricatorStringSetting.php', 'PhabricatorSubmitEditField' => 'applications/transactions/editfield/PhabricatorSubmitEditField.php', @@ -4133,6 +4135,7 @@ phutil_register_library_map(array( 'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php', 'PhabricatorTextConfigType' => 'applications/config/type/PhabricatorTextConfigType.php', 'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php', + 'PhabricatorTextListConfigType' => 'applications/config/type/PhabricatorTextListConfigType.php', 'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php', 'PhabricatorTimeFormatSetting' => 'applications/settings/setting/PhabricatorTimeFormatSetting.php', 'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php', @@ -9207,6 +9210,7 @@ phutil_register_library_map(array( 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', + 'PhabricatorRegexListConfigType' => 'PhabricatorTextListConfigType', 'PhabricatorRegistrationProfile' => 'Phobject', 'PhabricatorReleephApplication' => 'PhabricatorApplication', 'PhabricatorReleephApplicationConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -9609,6 +9613,7 @@ phutil_register_library_map(array( 'PhabricatorStorageSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorStorageSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorStringConfigType' => 'PhabricatorTextConfigType', + 'PhabricatorStringListConfigType' => 'PhabricatorTextListConfigType', 'PhabricatorStringListEditField' => 'PhabricatorEditField', 'PhabricatorStringSetting' => 'PhabricatorSetting', 'PhabricatorSubmitEditField' => 'PhabricatorEditField', @@ -9670,6 +9675,7 @@ phutil_register_library_map(array( 'PhabricatorTextAreaEditField' => 'PhabricatorEditField', 'PhabricatorTextConfigType' => 'PhabricatorConfigType', 'PhabricatorTextEditField' => 'PhabricatorEditField', + 'PhabricatorTextListConfigType' => 'PhabricatorTextConfigType', 'PhabricatorTime' => 'Phobject', 'PhabricatorTimeFormatSetting' => 'PhabricatorSelectSetting', 'PhabricatorTimeGuard' => 'Phobject', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index efdd64e92a..0d4aea3252 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -347,20 +347,6 @@ final class PhabricatorConfigEditController $set_value = null; switch ($type) { - case 'list': - case 'list': - $set_value = phutil_split_lines( - $request->getStr('value'), - $retain_endings = false); - - foreach ($set_value as $key => $v) { - if (!strlen($v)) { - unset($set_value[$key]); - } - } - $set_value = array_values($set_value); - - break; case 'set': $set_value = array_fill_keys($request->getStrList('value'), true); break; @@ -441,9 +427,6 @@ final class PhabricatorConfigEditController return $value; case 'bool': return $value ? 'true' : 'false'; - case 'list': - case 'list': - return implode("\n", nonempty($value, array())); case 'set': return implode("\n", nonempty(array_keys($value), array())); default: @@ -497,11 +480,6 @@ final class PhabricatorConfigEditController $control = id(new AphrontFormSelectControl()) ->setOptions($names); break; - case 'list': - case 'list': - $control = id(new AphrontFormTextAreaControl()) - ->setCaption(pht('Separate values with newlines.')); - break; case 'set': $control = id(new AphrontFormTextAreaControl()) ->setCaption(pht('Separate values with newlines or commas.')); diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index d0407291af..e1b54e4dcf 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -65,6 +65,7 @@ final class PhabricatorConfigManagementSetWorkflow $value = $type->newValueFromCommandLineValue( $option, $value); + $type->validateStoredValue($option, $value); } catch (PhabricatorConfigValidationException $ex) { throw new PhutilArgumentUsageException($ex->getMessage()); } @@ -109,26 +110,10 @@ final class PhabricatorConfigManagementSetWorkflow $command); break; default: - if (preg_match('/^list': - $valid = true; - if (!is_array($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of regular expressions, but value ". - "is not an array.", - $option->getKey())); - } - if ($value && array_keys($value) != range(0, count($value) - 1)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of regular expressions, but the ". - "value is a map with unnatural keys.", - $option->getKey())); - } - foreach ($value as $v) { - $ok = @preg_match($v, ''); - if ($ok === false) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of regular expressions, but the ". - "value '%s' is not a valid regular expression.", - $option->getKey(), - $v)); - } - } - break; - case 'list': - $valid = true; - if (!is_array($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of strings, but value is not ". - "an array.", - $option->getKey())); - } - if ($value && array_keys($value) != range(0, count($value) - 1)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of strings, but the value is a ". - "map with unnatural keys.", - $option->getKey())); - } - foreach ($value as $v) { - if (!is_string($v)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a list of strings, but it contains one ". - "or more non-strings.", - $option->getKey())); - } - } - break; case 'wild': default: break; diff --git a/src/applications/config/type/PhabricatorRegexListConfigType.php b/src/applications/config/type/PhabricatorRegexListConfigType.php new file mode 100644 index 0000000000..b37b440c7e --- /dev/null +++ b/src/applications/config/type/PhabricatorRegexListConfigType.php @@ -0,0 +1,24 @@ +'; + + protected function validateStoredItem( + PhabricatorConfigOption $option, + $value) { + + $ok = @preg_match($value, ''); + if ($ok === false) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s" and must be set to a list of valid '. + 'regular expressions, but "%s" is not a valid regular expression.', + $option->getKey(), + $this->getTypeKey(), + $value)); + } + } + +} diff --git a/src/applications/config/type/PhabricatorStringListConfigType.php b/src/applications/config/type/PhabricatorStringListConfigType.php new file mode 100644 index 0000000000..70f4c04316 --- /dev/null +++ b/src/applications/config/type/PhabricatorStringListConfigType.php @@ -0,0 +1,8 @@ +'; + +} diff --git a/src/applications/config/type/PhabricatorTextListConfigType.php b/src/applications/config/type/PhabricatorTextListConfigType.php new file mode 100644 index 0000000000..20f5362ea6 --- /dev/null +++ b/src/applications/config/type/PhabricatorTextListConfigType.php @@ -0,0 +1,98 @@ +setCaption(pht('Separate values with newlines.')); + } + + protected function newCanonicalValue( + PhabricatorConfigOption $option, + $value) { + + $value = phutil_split_lines($value, $retain_endings = false); + foreach ($value as $k => $v) { + if (!strlen($v)) { + unset($value[$k]); + } + } + + return array_values($value); + } + + public function newValueFromCommandLineValue( + PhabricatorConfigOption $option, + $value) { + + try { + $value = phutil_json_decode($value); + } catch (Exception $ex) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the value you provided is not a '. + 'valid JSON list. When setting a list option from the command '. + 'line, specify the value in JSON. You may need to quote the '. + 'value for your shell (for example: \'["a", "b", ...]\').', + $option->getKey(), + $this->getTypeKey())); + } + + return $value; + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + return implode("\n", $value); + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + + if (!is_array($value)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a list.', + $option->getKey(), + $this->getTypeKey())); + } + + $expect_key = 0; + foreach ($value as $k => $v) { + if (!is_string($v)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the item at index "%s" of the '. + 'list is not a string.', + $option->getKey(), + $this->getTypeKey(), + $k)); + } + + // Make sure this is a list with keys "0, 1, 2, ...", not a map with + // arbitrary keys. + if ($k != $expect_key) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the value is not a list: it '. + 'is a map with unnatural or sparse keys.', + $option->getKey(), + $this->getTypeKey())); + } + $expect_key++; + + $this->validateStoredItem($option, $v); + } + } + + protected function validateStoredItem( + PhabricatorConfigOption $option, + $value) { + return; + } + +} From 72119e786c7985c56887f0060bee4192f74051c6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 09:40:37 -0700 Subject: [PATCH 07/17] Convert "bool" config values to new modular system Summary: Ref T12845. Moves the "bool" values over. Test Plan: Set, deleted, and mangled bool values from CLI and web UI. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18158 --- src/__phutil_library_map__.php | 2 + .../PhabricatorConfigEditController.php | 25 -------- ...PhabricatorConfigManagementSetWorkflow.php | 15 ----- .../PhabricatorApplicationConfigOptions.php | 9 --- .../config/type/PhabricatorBoolConfigType.php | 62 +++++++++++++++++++ 5 files changed, 64 insertions(+), 49 deletions(-) create mode 100644 src/applications/config/type/PhabricatorBoolConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7753782858..87ddd3da74 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2153,6 +2153,7 @@ phutil_register_library_map(array( 'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php', 'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php', 'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php', + 'PhabricatorBoolConfigType' => 'applications/config/type/PhabricatorBoolConfigType.php', 'PhabricatorBoolEditField' => 'applications/transactions/editfield/PhabricatorBoolEditField.php', 'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php', 'PhabricatorBuiltinDraftEngine' => 'applications/transactions/draft/PhabricatorBuiltinDraftEngine.php', @@ -7356,6 +7357,7 @@ phutil_register_library_map(array( 'PhabricatorBoardLayoutEngine' => 'Phobject', 'PhabricatorBoardRenderingEngine' => 'Phobject', 'PhabricatorBoardResponseEngine' => 'Phobject', + 'PhabricatorBoolConfigType' => 'PhabricatorTextConfigType', 'PhabricatorBoolEditField' => 'PhabricatorEditField', 'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation', 'PhabricatorBuiltinDraftEngine' => 'PhabricatorDraftEngine', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 0d4aea3252..8c3779fdf4 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -350,20 +350,6 @@ final class PhabricatorConfigEditController case 'set': $set_value = array_fill_keys($request->getStrList('value'), true); break; - case 'bool': - switch ($value) { - case 'true': - $set_value = true; - break; - case 'false': - $set_value = false; - break; - default: - $e_value = pht('Invalid'); - $errors[] = pht('Value must be boolean, "true" or "false".'); - break; - } - break; case 'class': if (!class_exists($value)) { $e_value = pht('Invalid'); @@ -425,8 +411,6 @@ final class PhabricatorConfigEditController switch ($type) { case 'class': return $value; - case 'bool': - return $value ? 'true' : 'false'; case 'set': return implode("\n", nonempty(array_keys($value), array())); default: @@ -456,15 +440,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'bool': - $control = id(new AphrontFormSelectControl()) - ->setOptions( - array( - '' => pht('(Use Default)'), - 'true' => idx($option->getBoolOptions(), 0), - 'false' => idx($option->getBoolOptions(), 1), - )); - break; case 'class': $symbols = id(new PhutilSymbolLoader()) ->setType('class') diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index e1b54e4dcf..40ee7b373e 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -75,21 +75,6 @@ final class PhabricatorConfigManagementSetWorkflow case 'class': $value = (string)$value; break; - case 'bool': - if ($value == 'true') { - $value = true; - } else if ($value == 'false') { - $value = false; - } else { - throw new PhutilArgumentUsageException( - pht( - "Config key '%s' is of type '%s'. Specify '%s' or '%s'.", - $key, - $type, - 'true', - 'false')); - } - break; default: $value = json_decode($value, true); if (!is_array($value)) { diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 229ecdfd16..4381ecb7aa 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -43,15 +43,6 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { } switch ($option->getType()) { - case 'bool': - if ($value !== true && - $value !== false) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' is of type bool, but value is not true or false.", - $option->getKey())); - } - break; case 'class': $symbols = id(new PhutilSymbolLoader()) ->setType('class') diff --git a/src/applications/config/type/PhabricatorBoolConfigType.php b/src/applications/config/type/PhabricatorBoolConfigType.php new file mode 100644 index 0000000000..840d8b4015 --- /dev/null +++ b/src/applications/config/type/PhabricatorBoolConfigType.php @@ -0,0 +1,62 @@ +newException( + pht( + 'Value for option "%s" of type "%s" must be either '. + '"true" or "false".', + $option->getKey(), + $this->getTypeKey())); + } + + return ($value === 'true'); + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + + if ($value) { + return 'true'; + } else { + return 'false'; + } + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + + if (!is_bool($value)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a boolean.', + $option->getKey(), + $this->getTypeKey())); + } + } + + protected function newControl(PhabricatorConfigOption $option) { + $bool_map = $option->getBoolOptions(); + + $map = array( + '' => pht('(Use Default)'), + ) + array( + 'true' => idx($bool_map, 0), + 'false' => idx($bool_map, 1), + ); + + return id(new AphrontFormSelectControl()) + ->setOptions($map); + } +} From 0afdabff00ae178aa3dbde2f9b504b223378d10f Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 09:58:55 -0700 Subject: [PATCH 08/17] Convert 'class' config options to new validation Summary: Ref T12845. These options prompt the user to select from among concrete subclasses of some base class. Test Plan: Set, deleted and mangled these values from the web UI and CLI. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18159 --- src/__phutil_library_map__.php | 2 + .../PhabricatorConfigEditController.php | 31 -------- ...PhabricatorConfigManagementSetWorkflow.php | 3 - .../PhabricatorApplicationConfigOptions.php | 15 ---- .../type/PhabricatorClassConfigType.php | 76 +++++++++++++++++++ 5 files changed, 78 insertions(+), 49 deletions(-) create mode 100644 src/applications/config/type/PhabricatorClassConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 87ddd3da74..61ac0a3b22 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2329,6 +2329,7 @@ phutil_register_library_map(array( 'PhabricatorChatLogEvent' => 'applications/chatlog/storage/PhabricatorChatLogEvent.php', 'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php', 'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php', + 'PhabricatorClassConfigType' => 'applications/config/type/PhabricatorClassConfigType.php', 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', 'PhabricatorClusterDatabasesConfigOptionType' => 'infrastructure/cluster/config/PhabricatorClusterDatabasesConfigOptionType.php', 'PhabricatorClusterException' => 'infrastructure/cluster/exception/PhabricatorClusterException.php', @@ -7575,6 +7576,7 @@ phutil_register_library_map(array( ), 'PhabricatorChatLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine', + 'PhabricatorClassConfigType' => 'PhabricatorTextConfigType', 'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorClusterDatabasesConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorClusterException' => 'Exception', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 8c3779fdf4..d3d5b3160a 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -350,20 +350,6 @@ final class PhabricatorConfigEditController case 'set': $set_value = array_fill_keys($request->getStrList('value'), true); break; - case 'class': - if (!class_exists($value)) { - $e_value = pht('Invalid'); - $errors[] = pht('Class does not exist.'); - } else { - $base = $option->getBaseClass(); - if (!is_subclass_of($value, $base)) { - $e_value = pht('Invalid'); - $errors[] = pht('Class is not of valid type.'); - } else { - $set_value = $value; - } - } - break; default: $json = json_decode($value, true); if ($json === null && strtolower($value) != 'null') { @@ -409,8 +395,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'class': - return $value; case 'set': return implode("\n", nonempty(array_keys($value), array())); default: @@ -440,21 +424,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'class': - $symbols = id(new PhutilSymbolLoader()) - ->setType('class') - ->setAncestorClass($option->getBaseClass()) - ->setConcreteOnly(true) - ->selectSymbolsWithoutLoading(); - $names = ipull($symbols, 'name', 'name'); - asort($names); - $names = array( - '' => pht('(Use Default)'), - ) + $names; - - $control = id(new AphrontFormSelectControl()) - ->setOptions($names); - break; case 'set': $control = id(new AphrontFormTextAreaControl()) ->setCaption(pht('Separate values with newlines or commas.')); diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index 40ee7b373e..45fc9f8129 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -72,9 +72,6 @@ final class PhabricatorConfigManagementSetWorkflow } else { $type = $option->getType(); switch ($type) { - case 'class': - $value = (string)$value; - break; default: $value = json_decode($value, true); if (!is_array($value)) { diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 4381ecb7aa..07a765d94c 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -43,21 +43,6 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { } switch ($option->getType()) { - case 'class': - $symbols = id(new PhutilSymbolLoader()) - ->setType('class') - ->setAncestorClass($option->getBaseClass()) - ->setConcreteOnly(true) - ->selectSymbolsWithoutLoading(); - $names = ipull($symbols, 'name', 'name'); - if (empty($names[$value])) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' value must name a class extending '%s'.", - $option->getKey(), - $option->getBaseClass())); - } - break; case 'set': $valid = true; if (!is_array($value)) { diff --git a/src/applications/config/type/PhabricatorClassConfigType.php b/src/applications/config/type/PhabricatorClassConfigType.php new file mode 100644 index 0000000000..0ec5e0eb5e --- /dev/null +++ b/src/applications/config/type/PhabricatorClassConfigType.php @@ -0,0 +1,76 @@ +newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a string.', + $option->getKey(), + $this->getTypeKey())); + } + + $base = $option->getBaseClass(); + $map = $this->getClassOptions($option); + + try { + $ok = class_exists($value); + } catch (Exception $ex) { + $ok = false; + } + + if (!$ok) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not the '. + 'name of a known class. Valid selections are: %s.', + $option->getKey(), + $this->getTypeKey(), + implode(', ', array_keys($map)))); + } + + if (!isset($map[$value])) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the current value ("%s") is not '. + 'a known, concrete subclass of base class "%s". Valid selections '. + 'are: %s.', + $option->getKey(), + $this->getTypeKey(), + $value, + $base, + implode(', ', array_keys($map)))); + } + } + + protected function newControl(PhabricatorConfigOption $option) { + $map = array( + '' => pht('(Use Default)'), + ) + $this->getClassOptions($option); + + return id(new AphrontFormSelectControl()) + ->setOptions($map); + } + + private function getClassOptions(PhabricatorConfigOption $option) { + $symbols = id(new PhutilSymbolLoader()) + ->setType('class') + ->setAncestorClass($option->getBaseClass()) + ->setConcreteOnly(true) + ->selectSymbolsWithoutLoading(); + + $map = ipull($symbols, 'name', 'name'); + asort($map); + + return $map; + } + +} From ec2af086254bc369b9af797facfca1ac728c7ab4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 26 Jun 2017 10:26:04 -0700 Subject: [PATCH 09/17] Move 'set' config option type to new structure Summary: Ref T12845. This move 'set' options (a set of values). Test Plan: Set, deleted and mangled 'set' options from CLI and web UI. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18160 --- src/__phutil_library_map__.php | 2 + .../PhabricatorConfigEditController.php | 9 -- ...PhabricatorConfigManagementSetWorkflow.php | 15 --- .../PhabricatorApplicationConfigOptions.php | 18 ---- .../config/type/PhabricatorSetConfigType.php | 92 +++++++++++++++++++ 5 files changed, 94 insertions(+), 42 deletions(-) create mode 100644 src/applications/config/type/PhabricatorSetConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 61ac0a3b22..ac19fcfbe5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3943,6 +3943,7 @@ phutil_register_library_map(array( 'PhabricatorSelectSetting' => 'applications/settings/setting/PhabricatorSelectSetting.php', 'PhabricatorSendGridConfigOptions' => 'applications/config/option/PhabricatorSendGridConfigOptions.php', 'PhabricatorSessionsSettingsPanel' => 'applications/settings/panel/PhabricatorSessionsSettingsPanel.php', + 'PhabricatorSetConfigType' => 'applications/config/type/PhabricatorSetConfigType.php', 'PhabricatorSetting' => 'applications/settings/setting/PhabricatorSetting.php', 'PhabricatorSettingsAccountPanelGroup' => 'applications/settings/panelgroup/PhabricatorSettingsAccountPanelGroup.php', 'PhabricatorSettingsAddEmailAction' => 'applications/settings/action/PhabricatorSettingsAddEmailAction.php', @@ -9469,6 +9470,7 @@ phutil_register_library_map(array( 'PhabricatorSelectSetting' => 'PhabricatorSetting', 'PhabricatorSendGridConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSessionsSettingsPanel' => 'PhabricatorSettingsPanel', + 'PhabricatorSetConfigType' => 'PhabricatorTextConfigType', 'PhabricatorSetting' => 'Phobject', 'PhabricatorSettingsAccountPanelGroup' => 'PhabricatorSettingsPanelGroup', 'PhabricatorSettingsAddEmailAction' => 'PhabricatorSystemAction', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index d3d5b3160a..7b0227540a 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -347,9 +347,6 @@ final class PhabricatorConfigEditController $set_value = null; switch ($type) { - case 'set': - $set_value = array_fill_keys($request->getStrList('value'), true); - break; default: $json = json_decode($value, true); if ($json === null && strtolower($value) != 'null') { @@ -395,8 +392,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'set': - return implode("\n", nonempty(array_keys($value), array())); default: return PhabricatorConfigJSON::prettyPrintJSON($value); } @@ -424,10 +419,6 @@ final class PhabricatorConfigEditController } else { $type = $option->getType(); switch ($type) { - case 'set': - $control = id(new AphrontFormTextAreaControl()) - ->setCaption(pht('Separate values with newlines or commas.')); - break; default: $control = id(new AphrontFormTextAreaControl()) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index 45fc9f8129..b29ef8c8ba 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -76,21 +76,6 @@ final class PhabricatorConfigManagementSetWorkflow $value = json_decode($value, true); if (!is_array($value)) { switch ($type) { - case 'set': - $command = csprintf( - './bin/config set %R %s', - $key, - '{"value1": true, "value2": true}'); - - $message = sprintf( - "%s\n\n %s\n", - pht( - 'Config key "%s" is of type "%s". Specify it in JSON. '. - 'For example:', - $key, - $type), - $command); - break; default: $message = pht( 'Config key "%s" is of type "%s". Specify it in JSON.', diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 07a765d94c..89c9409aba 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -43,24 +43,6 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { } switch ($option->getType()) { - case 'set': - $valid = true; - if (!is_array($value)) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a set, but value is not an array.", - $option->getKey())); - } - foreach ($value as $v) { - if ($v !== true) { - throw new PhabricatorConfigValidationException( - pht( - "Option '%s' must be a set, but array contains values other ". - "than 'true'.", - $option->getKey())); - } - } - break; case 'wild': default: break; diff --git a/src/applications/config/type/PhabricatorSetConfigType.php b/src/applications/config/type/PhabricatorSetConfigType.php new file mode 100644 index 0000000000..805ae50468 --- /dev/null +++ b/src/applications/config/type/PhabricatorSetConfigType.php @@ -0,0 +1,92 @@ +setCaption(pht('Separate values with newlines or commas.')); + } + + protected function newCanonicalValue( + PhabricatorConfigOption $option, + $value) { + + $value = preg_split('/[\n,]+/', $value); + foreach ($value as $k => $v) { + if (!strlen($v)) { + unset($value[$k]); + } + $value[$k] = trim($v); + } + + return array_fill_keys($value, true); + } + + public function newValueFromCommandLineValue( + PhabricatorConfigOption $option, + $value) { + + try { + $value = phutil_json_decode($value); + } catch (Exception $ex) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the value you provided is not a '. + 'valid JSON list: when providing a set from the command line, '. + 'specify it as a list of values in JSON. You may need to quote the '. + 'value for your shell (for example: \'["a", "b", ...]\').', + $option->getKey(), + $this->getTypeKey())); + } + + if ($value) { + if (array_keys($value) !== range(0, count($value) - 1)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", and should be specified on the '. + 'command line as a JSON list of values. You may need to quote '. + 'the value for your shell (for example: \'["a", "b", ...]\').', + $option->getKey(), + $this->getTypeKey())); + } + } + + return array_fill_keys($value, true); + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + return implode("\n", array_keys($value)); + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + + if (!is_array($value)) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the configured value is not '. + 'a list.', + $option->getKey(), + $this->getTypeKey())); + } + + foreach ($value as $k => $v) { + if ($v !== true) { + throw $this->newException( + pht( + 'Option "%s" is of type "%s", but the value at index "%s" of the '. + 'list is not "true".', + $option->getKey(), + $this->getTypeKey(), + $k)); + } + } + } + +} From a14b82d4f466ec1efee5e97d771ac56622ea7112 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 27 Jun 2017 06:30:19 -0700 Subject: [PATCH 10/17] Move "wild" config types to new code Summary: Ref T12845. This is the last of the hard-coded types. These are mostly used for values which users don't directly edit, so it's largely OK that they aren't carefully validated. In some cases, it would be good to introduce a separate validator eventually. Test Plan: Edited, deleted and mangled these values via the web UI and CLI. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18164 --- src/__phutil_library_map__.php | 4 ++ .../PhabricatorConfigEditController.php | 66 ++++--------------- ...PhabricatorConfigManagementSetWorkflow.php | 1 + .../PhabricatorApplicationConfigOptions.php | 14 ++-- .../config/type/PhabricatorJSONConfigType.php | 38 +++++++++++ .../config/type/PhabricatorWildConfigType.php | 39 +++++++++++ .../config/PhabricatorUserConfigOptions.php | 2 +- 7 files changed, 104 insertions(+), 60 deletions(-) create mode 100644 src/applications/config/type/PhabricatorJSONConfigType.php create mode 100644 src/applications/config/type/PhabricatorWildConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ac19fcfbe5..e10b5fceef 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3010,6 +3010,7 @@ phutil_register_library_map(array( 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorIteratedMD5PasswordHasherTestCase.php', 'PhabricatorIteratorFileUploadSource' => 'applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php', 'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php', + 'PhabricatorJSONConfigType' => 'applications/config/type/PhabricatorJSONConfigType.php', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php', 'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php', @@ -4263,6 +4264,7 @@ phutil_register_library_map(array( 'PhabricatorWebContentSource' => 'infrastructure/contentsource/PhabricatorWebContentSource.php', 'PhabricatorWebServerSetupCheck' => 'applications/config/check/PhabricatorWebServerSetupCheck.php', 'PhabricatorWeekStartDaySetting' => 'applications/settings/setting/PhabricatorWeekStartDaySetting.php', + 'PhabricatorWildConfigType' => 'applications/config/type/PhabricatorWildConfigType.php', 'PhabricatorWordPressAuthProvider' => 'applications/auth/provider/PhabricatorWordPressAuthProvider.php', 'PhabricatorWorker' => 'infrastructure/daemon/workers/PhabricatorWorker.php', 'PhabricatorWorkerActiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php', @@ -8348,6 +8350,7 @@ phutil_register_library_map(array( 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase', 'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource', 'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider', + 'PhabricatorJSONConfigType' => 'PhabricatorTextConfigType', 'PhabricatorJavelinLinter' => 'ArcanistLinter', 'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorJumpNavHandler' => 'Phobject', @@ -9839,6 +9842,7 @@ phutil_register_library_map(array( 'PhabricatorWebContentSource' => 'PhabricatorContentSource', 'PhabricatorWebServerSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorWeekStartDaySetting' => 'PhabricatorSelectSetting', + 'PhabricatorWildConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorWordPressAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorWorker' => 'Phobject', 'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 7b0227540a..6ab4ccc253 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -325,40 +325,14 @@ final class PhabricatorConfigEditController $e_value = null; $errors = array(); - if ($option->isCustomType()) { $info = $option->getCustomObject()->readRequest($option, $request); list($e_value, $errors, $set_value, $value) = $info; } else { - $value = $request->getStr('value'); - if (!strlen($value)) { - $value = null; - - $xaction->setNewValue( - array( - 'deleted' => true, - 'value' => null, - )); - - return array($e_value, $errors, $value, $xaction); - } - - $type = $option->getType(); - $set_value = null; - - switch ($type) { - default: - $json = json_decode($value, true); - if ($json === null && strtolower($value) != 'null') { - $e_value = pht('Invalid'); - $errors[] = pht( - 'The given value must be valid JSON. This means, among '. - 'other things, that you must wrap strings in double-quotes.'); - } else { - $set_value = $json; - } - break; - } + throw new Exception( + pht( + 'Unknown configuration option type "%s".', + $option->getType())); } if (!$errors) { @@ -389,13 +363,12 @@ final class PhabricatorConfigEditController $option, $entry, $value); - } else { - $type = $option->getType(); - switch ($type) { - default: - return PhabricatorConfigJSON::prettyPrintJSON($value); - } } + + throw new Exception( + pht( + 'Unknown configuration option type "%s".', + $option->getType())); } private function renderControls( @@ -417,23 +390,10 @@ final class PhabricatorConfigEditController $display_value, $e_value); } else { - $type = $option->getType(); - switch ($type) { - default: - $control = id(new AphrontFormTextAreaControl()) - ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) - ->setCustomClass('PhabricatorMonospaced') - ->setCaption(pht('Enter value in JSON.')); - break; - } - - $control - ->setLabel(pht('Database Value')) - ->setError($e_value) - ->setValue($display_value) - ->setName('value'); - - $controls = array($control); + throw new Exception( + pht( + 'Unknown configuration option type "%s".', + $option->getType())); } return $controls; diff --git a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php index b29ef8c8ba..9f50d2eeed 100644 --- a/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php +++ b/src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php @@ -70,6 +70,7 @@ final class PhabricatorConfigManagementSetWorkflow throw new PhutilArgumentUsageException($ex->getMessage()); } } else { + // NOTE: For now, this handles both "wild" values and custom types. $type = $option->getType(); switch ($type) { default: diff --git a/src/applications/config/option/PhabricatorApplicationConfigOptions.php b/src/applications/config/option/PhabricatorApplicationConfigOptions.php index 89c9409aba..49cd9eb17e 100644 --- a/src/applications/config/option/PhabricatorApplicationConfigOptions.php +++ b/src/applications/config/option/PhabricatorApplicationConfigOptions.php @@ -24,6 +24,7 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { if ($type) { try { $type->validateStoredValue($option, $value); + $this->didValidateOption($option, $value); } catch (PhabricatorConfigValidationException $ex) { throw $ex; } catch (Exception $ex) { @@ -32,6 +33,8 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { // configuration and raise an error. throw new PhabricatorConfigValidationException($ex->getMessage()); } + + return; } if ($option->isCustomType()) { @@ -40,12 +43,11 @@ abstract class PhabricatorApplicationConfigOptions extends Phobject { } catch (Exception $ex) { throw new PhabricatorConfigValidationException($ex->getMessage()); } - } - - switch ($option->getType()) { - case 'wild': - default: - break; + } else { + throw new Exception( + pht( + 'Unknown configuration option type "%s".', + $option->getType())); } $this->didValidateOption($option, $value); diff --git a/src/applications/config/type/PhabricatorJSONConfigType.php b/src/applications/config/type/PhabricatorJSONConfigType.php new file mode 100644 index 0000000000..b1226c6e53 --- /dev/null +++ b/src/applications/config/type/PhabricatorJSONConfigType.php @@ -0,0 +1,38 @@ +newException( + pht( + 'Value for option "%s" (of type "%s") must be specified in JSON, '. + 'but input could not be decoded: %s', + $option->getKey(), + $this->getTypeKey(), + $ex->getMessage())); + } + + return $value; + } + + protected function newControl(PhabricatorConfigOption $option) { + return id(new AphrontFormTextAreaControl()) + ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) + ->setCustomClass('PhabricatorMonospaced') + ->setCaption(pht('Enter value in JSON.')); + } + + public function newDisplayValue( + PhabricatorConfigOption $option, + $value) { + return PhabricatorConfigJSON::prettyPrintJSON($value); + } + +} diff --git a/src/applications/config/type/PhabricatorWildConfigType.php b/src/applications/config/type/PhabricatorWildConfigType.php new file mode 100644 index 0000000000..3f278ca4e9 --- /dev/null +++ b/src/applications/config/type/PhabricatorWildConfigType.php @@ -0,0 +1,39 @@ +newException( + pht( + 'Value for option "%s" (of type "%s") must be specified in JSON, '. + 'but input could not be decoded. (Did you forget to quote a string?)', + $option->getKey(), + $this->getTypeKey())); + } + + return $value; + } + + public function validateStoredValue( + PhabricatorConfigOption $option, + $value) { + return; + } + +} diff --git a/src/applications/people/config/PhabricatorUserConfigOptions.php b/src/applications/people/config/PhabricatorUserConfigOptions.php index 79be912699..3ef736c8ca 100644 --- a/src/applications/people/config/PhabricatorUserConfigOptions.php +++ b/src/applications/people/config/PhabricatorUserConfigOptions.php @@ -43,7 +43,7 @@ final class PhabricatorUserConfigOptions $this->newOption('user.fields', $custom_field_type, $default) ->setCustomData(id(new PhabricatorUser())->getCustomFieldBaseClass()) ->setDescription(pht('Select and reorder user profile fields.')), - $this->newOption('user.custom-field-definitions', 'map', array()) + $this->newOption('user.custom-field-definitions', 'wild', array()) ->setDescription(pht('Add new simple fields to user profiles.')), $this->newOption('user.require-real-name', 'bool', true) ->setDescription(pht('Always require real name for user profiles.')) From 6984d239b02b035b9d49d23c0012351f0145075d Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 27 Jun 2017 07:05:58 -0700 Subject: [PATCH 11/17] Convert Maniphest custom config to new config types Summary: Fixes T12870. Ref T12845. Technically, this addresses the core issue in T12845 too, but I'm going to convert the rest of the `custom:...` types before closing that. In particular, for T12870: - Validates that keywords are unique across priorities. - Fixes missing newline in documentation. - Updates documentation to note that keywords are now mandatory and must be unique across priorities. Test Plan: Edited, deleted and mangled all the Maniphest custom options (priorities, statuses, points, subtypes). Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12870, T12845 Differential Revision: https://secure.phabricator.com/D18165 --- src/__phutil_library_map__.php | 16 ++--- .../PhabricatorConfigEditController.php | 5 ++ .../ManiphestPointsConfigOptionType.php | 10 --- .../config/ManiphestPointsConfigType.php | 14 ++++ .../config/ManiphestPrioritiesConfigType.php | 14 ++++ .../ManiphestPriorityConfigOptionType.php | 10 --- .../ManiphestStatusConfigOptionType.php | 10 --- .../config/ManiphestStatusesConfigType.php | 14 ++++ .../ManiphestSubtypesConfigOptionsType.php | 10 --- .../config/ManiphestSubtypesConfigType.php | 14 ++++ .../PhabricatorManiphestConfigOptions.php | 71 ++++++++++--------- .../constants/ManiphestTaskPriority.php | 15 +++- 12 files changed, 120 insertions(+), 83 deletions(-) delete mode 100644 src/applications/maniphest/config/ManiphestPointsConfigOptionType.php create mode 100644 src/applications/maniphest/config/ManiphestPointsConfigType.php create mode 100644 src/applications/maniphest/config/ManiphestPrioritiesConfigType.php delete mode 100644 src/applications/maniphest/config/ManiphestPriorityConfigOptionType.php delete mode 100644 src/applications/maniphest/config/ManiphestStatusConfigOptionType.php create mode 100644 src/applications/maniphest/config/ManiphestStatusesConfigType.php delete mode 100644 src/applications/maniphest/config/ManiphestSubtypesConfigOptionsType.php create mode 100644 src/applications/maniphest/config/ManiphestSubtypesConfigType.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e10b5fceef..1c2a647485 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1488,8 +1488,8 @@ phutil_register_library_map(array( 'ManiphestHovercardEngineExtension' => 'applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php', 'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php', 'ManiphestNameIndex' => 'applications/maniphest/storage/ManiphestNameIndex.php', - 'ManiphestPointsConfigOptionType' => 'applications/maniphest/config/ManiphestPointsConfigOptionType.php', - 'ManiphestPriorityConfigOptionType' => 'applications/maniphest/config/ManiphestPriorityConfigOptionType.php', + 'ManiphestPointsConfigType' => 'applications/maniphest/config/ManiphestPointsConfigType.php', + 'ManiphestPrioritiesConfigType' => 'applications/maniphest/config/ManiphestPrioritiesConfigType.php', 'ManiphestPriorityEmailCommand' => 'applications/maniphest/command/ManiphestPriorityEmailCommand.php', 'ManiphestPrioritySearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestPrioritySearchConduitAPIMethod.php', 'ManiphestProjectNameFulltextEngineExtension' => 'applications/maniphest/engineextension/ManiphestProjectNameFulltextEngineExtension.php', @@ -1500,11 +1500,11 @@ phutil_register_library_map(array( 'ManiphestReportController' => 'applications/maniphest/controller/ManiphestReportController.php', 'ManiphestSchemaSpec' => 'applications/maniphest/storage/ManiphestSchemaSpec.php', 'ManiphestSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestSearchConduitAPIMethod.php', - 'ManiphestStatusConfigOptionType' => 'applications/maniphest/config/ManiphestStatusConfigOptionType.php', 'ManiphestStatusEmailCommand' => 'applications/maniphest/command/ManiphestStatusEmailCommand.php', 'ManiphestStatusSearchConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestStatusSearchConduitAPIMethod.php', + 'ManiphestStatusesConfigType' => 'applications/maniphest/config/ManiphestStatusesConfigType.php', 'ManiphestSubpriorityController' => 'applications/maniphest/controller/ManiphestSubpriorityController.php', - 'ManiphestSubtypesConfigOptionsType' => 'applications/maniphest/config/ManiphestSubtypesConfigOptionsType.php', + 'ManiphestSubtypesConfigType' => 'applications/maniphest/config/ManiphestSubtypesConfigType.php', 'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php', 'ManiphestTaskAssignHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php', 'ManiphestTaskAssignOtherHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php', @@ -6603,8 +6603,8 @@ phutil_register_library_map(array( 'ManiphestHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestNameIndex' => 'ManiphestDAO', - 'ManiphestPointsConfigOptionType' => 'PhabricatorConfigJSONOptionType', - 'ManiphestPriorityConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'ManiphestPointsConfigType' => 'PhabricatorJSONConfigType', + 'ManiphestPrioritiesConfigType' => 'PhabricatorJSONConfigType', 'ManiphestPriorityEmailCommand' => 'ManiphestEmailCommand', 'ManiphestPrioritySearchConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestProjectNameFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', @@ -6615,11 +6615,11 @@ phutil_register_library_map(array( 'ManiphestReportController' => 'ManiphestController', 'ManiphestSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'ManiphestSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', - 'ManiphestStatusConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'ManiphestStatusEmailCommand' => 'ManiphestEmailCommand', 'ManiphestStatusSearchConduitAPIMethod' => 'ManiphestConduitAPIMethod', + 'ManiphestStatusesConfigType' => 'PhabricatorJSONConfigType', 'ManiphestSubpriorityController' => 'ManiphestController', - 'ManiphestSubtypesConfigOptionsType' => 'PhabricatorConfigJSONOptionType', + 'ManiphestSubtypesConfigType' => 'PhabricatorJSONConfigType', 'ManiphestTask' => array( 'ManiphestDAO', 'PhabricatorSubscribableInterface', diff --git a/src/applications/config/controller/PhabricatorConfigEditController.php b/src/applications/config/controller/PhabricatorConfigEditController.php index 6ab4ccc253..783fcb4dd3 100644 --- a/src/applications/config/controller/PhabricatorConfigEditController.php +++ b/src/applications/config/controller/PhabricatorConfigEditController.php @@ -290,6 +290,11 @@ final class PhabricatorConfigEditController } catch (PhabricatorConfigValidationException $ex) { $errors[] = $ex->getMessage(); $xaction = null; + } catch (Exception $ex) { + // NOTE: Some older validators throw bare exceptions. Purely in good + // taste, it would be nice to convert these at some point. + $errors[] = $ex->getMessage(); + $xaction = null; } return array( diff --git a/src/applications/maniphest/config/ManiphestPointsConfigOptionType.php b/src/applications/maniphest/config/ManiphestPointsConfigOptionType.php deleted file mode 100644 index f79050b4a1..0000000000 --- a/src/applications/maniphest/config/ManiphestPointsConfigOptionType.php +++ /dev/null @@ -1,10 +0,0 @@ - array( 'name' => pht('Unbreak Now!'), + 'keywords' => array('unbreak'), 'short' => pht('Unbreak!'), 'color' => 'pink', - 'keywords' => array('unbreak'), ), 90 => array( 'name' => pht('Needs Triage'), + 'keywords' => array('triage'), 'short' => pht('Triage'), 'color' => 'violet', - 'keywords' => array('triage'), ), 80 => array( 'name' => pht('High'), + 'keywords' => array('high'), 'short' => pht('High'), 'color' => 'red', - 'keywords' => array('high'), ), 50 => array( 'name' => pht('Normal'), + 'keywords' => array('normal'), 'short' => pht('Normal'), 'color' => 'orange', - 'keywords' => array('normal'), ), 25 => array( 'name' => pht('Low'), + 'keywords' => array('low'), 'short' => pht('Low'), 'color' => 'yellow', - 'keywords' => array('low'), ), 0 => array( 'name' => pht('Wishlist'), + 'keywords' => array('wish', 'wishlist'), 'short' => pht('Wish'), 'color' => 'sky', - 'keywords' => array('wish', 'wishlist'), ), ); - $status_type = 'custom:ManiphestStatusConfigOptionType'; + $status_type = 'maniphest.statuses'; $status_defaults = array( 'open' => array( 'name' => pht('Open'), @@ -265,7 +265,7 @@ EOTEXT ); $fields_json = id(new PhutilJSON())->encodeFormatted($fields_example); - $points_type = 'custom:ManiphestPointsConfigOptionType'; + $points_type = 'maniphest.points'; $points_example_1 = array( 'enabled' => true, @@ -299,7 +299,7 @@ See the example below for a starting point. EOTEXT )); - $subtype_type = 'custom:ManiphestSubtypesConfigOptionsType'; + $subtype_type = 'maniphest.subtypes'; $subtype_default_key = PhabricatorEditEngineSubtype::SUBTYPE_DEFAULT; $subtype_example = array( array( @@ -349,6 +349,32 @@ EOTEXT , $subtype_default_key)); + $priorities_description = $this->deformat(pht(<<.// List of unique keywords which identify + this priority, like "high" or "low". Each priority must have at least one + keyword and two priorities may not share the same keyword. + - `short` //Optional string.// Alternate shorter name, used in UIs where + there is less space available. + - `color` //Optional string.// Color for this priority, like "red" or + "blue". + - `disabled` //Optional bool.// Set to true to prevent users from choosing + this priority when creating or editing tasks. Existing tasks will not be + affected, and can be batch edited to a different priority or left to + eventually die out. + +You can choose the default priority for newly created tasks with +"maniphest.default-priority". +EOTEXT + )); + return array( $this->newOption('maniphest.custom-field-definitions', 'wild', array()) @@ -367,30 +393,7 @@ EOTEXT $priority_type, $priority_defaults) ->setSummary(pht('Configure Maniphest priority names.')) - ->setDescription( - pht( - 'Allows you to edit or override the default priorities available '. - 'in Maniphest, like "High", "Normal" and "Low". The configuration '. - 'should contain a map of priority constants to priority '. - 'specifications (see defaults below for examples).'. - "\n\n". - 'The keys you can define for a priority are:'. - "\n\n". - ' - `name` Name of the priority.'."\n". - ' - `short` Alternate shorter name, used in UIs where there is '. - ' not much space available.'."\n". - ' - `color` A color for this priority, like "red" or "blue".'. - ' - `keywords` An optional list of keywords which can '. - ' be used to select this priority when using `!priority` '. - ' commands in email.'."\n". - ' - `disabled` Optional boolean to prevent users from choosing '. - ' this priority when creating or editing tasks. Existing '. - ' tasks will be unaffected, and can be batch edited to a '. - ' different priority or left to eventually die out.'. - "\n\n". - 'You can choose which priority is the default for newly created '. - 'tasks with `%s`.', - 'maniphest.default-priority')), + ->setDescription($priorities_description), $this->newOption('maniphest.statuses', $status_type, $status_defaults) ->setSummary(pht('Configure Maniphest task statuses.')) ->setDescription($status_description) diff --git a/src/applications/maniphest/constants/ManiphestTaskPriority.php b/src/applications/maniphest/constants/ManiphestTaskPriority.php index 86c7adbcb7..d559299af9 100644 --- a/src/applications/maniphest/constants/ManiphestTaskPriority.php +++ b/src/applications/maniphest/constants/ManiphestTaskPriority.php @@ -203,6 +203,7 @@ final class ManiphestTaskPriority extends ManiphestConstants { $config)); } + $all_keywords = array(); foreach ($config as $key => $value) { if (!ctype_digit((string)$key)) { throw new Exception( @@ -223,9 +224,9 @@ final class ManiphestTaskPriority extends ManiphestConstants { $value, array( 'name' => 'string', + 'keywords' => 'list', 'short' => 'optional string', 'color' => 'optional string', - 'keywords' => 'list', 'disabled' => 'optional bool', )); @@ -242,6 +243,18 @@ final class ManiphestTaskPriority extends ManiphestConstants { 'low', 'critical')); } + + if (isset($all_keywords[$keyword])) { + throw new Exception( + pht( + 'Two different task priorities ("%s" and "%s") have the same '. + 'keyword ("%s"). Keywords must uniquely identify priorities.', + $value['name'], + $all_keywords[$keyword], + $keyword)); + } + + $all_keywords[$keyword] = $value['name']; } } } From b46e2bb4cc6f458a362946620ee8a86e97a07712 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 27 Jun 2017 09:50:57 -0700 Subject: [PATCH 12/17] Convert cluster/projects config options to newer modular structure Summary: Ref T12845. Converts the cluster and project config options to the new stuff; this is mostly just shifting boilerplate around. Test Plan: Edited, deleted, and mangled these options from the web UI and CLI. Reviewers: chad, amckinley Reviewed By: amckinley Maniphest Tasks: T12845 Differential Revision: https://secure.phabricator.com/D18166 --- src/__phutil_library_map__.php | 20 ++++++------ .../PhabricatorClusterConfigOptions.php | 4 +-- .../PhabricatorNotificationConfigOptions.php | 2 +- .../config/type/PhabricatorConfigType.php | 4 --- ...bricatorNotificationServersConfigType.php} | 32 +++++++++---------- ...abricatorProjectColorsConfigOptionType.php | 10 ------ .../PhabricatorProjectColorsConfigType.php | 14 ++++++++ .../PhabricatorProjectConfigOptions.php | 4 +-- ...habricatorProjectIconsConfigOptionType.php | 10 ------ .../PhabricatorProjectIconsConfigType.php | 14 ++++++++ .../PhabricatorSearchManagementWorkflow.php | 2 +- ...PhabricatorClusterDatabasesConfigType.php} | 24 +++++++------- ...=> PhabricatorClusterSearchConfigType.php} | 17 ++++------ 13 files changed, 77 insertions(+), 80 deletions(-) rename src/applications/notification/config/{PhabricatorNotificationServersConfigOptionType.php => PhabricatorNotificationServersConfigType.php} (85%) delete mode 100644 src/applications/project/config/PhabricatorProjectColorsConfigOptionType.php create mode 100644 src/applications/project/config/PhabricatorProjectColorsConfigType.php delete mode 100644 src/applications/project/config/PhabricatorProjectIconsConfigOptionType.php create mode 100644 src/applications/project/config/PhabricatorProjectIconsConfigType.php rename src/infrastructure/cluster/config/{PhabricatorClusterDatabasesConfigOptionType.php => PhabricatorClusterDatabasesConfigType.php} (82%) rename src/infrastructure/cluster/config/{PhabricatorClusterSearchConfigOptionType.php => PhabricatorClusterSearchConfigType.php} (86%) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1c2a647485..095759a351 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2331,13 +2331,13 @@ phutil_register_library_map(array( 'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php', 'PhabricatorClassConfigType' => 'applications/config/type/PhabricatorClassConfigType.php', 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', - 'PhabricatorClusterDatabasesConfigOptionType' => 'infrastructure/cluster/config/PhabricatorClusterDatabasesConfigOptionType.php', + 'PhabricatorClusterDatabasesConfigType' => 'infrastructure/cluster/config/PhabricatorClusterDatabasesConfigType.php', 'PhabricatorClusterException' => 'infrastructure/cluster/exception/PhabricatorClusterException.php', 'PhabricatorClusterExceptionHandler' => 'infrastructure/cluster/exception/PhabricatorClusterExceptionHandler.php', 'PhabricatorClusterImpossibleWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImpossibleWriteException.php', 'PhabricatorClusterImproperWriteException' => 'infrastructure/cluster/exception/PhabricatorClusterImproperWriteException.php', 'PhabricatorClusterNoHostForRoleException' => 'infrastructure/cluster/exception/PhabricatorClusterNoHostForRoleException.php', - 'PhabricatorClusterSearchConfigOptionType' => 'infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php', + 'PhabricatorClusterSearchConfigType' => 'infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php', 'PhabricatorClusterServiceHealthRecord' => 'infrastructure/cluster/PhabricatorClusterServiceHealthRecord.php', 'PhabricatorClusterStrandedException' => 'infrastructure/cluster/exception/PhabricatorClusterStrandedException.php', 'PhabricatorColumnProxyInterface' => 'applications/project/interface/PhabricatorColumnProxyInterface.php', @@ -3199,7 +3199,7 @@ phutil_register_library_map(array( 'PhabricatorNotificationQuery' => 'applications/notification/query/PhabricatorNotificationQuery.php', 'PhabricatorNotificationSearchEngine' => 'applications/notification/query/PhabricatorNotificationSearchEngine.php', 'PhabricatorNotificationServerRef' => 'applications/notification/client/PhabricatorNotificationServerRef.php', - 'PhabricatorNotificationServersConfigOptionType' => 'applications/notification/config/PhabricatorNotificationServersConfigOptionType.php', + 'PhabricatorNotificationServersConfigType' => 'applications/notification/config/PhabricatorNotificationServersConfigType.php', 'PhabricatorNotificationStatusView' => 'applications/notification/view/PhabricatorNotificationStatusView.php', 'PhabricatorNotificationTestController' => 'applications/notification/controller/PhabricatorNotificationTestController.php', 'PhabricatorNotificationTestFeedStory' => 'applications/notification/feed/PhabricatorNotificationTestFeedStory.php', @@ -3614,7 +3614,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php', 'PhabricatorProjectCardView' => 'applications/project/view/PhabricatorProjectCardView.php', 'PhabricatorProjectColorTransaction' => 'applications/project/xaction/PhabricatorProjectColorTransaction.php', - 'PhabricatorProjectColorsConfigOptionType' => 'applications/project/config/PhabricatorProjectColorsConfigOptionType.php', + 'PhabricatorProjectColorsConfigType' => 'applications/project/config/PhabricatorProjectColorsConfigType.php', 'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php', 'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php', 'PhabricatorProjectColumnEditController' => 'applications/project/controller/PhabricatorProjectColumnEditController.php', @@ -3652,7 +3652,7 @@ phutil_register_library_map(array( 'PhabricatorProjectHovercardEngineExtension' => 'applications/project/engineextension/PhabricatorProjectHovercardEngineExtension.php', 'PhabricatorProjectIconSet' => 'applications/project/icon/PhabricatorProjectIconSet.php', 'PhabricatorProjectIconTransaction' => 'applications/project/xaction/PhabricatorProjectIconTransaction.php', - 'PhabricatorProjectIconsConfigOptionType' => 'applications/project/config/PhabricatorProjectIconsConfigOptionType.php', + 'PhabricatorProjectIconsConfigType' => 'applications/project/config/PhabricatorProjectIconsConfigType.php', 'PhabricatorProjectImageTransaction' => 'applications/project/xaction/PhabricatorProjectImageTransaction.php', 'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php', 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', @@ -7581,13 +7581,13 @@ phutil_register_library_map(array( 'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorClassConfigType' => 'PhabricatorTextConfigType', 'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions', - 'PhabricatorClusterDatabasesConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorClusterDatabasesConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorClusterException' => 'Exception', 'PhabricatorClusterExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorClusterImpossibleWriteException' => 'PhabricatorClusterException', 'PhabricatorClusterImproperWriteException' => 'PhabricatorClusterException', 'PhabricatorClusterNoHostForRoleException' => 'Exception', - 'PhabricatorClusterSearchConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorClusterSearchConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorClusterServiceHealthRecord' => 'Phobject', 'PhabricatorClusterStrandedException' => 'PhabricatorClusterException', 'PhabricatorColumnsEditField' => 'PhabricatorPHIDListEditField', @@ -8552,7 +8552,7 @@ phutil_register_library_map(array( 'PhabricatorNotificationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorNotificationSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorNotificationServerRef' => 'Phobject', - 'PhabricatorNotificationServersConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorNotificationServersConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorNotificationStatusView' => 'AphrontTagView', 'PhabricatorNotificationTestController' => 'PhabricatorNotificationController', 'PhabricatorNotificationTestFeedStory' => 'PhabricatorFeedStory', @@ -9065,7 +9065,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectCardView' => 'AphrontTagView', 'PhabricatorProjectColorTransaction' => 'PhabricatorProjectTransactionType', - 'PhabricatorProjectColorsConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorProjectColorsConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorProjectColumn' => array( 'PhabricatorProjectDAO', 'PhabricatorApplicationTransactionInterface', @@ -9116,7 +9116,7 @@ phutil_register_library_map(array( 'PhabricatorProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'PhabricatorProjectIconSet' => 'PhabricatorIconSet', 'PhabricatorProjectIconTransaction' => 'PhabricatorProjectTransactionType', - 'PhabricatorProjectIconsConfigOptionType' => 'PhabricatorConfigJSONOptionType', + 'PhabricatorProjectIconsConfigType' => 'PhabricatorJSONConfigType', 'PhabricatorProjectImageTransaction' => 'PhabricatorProjectTransactionType', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectListView' => 'AphrontView', diff --git a/src/applications/config/option/PhabricatorClusterConfigOptions.php b/src/applications/config/option/PhabricatorClusterConfigOptions.php index c3636c31e0..ca4153a8b2 100644 --- a/src/applications/config/option/PhabricatorClusterConfigOptions.php +++ b/src/applications/config/option/PhabricatorClusterConfigOptions.php @@ -20,7 +20,7 @@ final class PhabricatorClusterConfigOptions } public function getOptions() { - $databases_type = 'custom:PhabricatorClusterDatabasesConfigOptionType'; + $databases_type = 'cluster.databases'; $databases_help = $this->deformat(pht(<<deformat(pht(<<deformat(pht(<< $spec) { if (!is_array($spec)) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration is not valid: each entry in '. 'the list must be a dictionary describing a service, but '. @@ -38,7 +36,7 @@ final class PhabricatorNotificationServersConfigOptionType 'disabled' => 'optional bool', )); } catch (Exception $ex) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration has an invalid service '. 'specification (at index "%s"): %s.', @@ -64,7 +62,7 @@ final class PhabricatorNotificationServersConfigOptionType } break; default: - throw new Exception( + throw $this->newException( pht( 'Notification server configuration describes an invalid '. 'host ("%s", at index "%s") with an unrecognized type ("%s"). '. @@ -81,7 +79,7 @@ final class PhabricatorNotificationServersConfigOptionType case 'https': break; default: - throw new Exception( + throw $this->newException( pht( 'Notification server configuration describes an invalid '. 'host ("%s", at index "%s") with an invalid protocol ("%s"). '. @@ -95,7 +93,7 @@ final class PhabricatorNotificationServersConfigOptionType $path = idx($spec, 'path'); if ($type == 'admin' && strlen($path)) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration describes an invalid host '. '("%s", at index "%s"). This is an "admin" service but it has a '. @@ -108,7 +106,7 @@ final class PhabricatorNotificationServersConfigOptionType // mistakes. $key = "{$host}:{$port}"; if (isset($map[$key])) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration is invalid: it describes the '. 'same host and port ("%s") multiple times. Each host and port '. @@ -120,7 +118,7 @@ final class PhabricatorNotificationServersConfigOptionType if ($value) { if (!$has_admin) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration is invalid: it does not '. 'specify any enabled servers with type "admin". Notifications '. @@ -128,7 +126,7 @@ final class PhabricatorNotificationServersConfigOptionType } if (!$has_client) { - throw new Exception( + throw $this->newException( pht( 'Notification server configuration is invalid: it does not '. 'specify any enabled servers with type "client". Notifications '. diff --git a/src/applications/project/config/PhabricatorProjectColorsConfigOptionType.php b/src/applications/project/config/PhabricatorProjectColorsConfigOptionType.php deleted file mode 100644 index 4cd8c09bbc..0000000000 --- a/src/applications/project/config/PhabricatorProjectColorsConfigOptionType.php +++ /dev/null @@ -1,10 +0,0 @@ -deformat(pht(<<deformat(pht(<< $spec) { if (!is_array($spec)) { - throw new Exception( + throw $this->newException( pht( 'Database cluster configuration is not valid: each entry in the '. 'list must be a dictionary describing a database host, but '. @@ -40,7 +38,7 @@ final class PhabricatorClusterDatabasesConfigOptionType 'persistent' => 'optional bool', )); } catch (Exception $ex) { - throw new Exception( + throw $this->newException( pht( 'Database cluster configuration has an invalid host '. 'specification (at index "%s"): %s.', @@ -57,7 +55,7 @@ final class PhabricatorClusterDatabasesConfigOptionType case 'replica': break; default: - throw new Exception( + throw $this->newException( pht( 'Database cluster configuration describes an invalid '. 'host ("%s", at index "%s") with an unrecognized role ("%s"). '. @@ -78,7 +76,7 @@ final class PhabricatorClusterDatabasesConfigOptionType // mistakes. $key = "{$host}:{$port}"; if (isset($map[$key])) { - throw new Exception( + throw $this->newException( pht( 'Database cluster configuration is invalid: it describes the '. 'same host ("%s") multiple times. Each host should appear only '. diff --git a/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php b/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php similarity index 86% rename from src/infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php rename to src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php index 90ead23e6d..ae54ff1a11 100644 --- a/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigOptionType.php +++ b/src/infrastructure/cluster/config/PhabricatorClusterSearchConfigType.php @@ -1,20 +1,17 @@ $spec) { From 8e5afb56af200571c045f1d9c17b6ad1bb5d3aea Mon Sep 17 00:00:00 2001 From: Austin McKinley Date: Tue, 27 Jun 2017 12:08:13 -0700 Subject: [PATCH 13/17] Drop interactive login from sshd example Summary: Prevent slightly disturbing output on SSH authentication failure that implies some kind of interactive logins are possible: `Permission denied (publickey,keyboard-interactive).` Test Plan: doitlive Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18167 --- resources/sshd/sshd_config.phabricator.example | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/sshd/sshd_config.phabricator.example b/resources/sshd/sshd_config.phabricator.example index 2f2a581223..506d32bbbf 100644 --- a/resources/sshd/sshd_config.phabricator.example +++ b/resources/sshd/sshd_config.phabricator.example @@ -18,6 +18,7 @@ AllowTcpForwarding no PrintMotd no PrintLastLog no PasswordAuthentication no +ChallengeResponseAuthentication no AuthorizedKeysFile none PidFile /var/run/sshd-phabricator.pid From 83266e805c4615689b62af2325411c9c9c5ec5a9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 28 Jun 2017 10:37:43 +0200 Subject: [PATCH 14/17] Update people image for projects Summary: Better diversity. Test Plan: Photoshop Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D18170 --- resources/builtin/projects/v3/people.png | Bin 13654 -> 13619 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/builtin/projects/v3/people.png b/resources/builtin/projects/v3/people.png index b9118c2cfaa4e905e315cd7b5421fcdcf93de0c5..5bb42656dfdd08b038a15cb304093d026588ef54 100644 GIT binary patch literal 13619 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 NMMXhVzFHOx`Ck6Fj(kaQE+?x4wVh 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 Date: Fri, 30 Jun 2017 06:24:53 -0700 Subject: [PATCH 15/17] Move misplaced validation for ambiguous fields in "Test Plan" to the right place Summary: When users use the web UI to enter text like "Reviewers: x" into the "Summary" or "Test Plan", we can end up with an ambiguous commit message. Some time ago we added a warning about this to the "Summary" field, and //attempted// to add it to the "Test Plan" field, but it actually gets called from the wrong place. Remove the code from the wrong place (no callers, not reachable) and put it in the right place. This fixes an issue where users could edit a test plan from the web UI to add the text "Tests: ..." and cause ambiguities on a later "arc diff --edit". Test Plan: {F5026603} Reviewers: chad, amckinley Reviewed By: chad Differential Revision: https://secure.phabricator.com/D18175 --- .../field/DifferentialTestPlanCommitMessageField.php | 7 ------- .../xaction/DifferentialRevisionTestPlanTransaction.php | 5 ++++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/applications/differential/field/DifferentialTestPlanCommitMessageField.php b/src/applications/differential/field/DifferentialTestPlanCommitMessageField.php index 178e51d985..a477a9036e 100644 --- a/src/applications/differential/field/DifferentialTestPlanCommitMessageField.php +++ b/src/applications/differential/field/DifferentialTestPlanCommitMessageField.php @@ -50,11 +50,4 @@ final class DifferentialTestPlanCommitMessageField ); } - public function validateTransactions($object, array $xactions) { - return $this->validateCommitMessageCorpusTransactions( - $object, - $xactions, - pht('Test Plan')); - } - } diff --git a/src/applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php b/src/applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php index bf2beab3d8..c7c77fbcff 100644 --- a/src/applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionTestPlanTransaction.php @@ -55,7 +55,10 @@ final class DifferentialRevisionTestPlanTransaction } public function validateTransactions($object, array $xactions) { - $errors = array(); + $errors = $this->validateCommitMessageCorpusTransactions( + $object, + $xactions, + pht('Test Plan')); $is_required = PhabricatorEnv::getEnvConfig( 'differential.require-test-plan-field'); From 4e047f7b31d2734a0e69c1bb7333941217361f71 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 30 Jun 2017 06:56:10 -0700 Subject: [PATCH 16/17] Correct a datasource issue when viewing repository URIs in "Manage Repository" Summary: Fixes T12884. In cases other than this UI, applications access URIs through the Repository they're part of. This means that applications interact with URIs which have gone through the correction/adjustment logic in `PhabricatorRepository->attachURIs()`, which fixes up "builtin" URIs to have the right values based on configuration. In this case (and, as far as I can tell, only this case) we load the URI directly //and// act on its properties which depend on configuration and repository state. This can mean we're using a different view of the URI than we should be. To fix this: after loading the URI, reload it through the repository so the relevant adjustments are applied. I think this is the most reasonable fix. We could try to make `RepositoryURIQuery` somehow enforce this, but the cost of this error is small (mild confusion about display state), the other things which do direct loads don't depend on this state (editing), and everything else loads via a repository and is likely to continue doing that forever. Test Plan: {F5026633} Reviewers: chad, amckinley Reviewed By: chad Maniphest Tasks: T12884 Differential Revision: https://secure.phabricator.com/D18176 --- .../DiffusionRepositoryURIViewController.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php b/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php index 61335d20d0..308df8f0d2 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryURIViewController.php @@ -23,6 +23,22 @@ final class DiffusionRepositoryURIViewController return new Aphront404Response(); } + // For display, reload the URI by loading it through the repository. This + // may adjust builtin URIs for repository configuration, so we may end up + // with a different view of builtin URIs than we'd see if we loaded them + // directly from the database. See T12884. + $repository_with_uris = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->needURIs(true) + ->execute(); + + $repository_uris = $repository->getURIs(); + $repository_uris = mpull($repository_uris, null, 'getID'); + $uri = idx($repository_uris, $uri->getID()); + if (!$uri) { + return new Aphront404Response(); + } + $title = array( pht('URI'), $repository->getDisplayName(), From eab8d8a22c4fc93d47ed2f185632a2a7d2f41876 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 30 Jun 2017 07:02:36 -0700 Subject: [PATCH 17/17] Use the correct "completed" time in Harbormaster display UI Summary: Fixes T12883. The task seems correct to me and I think this is a copy/paste mistake that probably blames to me. Test Plan: Fiddled these numbers, viewed a build in Harbormaster, saw the adjusted time. Reviewers: chad, amckinley Reviewed By: chad Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam Maniphest Tasks: T12883 Differential Revision: https://secure.phabricator.com/D18177 --- .../harbormaster/controller/HarbormasterBuildViewController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index 7932451d2e..5b7171a200 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -141,7 +141,7 @@ final class HarbormasterBuildViewController if ($ended) { $when[] = pht( 'Completed at %s', - phabricator_datetime($started, $viewer)); + phabricator_datetime($ended, $viewer)); $duration = ($ended - $started); if ($duration) {