diff --git a/resources/builtin/agent.png b/resources/builtin/agent.png new file mode 100644 index 0000000000..ba103e67ec Binary files /dev/null and b/resources/builtin/agent.png differ diff --git a/resources/builtin/blog.png b/resources/builtin/blog.png new file mode 100644 index 0000000000..bb68581a01 Binary files /dev/null and b/resources/builtin/blog.png differ diff --git a/resources/builtin/macro/phabricator1/angelic.png b/resources/builtin/macro/phabricator1/angelic.png deleted file mode 100644 index 3238593254..0000000000 Binary files a/resources/builtin/macro/phabricator1/angelic.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/capn.png b/resources/builtin/macro/phabricator1/capn.png deleted file mode 100644 index 136a375198..0000000000 Binary files a/resources/builtin/macro/phabricator1/capn.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/disapprovey.png b/resources/builtin/macro/phabricator1/disapprovey.png deleted file mode 100644 index 5765cbb3f6..0000000000 Binary files a/resources/builtin/macro/phabricator1/disapprovey.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/hahalol.png b/resources/builtin/macro/phabricator1/hahalol.png deleted file mode 100644 index 50cf5ebd21..0000000000 Binary files a/resources/builtin/macro/phabricator1/hahalol.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/happyguy.png b/resources/builtin/macro/phabricator1/happyguy.png deleted file mode 100644 index 0fa43e3950..0000000000 Binary files a/resources/builtin/macro/phabricator1/happyguy.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/itsthedevil.png b/resources/builtin/macro/phabricator1/itsthedevil.png deleted file mode 100644 index a144ffd6ee..0000000000 Binary files a/resources/builtin/macro/phabricator1/itsthedevil.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/mater.png b/resources/builtin/macro/phabricator1/mater.png deleted file mode 100644 index 14da77060e..0000000000 Binary files a/resources/builtin/macro/phabricator1/mater.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/mcsquiggles.png b/resources/builtin/macro/phabricator1/mcsquiggles.png deleted file mode 100644 index 4d6b1d6a55..0000000000 Binary files a/resources/builtin/macro/phabricator1/mcsquiggles.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/mesmerized.png b/resources/builtin/macro/phabricator1/mesmerized.png deleted file mode 100644 index eb52eec2e3..0000000000 Binary files a/resources/builtin/macro/phabricator1/mesmerized.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/mrclowny.png b/resources/builtin/macro/phabricator1/mrclowny.png deleted file mode 100644 index 0777a735c3..0000000000 Binary files a/resources/builtin/macro/phabricator1/mrclowny.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/mumbles.png b/resources/builtin/macro/phabricator1/mumbles.png deleted file mode 100644 index 17bff50699..0000000000 Binary files a/resources/builtin/macro/phabricator1/mumbles.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/ohhrm.png b/resources/builtin/macro/phabricator1/ohhrm.png deleted file mode 100644 index 28b0c481aa..0000000000 Binary files a/resources/builtin/macro/phabricator1/ohhrm.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/ohisee.png b/resources/builtin/macro/phabricator1/ohisee.png deleted file mode 100644 index 80017e07cc..0000000000 Binary files a/resources/builtin/macro/phabricator1/ohisee.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/ohunhappy.png b/resources/builtin/macro/phabricator1/ohunhappy.png deleted file mode 100644 index 8c20a56f9c..0000000000 Binary files a/resources/builtin/macro/phabricator1/ohunhappy.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/sayahh.png b/resources/builtin/macro/phabricator1/sayahh.png deleted file mode 100644 index 097090a0e0..0000000000 Binary files a/resources/builtin/macro/phabricator1/sayahh.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/smileytongue.png b/resources/builtin/macro/phabricator1/smileytongue.png deleted file mode 100644 index f3786e7842..0000000000 Binary files a/resources/builtin/macro/phabricator1/smileytongue.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/smirky.png b/resources/builtin/macro/phabricator1/smirky.png deleted file mode 100644 index 2644ba75cf..0000000000 Binary files a/resources/builtin/macro/phabricator1/smirky.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/squintytongue.png b/resources/builtin/macro/phabricator1/squintytongue.png deleted file mode 100644 index 36e3699562..0000000000 Binary files a/resources/builtin/macro/phabricator1/squintytongue.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/stunna.png b/resources/builtin/macro/phabricator1/stunna.png deleted file mode 100644 index e32de27244..0000000000 Binary files a/resources/builtin/macro/phabricator1/stunna.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/thxu.png b/resources/builtin/macro/phabricator1/thxu.png deleted file mode 100644 index 15308e756d..0000000000 Binary files a/resources/builtin/macro/phabricator1/thxu.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/udidwat.png b/resources/builtin/macro/phabricator1/udidwat.png deleted file mode 100644 index 2c938647ad..0000000000 Binary files a/resources/builtin/macro/phabricator1/udidwat.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/uhwat.png b/resources/builtin/macro/phabricator1/uhwat.png deleted file mode 100644 index 8712ef31e3..0000000000 Binary files a/resources/builtin/macro/phabricator1/uhwat.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/undecided.png b/resources/builtin/macro/phabricator1/undecided.png deleted file mode 100644 index a86d50e68a..0000000000 Binary files a/resources/builtin/macro/phabricator1/undecided.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/vampy.png b/resources/builtin/macro/phabricator1/vampy.png deleted file mode 100644 index 6b7cb3117c..0000000000 Binary files a/resources/builtin/macro/phabricator1/vampy.png and /dev/null differ diff --git a/resources/builtin/macro/phabricator1/wowzers.png b/resources/builtin/macro/phabricator1/wowzers.png deleted file mode 100644 index 228e4c5da5..0000000000 Binary files a/resources/builtin/macro/phabricator1/wowzers.png and /dev/null differ diff --git a/resources/builtin/mailinglist.png b/resources/builtin/mailinglist.png new file mode 100644 index 0000000000..58e973608f Binary files /dev/null and b/resources/builtin/mailinglist.png differ diff --git a/resources/builtin/profile.png b/resources/builtin/profile.png index 755d19cd2e..1e1be2a8f9 100644 Binary files a/resources/builtin/profile.png and b/resources/builtin/profile.png differ diff --git a/resources/builtin/project.png b/resources/builtin/project.png index 59cf6cf1ad..1e16790ea8 100644 Binary files a/resources/builtin/project.png and b/resources/builtin/project.png differ diff --git a/resources/builtin/user0.png b/resources/builtin/user0.png new file mode 100644 index 0000000000..43a4ebceca Binary files /dev/null and b/resources/builtin/user0.png differ diff --git a/resources/builtin/user1.png b/resources/builtin/user1.png new file mode 100644 index 0000000000..0f94001ed7 Binary files /dev/null and b/resources/builtin/user1.png differ diff --git a/resources/builtin/user2.png b/resources/builtin/user2.png new file mode 100644 index 0000000000..9629b2d38f Binary files /dev/null and b/resources/builtin/user2.png differ diff --git a/resources/builtin/user3.png b/resources/builtin/user3.png new file mode 100644 index 0000000000..f2f5e2338d Binary files /dev/null and b/resources/builtin/user3.png differ diff --git a/resources/builtin/user4.png b/resources/builtin/user4.png new file mode 100644 index 0000000000..75227cdb8d Binary files /dev/null and b/resources/builtin/user4.png differ diff --git a/resources/builtin/user5.png b/resources/builtin/user5.png new file mode 100644 index 0000000000..3e8aad37dc Binary files /dev/null and b/resources/builtin/user5.png differ diff --git a/resources/builtin/user6.png b/resources/builtin/user6.png new file mode 100644 index 0000000000..20eb2cd9af Binary files /dev/null and b/resources/builtin/user6.png differ diff --git a/resources/builtin/user7.png b/resources/builtin/user7.png new file mode 100644 index 0000000000..a224cdabdf Binary files /dev/null and b/resources/builtin/user7.png differ diff --git a/resources/builtin/user8.png b/resources/builtin/user8.png new file mode 100644 index 0000000000..e0d9fa506d Binary files /dev/null and b/resources/builtin/user8.png differ diff --git a/resources/builtin/user9.png b/resources/builtin/user9.png new file mode 100644 index 0000000000..001f696b94 Binary files /dev/null and b/resources/builtin/user9.png differ diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 44ba4be6cd..026d46f6df 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -1,2267 +1,2301 @@ array( - 'core.pkg.css' => '91bbffc2', + 'core.pkg.css' => '4d47b0a9', 'core.pkg.js' => '47dc9ebb', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', 'differential.pkg.js' => '6223dd9d', 'diffusion.pkg.css' => 'f45955ed', 'diffusion.pkg.js' => 'ca1c8b5a', 'maniphest.pkg.css' => '4845691a', 'maniphest.pkg.js' => '3ec6a6d5', 'rsrc/css/aphront/aphront-bars.css' => '231ac33c', 'rsrc/css/aphront/dark-console.css' => '6378ef3d', 'rsrc/css/aphront/dialog-view.css' => 'be0e3a46', 'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d', 'rsrc/css/aphront/list-filter-view.css' => '5d6f0526', 'rsrc/css/aphront/multi-column.css' => 'fd18389d', 'rsrc/css/aphront/notification.css' => '9c279160', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'a24cb589', 'rsrc/css/aphront/table-view.css' => '6d01d468', 'rsrc/css/aphront/tokenizer.css' => '056da01b', 'rsrc/css/aphront/tooltip.css' => '7672b60f', 'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c', 'rsrc/css/aphront/typeahead.css' => '0e403212', 'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af', 'rsrc/css/application/auth/auth.css' => '0877ed6e', 'rsrc/css/application/base/main-menu-view.css' => '2f670a96', 'rsrc/css/application/base/notification-menu.css' => 'f31c0bde', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601', 'rsrc/css/application/base/phui-theme.css' => '6b451f24', - 'rsrc/css/application/base/standard-page-view.css' => 'a1096ed4', + 'rsrc/css/application/base/standard-page-view.css' => '3c99cdf4', 'rsrc/css/application/calendar/calendar-icon.css' => 'c69aa59f', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '0ede4c9b', 'rsrc/css/application/config/config-template.css' => '8e6c6fcd', 'rsrc/css/application/config/config-welcome.css' => '6abd79be', 'rsrc/css/application/config/setup-issue.css' => 'db7e9c40', 'rsrc/css/application/config/unhandled-exception.css' => '4c96257a', 'rsrc/css/application/conpherence/durable-column.css' => '86396117', 'rsrc/css/application/conpherence/menu.css' => 'f99fee4c', 'rsrc/css/application/conpherence/message-pane.css' => '5897d3ac', 'rsrc/css/application/conpherence/notification.css' => '6cdcc253', 'rsrc/css/application/conpherence/transaction.css' => '85d0974c', 'rsrc/css/application/conpherence/update.css' => 'faf6be09', 'rsrc/css/application/conpherence/widget-pane.css' => '775eaaba', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', 'rsrc/css/application/countdown/timer.css' => 'e7544472', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', 'rsrc/css/application/dashboard/dashboard.css' => 'eb458607', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 'rsrc/css/application/differential/changeset-view.css' => 'b6b0d1bb', 'rsrc/css/application/differential/core.css' => '7ac3cabc', 'rsrc/css/application/differential/phui-inline-comment.css' => '0fdb3667', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', 'rsrc/css/application/differential/table-of-contents.css' => 'ae4b7a55', 'rsrc/css/application/diffusion/diffusion-icons.css' => '2941baf1', 'rsrc/css/application/diffusion/diffusion-readme.css' => '2106ea08', 'rsrc/css/application/diffusion/diffusion-source.css' => '075ba788', 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '697324ad', 'rsrc/css/application/flag/flag.css' => '5337623f', 'rsrc/css/application/harbormaster/harbormaster.css' => 'b0758ca5', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 'rsrc/css/application/herald/herald.css' => '826075fa', 'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5', 'rsrc/css/application/maniphest/report.css' => 'f6931fdf', 'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b', 'rsrc/css/application/maniphest/task-summary.css' => '11cc5344', 'rsrc/css/application/objectselector/object-selector.css' => '85ee8ce6', 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 'rsrc/css/application/paste/paste.css' => 'b2f5a543', 'rsrc/css/application/people/people-profile.css' => '25970776', - 'rsrc/css/application/phame/phame.css' => 'bb147387', + 'rsrc/css/application/phame/phame.css' => 'cea3c9e1', 'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => '95174bdd', 'rsrc/css/application/phortune/phortune-credit-card-form.css' => '8391eb02', 'rsrc/css/application/phortune/phortune.css' => '9149f103', 'rsrc/css/application/phrequent/phrequent.css' => 'ffc185ad', 'rsrc/css/application/phriction/phriction-document-css.css' => 'd1861e06', 'rsrc/css/application/policy/policy-edit.css' => '815c66f7', 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/ponder/ponder-view.css' => '7b0df4da', 'rsrc/css/application/projects/project-icon.css' => '4e3eaa5a', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', 'rsrc/css/application/search/search-results.css' => '7dea472c', 'rsrc/css/application/slowvote/slowvote.css' => 'da0afb1b', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '78e8d7ea', - 'rsrc/css/core/remarkup.css' => '88e1ebb6', + 'rsrc/css/core/core.css' => 'a76cefc9', + 'rsrc/css/core/remarkup.css' => 'b1c10368', 'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/z-index.css' => '57ddcaa2', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', 'rsrc/css/font/font-aleo.css' => '8bdb2835', 'rsrc/css/font/font-awesome.css' => 'c43323c5', 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => 'ecbbb4c2', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-hovercard-view.css' => '1239cd52', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'bec2458e', 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1c7f338', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '476be7e0', 'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893', 'rsrc/css/phui/phui-action-list.css' => 'c5eba19d', 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => 'f25c3476', 'rsrc/css/phui/phui-box.css' => 'a5bb366d', 'rsrc/css/phui/phui-button.css' => '16020a60', 'rsrc/css/phui/phui-crumbs-view.css' => '414406b5', - 'rsrc/css/phui/phui-document-pro.css' => '5f75ed99', + 'rsrc/css/phui/phui-document-pro.css' => 'e0fad431', + 'rsrc/css/phui/phui-document-summary.css' => '8c1e0aca', 'rsrc/css/phui/phui-document.css' => 'a4a1c3b9', 'rsrc/css/phui/phui-feed-story.css' => 'b7b26d23', 'rsrc/css/phui/phui-fontkit.css' => '9cda225e', 'rsrc/css/phui/phui-form-view.css' => 'c1d2ef29', 'rsrc/css/phui/phui-form.css' => 'afdb2c6e', 'rsrc/css/phui/phui-header-view.css' => '55bb32dd', 'rsrc/css/phui/phui-icon.css' => 'b0a6b1b6', 'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8', 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => '6d7c3509', 'rsrc/css/phui/phui-list.css' => '125599df', 'rsrc/css/phui/phui-object-box.css' => '407eaf5a', 'rsrc/css/phui/phui-object-item-list-view.css' => '26c30d3f', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-property-list-view.css' => '27b2849e', 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => '888cedb8', 'rsrc/css/phui/phui-tag-view.css' => 'e60e227b', 'rsrc/css/phui/phui-text.css' => 'cf019f54', 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', 'rsrc/css/phui/phui-two-column-view.css' => '39ecafb1', 'rsrc/css/phui/phui-workboard-view.css' => '6704d68d', 'rsrc/css/phui/phui-workpanel-view.css' => 'adec7699', 'rsrc/css/sprite-login.css' => '60e8560e', 'rsrc/css/sprite-main-header.css' => 'f07bbb87', 'rsrc/css/sprite-menu.css' => '9dd65b92', 'rsrc/css/sprite-projects.css' => 'e5ad842a', 'rsrc/css/sprite-tokens.css' => '4f399012', 'rsrc/externals/font/aleo/aleo-bold.eot' => 'd3d3bed7', 'rsrc/externals/font/aleo/aleo-bold.svg' => '45899c8e', 'rsrc/externals/font/aleo/aleo-bold.ttf' => '4b08bef0', 'rsrc/externals/font/aleo/aleo-bold.woff' => '93b513a1', 'rsrc/externals/font/aleo/aleo-bold.woff2' => '75fbf322', 'rsrc/externals/font/aleo/aleo-regular.eot' => 'a4e29e2f', 'rsrc/externals/font/aleo/aleo-regular.svg' => '42a86f7a', 'rsrc/externals/font/aleo/aleo-regular.ttf' => '751e7479', 'rsrc/externals/font/aleo/aleo-regular.woff' => 'c3744be9', 'rsrc/externals/font/aleo/aleo-regular.woff2' => '851aa0ee', 'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '346fbcc5', 'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => '510fccb2', 'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => '0334f580', 'rsrc/externals/font/fontawesome/fontawesome-webfont.woff2' => '45dca585', 'rsrc/externals/font/lato/lato-bold.eot' => '99fbcf8c', 'rsrc/externals/font/lato/lato-bold.svg' => '2aa83045', 'rsrc/externals/font/lato/lato-bold.ttf' => '0a7141f7', 'rsrc/externals/font/lato/lato-bold.woff' => 'f5db2061', 'rsrc/externals/font/lato/lato-bold.woff2' => '37a94ecd', 'rsrc/externals/font/lato/lato-bolditalic.eot' => 'b93389d0', 'rsrc/externals/font/lato/lato-bolditalic.svg' => '5442e1ef', 'rsrc/externals/font/lato/lato-bolditalic.ttf' => 'dad31252', 'rsrc/externals/font/lato/lato-bolditalic.woff' => 'e53bcf47', 'rsrc/externals/font/lato/lato-bolditalic.woff2' => 'd035007f', 'rsrc/externals/font/lato/lato-italic.eot' => '6a903f5d', 'rsrc/externals/font/lato/lato-italic.svg' => '0dc7cf2f', 'rsrc/externals/font/lato/lato-italic.ttf' => '629f64f0', 'rsrc/externals/font/lato/lato-italic.woff' => '678dc4bb', 'rsrc/externals/font/lato/lato-italic.woff2' => '7c8dd650', 'rsrc/externals/font/lato/lato-regular.eot' => '848dfb1e', 'rsrc/externals/font/lato/lato-regular.svg' => 'cbd5fd6b', 'rsrc/externals/font/lato/lato-regular.ttf' => 'e270165b', 'rsrc/externals/font/lato/lato-regular.woff' => '13d39fe2', 'rsrc/externals/font/lato/lato-regular.woff2' => '57a9f742', 'rsrc/externals/javelin/core/Event.js' => '85ea0626', 'rsrc/externals/javelin/core/Stratcom.js' => '6c53634d', 'rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js' => '717554e4', 'rsrc/externals/javelin/core/__tests__/install.js' => 'c432ee85', 'rsrc/externals/javelin/core/__tests__/stratcom.js' => '88bf7313', 'rsrc/externals/javelin/core/__tests__/util.js' => 'e251703d', 'rsrc/externals/javelin/core/init.js' => '3010e992', 'rsrc/externals/javelin/core/init_node.js' => 'c234aded', 'rsrc/externals/javelin/core/install.js' => '05270951', 'rsrc/externals/javelin/core/util.js' => '93cc50d6', 'rsrc/externals/javelin/docs/Base.js' => '74676256', 'rsrc/externals/javelin/docs/onload.js' => 'e819c479', 'rsrc/externals/javelin/ext/fx/Color.js' => '7e41274a', 'rsrc/externals/javelin/ext/fx/FX.js' => '54b612ba', 'rsrc/externals/javelin/ext/reactor/core/DynVal.js' => 'f6555212', 'rsrc/externals/javelin/ext/reactor/core/Reactor.js' => '2b8de964', 'rsrc/externals/javelin/ext/reactor/core/ReactorNode.js' => '1ad0a787', 'rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js' => '76f4ebed', 'rsrc/externals/javelin/ext/reactor/dom/RDOM.js' => 'c90a04fc', 'rsrc/externals/javelin/ext/view/HTMLView.js' => 'fe287620', 'rsrc/externals/javelin/ext/view/View.js' => '0f764c35', 'rsrc/externals/javelin/ext/view/ViewInterpreter.js' => 'f829edb3', 'rsrc/externals/javelin/ext/view/ViewPlaceholder.js' => '47830651', 'rsrc/externals/javelin/ext/view/ViewRenderer.js' => '6c2b09a2', 'rsrc/externals/javelin/ext/view/ViewVisitor.js' => 'efe49472', 'rsrc/externals/javelin/ext/view/__tests__/HTMLView.js' => 'f92d7bcb', 'rsrc/externals/javelin/ext/view/__tests__/View.js' => '6450b38b', 'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => '7a94d6a5', 'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '6ea96ac9', 'rsrc/externals/javelin/lib/Cookie.js' => '62dfea03', 'rsrc/externals/javelin/lib/DOM.js' => '805b806a', 'rsrc/externals/javelin/lib/History.js' => 'd4505101', 'rsrc/externals/javelin/lib/JSON.js' => '69adf288', 'rsrc/externals/javelin/lib/Leader.js' => '331b1611', 'rsrc/externals/javelin/lib/Mask.js' => '8a41885b', 'rsrc/externals/javelin/lib/Quicksand.js' => '4cebc641', 'rsrc/externals/javelin/lib/Request.js' => '94b750d2', 'rsrc/externals/javelin/lib/Resource.js' => '44959b73', 'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692', 'rsrc/externals/javelin/lib/Router.js' => '29274e2b', 'rsrc/externals/javelin/lib/Scrollbar.js' => '087e919c', 'rsrc/externals/javelin/lib/Sound.js' => '949c0fe5', 'rsrc/externals/javelin/lib/URI.js' => '6eff08aa', 'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8', 'rsrc/externals/javelin/lib/WebSocket.js' => 'e292eaf4', 'rsrc/externals/javelin/lib/Workflow.js' => '5b2e3e2b', 'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8', 'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b', 'rsrc/externals/javelin/lib/__tests__/JSON.js' => '837a7d68', 'rsrc/externals/javelin/lib/__tests__/URI.js' => '1e45fda9', 'rsrc/externals/javelin/lib/__tests__/behavior.js' => '1ea62783', 'rsrc/externals/javelin/lib/behavior.js' => '61cbc29a', 'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => 'ab5f468d', 'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => '70baed2f', 'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => 'e6e25838', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '8b3fd187', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '2818f5ce', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '6c0e62fa', 'rsrc/externals/raphael/g.raphael.js' => '40dde778', 'rsrc/externals/raphael/g.raphael.line.js' => '40da039e', 'rsrc/externals/raphael/raphael.js' => '51ee6b43', 'rsrc/favicons/apple-touch-icon-120x120.png' => '43742962', 'rsrc/favicons/apple-touch-icon-152x152.png' => '669eaec3', 'rsrc/favicons/apple-touch-icon-76x76.png' => 'ecdef672', 'rsrc/favicons/favicon-128.png' => '47cdff03', 'rsrc/favicons/favicon-16x16.png' => 'ee2523ac', 'rsrc/favicons/favicon-32x32.png' => 'b6a8150e', 'rsrc/favicons/favicon-96x96.png' => '8f7ea177', 'rsrc/favicons/mask-icon.svg' => '0460cb1f', 'rsrc/image/BFCFDA.png' => 'd5ec91f4', 'rsrc/image/actions/edit.png' => '2fc41442', - 'rsrc/image/avatar.png' => '3eb28cd9', + 'rsrc/image/avatar.png' => 'e132bb6a', 'rsrc/image/checker_dark.png' => 'd8e65881', 'rsrc/image/checker_light.png' => 'a0155918', 'rsrc/image/checker_lighter.png' => 'd5da91b6', 'rsrc/image/darkload.gif' => '1ffd3ec6', 'rsrc/image/divot.png' => '94dded62', 'rsrc/image/examples/hero.png' => '979a86ae', 'rsrc/image/grippy_texture.png' => 'aca81e2f', 'rsrc/image/icon/fatcow/arrow_branch.png' => '2537c01c', 'rsrc/image/icon/fatcow/arrow_merge.png' => '21b660e0', 'rsrc/image/icon/fatcow/bullet_black.png' => 'ff190031', 'rsrc/image/icon/fatcow/bullet_orange.png' => 'e273e5bb', 'rsrc/image/icon/fatcow/bullet_red.png' => 'c0b75434', 'rsrc/image/icon/fatcow/calendar_edit.png' => '24632275', 'rsrc/image/icon/fatcow/document_black.png' => '45fe1c60', 'rsrc/image/icon/fatcow/flag_blue.png' => 'a01abb1d', 'rsrc/image/icon/fatcow/flag_finish.png' => '67825cee', 'rsrc/image/icon/fatcow/flag_ghost.png' => '20ca8783', 'rsrc/image/icon/fatcow/flag_green.png' => '7e0eaa7a', 'rsrc/image/icon/fatcow/flag_orange.png' => '9e73df66', 'rsrc/image/icon/fatcow/flag_pink.png' => '7e92f3b2', 'rsrc/image/icon/fatcow/flag_purple.png' => 'cc517522', 'rsrc/image/icon/fatcow/flag_red.png' => '04ec726f', 'rsrc/image/icon/fatcow/flag_yellow.png' => '73946fd4', 'rsrc/image/icon/fatcow/key_question.png' => '52a0c26a', 'rsrc/image/icon/fatcow/link.png' => '7afd4d5e', 'rsrc/image/icon/fatcow/page_white_edit.png' => '39a2eed8', 'rsrc/image/icon/fatcow/page_white_put.png' => '08c95a0c', 'rsrc/image/icon/fatcow/source/conduit.png' => '4ea01d2f', 'rsrc/image/icon/fatcow/source/email.png' => '9bab3239', 'rsrc/image/icon/fatcow/source/fax.png' => '04195e68', 'rsrc/image/icon/fatcow/source/mobile.png' => 'f1321264', 'rsrc/image/icon/fatcow/source/tablet.png' => '49396799', 'rsrc/image/icon/fatcow/source/web.png' => '136ccb5d', 'rsrc/image/icon/lightbox/close-2.png' => 'cc40e7c8', 'rsrc/image/icon/lightbox/close-hover-2.png' => 'fb5d6d9e', 'rsrc/image/icon/lightbox/left-arrow-2.png' => '8426133b', 'rsrc/image/icon/lightbox/left-arrow-hover-2.png' => '701e5ee3', 'rsrc/image/icon/lightbox/right-arrow-2.png' => '6d5519a0', 'rsrc/image/icon/lightbox/right-arrow-hover-2.png' => '3a04aa21', 'rsrc/image/icon/subscribe.png' => 'd03ed5a5', 'rsrc/image/icon/tango/attachment.png' => 'ecc8022e', 'rsrc/image/icon/tango/edit.png' => '929a1363', 'rsrc/image/icon/tango/go-down.png' => '96d95e43', 'rsrc/image/icon/tango/log.png' => 'b08cc63a', 'rsrc/image/icon/tango/upload.png' => '7bbb7984', 'rsrc/image/icon/unsubscribe.png' => '25725013', 'rsrc/image/lightblue-header.png' => '5c168b6d', 'rsrc/image/main_texture.png' => '29a2c5ad', 'rsrc/image/menu_texture.png' => '5a17580d', 'rsrc/image/people/harding.png' => '45aa614e', 'rsrc/image/people/jefferson.png' => 'afca0e53', 'rsrc/image/people/lincoln.png' => '9369126d', 'rsrc/image/people/mckinley.png' => 'fb8f16ce', 'rsrc/image/people/taft.png' => 'd7bc402c', + 'rsrc/image/people/user0.png' => '03dacaea', + 'rsrc/image/people/user1.png' => '4a4e7702', + 'rsrc/image/people/user2.png' => '47a0ee40', + 'rsrc/image/people/user3.png' => '835ff627', + 'rsrc/image/people/user4.png' => 'b0e830f1', + 'rsrc/image/people/user5.png' => '9c95b369', + 'rsrc/image/people/user6.png' => 'ba3fbfb0', + 'rsrc/image/people/user7.png' => 'da613924', + 'rsrc/image/people/user8.png' => 'f1035edf', + 'rsrc/image/people/user9.png' => '66730be3', 'rsrc/image/people/washington.png' => '40dd301c', 'rsrc/image/phrequent_active.png' => 'a466a8ed', 'rsrc/image/phrequent_inactive.png' => 'bfc15a69', 'rsrc/image/sprite-login-X2.png' => 'e3991e37', 'rsrc/image/sprite-login.png' => '03d5af29', 'rsrc/image/sprite-main-header.png' => '3673af44', 'rsrc/image/sprite-menu-X2.png' => 'cfd8fca5', 'rsrc/image/sprite-menu.png' => 'd7a99faa', 'rsrc/image/sprite-projects-X2.png' => '853552c7', 'rsrc/image/sprite-projects.png' => 'b9dd74b8', 'rsrc/image/sprite-tokens-X2.png' => '348f1745', 'rsrc/image/sprite-tokens.png' => 'ce0b62be', 'rsrc/image/texture/card-gradient.png' => '815f26e8', 'rsrc/image/texture/dark-menu-hover.png' => '5fa7ece8', 'rsrc/image/texture/dark-menu.png' => '7e22296e', 'rsrc/image/texture/grip.png' => '719404f3', 'rsrc/image/texture/panel-header-gradient.png' => 'e3b8dcfe', 'rsrc/image/texture/phlnx-bg.png' => '8d819209', 'rsrc/image/texture/pholio-background.gif' => 'ba29239c', 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', 'rsrc/js/application/aphlict/Aphlict.js' => '5359e785', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '031cee25', 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'fb20ac8d', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/calendar/behavior-day-view.js' => '5c46cff2', 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', 'rsrc/js/application/calendar/behavior-recurring-edit.js' => '5f1c4d5f', 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', 'rsrc/js/application/conpherence/behavior-durable-column.js' => 'c72aa091', 'rsrc/js/application/conpherence/behavior-menu.js' => '1d45c74d', 'rsrc/js/application/conpherence/behavior-pontificate.js' => '21ba5861', 'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '7927a7d3', 'rsrc/js/application/conpherence/behavior-widget-pane.js' => 'a8458711', 'rsrc/js/application/countdown/timer.js' => 'e4cc26b3', 'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => 'edf8a145', 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e', 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '82439934', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/differential/ChangesetViewManager.js' => '58562350', 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '64a5550f', 'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => 'e10f8e18', 'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d', 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '2035b9cb', 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '65ef6074', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '2c426492', 'rsrc/js/application/differential/behavior-populate.js' => '8694b1df', 'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb', 'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d', 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'b42eddc7', 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a', 'rsrc/js/application/diffusion/behavior-commit-branches.js' => 'bdaf4d04', 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '9007c197', 'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef', 'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667', 'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947', 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc', 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781', 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', 'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 'rsrc/js/application/herald/HeraldRuleEditor.js' => '91a6031b', 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7', 'rsrc/js/application/maniphest/behavior-batch-selector.js' => '7b98d7c5', 'rsrc/js/application/maniphest/behavior-line-chart.js' => '88f0c5b3', 'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2', 'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '71237763', 'rsrc/js/application/maniphest/behavior-transaction-controls.js' => '44168bad', 'rsrc/js/application/maniphest/behavior-transaction-expand.js' => '5fefb143', 'rsrc/js/application/maniphest/behavior-transaction-preview.js' => '4c95d29e', 'rsrc/js/application/owners/OwnersPathEditor.js' => 'aa1733d0', 'rsrc/js/application/owners/owners-path-editor.js' => '7a68dda3', 'rsrc/js/application/passphrase/passphrase-credential-control.js' => '3cb0b2fc', 'rsrc/js/application/phame/phame-post-preview.js' => 'd6bba572', 'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => '246dc085', 'rsrc/js/application/pholio/behavior-pholio-mock-view.js' => 'fbe497e7', 'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '3f5d6dbf', 'rsrc/js/application/phortune/behavior-test-payment-form.js' => 'fc91ab6c', 'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef', 'rsrc/js/application/policy/behavior-policy-control.js' => '7d470398', 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', 'rsrc/js/application/projects/behavior-project-boards.js' => 'ba4fa35c', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', 'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb', 'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf', 'rsrc/js/application/releeph/releeph-request-state-change.js' => 'a0b57eb8', 'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f', 'rsrc/js/application/repository/repository-crossreference.js' => 'e5339c43', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08', 'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f', + 'rsrc/js/application/transactions/behavior-comment-actions.js' => 'f2c64202', 'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96', 'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'dbbf48b6', 'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => 'b23b49e6', 'rsrc/js/application/transactions/behavior-transaction-list.js' => '13c739ea', 'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '635de1ec', 'rsrc/js/application/typeahead/behavior-typeahead-search.js' => '93d0c9e3', 'rsrc/js/application/uiexample/JavelinViewExample.js' => 'd4a14807', 'rsrc/js/application/uiexample/ReactorButtonExample.js' => 'd19198c8', 'rsrc/js/application/uiexample/ReactorCheckboxExample.js' => '519705ea', 'rsrc/js/application/uiexample/ReactorFocusExample.js' => '40a6a403', 'rsrc/js/application/uiexample/ReactorInputExample.js' => '886fd850', 'rsrc/js/application/uiexample/ReactorMouseoverExample.js' => '47c794d8', 'rsrc/js/application/uiexample/ReactorRadioExample.js' => '988040b4', 'rsrc/js/application/uiexample/ReactorSelectExample.js' => 'a155550f', 'rsrc/js/application/uiexample/ReactorSendClassExample.js' => '1def2711', 'rsrc/js/application/uiexample/ReactorSendPropertiesExample.js' => 'b1f0ccee', 'rsrc/js/application/uiexample/busy-example.js' => '60479091', 'rsrc/js/application/uiexample/gesture-example.js' => '558829c2', 'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5', 'rsrc/js/core/Busy.js' => '59a7976a', 'rsrc/js/core/DragAndDropFileUpload.js' => 'ad10aeac', 'rsrc/js/core/DraggableList.js' => 'a16ec1c6', 'rsrc/js/core/FileUpload.js' => '477359c8', 'rsrc/js/core/Hovercard.js' => '14ac66f5', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 'rsrc/js/core/Notification.js' => 'ccf1cbf8', 'rsrc/js/core/Prefab.js' => '6920d200', 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '5c93c52c', 'rsrc/js/core/Title.js' => 'df5e11d2', 'rsrc/js/core/ToolTip.js' => '1d298e3a', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 'rsrc/js/core/behavior-choose-control.js' => '6153c708', 'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2', 'rsrc/js/core/behavior-dark-console.js' => 'f411b6ae', 'rsrc/js/core/behavior-device.js' => 'a205cf28', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', 'rsrc/js/core/behavior-fancy-datepicker.js' => '8ae55229', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', 'rsrc/js/core/behavior-global-drag-and-drop.js' => 'c8e57404', 'rsrc/js/core/behavior-high-security-warning.js' => 'a464fe03', 'rsrc/js/core/behavior-history-install.js' => '7ee2b591', 'rsrc/js/core/behavior-hovercard.js' => 'f36e01af', 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', 'rsrc/js/core/behavior-keyboard-shortcuts.js' => 'd75709e6', 'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7', 'rsrc/js/core/behavior-line-linker.js' => '1499a8cb', 'rsrc/js/core/behavior-more.js' => 'a80d0378', 'rsrc/js/core/behavior-object-selector.js' => '49b73b36', 'rsrc/js/core/behavior-oncopy.js' => '2926fff2', 'rsrc/js/core/behavior-phabricator-nav.js' => '56a1ca03', 'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'eeaa9e5a', 'rsrc/js/core/behavior-refresh-csrf.js' => '7814b593', 'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45', 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', 'rsrc/js/core/behavior-search-typeahead.js' => '048330fa', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-time-typeahead.js' => 'f80d6bf0', 'rsrc/js/core/behavior-toggle-class.js' => '5d7c9f33', 'rsrc/js/core/behavior-tokenizer.js' => 'b3a4b884', 'rsrc/js/core/behavior-tooltip.js' => '3ee3408b', 'rsrc/js/core/behavior-watch-anchor.js' => '9f36c42d', 'rsrc/js/core/behavior-workflow.js' => '0a3f3021', 'rsrc/js/core/phtize.js' => 'd254d646', 'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '54733475', 'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca', + 'rsrc/js/phuix/PHUIXFormControl.js' => 'f9fba5ee', + 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', ), 'symbols' => array( 'almanac-css' => 'dbb9b3af', 'aphront-bars' => '231ac33c', 'aphront-dark-console-css' => '6378ef3d', 'aphront-dialog-view-css' => 'be0e3a46', 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => 'fd18389d', 'aphront-panel-view-css' => '8427b78d', 'aphront-table-view-css' => '6d01d468', 'aphront-tokenizer-control-css' => '056da01b', 'aphront-tooltip-css' => '7672b60f', 'aphront-typeahead-control-css' => '0e403212', 'auth-css' => '0877ed6e', 'bulk-job-css' => 'df9c1d4a', 'calendar-icon-css' => 'c69aa59f', 'changeset-view-manager' => '58562350', 'conduit-api-css' => '7bc725c4', 'config-options-css' => '0ede4c9b', 'config-welcome-css' => '6abd79be', 'conpherence-durable-column-view' => '86396117', 'conpherence-menu-css' => 'f99fee4c', 'conpherence-message-pane-css' => '5897d3ac', 'conpherence-notification-css' => '6cdcc253', 'conpherence-thread-manager' => '01774ab2', 'conpherence-transaction-css' => '85d0974c', 'conpherence-update-css' => 'faf6be09', 'conpherence-widget-pane-css' => '775eaaba', 'differential-changeset-view-css' => 'b6b0d1bb', 'differential-core-view-css' => '7ac3cabc', 'differential-inline-comment-editor' => '64a5550f', 'differential-revision-add-comment-css' => 'c47f8c40', 'differential-revision-comment-css' => '14b8565a', 'differential-revision-history-css' => '0e8eb855', 'differential-revision-list-css' => 'f3c47d33', 'differential-table-of-contents-css' => 'ae4b7a55', 'diffusion-icons-css' => '2941baf1', 'diffusion-readme-css' => '2106ea08', 'diffusion-source-css' => '075ba788', 'diviner-shared-css' => 'aa3656aa', 'font-aleo' => '8bdb2835', 'font-fontawesome' => 'c43323c5', 'font-lato' => 'c7ccd872', 'global-drag-and-drop-css' => '697324ad', 'harbormaster-css' => 'b0758ca5', 'herald-css' => '826075fa', 'herald-rule-editor' => '91a6031b', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', 'javelin-aphlict' => '5359e785', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => '031cee25', 'javelin-behavior-aphlict-listen' => 'fb20ac8d', 'javelin-behavior-aphlict-status' => 'ea681761', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-crop' => 'fa0f4fc2', 'javelin-behavior-aphront-drag-and-drop-textarea' => '6d49590e', 'javelin-behavior-aphront-form-disable-on-submit' => '5c54cbf3', 'javelin-behavior-aphront-more' => 'a80d0378', 'javelin-behavior-audio-source' => '59b251eb', 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-bulk-job-reload' => 'edf8a145', 'javelin-behavior-choose-control' => '6153c708', + 'javelin-behavior-comment-actions' => 'f2c64202', 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', 'javelin-behavior-conpherence-menu' => '1d45c74d', 'javelin-behavior-conpherence-pontificate' => '21ba5861', 'javelin-behavior-conpherence-widget-pane' => 'a8458711', 'javelin-behavior-countdown-timer' => 'e4cc26b3', 'javelin-behavior-dark-console' => 'f411b6ae', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', 'javelin-behavior-dashboard-move-panels' => '82439934', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', 'javelin-behavior-day-view' => '5c46cff2', 'javelin-behavior-desktop-notifications-control' => 'edd1ba66', 'javelin-behavior-device' => 'a205cf28', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-dropdown-menus' => '2035b9cb', 'javelin-behavior-differential-edit-inline-comments' => '65ef6074', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '2c426492', 'javelin-behavior-differential-populate' => '8694b1df', 'javelin-behavior-differential-toggle-files' => 'ca3f91eb', 'javelin-behavior-differential-user-select' => 'a8d8459d', 'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04', 'javelin-behavior-diffusion-commit-graph' => '9007c197', 'javelin-behavior-diffusion-jump-to' => '73d09eef', 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-drydock-live-operation-status' => '901935ef', 'javelin-behavior-durable-column' => 'c72aa091', 'javelin-behavior-editengine-reorder-fields' => 'b59e1e96', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => '38dcf3c8', 'javelin-behavior-fancy-datepicker' => '8ae55229', 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-high-security-warning' => 'a464fe03', 'javelin-behavior-history-install' => '7ee2b591', 'javelin-behavior-icon-composer' => '8ef9ab58', 'javelin-behavior-launch-icon-composer' => '48086888', 'javelin-behavior-lightbox-attachments' => 'f8ba29d7', 'javelin-behavior-line-chart' => '88f0c5b3', 'javelin-behavior-load-blame' => '42126667', 'javelin-behavior-maniphest-batch-editor' => '782ab6e7', 'javelin-behavior-maniphest-batch-selector' => '7b98d7c5', 'javelin-behavior-maniphest-list-editor' => 'a9f88de2', 'javelin-behavior-maniphest-subpriority-editor' => '71237763', 'javelin-behavior-maniphest-transaction-controls' => '44168bad', 'javelin-behavior-maniphest-transaction-expand' => '5fefb143', 'javelin-behavior-maniphest-transaction-preview' => '4c95d29e', 'javelin-behavior-owners-path-editor' => '7a68dda3', 'javelin-behavior-passphrase-credential-control' => '3cb0b2fc', 'javelin-behavior-persona-login' => '9414ff18', 'javelin-behavior-phabricator-active-nav' => 'e379b58e', 'javelin-behavior-phabricator-autofocus' => '7319e029', 'javelin-behavior-phabricator-busy-example' => '60479091', 'javelin-behavior-phabricator-file-tree' => '88236f00', 'javelin-behavior-phabricator-gesture' => '3ab51e2c', 'javelin-behavior-phabricator-gesture-example' => '558829c2', 'javelin-behavior-phabricator-hovercards' => 'f36e01af', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => 'd75709e6', 'javelin-behavior-phabricator-line-linker' => '1499a8cb', 'javelin-behavior-phabricator-nav' => '56a1ca03', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', 'javelin-behavior-phabricator-object-selector' => '49b73b36', 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => 'eeaa9e5a', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', 'javelin-behavior-phabricator-search-typeahead' => '048330fa', 'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6', 'javelin-behavior-phabricator-tooltips' => '3ee3408b', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', 'javelin-behavior-phabricator-transaction-list' => '13c739ea', 'javelin-behavior-phabricator-watch-anchor' => '9f36c42d', 'javelin-behavior-phame-post-preview' => 'd6bba572', 'javelin-behavior-pholio-mock-edit' => '246dc085', 'javelin-behavior-pholio-mock-view' => 'fbe497e7', 'javelin-behavior-phui-dropdown-menu' => '54733475', 'javelin-behavior-phui-object-box-tabs' => '2bfa2836', 'javelin-behavior-policy-control' => '7d470398', 'javelin-behavior-policy-rule-editor' => '5e9f347c', 'javelin-behavior-project-boards' => 'ba4fa35c', 'javelin-behavior-project-create' => '065227cc', 'javelin-behavior-quicksand-blacklist' => '7927a7d3', 'javelin-behavior-recurring-edit' => '5f1c4d5f', 'javelin-behavior-refresh-csrf' => '7814b593', 'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf', 'javelin-behavior-releeph-request-state-change' => 'a0b57eb8', 'javelin-behavior-releeph-request-typeahead' => 'de2e896f', 'javelin-behavior-remarkup-preview' => 'f7379f45', 'javelin-behavior-reorder-applications' => '76b9fc3e', 'javelin-behavior-reorder-columns' => 'e1d25dfb', 'javelin-behavior-repository-crossreference' => 'e5339c43', 'javelin-behavior-scrollbar' => '834a1173', 'javelin-behavior-search-reorder-queries' => 'e9581f08', 'javelin-behavior-select-on-click' => '4e3e79a6', 'javelin-behavior-slowvote-embed' => '887ad43f', 'javelin-behavior-stripe-payment-form' => '3f5d6dbf', 'javelin-behavior-test-payment-form' => 'fc91ab6c', 'javelin-behavior-time-typeahead' => 'f80d6bf0', 'javelin-behavior-toggle-class' => '5d7c9f33', 'javelin-behavior-typeahead-browse' => '635de1ec', 'javelin-behavior-typeahead-search' => '93d0c9e3', 'javelin-behavior-view-placeholder' => '47830651', 'javelin-behavior-workflow' => '0a3f3021', 'javelin-color' => '7e41274a', 'javelin-cookie' => '62dfea03', 'javelin-diffusion-locate-file-source' => 'b42eddc7', 'javelin-dom' => '805b806a', 'javelin-dynval' => 'f6555212', 'javelin-event' => '85ea0626', 'javelin-fx' => '54b612ba', 'javelin-history' => 'd4505101', 'javelin-install' => '05270951', 'javelin-json' => '69adf288', 'javelin-leader' => '331b1611', 'javelin-magical-init' => '3010e992', 'javelin-mask' => '8a41885b', 'javelin-quicksand' => '4cebc641', 'javelin-reactor' => '2b8de964', 'javelin-reactor-dom' => 'c90a04fc', 'javelin-reactor-node-calmer' => '76f4ebed', 'javelin-reactornode' => '1ad0a787', 'javelin-request' => '94b750d2', 'javelin-resource' => '44959b73', 'javelin-routable' => 'b3e7d692', 'javelin-router' => '29274e2b', 'javelin-scrollbar' => '087e919c', 'javelin-sound' => '949c0fe5', 'javelin-stratcom' => '6c53634d', 'javelin-tokenizer' => 'ab5f468d', 'javelin-typeahead' => '70baed2f', 'javelin-typeahead-composite-source' => '503e17fd', 'javelin-typeahead-normalizer' => 'e6e25838', 'javelin-typeahead-ondemand-source' => '8b3fd187', 'javelin-typeahead-preloaded-source' => '54f314a0', 'javelin-typeahead-source' => '2818f5ce', 'javelin-typeahead-static-source' => '6c0e62fa', 'javelin-uri' => '6eff08aa', 'javelin-util' => '93cc50d6', 'javelin-vector' => '2caa8fb8', 'javelin-view' => '0f764c35', 'javelin-view-html' => 'fe287620', 'javelin-view-interpreter' => 'f829edb3', 'javelin-view-renderer' => '6c2b09a2', 'javelin-view-visitor' => 'efe49472', 'javelin-websocket' => 'e292eaf4', 'javelin-workflow' => '5b2e3e2b', 'lightbox-attachment-css' => '7acac05d', 'maniphest-batch-editor' => 'b0f0b6d5', 'maniphest-report-css' => 'f6931fdf', 'maniphest-task-edit-css' => 'fda62a9b', 'maniphest-task-summary-css' => '11cc5344', 'multirow-row-manager' => 'b5d57730', 'owners-path-editor' => 'aa1733d0', 'owners-path-editor-css' => '2f00933b', 'paste-css' => 'b2f5a543', 'path-typeahead' => 'f7fc67ec', 'people-profile-css' => '25970776', 'phabricator-action-list-view-css' => 'c5eba19d', 'phabricator-application-launch-view-css' => '95351601', 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '78e8d7ea', + 'phabricator-core-css' => 'a76cefc9', 'phabricator-countdown-css' => 'e7544472', 'phabricator-dashboard-css' => 'eb458607', 'phabricator-drag-and-drop-file-upload' => 'ad10aeac', 'phabricator-draggable-list' => 'a16ec1c6', 'phabricator-fatal-config-template-css' => '8e6c6fcd', 'phabricator-feed-css' => 'ecd4ec57', 'phabricator-file-upload' => '477359c8', 'phabricator-filetree-view-css' => 'fccf9f82', 'phabricator-flag-css' => '5337623f', 'phabricator-hovercard' => '14ac66f5', 'phabricator-hovercard-view-css' => '1239cd52', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'c1700f6f', 'phabricator-main-menu-view' => '2f670a96', 'phabricator-nav-view-css' => 'a24cb589', 'phabricator-notification' => 'ccf1cbf8', 'phabricator-notification-css' => '9c279160', 'phabricator-notification-menu-css' => 'f31c0bde', 'phabricator-object-selector-css' => '85ee8ce6', 'phabricator-phtize' => 'd254d646', 'phabricator-prefab' => '6920d200', - 'phabricator-remarkup-css' => '88e1ebb6', + 'phabricator-remarkup-css' => 'b1c10368', 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-side-menu-view-css' => 'bec2458e', 'phabricator-slowvote-css' => 'da0afb1b', 'phabricator-source-code-view-css' => 'cbeef983', - 'phabricator-standard-page-view' => 'a1096ed4', + 'phabricator-standard-page-view' => '3c99cdf4', 'phabricator-textareautils' => '5c93c52c', 'phabricator-title' => 'df5e11d2', 'phabricator-tooltip' => '1d298e3a', 'phabricator-ui-example-css' => '528b19de', 'phabricator-uiexample-javelin-view' => 'd4a14807', 'phabricator-uiexample-reactor-button' => 'd19198c8', 'phabricator-uiexample-reactor-checkbox' => '519705ea', 'phabricator-uiexample-reactor-focus' => '40a6a403', 'phabricator-uiexample-reactor-input' => '886fd850', 'phabricator-uiexample-reactor-mouseover' => '47c794d8', 'phabricator-uiexample-reactor-radio' => '988040b4', 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 'phabricator-zindex-css' => '57ddcaa2', - 'phame-css' => 'bb147387', + 'phame-css' => 'cea3c9e1', 'pholio-css' => '95174bdd', 'pholio-edit-css' => '3ad9d1ee', 'pholio-inline-comments-css' => '8e545e49', 'phortune-credit-card-form' => '2290aeef', 'phortune-credit-card-form-css' => '8391eb02', 'phortune-css' => '9149f103', 'phrequent-css' => 'ffc185ad', 'phriction-document-css' => 'd1861e06', 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => 'f25c3476', 'phui-box-css' => 'a5bb366d', 'phui-button-css' => '16020a60', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', 'phui-calendar-list-css' => 'c1c7f338', 'phui-calendar-month-css' => '476be7e0', 'phui-crumbs-view-css' => '414406b5', + 'phui-document-summary-view-css' => '8c1e0aca', 'phui-document-view-css' => 'a4a1c3b9', - 'phui-document-view-pro-css' => '5f75ed99', + 'phui-document-view-pro-css' => 'e0fad431', 'phui-feed-story-css' => 'b7b26d23', 'phui-font-icon-base-css' => 'ecbbb4c2', 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'afdb2c6e', 'phui-form-view-css' => 'c1d2ef29', 'phui-header-view-css' => '55bb32dd', 'phui-icon-view-css' => 'b0a6b1b6', 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '6d7c3509', 'phui-inline-comment-view-css' => '0fdb3667', 'phui-list-view-css' => '125599df', 'phui-object-box-css' => '407eaf5a', 'phui-object-item-list-view-css' => '26c30d3f', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', 'phui-property-list-view-css' => '27b2849e', 'phui-remarkup-preview-css' => '1a8f2591', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => '888cedb8', 'phui-tag-view-css' => 'e60e227b', 'phui-text-css' => 'cf019f54', 'phui-theme-css' => '6b451f24', 'phui-timeline-view-css' => '2efceff8', 'phui-two-column-view-css' => '39ecafb1', 'phui-workboard-view-css' => '6704d68d', 'phui-workpanel-view-css' => 'adec7699', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '8cf6d262', 'phuix-dropdown-menu' => 'bd4c8dca', + 'phuix-form-control-view' => 'f9fba5ee', + 'phuix-icon-view' => 'bff6884b', 'policy-css' => '957ea14c', 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', 'ponder-view-css' => '7b0df4da', 'project-icon-css' => '4e3eaa5a', 'raphael-core' => '51ee6b43', 'raphael-g' => '40dde778', 'raphael-g-line' => '40da039e', 'releeph-core' => '9b3c5733', 'releeph-preview-branch' => 'b7a6f4a5', 'releeph-request-differential-create-dialog' => '8d8b92cd', 'releeph-request-typeahead-css' => '667a48ae', 'setup-issue-css' => 'db7e9c40', 'sprite-login-css' => '60e8560e', 'sprite-main-header-css' => 'f07bbb87', 'sprite-menu-css' => '9dd65b92', 'sprite-projects-css' => 'e5ad842a', 'sprite-tokens-css' => '4f399012', 'syntax-highlighting-css' => '9fd11da8', 'tokens-css' => '3d0f239e', 'typeahead-browse-css' => 'd8581d2c', 'unhandled-exception-css' => '4c96257a', ), 'requires' => array( '01774ab2' => array( 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-install', 'javelin-aphlict', 'javelin-workflow', 'javelin-router', 'javelin-behavior-device', 'javelin-vector', ), '031cee25' => array( 'javelin-behavior', 'javelin-request', 'javelin-stratcom', 'javelin-vector', 'javelin-dom', 'javelin-uri', 'javelin-behavior-device', 'phabricator-title', ), '048330fa' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', 'javelin-typeahead', 'javelin-dom', 'javelin-uri', 'javelin-util', 'javelin-stratcom', 'phabricator-prefab', ), '05270951' => array( 'javelin-util', 'javelin-magical-init', ), '056da01b' => array( 'aphront-typeahead-control-css', 'phui-tag-view-css', ), '065227cc' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-workflow', ), '087e919c' => array( 'javelin-install', 'javelin-dom', 'javelin-stratcom', 'javelin-vector', ), '0a3f3021' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', 'javelin-router', ), '0f764c35' => array( 'javelin-install', 'javelin-util', ), '13c739ea' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', 'javelin-uri', 'phabricator-textareautils', ), '1499a8cb' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-history', ), '14ac66f5' => array( 'javelin-install', 'javelin-dom', 'javelin-vector', 'javelin-request', 'javelin-uri', ), '1ad0a787' => array( 'javelin-install', 'javelin-reactor', 'javelin-util', 'javelin-reactor-node-calmer', ), '1ae869f2' => array( 'javelin-install', 'javelin-util', 'phabricator-keyboard-shortcut-manager', ), '1d298e3a' => array( 'javelin-install', 'javelin-util', 'javelin-dom', 'javelin-vector', ), '1d45c74d' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-workflow', 'javelin-behavior-device', 'javelin-history', 'javelin-vector', 'javelin-scrollbar', 'phabricator-title', 'phabricator-shaped-request', 'conpherence-thread-manager', ), '1def2711' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), '2035b9cb' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-workflow', 'phuix-dropdown-menu', 'phuix-action-list-view', 'phuix-action-view', 'phabricator-phtize', 'changeset-view-manager', ), '21ba5861' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-workflow', 'javelin-stratcom', 'conpherence-thread-manager', ), '2290aeef' => array( 'javelin-install', 'javelin-dom', 'javelin-json', 'javelin-workflow', 'javelin-util', ), '246dc085' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-workflow', 'javelin-quicksand', 'phabricator-phtize', 'phabricator-drag-and-drop-file-upload', 'phabricator-draggable-list', ), '2818f5ce' => array( 'javelin-install', 'javelin-util', 'javelin-dom', 'javelin-typeahead-normalizer', ), '2926fff2' => array( 'javelin-behavior', 'javelin-dom', ), '29274e2b' => array( 'javelin-install', 'javelin-util', ), '2b8de964' => array( 'javelin-install', 'javelin-util', ), '2bfa2836' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), '2c426492' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'phabricator-keyboard-shortcut', ), '2caa8fb8' => array( 'javelin-install', 'javelin-event', ), '2f670a96' => array( 'phui-theme-css', ), '331b1611' => array( 'javelin-install', ), '3ab51e2c' => array( 'javelin-behavior', 'javelin-behavior-device', 'javelin-stratcom', 'javelin-vector', 'javelin-dom', 'javelin-magical-init', ), '3cb0b2fc' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-workflow', 'javelin-util', 'javelin-uri', ), '3ee3408b' => array( 'javelin-behavior', 'javelin-behavior-device', 'javelin-stratcom', 'phabricator-tooltip', ), '3f5d6dbf' => array( 'javelin-behavior', 'javelin-dom', 'phortune-credit-card-form', ), '40a6a403' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), 42126667 => array( 'javelin-behavior', 'javelin-dom', 'javelin-request', ), '44168bad' => array( 'javelin-behavior', 'javelin-dom', 'phabricator-prefab', ), '44959b73' => array( 'javelin-util', 'javelin-uri', 'javelin-install', ), '453c5375' => array( 'javelin-behavior', 'javelin-dom', ), '469c0d9e' => array( 'javelin-behavior', 'javelin-dom', 'javelin-workflow', ), '477359c8' => array( 'javelin-install', 'javelin-dom', 'phabricator-notification', ), 47830651 => array( 'javelin-behavior', 'javelin-dom', 'javelin-view-renderer', 'javelin-install', ), '47c794d8' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), 48086888 => array( 'javelin-behavior', 'javelin-dom', 'javelin-workflow', ), '49b73b36' => array( 'javelin-behavior', 'javelin-dom', 'javelin-request', 'javelin-util', ), '4c95d29e' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-json', 'javelin-stratcom', 'phabricator-shaped-request', ), '4cebc641' => array( 'javelin-install', ), '4e3e79a6' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), '4fdb476d' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), '503e17fd' => array( 'javelin-install', 'javelin-typeahead-source', 'javelin-util', ), '519705ea' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), '5359e785' => array( 'javelin-install', 'javelin-util', 'javelin-websocket', 'javelin-leader', 'javelin-json', ), 54733475 => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'phuix-dropdown-menu', ), '54b612ba' => array( 'javelin-color', 'javelin-install', 'javelin-util', ), '54f314a0' => array( 'javelin-install', 'javelin-util', 'javelin-request', 'javelin-typeahead-source', ), '558829c2' => array( 'javelin-stratcom', 'javelin-behavior', 'javelin-vector', 'javelin-dom', ), '56a1ca03' => array( 'javelin-behavior', 'javelin-behavior-device', 'javelin-stratcom', 'javelin-dom', 'javelin-magical-init', 'javelin-vector', 'javelin-request', 'javelin-util', ), 58562350 => array( 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-install', 'javelin-workflow', 'javelin-router', 'javelin-behavior-device', 'javelin-vector', ), '59a7976a' => array( 'javelin-install', 'javelin-dom', 'javelin-fx', ), '59b251eb' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-vector', 'javelin-dom', ), '5b2e3e2b' => array( 'javelin-stratcom', 'javelin-request', 'javelin-dom', 'javelin-vector', 'javelin-install', 'javelin-util', 'javelin-mask', 'javelin-uri', 'javelin-routable', ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), '5c93c52c' => array( 'javelin-install', 'javelin-dom', 'javelin-vector', ), '5d7c9f33' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), '5e9f347c' => array( 'javelin-behavior', 'multirow-row-manager', 'javelin-dom', 'javelin-util', 'phabricator-prefab', 'javelin-json', ), '5fefb143' => array( 'javelin-behavior', 'javelin-dom', 'javelin-workflow', 'javelin-stratcom', ), 60479091 => array( 'phabricator-busy', 'javelin-behavior', ), '60821bc7' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), '6153c708' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-workflow', ), '61cbc29a' => array( 'javelin-magical-init', 'javelin-util', ), '62dfea03' => array( 'javelin-install', 'javelin-util', ), '635de1ec' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', ), '64a5550f' => array( 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-install', 'javelin-request', 'javelin-workflow', ), '65ef6074' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-util', 'javelin-vector', 'differential-inline-comment-editor', ), '6882e80a' => array( 'javelin-dom', ), '6920d200' => array( 'javelin-install', 'javelin-util', 'javelin-dom', 'javelin-typeahead', 'javelin-tokenizer', 'javelin-typeahead-preloaded-source', 'javelin-typeahead-ondemand-source', 'javelin-dom', 'javelin-stratcom', 'javelin-util', ), '69adf288' => array( 'javelin-install', ), '6c0e62fa' => array( 'javelin-install', 'javelin-typeahead-source', ), '6c2b09a2' => array( 'javelin-install', 'javelin-util', ), '6c53634d' => array( 'javelin-install', 'javelin-event', 'javelin-util', 'javelin-magical-init', ), '6d3e1947' => array( 'javelin-behavior', 'javelin-diffusion-locate-file-source', 'javelin-dom', 'javelin-typeahead', 'javelin-uri', ), '6d49590e' => array( 'javelin-behavior', 'javelin-dom', 'phabricator-drag-and-drop-file-upload', 'phabricator-textareautils', ), '6eff08aa' => array( 'javelin-install', 'javelin-util', 'javelin-stratcom', ), '70baed2f' => array( 'javelin-install', 'javelin-dom', 'javelin-vector', 'javelin-util', ), 71237763 => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-workflow', 'phabricator-draggable-list', ), '7319e029' => array( 'javelin-behavior', 'javelin-dom', ), '73d09eef' => array( 'javelin-behavior', 'javelin-vector', 'javelin-dom', ), '76b9fc3e' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', 'phabricator-draggable-list', ), '76f4ebed' => array( 'javelin-install', 'javelin-reactor', 'javelin-util', ), '7814b593' => array( 'javelin-request', 'javelin-behavior', 'javelin-dom', 'javelin-router', 'javelin-util', 'phabricator-busy', ), '782ab6e7' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'phabricator-prefab', 'multirow-row-manager', 'javelin-json', ), '7927a7d3' => array( 'javelin-behavior', 'javelin-quicksand', ), '7a68dda3' => array( 'owners-path-editor', 'javelin-behavior', ), '7b98d7c5' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-util', ), '7cbe244b' => array( 'javelin-install', 'javelin-util', 'javelin-request', 'javelin-router', ), '7d470398' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'phuix-dropdown-menu', 'phuix-action-list-view', 'phuix-action-view', 'javelin-workflow', ), '7e41274a' => array( 'javelin-install', ), '7ebaeed3' => array( 'herald-rule-editor', 'javelin-behavior', ), '7ee2b591' => array( 'javelin-behavior', 'javelin-history', ), '805b806a' => array( 'javelin-magical-init', 'javelin-install', 'javelin-util', 'javelin-vector', 'javelin-stratcom', ), 82439934 => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-workflow', 'phabricator-draggable-list', ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', ), '85ea0626' => array( 'javelin-install', ), '85ee8ce6' => array( 'aphront-dialog-view-css', ), '8694b1df' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'phabricator-tooltip', 'changeset-view-manager', ), '88236f00' => array( 'javelin-behavior', 'phabricator-keyboard-shortcut', 'javelin-stratcom', ), '886fd850' => array( 'javelin-install', 'javelin-reactor-dom', 'javelin-view-html', 'javelin-view-interpreter', 'javelin-view-renderer', ), '887ad43f' => array( 'javelin-behavior', 'javelin-request', 'javelin-stratcom', 'javelin-dom', ), '88f0c5b3' => array( 'javelin-behavior', 'javelin-dom', 'javelin-vector', ), '8a41885b' => array( 'javelin-install', 'javelin-dom', ), '8ae55229' => array( 'javelin-behavior', 'javelin-util', 'javelin-dom', 'javelin-stratcom', 'javelin-vector', ), '8b3fd187' => array( 'javelin-install', 'javelin-util', 'javelin-request', 'javelin-typeahead-source', ), '8bdb2835' => array( 'phui-fontkit-css', ), '8ce821c5' => array( 'phabricator-notification', 'javelin-stratcom', 'javelin-behavior', ), '8cf6d262' => array( 'javelin-install', 'javelin-dom', 'javelin-util', ), '8ef9ab58' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', ), '9007c197' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', ), '901935ef' => array( 'javelin-behavior', 'javelin-dom', 'javelin-request', ), '91a6031b' => array( 'multirow-row-manager', 'javelin-install', 'javelin-util', 'javelin-dom', 'javelin-stratcom', 'javelin-json', 'phabricator-prefab', ), '93d0c9e3' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', ), '9414ff18' => array( 'javelin-behavior', 'javelin-resource', 'javelin-stratcom', 'javelin-workflow', 'javelin-util', ), '949c0fe5' => array( 'javelin-install', ), '94b750d2' => array( 'javelin-install', 'javelin-stratcom', 'javelin-util', 'javelin-behavior', 'javelin-json', 'javelin-dom', 'javelin-resource', 'javelin-routable', ), '988040b4' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), '9f36c42d' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-vector', ), 'a0b57eb8' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-workflow', 'javelin-util', 'phabricator-keyboard-shortcut', ), 'a155550f' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), 'a16ec1c6' => array( 'javelin-install', 'javelin-dom', 'javelin-stratcom', 'javelin-util', 'javelin-vector', 'javelin-magical-init', ), 'a205cf28' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-vector', 'javelin-install', ), 'a464fe03' => array( 'javelin-behavior', 'javelin-uri', 'phabricator-notification', ), 'a80d0378' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), 'a8458711' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-workflow', 'javelin-util', 'phabricator-notification', 'javelin-behavior-device', 'phuix-dropdown-menu', 'phuix-action-list-view', 'phuix-action-view', 'conpherence-thread-manager', ), 'a8d8459d' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', ), 'a8da01f0' => array( 'javelin-behavior', 'javelin-uri', 'phabricator-keyboard-shortcut', ), 'a9f88de2' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-workflow', 'javelin-fx', 'javelin-util', ), 'aa1733d0' => array( 'multirow-row-manager', 'javelin-install', 'path-typeahead', 'javelin-dom', 'javelin-util', 'phabricator-prefab', ), 'ab5f468d' => array( 'javelin-dom', 'javelin-util', 'javelin-stratcom', 'javelin-install', ), 'ad10aeac' => array( 'javelin-install', 'javelin-util', 'javelin-request', 'javelin-dom', 'javelin-uri', 'phabricator-file-upload', ), 'b064af76' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-request', 'javelin-util', 'phabricator-shaped-request', ), 'b1f0ccee' => array( 'javelin-install', 'javelin-dom', 'javelin-reactor-dom', ), 'b23b49e6' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-request', 'phabricator-shaped-request', ), 'b2b4fbaf' => array( 'javelin-behavior', 'javelin-dom', 'javelin-uri', 'javelin-request', ), 'b3a4b884' => array( 'javelin-behavior', 'phabricator-prefab', ), 'b3e7d692' => array( 'javelin-install', ), 'b42eddc7' => array( 'javelin-install', 'javelin-dom', 'javelin-typeahead-preloaded-source', 'javelin-util', ), 'b59e1e96' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', 'phabricator-draggable-list', ), 'b5c256b8' => array( 'javelin-install', 'javelin-dom', ), 'b5d57730' => array( 'javelin-install', 'javelin-stratcom', 'javelin-dom', 'javelin-util', ), 'b6993408' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-json', 'phabricator-draggable-list', ), 'b6b0d1bb' => array( 'phui-inline-comment-view-css', ), 'ba4fa35c' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-vector', 'javelin-stratcom', 'javelin-workflow', 'phabricator-draggable-list', ), 'bd4c8dca' => array( 'javelin-install', 'javelin-util', 'javelin-dom', 'javelin-vector', 'javelin-stratcom', ), 'bdaf4d04' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-request', ), + 'bff6884b' => array( + 'javelin-install', + 'javelin-dom', + ), 'c1700f6f' => array( 'javelin-install', 'javelin-util', 'javelin-stratcom', 'javelin-dom', 'javelin-vector', ), 'c72aa091' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-behavior-device', 'javelin-scrollbar', 'javelin-quicksand', 'phabricator-keyboard-shortcut', 'conpherence-thread-manager', ), 'c7ccd872' => array( 'phui-fontkit-css', ), 'c8e57404' => array( 'javelin-behavior', 'javelin-dom', 'javelin-uri', 'javelin-mask', 'phabricator-drag-and-drop-file-upload', ), 'c90a04fc' => array( 'javelin-dom', 'javelin-dynval', 'javelin-reactor', 'javelin-reactornode', 'javelin-install', 'javelin-util', ), 'ca3f91eb' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'phabricator-phtize', ), 'ccf1cbf8' => array( 'javelin-install', 'javelin-dom', 'javelin-stratcom', 'javelin-util', 'phabricator-notification-css', ), 'cf86d16a' => array( 'javelin-behavior', 'javelin-dom', 'javelin-workflow', 'phabricator-drag-and-drop-file-upload', ), 'd19198c8' => array( 'javelin-install', 'javelin-dom', 'javelin-util', 'javelin-dynval', 'javelin-reactor-dom', ), 'd254d646' => array( 'javelin-util', ), 'd4505101' => array( 'javelin-stratcom', 'javelin-install', 'javelin-uri', 'javelin-util', ), 'd4a14807' => array( 'javelin-install', 'javelin-dom', 'javelin-view', ), 'd4eecc63' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', ), 'd6bba572' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'phabricator-shaped-request', ), 'd75709e6' => array( 'javelin-behavior', 'javelin-workflow', 'javelin-json', 'javelin-dom', 'phabricator-keyboard-shortcut', ), 'd835b03a' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'phabricator-shaped-request', ), 'dbbf48b6' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'phabricator-busy', ), 'de2e896f' => array( 'javelin-behavior', 'javelin-dom', 'javelin-typeahead', 'javelin-typeahead-ondemand-source', 'javelin-dom', ), 'df5e11d2' => array( 'javelin-install', ), 'e10f8e18' => array( 'javelin-behavior', 'javelin-dom', 'phabricator-prefab', ), 'e1d25dfb' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', 'phabricator-draggable-list', ), 'e1ff79b1' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', ), 'e292eaf4' => array( 'javelin-install', ), 'e379b58e' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-vector', 'javelin-dom', 'javelin-uri', ), 'e4cc26b3' => array( 'javelin-behavior', 'javelin-dom', ), 'e5339c43' => array( 'javelin-behavior', 'javelin-dom', 'javelin-stratcom', 'javelin-uri', ), 'e5822781' => array( 'javelin-behavior', 'javelin-dom', 'javelin-json', 'javelin-workflow', 'javelin-magical-init', ), 'e6e25838' => array( 'javelin-install', ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-workflow', 'javelin-dom', 'phabricator-draggable-list', ), 'ea681761' => array( 'javelin-behavior', 'javelin-aphlict', 'phabricator-phtize', 'javelin-dom', ), 'edd1ba66' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-uri', 'phabricator-notification', ), 'edf8a145' => array( 'javelin-behavior', 'javelin-uri', ), 'eeaa9e5a' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'phabricator-phtize', 'phabricator-textareautils', 'javelin-workflow', 'javelin-vector', ), 'efe49472' => array( 'javelin-install', 'javelin-util', ), 'f01586dc' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'javelin-workflow', 'javelin-json', ), + 'f2c64202' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-workflow', + 'javelin-dom', + 'phuix-form-control-view', + 'phuix-icon-view', + ), 'f36e01af' => array( 'javelin-behavior', 'javelin-behavior-device', 'javelin-stratcom', 'javelin-vector', 'phabricator-hovercard', ), 'f411b6ae' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-util', 'javelin-dom', 'javelin-request', 'phabricator-keyboard-shortcut', ), 'f6555212' => array( 'javelin-install', 'javelin-reactornode', 'javelin-util', 'javelin-reactor', ), 'f7379f45' => array( 'javelin-behavior', 'javelin-dom', 'javelin-util', 'phabricator-shaped-request', ), 'f7fc67ec' => array( 'javelin-install', 'javelin-typeahead', 'javelin-dom', 'javelin-request', 'javelin-typeahead-ondemand-source', 'javelin-util', ), 'f80d6bf0' => array( 'javelin-behavior', 'javelin-util', 'javelin-dom', 'javelin-stratcom', 'javelin-vector', 'javelin-typeahead-static-source', ), 'f829edb3' => array( 'javelin-view', 'javelin-install', 'javelin-dom', ), 'f8ba29d7' => array( 'javelin-behavior', 'javelin-stratcom', 'javelin-dom', 'javelin-mask', 'javelin-util', 'phabricator-busy', ), + 'f9fba5ee' => array( + 'javelin-install', + 'javelin-dom', + ), 'fa0f4fc2' => array( 'javelin-behavior', 'javelin-dom', 'javelin-vector', 'javelin-magical-init', ), 'fb20ac8d' => array( 'javelin-behavior', 'javelin-aphlict', 'javelin-stratcom', 'javelin-request', 'javelin-uri', 'javelin-dom', 'javelin-json', 'javelin-router', 'javelin-util', 'javelin-leader', 'javelin-sound', 'phabricator-notification', ), 'fbe497e7' => array( 'javelin-behavior', 'javelin-util', 'javelin-stratcom', 'javelin-dom', 'javelin-vector', 'javelin-magical-init', 'javelin-request', 'javelin-history', 'javelin-workflow', 'javelin-mask', 'javelin-behavior-device', 'phabricator-keyboard-shortcut', ), 'fc91ab6c' => array( 'javelin-behavior', 'javelin-dom', 'phortune-credit-card-form', ), 'fe287620' => array( 'javelin-install', 'javelin-dom', 'javelin-view-visitor', 'javelin-util', ), ), 'packages' => array( 'core.pkg.css' => array( 'phabricator-core-css', 'phabricator-zindex-css', 'phui-button-css', 'phabricator-standard-page-view', 'aphront-dialog-view-css', 'phui-form-view-css', 'aphront-panel-view-css', 'aphront-table-view-css', 'aphront-tokenizer-control-css', 'aphront-typeahead-control-css', 'aphront-list-filter-view-css', 'phabricator-remarkup-css', 'syntax-highlighting-css', 'phui-pager-css', 'aphront-tooltip-css', 'phabricator-flag-css', 'phui-info-view-css', 'sprite-menu-css', 'phabricator-main-menu-view', 'phabricator-notification-css', 'phabricator-notification-menu-css', 'lightbox-attachment-css', 'phui-header-view-css', 'phabricator-filetree-view-css', 'phabricator-nav-view-css', 'phabricator-side-menu-view-css', 'phui-crumbs-view-css', 'phui-object-item-list-view-css', 'global-drag-and-drop-css', 'phui-spacing-css', 'phui-form-css', 'phui-icon-view-css', 'phabricator-application-launch-view-css', 'phabricator-action-list-view-css', 'phui-property-list-view-css', 'phui-tag-view-css', 'phui-list-view-css', 'font-fontawesome', 'phui-font-icon-base-css', 'phui-box-css', 'phui-object-box-css', 'phui-timeline-view-css', 'sprite-tokens-css', 'tokens-css', 'phui-status-list-view-css', 'phui-feed-story-css', 'phabricator-feed-css', 'phabricator-dashboard-css', 'aphront-multi-column-view-css', 'conpherence-durable-column-view', ), 'core.pkg.js' => array( 'javelin-util', 'javelin-install', 'javelin-event', 'javelin-stratcom', 'javelin-behavior', 'javelin-resource', 'javelin-request', 'javelin-vector', 'javelin-dom', 'javelin-json', 'javelin-uri', 'javelin-workflow', 'javelin-mask', 'javelin-typeahead', 'javelin-typeahead-normalizer', 'javelin-typeahead-source', 'javelin-typeahead-preloaded-source', 'javelin-typeahead-ondemand-source', 'javelin-tokenizer', 'javelin-history', 'javelin-router', 'javelin-routable', 'javelin-behavior-aphront-basic-tokenizer', 'javelin-behavior-workflow', 'javelin-behavior-aphront-form-disable-on-submit', 'phabricator-keyboard-shortcut-manager', 'phabricator-keyboard-shortcut', 'javelin-behavior-phabricator-keyboard-shortcuts', 'javelin-behavior-refresh-csrf', 'javelin-behavior-phabricator-watch-anchor', 'javelin-behavior-phabricator-autofocus', 'phuix-dropdown-menu', 'phuix-action-list-view', 'phuix-action-view', 'phabricator-phtize', 'javelin-behavior-phabricator-oncopy', 'phabricator-tooltip', 'javelin-behavior-phabricator-tooltips', 'phabricator-prefab', 'javelin-behavior-device', 'javelin-behavior-toggle-class', 'javelin-behavior-lightbox-attachments', 'phabricator-busy', 'javelin-aphlict', 'phabricator-notification', 'javelin-behavior-aphlict-listen', 'javelin-behavior-phabricator-search-typeahead', 'javelin-behavior-aphlict-dropdown', 'javelin-behavior-history-install', 'javelin-behavior-phabricator-gesture', 'javelin-behavior-phabricator-active-nav', 'javelin-behavior-phabricator-nav', 'javelin-behavior-phabricator-remarkup-assist', 'phabricator-textareautils', 'phabricator-file-upload', 'javelin-behavior-global-drag-and-drop', 'javelin-behavior-phabricator-reveal-content', 'phabricator-hovercard', 'javelin-behavior-phabricator-hovercards', 'javelin-color', 'javelin-fx', 'phabricator-draggable-list', 'javelin-behavior-phabricator-transaction-list', 'javelin-behavior-phabricator-show-older-transactions', 'javelin-behavior-phui-dropdown-menu', 'javelin-behavior-doorkeeper-tag', 'phabricator-title', 'javelin-leader', 'javelin-websocket', 'javelin-behavior-dashboard-async-panel', 'javelin-behavior-dashboard-tab-panel', 'javelin-quicksand', 'javelin-behavior-quicksand-blacklist', 'javelin-behavior-high-security-warning', 'javelin-scrollbar', 'javelin-behavior-scrollbar', 'javelin-behavior-durable-column', 'conpherence-thread-manager', ), 'darkconsole.pkg.js' => array( 'javelin-behavior-dark-console', 'javelin-behavior-error-log', ), 'differential.pkg.css' => array( 'differential-core-view-css', 'differential-changeset-view-css', 'differential-revision-history-css', 'differential-revision-list-css', 'differential-table-of-contents-css', 'differential-revision-comment-css', 'differential-revision-add-comment-css', 'phabricator-object-selector-css', 'phabricator-content-source-view-css', 'inline-comment-summary-css', 'phui-inline-comment-view-css', ), 'differential.pkg.js' => array( 'phabricator-drag-and-drop-file-upload', 'phabricator-shaped-request', 'javelin-behavior-differential-feedback-preview', 'javelin-behavior-differential-edit-inline-comments', 'javelin-behavior-differential-populate', 'javelin-behavior-differential-diff-radios', 'javelin-behavior-differential-comment-jump', 'javelin-behavior-differential-add-reviewers-and-ccs', 'javelin-behavior-differential-keyboard-navigation', 'javelin-behavior-aphront-drag-and-drop-textarea', 'javelin-behavior-phabricator-object-selector', 'javelin-behavior-repository-crossreference', 'javelin-behavior-load-blame', 'differential-inline-comment-editor', 'javelin-behavior-differential-dropdown-menus', 'javelin-behavior-differential-toggle-files', 'javelin-behavior-differential-user-select', 'javelin-behavior-aphront-more', 'changeset-view-manager', ), 'diffusion.pkg.css' => array( 'diffusion-icons-css', ), 'diffusion.pkg.js' => array( 'javelin-behavior-diffusion-pull-lastmodified', 'javelin-behavior-diffusion-commit-graph', 'javelin-behavior-audit-preview', ), 'maniphest.pkg.css' => array( 'maniphest-task-summary-css', ), 'maniphest.pkg.js' => array( 'javelin-behavior-maniphest-batch-selector', 'javelin-behavior-maniphest-transaction-controls', 'javelin-behavior-maniphest-transaction-preview', 'javelin-behavior-maniphest-transaction-expand', 'javelin-behavior-maniphest-subpriority-editor', 'javelin-behavior-maniphest-list-editor', ), ), ); diff --git a/resources/sql/autopatches/20151128.phame.blog.picture.1.sql b/resources/sql/autopatches/20151128.phame.blog.picture.1.sql new file mode 100644 index 0000000000..1db4ca8393 --- /dev/null +++ b/resources/sql/autopatches/20151128.phame.blog.picture.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phame.phame_blog + ADD profileImagePHID VARBINARY(64); diff --git a/resources/sql/autopatches/20151130.phurl.mailkey.1.sql b/resources/sql/autopatches/20151130.phurl.mailkey.1.sql new file mode 100644 index 0000000000..67e9e25586 --- /dev/null +++ b/resources/sql/autopatches/20151130.phurl.mailkey.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phurl.phurl_url + ADD mailKey binary(20) NOT NULL; diff --git a/resources/sql/autopatches/20151130.phurl.mailkey.2.php b/resources/sql/autopatches/20151130.phurl.mailkey.2.php new file mode 100644 index 0000000000..93c7e7a4d2 --- /dev/null +++ b/resources/sql/autopatches/20151130.phurl.mailkey.2.php @@ -0,0 +1,18 @@ +establishConnection('w'); +$iterator = new LiskMigrationIterator($table); +foreach ($iterator as $url) { + $id = $url->getID(); + + echo pht('Adding mail key for Phurl %d...', $id); + echo "\n"; + + queryfx( + $conn_w, + 'UPDATE %T SET mailKey = %s WHERE id = %d', + $table->getTableName(), + Filesystem::readRandomCharacters(20), + $id); +} diff --git a/resources/sql/autopatches/20151202.versioneddraft.1.sql b/resources/sql/autopatches/20151202.versioneddraft.1.sql new file mode 100644 index 0000000000..2ad4332732 --- /dev/null +++ b/resources/sql/autopatches/20151202.versioneddraft.1.sql @@ -0,0 +1,10 @@ +CREATE TABLE {$NAMESPACE}_draft.draft_versioneddraft ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectPHID VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + version INT UNSIGNED NOT NULL, + properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_object` (objectPHID, authorPHID, version) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/scripts/sql/manage_storage.php b/scripts/sql/manage_storage.php index 64a50dcdb0..d1e1d16b6f 100755 --- a/scripts/sql/manage_storage.php +++ b/scripts/sql/manage_storage.php @@ -1,176 +1,172 @@ #!/usr/bin/env php setTagline(pht('manage Phabricator storage and schemata')); $args->setSynopsis(<<parseStandardArguments(); $conf = PhabricatorEnv::newObjectFromConfig( 'mysql.configuration-provider', array($dao = null, 'w')); $default_user = $conf->getUser(); $default_host = $conf->getHost(); $default_port = $conf->getPort(); $default_namespace = PhabricatorLiskDAO::getDefaultStorageNamespace(); try { $args->parsePartial( array( array( 'name' => 'force', 'short' => 'f', 'help' => pht( 'Do not prompt before performing dangerous operations.'), ), array( 'name' => 'user', 'short' => 'u', 'param' => 'username', 'default' => $default_user, 'help' => pht( "Connect with __username__ instead of the configured default ('%s').", $default_user), ), array( 'name' => 'password', 'short' => 'p', 'param' => 'password', 'help' => pht('Use __password__ instead of the configured default.'), ), array( 'name' => 'namespace', 'param' => 'name', 'default' => $default_namespace, 'help' => pht( "Use namespace __namespace__ instead of the configured ". "default ('%s'). This is an advanced feature used by unit tests; ". "you should not normally use this flag.", $default_namespace), ), array( - 'name' => 'dryrun', - 'help' => pht( + 'name' => 'dryrun', + 'help' => pht( 'Do not actually change anything, just show what would be changed.'), ), array( - 'name' => 'disable-utf8mb4', - 'help' => pht( - 'Disable utf8mb4, even if the database supports it. This is an '. + 'name' => 'disable-utf8mb4', + 'help' => pht( + 'Disable %s, even if the database supports it. This is an '. 'advanced feature used for testing changes to Phabricator; you '. - 'should not normally use this flag.'), + 'should not normally use this flag.', + 'utf8mb4'), ), )); } catch (PhutilArgumentUsageException $ex) { $args->printUsageException($ex); exit(77); } // First, test that the Phabricator configuration is set up correctly. After // we know this works we'll test any administrative credentials specifically. -$test_api = new PhabricatorStorageManagementAPI(); -$test_api->setUser($default_user); -$test_api->setHost($default_host); -$test_api->setPort($default_port); -$test_api->setPassword($conf->getPassword()); -$test_api->setNamespace($args->getArg('namespace')); +$test_api = id(new PhabricatorStorageManagementAPI()) + ->setUser($default_user) + ->setHost($default_host) + ->setPort($default_port) + ->setPassword($conf->getPassword()) + ->setNamespace($args->getArg('namespace')); try { queryfx( $test_api->getConn(null), 'SELECT 1'); } catch (AphrontQueryException $ex) { $message = phutil_console_format( "**%s**\n\n%s\n\n%s\n\n%s\n\n**%s**: %s\n", pht('MySQL Credentials Not Configured'), pht( 'Unable to connect to MySQL using the configured credentials. '. 'You must configure standard credentials before you can upgrade '. 'storage. Run these commands to set up credentials:'), " phabricator/ $ ./bin/config set mysql.host __host__\n". " phabricator/ $ ./bin/config set mysql.user __username__\n". " phabricator/ $ ./bin/config set mysql.pass __password__", pht( 'These standard credentials are separate from any administrative '. 'credentials provided to this command with __%s__ or '. '__%s__, and must be configured correctly before you can proceed.', '--user', '--password'), pht('Raw MySQL Error'), $ex->getMessage()); - echo phutil_console_wrap($message); - exit(1); } - if ($args->getArg('password') === null) { // This is already a PhutilOpaqueEnvelope. $password = $conf->getPassword(); } else { // Put this in a PhutilOpaqueEnvelope. $password = new PhutilOpaqueEnvelope($args->getArg('password')); PhabricatorEnv::overrideConfig('mysql.pass', $args->getArg('password')); } -$api = new PhabricatorStorageManagementAPI(); -$api->setUser($args->getArg('user')); -PhabricatorEnv::overrideConfig('mysql.user', $args->getArg('user')); -$api->setHost($default_host); -$api->setPort($default_port); -$api->setPassword($password); -$api->setNamespace($args->getArg('namespace')); -$api->setDisableUTF8MB4($args->getArg('disable-utf8mb4')); +$api = id(new PhabricatorStorageManagementAPI()) + ->setUser($args->getArg('user')) + ->setHost($default_host) + ->setPort($default_port) + ->setPassword($password) + ->setNamespace($args->getArg('namespace')) + ->setDisableUTF8MB4($args->getArg('disable-utf8mb4')); +PhabricatorEnv::overrideConfig('mysql.user', $api->getUser()); try { queryfx( $api->getConn(null), 'SELECT 1'); } catch (AphrontQueryException $ex) { $message = phutil_console_format( "**%s**\n\n%s\n\n**%s**: %s\n", pht('Bad Administrative Credentials'), pht( 'Unable to connect to MySQL using the administrative credentials '. 'provided with the __%s__ and __%s__ flags. Check that '. 'you have entered them correctly.', '--user', '--password'), pht('Raw MySQL Error'), $ex->getMessage()); - echo phutil_console_wrap($message); - exit(1); } $workflows = id(new PhutilClassMapQuery()) ->setAncestorClass('PhabricatorStorageManagementWorkflow') ->execute(); $patches = PhabricatorSQLPatchList::buildAllPatches(); foreach ($workflows as $workflow) { $workflow->setAPI($api); $workflow->setPatches($patches); } $workflows[] = new PhutilHelpArgumentWorkflow(); $args->parseWorkflows($workflows); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4adf9deccd..859c551ff5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,8168 +1,8220 @@ 2, 'class' => array( 'AlmanacAddress' => 'applications/almanac/util/AlmanacAddress.php', 'AlmanacBinding' => 'applications/almanac/storage/AlmanacBinding.php', 'AlmanacBindingEditController' => 'applications/almanac/controller/AlmanacBindingEditController.php', 'AlmanacBindingEditor' => 'applications/almanac/editor/AlmanacBindingEditor.php', 'AlmanacBindingPHIDType' => 'applications/almanac/phid/AlmanacBindingPHIDType.php', 'AlmanacBindingQuery' => 'applications/almanac/query/AlmanacBindingQuery.php', 'AlmanacBindingTableView' => 'applications/almanac/view/AlmanacBindingTableView.php', 'AlmanacBindingTransaction' => 'applications/almanac/storage/AlmanacBindingTransaction.php', 'AlmanacBindingTransactionQuery' => 'applications/almanac/query/AlmanacBindingTransactionQuery.php', 'AlmanacBindingViewController' => 'applications/almanac/controller/AlmanacBindingViewController.php', 'AlmanacClusterDatabaseServiceType' => 'applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php', 'AlmanacClusterRepositoryServiceType' => 'applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php', 'AlmanacClusterServiceType' => 'applications/almanac/servicetype/AlmanacClusterServiceType.php', 'AlmanacConduitAPIMethod' => 'applications/almanac/conduit/AlmanacConduitAPIMethod.php', 'AlmanacConsoleController' => 'applications/almanac/controller/AlmanacConsoleController.php', 'AlmanacController' => 'applications/almanac/controller/AlmanacController.php', 'AlmanacCoreCustomField' => 'applications/almanac/customfield/AlmanacCoreCustomField.php', 'AlmanacCreateClusterServicesCapability' => 'applications/almanac/capability/AlmanacCreateClusterServicesCapability.php', 'AlmanacCreateDevicesCapability' => 'applications/almanac/capability/AlmanacCreateDevicesCapability.php', 'AlmanacCreateNetworksCapability' => 'applications/almanac/capability/AlmanacCreateNetworksCapability.php', 'AlmanacCreateServicesCapability' => 'applications/almanac/capability/AlmanacCreateServicesCapability.php', 'AlmanacCustomField' => 'applications/almanac/customfield/AlmanacCustomField.php', 'AlmanacCustomServiceType' => 'applications/almanac/servicetype/AlmanacCustomServiceType.php', 'AlmanacDAO' => 'applications/almanac/storage/AlmanacDAO.php', 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php', 'AlmanacDeviceEditController' => 'applications/almanac/controller/AlmanacDeviceEditController.php', 'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php', 'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php', 'AlmanacDevicePHIDType' => 'applications/almanac/phid/AlmanacDevicePHIDType.php', 'AlmanacDeviceQuery' => 'applications/almanac/query/AlmanacDeviceQuery.php', 'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php', 'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php', 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', 'AlmanacDeviceViewController' => 'applications/almanac/controller/AlmanacDeviceViewController.php', 'AlmanacDrydockPoolServiceType' => 'applications/almanac/servicetype/AlmanacDrydockPoolServiceType.php', 'AlmanacInterface' => 'applications/almanac/storage/AlmanacInterface.php', 'AlmanacInterfaceDatasource' => 'applications/almanac/typeahead/AlmanacInterfaceDatasource.php', 'AlmanacInterfaceEditController' => 'applications/almanac/controller/AlmanacInterfaceEditController.php', 'AlmanacInterfacePHIDType' => 'applications/almanac/phid/AlmanacInterfacePHIDType.php', 'AlmanacInterfaceQuery' => 'applications/almanac/query/AlmanacInterfaceQuery.php', 'AlmanacInterfaceTableView' => 'applications/almanac/view/AlmanacInterfaceTableView.php', 'AlmanacKeys' => 'applications/almanac/util/AlmanacKeys.php', 'AlmanacManagementLockWorkflow' => 'applications/almanac/management/AlmanacManagementLockWorkflow.php', 'AlmanacManagementRegisterWorkflow' => 'applications/almanac/management/AlmanacManagementRegisterWorkflow.php', 'AlmanacManagementTrustKeyWorkflow' => 'applications/almanac/management/AlmanacManagementTrustKeyWorkflow.php', 'AlmanacManagementUnlockWorkflow' => 'applications/almanac/management/AlmanacManagementUnlockWorkflow.php', 'AlmanacManagementUntrustKeyWorkflow' => 'applications/almanac/management/AlmanacManagementUntrustKeyWorkflow.php', 'AlmanacManagementWorkflow' => 'applications/almanac/management/AlmanacManagementWorkflow.php', 'AlmanacNames' => 'applications/almanac/util/AlmanacNames.php', 'AlmanacNamesTestCase' => 'applications/almanac/util/__tests__/AlmanacNamesTestCase.php', 'AlmanacNetwork' => 'applications/almanac/storage/AlmanacNetwork.php', 'AlmanacNetworkController' => 'applications/almanac/controller/AlmanacNetworkController.php', 'AlmanacNetworkEditController' => 'applications/almanac/controller/AlmanacNetworkEditController.php', 'AlmanacNetworkEditor' => 'applications/almanac/editor/AlmanacNetworkEditor.php', 'AlmanacNetworkListController' => 'applications/almanac/controller/AlmanacNetworkListController.php', 'AlmanacNetworkPHIDType' => 'applications/almanac/phid/AlmanacNetworkPHIDType.php', 'AlmanacNetworkQuery' => 'applications/almanac/query/AlmanacNetworkQuery.php', 'AlmanacNetworkSearchEngine' => 'applications/almanac/query/AlmanacNetworkSearchEngine.php', 'AlmanacNetworkTransaction' => 'applications/almanac/storage/AlmanacNetworkTransaction.php', 'AlmanacNetworkTransactionQuery' => 'applications/almanac/query/AlmanacNetworkTransactionQuery.php', 'AlmanacNetworkViewController' => 'applications/almanac/controller/AlmanacNetworkViewController.php', 'AlmanacProperty' => 'applications/almanac/storage/AlmanacProperty.php', 'AlmanacPropertyController' => 'applications/almanac/controller/AlmanacPropertyController.php', 'AlmanacPropertyDeleteController' => 'applications/almanac/controller/AlmanacPropertyDeleteController.php', 'AlmanacPropertyEditController' => 'applications/almanac/controller/AlmanacPropertyEditController.php', 'AlmanacPropertyInterface' => 'applications/almanac/property/AlmanacPropertyInterface.php', 'AlmanacPropertyQuery' => 'applications/almanac/query/AlmanacPropertyQuery.php', 'AlmanacQuery' => 'applications/almanac/query/AlmanacQuery.php', 'AlmanacQueryDevicesConduitAPIMethod' => 'applications/almanac/conduit/AlmanacQueryDevicesConduitAPIMethod.php', 'AlmanacQueryServicesConduitAPIMethod' => 'applications/almanac/conduit/AlmanacQueryServicesConduitAPIMethod.php', 'AlmanacSchemaSpec' => 'applications/almanac/storage/AlmanacSchemaSpec.php', 'AlmanacService' => 'applications/almanac/storage/AlmanacService.php', 'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php', 'AlmanacServiceDatasource' => 'applications/almanac/typeahead/AlmanacServiceDatasource.php', 'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php', 'AlmanacServiceEditor' => 'applications/almanac/editor/AlmanacServiceEditor.php', 'AlmanacServiceListController' => 'applications/almanac/controller/AlmanacServiceListController.php', 'AlmanacServicePHIDType' => 'applications/almanac/phid/AlmanacServicePHIDType.php', 'AlmanacServiceQuery' => 'applications/almanac/query/AlmanacServiceQuery.php', 'AlmanacServiceSearchEngine' => 'applications/almanac/query/AlmanacServiceSearchEngine.php', 'AlmanacServiceTransaction' => 'applications/almanac/storage/AlmanacServiceTransaction.php', 'AlmanacServiceTransactionQuery' => 'applications/almanac/query/AlmanacServiceTransactionQuery.php', 'AlmanacServiceType' => 'applications/almanac/servicetype/AlmanacServiceType.php', 'AlmanacServiceTypeTestCase' => 'applications/almanac/servicetype/__tests__/AlmanacServiceTypeTestCase.php', 'AlmanacServiceViewController' => 'applications/almanac/controller/AlmanacServiceViewController.php', 'AphlictDropdownDataQuery' => 'applications/aphlict/query/AphlictDropdownDataQuery.php', 'Aphront304Response' => 'aphront/response/Aphront304Response.php', 'Aphront400Response' => 'aphront/response/Aphront400Response.php', 'Aphront403Response' => 'aphront/response/Aphront403Response.php', 'Aphront404Response' => 'aphront/response/Aphront404Response.php', 'AphrontAjaxResponse' => 'aphront/response/AphrontAjaxResponse.php', 'AphrontApplicationConfiguration' => 'aphront/configuration/AphrontApplicationConfiguration.php', 'AphrontBarView' => 'view/widget/bars/AphrontBarView.php', + 'AphrontBoolHTTPParameterType' => 'aphront/httpparametertype/AphrontBoolHTTPParameterType.php', 'AphrontCSRFException' => 'aphront/exception/AphrontCSRFException.php', 'AphrontCalendarEventView' => 'applications/calendar/view/AphrontCalendarEventView.php', 'AphrontController' => 'aphront/AphrontController.php', 'AphrontCursorPagerView' => 'view/control/AphrontCursorPagerView.php', 'AphrontDefaultApplicationConfiguration' => 'aphront/configuration/AphrontDefaultApplicationConfiguration.php', 'AphrontDialogResponse' => 'aphront/response/AphrontDialogResponse.php', 'AphrontDialogView' => 'view/AphrontDialogView.php', 'AphrontException' => 'aphront/exception/AphrontException.php', 'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php', 'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php', 'AphrontFormChooseButtonControl' => 'view/form/control/AphrontFormChooseButtonControl.php', 'AphrontFormControl' => 'view/form/control/AphrontFormControl.php', 'AphrontFormDateControl' => 'view/form/control/AphrontFormDateControl.php', 'AphrontFormDateControlValue' => 'view/form/control/AphrontFormDateControlValue.php', 'AphrontFormDividerControl' => 'view/form/control/AphrontFormDividerControl.php', 'AphrontFormFileControl' => 'view/form/control/AphrontFormFileControl.php', 'AphrontFormMarkupControl' => 'view/form/control/AphrontFormMarkupControl.php', 'AphrontFormPasswordControl' => 'view/form/control/AphrontFormPasswordControl.php', 'AphrontFormPolicyControl' => 'view/form/control/AphrontFormPolicyControl.php', 'AphrontFormRadioButtonControl' => 'view/form/control/AphrontFormRadioButtonControl.php', 'AphrontFormRecaptchaControl' => 'view/form/control/AphrontFormRecaptchaControl.php', 'AphrontFormSelectControl' => 'view/form/control/AphrontFormSelectControl.php', 'AphrontFormStaticControl' => 'view/form/control/AphrontFormStaticControl.php', 'AphrontFormSubmitControl' => 'view/form/control/AphrontFormSubmitControl.php', 'AphrontFormTextAreaControl' => 'view/form/control/AphrontFormTextAreaControl.php', 'AphrontFormTextControl' => 'view/form/control/AphrontFormTextControl.php', 'AphrontFormTextWithSubmitControl' => 'view/form/control/AphrontFormTextWithSubmitControl.php', 'AphrontFormTokenizerControl' => 'view/form/control/AphrontFormTokenizerControl.php', 'AphrontFormTypeaheadControl' => 'view/form/control/AphrontFormTypeaheadControl.php', 'AphrontFormView' => 'view/form/AphrontFormView.php', 'AphrontGlyphBarView' => 'view/widget/bars/AphrontGlyphBarView.php', 'AphrontHTMLResponse' => 'aphront/response/AphrontHTMLResponse.php', 'AphrontHTTPParameterType' => 'aphront/httpparametertype/AphrontHTTPParameterType.php', 'AphrontHTTPProxyResponse' => 'aphront/response/AphrontHTTPProxyResponse.php', 'AphrontHTTPSink' => 'aphront/sink/AphrontHTTPSink.php', 'AphrontHTTPSinkTestCase' => 'aphront/sink/__tests__/AphrontHTTPSinkTestCase.php', + 'AphrontIntHTTPParameterType' => 'aphront/httpparametertype/AphrontIntHTTPParameterType.php', 'AphrontIsolatedDatabaseConnectionTestCase' => 'infrastructure/storage/__tests__/AphrontIsolatedDatabaseConnectionTestCase.php', 'AphrontIsolatedHTTPSink' => 'aphront/sink/AphrontIsolatedHTTPSink.php', 'AphrontJSONResponse' => 'aphront/response/AphrontJSONResponse.php', 'AphrontJavelinView' => 'view/AphrontJavelinView.php', 'AphrontKeyboardShortcutsAvailableView' => 'view/widget/AphrontKeyboardShortcutsAvailableView.php', 'AphrontListFilterView' => 'view/layout/AphrontListFilterView.php', + 'AphrontListHTTPParameterType' => 'aphront/httpparametertype/AphrontListHTTPParameterType.php', 'AphrontMalformedRequestException' => 'aphront/exception/AphrontMalformedRequestException.php', 'AphrontMoreView' => 'view/layout/AphrontMoreView.php', 'AphrontMultiColumnView' => 'view/layout/AphrontMultiColumnView.php', 'AphrontMySQLDatabaseConnectionTestCase' => 'infrastructure/storage/__tests__/AphrontMySQLDatabaseConnectionTestCase.php', 'AphrontNullView' => 'view/AphrontNullView.php', 'AphrontPHIDHTTPParameterType' => 'aphront/httpparametertype/AphrontPHIDHTTPParameterType.php', 'AphrontPHIDListHTTPParameterType' => 'aphront/httpparametertype/AphrontPHIDListHTTPParameterType.php', 'AphrontPHPHTTPSink' => 'aphront/sink/AphrontPHPHTTPSink.php', 'AphrontPageView' => 'view/page/AphrontPageView.php', 'AphrontPlainTextResponse' => 'aphront/response/AphrontPlainTextResponse.php', 'AphrontProgressBarView' => 'view/widget/bars/AphrontProgressBarView.php', 'AphrontProjectListHTTPParameterType' => 'aphront/httpparametertype/AphrontProjectListHTTPParameterType.php', 'AphrontProxyResponse' => 'aphront/response/AphrontProxyResponse.php', 'AphrontRedirectResponse' => 'aphront/response/AphrontRedirectResponse.php', 'AphrontRedirectResponseTestCase' => 'aphront/response/__tests__/AphrontRedirectResponseTestCase.php', 'AphrontReloadResponse' => 'aphront/response/AphrontReloadResponse.php', 'AphrontRequest' => 'aphront/AphrontRequest.php', 'AphrontRequestExceptionHandler' => 'aphront/handler/AphrontRequestExceptionHandler.php', 'AphrontRequestTestCase' => 'aphront/__tests__/AphrontRequestTestCase.php', 'AphrontResponse' => 'aphront/response/AphrontResponse.php', 'AphrontResponseProducerInterface' => 'aphront/interface/AphrontResponseProducerInterface.php', 'AphrontRoutingMap' => 'aphront/site/AphrontRoutingMap.php', 'AphrontRoutingResult' => 'aphront/site/AphrontRoutingResult.php', 'AphrontSelectHTTPParameterType' => 'aphront/httpparametertype/AphrontSelectHTTPParameterType.php', 'AphrontSideNavFilterView' => 'view/layout/AphrontSideNavFilterView.php', 'AphrontSite' => 'aphront/site/AphrontSite.php', 'AphrontStackTraceView' => 'view/widget/AphrontStackTraceView.php', 'AphrontStandaloneHTMLResponse' => 'aphront/response/AphrontStandaloneHTMLResponse.php', 'AphrontStringHTTPParameterType' => 'aphront/httpparametertype/AphrontStringHTTPParameterType.php', 'AphrontStringListHTTPParameterType' => 'aphront/httpparametertype/AphrontStringListHTTPParameterType.php', 'AphrontTableView' => 'view/control/AphrontTableView.php', 'AphrontTagView' => 'view/AphrontTagView.php', 'AphrontTokenizerTemplateView' => 'view/control/AphrontTokenizerTemplateView.php', 'AphrontTypeaheadTemplateView' => 'view/control/AphrontTypeaheadTemplateView.php', 'AphrontUnhandledExceptionResponse' => 'aphront/response/AphrontUnhandledExceptionResponse.php', 'AphrontUserListHTTPParameterType' => 'aphront/httpparametertype/AphrontUserListHTTPParameterType.php', 'AphrontView' => 'view/AphrontView.php', 'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php', 'ArcanistConduitAPIMethod' => 'applications/arcanist/conduit/ArcanistConduitAPIMethod.php', 'AuditConduitAPIMethod' => 'applications/audit/conduit/AuditConduitAPIMethod.php', 'AuditQueryConduitAPIMethod' => 'applications/audit/conduit/AuditQueryConduitAPIMethod.php', 'AuthManageProvidersCapability' => 'applications/auth/capability/AuthManageProvidersCapability.php', 'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php', 'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php', 'CelerityAPI' => 'applications/celerity/CelerityAPI.php', 'CelerityDefaultPostprocessor' => 'applications/celerity/postprocessor/CelerityDefaultPostprocessor.php', 'CelerityHighContrastPostprocessor' => 'applications/celerity/postprocessor/CelerityHighContrastPostprocessor.php', 'CelerityLargeFontPostprocessor' => 'applications/celerity/postprocessor/CelerityLargeFontPostprocessor.php', 'CelerityManagementMapWorkflow' => 'applications/celerity/management/CelerityManagementMapWorkflow.php', 'CelerityManagementWorkflow' => 'applications/celerity/management/CelerityManagementWorkflow.php', 'CelerityPhabricatorResourceController' => 'applications/celerity/controller/CelerityPhabricatorResourceController.php', 'CelerityPhabricatorResources' => 'applications/celerity/resources/CelerityPhabricatorResources.php', 'CelerityPhysicalResources' => 'applications/celerity/resources/CelerityPhysicalResources.php', 'CelerityPhysicalResourcesTestCase' => 'applications/celerity/resources/__tests__/CelerityPhysicalResourcesTestCase.php', 'CelerityPostprocessor' => 'applications/celerity/postprocessor/CelerityPostprocessor.php', 'CelerityPostprocessorTestCase' => 'applications/celerity/__tests__/CelerityPostprocessorTestCase.php', 'CelerityResourceController' => 'applications/celerity/controller/CelerityResourceController.php', 'CelerityResourceGraph' => 'applications/celerity/CelerityResourceGraph.php', 'CelerityResourceMap' => 'applications/celerity/CelerityResourceMap.php', 'CelerityResourceMapGenerator' => 'applications/celerity/CelerityResourceMapGenerator.php', 'CelerityResourceTransformer' => 'applications/celerity/CelerityResourceTransformer.php', 'CelerityResourceTransformerTestCase' => 'applications/celerity/__tests__/CelerityResourceTransformerTestCase.php', 'CelerityResources' => 'applications/celerity/resources/CelerityResources.php', 'CelerityResourcesOnDisk' => 'applications/celerity/resources/CelerityResourcesOnDisk.php', 'CeleritySpriteGenerator' => 'applications/celerity/CeleritySpriteGenerator.php', 'CelerityStaticResourceResponse' => 'applications/celerity/CelerityStaticResourceResponse.php', 'ChatLogConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogConduitAPIMethod.php', 'ChatLogQueryConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogQueryConduitAPIMethod.php', 'ChatLogRecordConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogRecordConduitAPIMethod.php', 'ConduitAPIMethod' => 'applications/conduit/method/ConduitAPIMethod.php', 'ConduitAPIMethodTestCase' => 'applications/conduit/method/__tests__/ConduitAPIMethodTestCase.php', 'ConduitAPIRequest' => 'applications/conduit/protocol/ConduitAPIRequest.php', 'ConduitAPIResponse' => 'applications/conduit/protocol/ConduitAPIResponse.php', 'ConduitApplicationNotInstalledException' => 'applications/conduit/protocol/exception/ConduitApplicationNotInstalledException.php', 'ConduitCall' => 'applications/conduit/call/ConduitCall.php', 'ConduitCallTestCase' => 'applications/conduit/call/__tests__/ConduitCallTestCase.php', 'ConduitConnectConduitAPIMethod' => 'applications/conduit/method/ConduitConnectConduitAPIMethod.php', 'ConduitConnectionGarbageCollector' => 'applications/conduit/garbagecollector/ConduitConnectionGarbageCollector.php', 'ConduitDeprecatedCallSetupCheck' => 'applications/conduit/check/ConduitDeprecatedCallSetupCheck.php', 'ConduitException' => 'applications/conduit/protocol/exception/ConduitException.php', 'ConduitGetCapabilitiesConduitAPIMethod' => 'applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php', 'ConduitGetCertificateConduitAPIMethod' => 'applications/conduit/method/ConduitGetCertificateConduitAPIMethod.php', 'ConduitLogGarbageCollector' => 'applications/conduit/garbagecollector/ConduitLogGarbageCollector.php', 'ConduitMethodDoesNotExistException' => 'applications/conduit/protocol/exception/ConduitMethodDoesNotExistException.php', 'ConduitMethodNotFoundException' => 'applications/conduit/protocol/exception/ConduitMethodNotFoundException.php', 'ConduitPingConduitAPIMethod' => 'applications/conduit/method/ConduitPingConduitAPIMethod.php', 'ConduitQueryConduitAPIMethod' => 'applications/conduit/method/ConduitQueryConduitAPIMethod.php', 'ConduitSSHWorkflow' => 'applications/conduit/ssh/ConduitSSHWorkflow.php', 'ConduitTokenGarbageCollector' => 'applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php', 'ConpherenceColumnViewController' => 'applications/conpherence/controller/ConpherenceColumnViewController.php', 'ConpherenceConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceConduitAPIMethod.php', 'ConpherenceConfigOptions' => 'applications/conpherence/config/ConpherenceConfigOptions.php', 'ConpherenceConstants' => 'applications/conpherence/constants/ConpherenceConstants.php', 'ConpherenceController' => 'applications/conpherence/controller/ConpherenceController.php', 'ConpherenceCreateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceCreateThreadConduitAPIMethod.php', 'ConpherenceCreateThreadMailReceiver' => 'applications/conpherence/mail/ConpherenceCreateThreadMailReceiver.php', 'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php', 'ConpherenceDurableColumnView' => 'applications/conpherence/view/ConpherenceDurableColumnView.php', 'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php', 'ConpherenceFormDragAndDropUploadControl' => 'applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php', 'ConpherenceFulltextQuery' => 'applications/conpherence/query/ConpherenceFulltextQuery.php', 'ConpherenceHovercardEventListener' => 'applications/conpherence/events/ConpherenceHovercardEventListener.php', 'ConpherenceImageData' => 'applications/conpherence/constants/ConpherenceImageData.php', 'ConpherenceIndex' => 'applications/conpherence/storage/ConpherenceIndex.php', 'ConpherenceLayoutView' => 'applications/conpherence/view/ConpherenceLayoutView.php', 'ConpherenceListController' => 'applications/conpherence/controller/ConpherenceListController.php', 'ConpherenceMenuItemView' => 'applications/conpherence/view/ConpherenceMenuItemView.php', 'ConpherenceNewRoomController' => 'applications/conpherence/controller/ConpherenceNewRoomController.php', 'ConpherenceNotificationPanelController' => 'applications/conpherence/controller/ConpherenceNotificationPanelController.php', 'ConpherenceParticipant' => 'applications/conpherence/storage/ConpherenceParticipant.php', 'ConpherenceParticipantCountQuery' => 'applications/conpherence/query/ConpherenceParticipantCountQuery.php', 'ConpherenceParticipantQuery' => 'applications/conpherence/query/ConpherenceParticipantQuery.php', 'ConpherenceParticipationStatus' => 'applications/conpherence/constants/ConpherenceParticipationStatus.php', 'ConpherencePeopleWidgetView' => 'applications/conpherence/view/ConpherencePeopleWidgetView.php', 'ConpherencePicCropControl' => 'applications/conpherence/view/ConpherencePicCropControl.php', 'ConpherenceQueryThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryThreadConduitAPIMethod.php', 'ConpherenceQueryTransactionConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceQueryTransactionConduitAPIMethod.php', 'ConpherenceReplyHandler' => 'applications/conpherence/mail/ConpherenceReplyHandler.php', 'ConpherenceRoomListController' => 'applications/conpherence/controller/ConpherenceRoomListController.php', 'ConpherenceRoomTestCase' => 'applications/conpherence/__tests__/ConpherenceRoomTestCase.php', 'ConpherenceSchemaSpec' => 'applications/conpherence/storage/ConpherenceSchemaSpec.php', 'ConpherenceSettings' => 'applications/conpherence/constants/ConpherenceSettings.php', 'ConpherenceTestCase' => 'applications/conpherence/__tests__/ConpherenceTestCase.php', 'ConpherenceThread' => 'applications/conpherence/storage/ConpherenceThread.php', 'ConpherenceThreadIndexer' => 'applications/conpherence/search/ConpherenceThreadIndexer.php', 'ConpherenceThreadListView' => 'applications/conpherence/view/ConpherenceThreadListView.php', 'ConpherenceThreadMailReceiver' => 'applications/conpherence/mail/ConpherenceThreadMailReceiver.php', 'ConpherenceThreadMembersPolicyRule' => 'applications/conpherence/policyrule/ConpherenceThreadMembersPolicyRule.php', 'ConpherenceThreadQuery' => 'applications/conpherence/query/ConpherenceThreadQuery.php', 'ConpherenceThreadRemarkupRule' => 'applications/conpherence/remarkup/ConpherenceThreadRemarkupRule.php', 'ConpherenceThreadSearchEngine' => 'applications/conpherence/query/ConpherenceThreadSearchEngine.php', 'ConpherenceTransaction' => 'applications/conpherence/storage/ConpherenceTransaction.php', 'ConpherenceTransactionComment' => 'applications/conpherence/storage/ConpherenceTransactionComment.php', 'ConpherenceTransactionQuery' => 'applications/conpherence/query/ConpherenceTransactionQuery.php', 'ConpherenceTransactionRenderer' => 'applications/conpherence/ConpherenceTransactionRenderer.php', 'ConpherenceTransactionView' => 'applications/conpherence/view/ConpherenceTransactionView.php', 'ConpherenceUpdateActions' => 'applications/conpherence/constants/ConpherenceUpdateActions.php', 'ConpherenceUpdateController' => 'applications/conpherence/controller/ConpherenceUpdateController.php', 'ConpherenceUpdateThreadConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceUpdateThreadConduitAPIMethod.php', 'ConpherenceViewController' => 'applications/conpherence/controller/ConpherenceViewController.php', 'ConpherenceWidgetConfigConstants' => 'applications/conpherence/constants/ConpherenceWidgetConfigConstants.php', 'ConpherenceWidgetController' => 'applications/conpherence/controller/ConpherenceWidgetController.php', 'ConpherenceWidgetView' => 'applications/conpherence/view/ConpherenceWidgetView.php', 'DarkConsoleController' => 'applications/console/controller/DarkConsoleController.php', 'DarkConsoleCore' => 'applications/console/core/DarkConsoleCore.php', 'DarkConsoleDataController' => 'applications/console/controller/DarkConsoleDataController.php', 'DarkConsoleErrorLogPlugin' => 'applications/console/plugin/DarkConsoleErrorLogPlugin.php', 'DarkConsoleErrorLogPluginAPI' => 'applications/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php', 'DarkConsoleEventPlugin' => 'applications/console/plugin/DarkConsoleEventPlugin.php', 'DarkConsoleEventPluginAPI' => 'applications/console/plugin/event/DarkConsoleEventPluginAPI.php', 'DarkConsolePlugin' => 'applications/console/plugin/DarkConsolePlugin.php', 'DarkConsoleRequestPlugin' => 'applications/console/plugin/DarkConsoleRequestPlugin.php', 'DarkConsoleServicesPlugin' => 'applications/console/plugin/DarkConsoleServicesPlugin.php', 'DarkConsoleStartupPlugin' => 'applications/console/plugin/DarkConsoleStartupPlugin.php', 'DarkConsoleXHProfPlugin' => 'applications/console/plugin/DarkConsoleXHProfPlugin.php', 'DarkConsoleXHProfPluginAPI' => 'applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php', 'DatabaseConfigurationProvider' => 'infrastructure/storage/configuration/DatabaseConfigurationProvider.php', 'DefaultDatabaseConfigurationProvider' => 'infrastructure/storage/configuration/DefaultDatabaseConfigurationProvider.php', 'DifferentialAction' => 'applications/differential/constants/DifferentialAction.php', 'DifferentialActionEmailCommand' => 'applications/differential/command/DifferentialActionEmailCommand.php', 'DifferentialActionMenuEventListener' => 'applications/differential/event/DifferentialActionMenuEventListener.php', 'DifferentialAddCommentView' => 'applications/differential/view/DifferentialAddCommentView.php', 'DifferentialAdjustmentMapTestCase' => 'applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php', 'DifferentialAffectedPath' => 'applications/differential/storage/DifferentialAffectedPath.php', 'DifferentialApplyPatchField' => 'applications/differential/customfield/DifferentialApplyPatchField.php', 'DifferentialAsanaRepresentationField' => 'applications/differential/customfield/DifferentialAsanaRepresentationField.php', 'DifferentialAuditorsField' => 'applications/differential/customfield/DifferentialAuditorsField.php', 'DifferentialAuthorField' => 'applications/differential/customfield/DifferentialAuthorField.php', 'DifferentialBlameRevisionField' => 'applications/differential/customfield/DifferentialBlameRevisionField.php', 'DifferentialBlockHeraldAction' => 'applications/differential/herald/DifferentialBlockHeraldAction.php', 'DifferentialBranchField' => 'applications/differential/customfield/DifferentialBranchField.php', 'DifferentialChangeHeraldFieldGroup' => 'applications/differential/herald/DifferentialChangeHeraldFieldGroup.php', 'DifferentialChangeType' => 'applications/differential/constants/DifferentialChangeType.php', 'DifferentialChangesSinceLastUpdateField' => 'applications/differential/customfield/DifferentialChangesSinceLastUpdateField.php', 'DifferentialChangeset' => 'applications/differential/storage/DifferentialChangeset.php', 'DifferentialChangesetDetailView' => 'applications/differential/view/DifferentialChangesetDetailView.php', 'DifferentialChangesetFileTreeSideNavBuilder' => 'applications/differential/view/DifferentialChangesetFileTreeSideNavBuilder.php', 'DifferentialChangesetHTMLRenderer' => 'applications/differential/render/DifferentialChangesetHTMLRenderer.php', 'DifferentialChangesetListView' => 'applications/differential/view/DifferentialChangesetListView.php', 'DifferentialChangesetOneUpRenderer' => 'applications/differential/render/DifferentialChangesetOneUpRenderer.php', 'DifferentialChangesetOneUpTestRenderer' => 'applications/differential/render/DifferentialChangesetOneUpTestRenderer.php', 'DifferentialChangesetParser' => 'applications/differential/parser/DifferentialChangesetParser.php', 'DifferentialChangesetParserTestCase' => 'applications/differential/parser/__tests__/DifferentialChangesetParserTestCase.php', 'DifferentialChangesetQuery' => 'applications/differential/query/DifferentialChangesetQuery.php', 'DifferentialChangesetRenderer' => 'applications/differential/render/DifferentialChangesetRenderer.php', 'DifferentialChangesetTestRenderer' => 'applications/differential/render/DifferentialChangesetTestRenderer.php', 'DifferentialChangesetTwoUpRenderer' => 'applications/differential/render/DifferentialChangesetTwoUpRenderer.php', 'DifferentialChangesetTwoUpTestRenderer' => 'applications/differential/render/DifferentialChangesetTwoUpTestRenderer.php', 'DifferentialChangesetViewController' => 'applications/differential/controller/DifferentialChangesetViewController.php', 'DifferentialCloseConduitAPIMethod' => 'applications/differential/conduit/DifferentialCloseConduitAPIMethod.php', 'DifferentialCommentPreviewController' => 'applications/differential/controller/DifferentialCommentPreviewController.php', 'DifferentialCommentSaveController' => 'applications/differential/controller/DifferentialCommentSaveController.php', 'DifferentialCommitMessageParser' => 'applications/differential/parser/DifferentialCommitMessageParser.php', 'DifferentialCommitMessageParserTestCase' => 'applications/differential/parser/__tests__/DifferentialCommitMessageParserTestCase.php', 'DifferentialCommitsField' => 'applications/differential/customfield/DifferentialCommitsField.php', 'DifferentialConduitAPIMethod' => 'applications/differential/conduit/DifferentialConduitAPIMethod.php', 'DifferentialConflictsField' => 'applications/differential/customfield/DifferentialConflictsField.php', 'DifferentialController' => 'applications/differential/controller/DifferentialController.php', 'DifferentialCoreCustomField' => 'applications/differential/customfield/DifferentialCoreCustomField.php', 'DifferentialCreateCommentConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php', 'DifferentialCreateDiffConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateDiffConduitAPIMethod.php', 'DifferentialCreateInlineConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateInlineConduitAPIMethod.php', 'DifferentialCreateMailReceiver' => 'applications/differential/mail/DifferentialCreateMailReceiver.php', 'DifferentialCreateRawDiffConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateRawDiffConduitAPIMethod.php', 'DifferentialCreateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateRevisionConduitAPIMethod.php', 'DifferentialCustomField' => 'applications/differential/customfield/DifferentialCustomField.php', 'DifferentialCustomFieldDependsOnParser' => 'applications/differential/parser/DifferentialCustomFieldDependsOnParser.php', 'DifferentialCustomFieldDependsOnParserTestCase' => 'applications/differential/parser/__tests__/DifferentialCustomFieldDependsOnParserTestCase.php', 'DifferentialCustomFieldNumericIndex' => 'applications/differential/storage/DifferentialCustomFieldNumericIndex.php', 'DifferentialCustomFieldRevertsParser' => 'applications/differential/parser/DifferentialCustomFieldRevertsParser.php', 'DifferentialCustomFieldRevertsParserTestCase' => 'applications/differential/parser/__tests__/DifferentialCustomFieldRevertsParserTestCase.php', 'DifferentialCustomFieldStorage' => 'applications/differential/storage/DifferentialCustomFieldStorage.php', 'DifferentialCustomFieldStringIndex' => 'applications/differential/storage/DifferentialCustomFieldStringIndex.php', 'DifferentialDAO' => 'applications/differential/storage/DifferentialDAO.php', 'DifferentialDefaultViewCapability' => 'applications/differential/capability/DifferentialDefaultViewCapability.php', 'DifferentialDependenciesField' => 'applications/differential/customfield/DifferentialDependenciesField.php', 'DifferentialDependsOnField' => 'applications/differential/customfield/DifferentialDependsOnField.php', 'DifferentialDiff' => 'applications/differential/storage/DifferentialDiff.php', 'DifferentialDiffAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialDiffAffectedFilesHeraldField.php', 'DifferentialDiffAuthorHeraldField' => 'applications/differential/herald/DifferentialDiffAuthorHeraldField.php', 'DifferentialDiffAuthorProjectsHeraldField' => 'applications/differential/herald/DifferentialDiffAuthorProjectsHeraldField.php', 'DifferentialDiffContentAddedHeraldField' => 'applications/differential/herald/DifferentialDiffContentAddedHeraldField.php', 'DifferentialDiffContentHeraldField' => 'applications/differential/herald/DifferentialDiffContentHeraldField.php', 'DifferentialDiffContentRemovedHeraldField' => 'applications/differential/herald/DifferentialDiffContentRemovedHeraldField.php', 'DifferentialDiffCreateController' => 'applications/differential/controller/DifferentialDiffCreateController.php', 'DifferentialDiffEditor' => 'applications/differential/editor/DifferentialDiffEditor.php', 'DifferentialDiffHeraldField' => 'applications/differential/herald/DifferentialDiffHeraldField.php', 'DifferentialDiffHeraldFieldGroup' => 'applications/differential/herald/DifferentialDiffHeraldFieldGroup.php', 'DifferentialDiffInlineCommentQuery' => 'applications/differential/query/DifferentialDiffInlineCommentQuery.php', 'DifferentialDiffPHIDType' => 'applications/differential/phid/DifferentialDiffPHIDType.php', 'DifferentialDiffProperty' => 'applications/differential/storage/DifferentialDiffProperty.php', 'DifferentialDiffQuery' => 'applications/differential/query/DifferentialDiffQuery.php', 'DifferentialDiffRepositoryHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryHeraldField.php', 'DifferentialDiffRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialDiffRepositoryProjectsHeraldField.php', 'DifferentialDiffTestCase' => 'applications/differential/storage/__tests__/DifferentialDiffTestCase.php', 'DifferentialDiffTransaction' => 'applications/differential/storage/DifferentialDiffTransaction.php', 'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php', 'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php', 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php', 'DifferentialDraft' => 'applications/differential/storage/DifferentialDraft.php', 'DifferentialEditPolicyField' => 'applications/differential/customfield/DifferentialEditPolicyField.php', 'DifferentialFieldParseException' => 'applications/differential/exception/DifferentialFieldParseException.php', 'DifferentialFieldValidationException' => 'applications/differential/exception/DifferentialFieldValidationException.php', 'DifferentialFindConduitAPIMethod' => 'applications/differential/conduit/DifferentialFindConduitAPIMethod.php', 'DifferentialGetAllDiffsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetAllDiffsConduitAPIMethod.php', 'DifferentialGetCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetCommitMessageConduitAPIMethod.php', 'DifferentialGetCommitPathsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetCommitPathsConduitAPIMethod.php', 'DifferentialGetDiffConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetDiffConduitAPIMethod.php', 'DifferentialGetRawDiffConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRawDiffConduitAPIMethod.php', 'DifferentialGetRevisionCommentsConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRevisionCommentsConduitAPIMethod.php', 'DifferentialGetRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialGetRevisionConduitAPIMethod.php', 'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php', 'DifferentialGitHubLandingStrategy' => 'applications/differential/landing/DifferentialGitHubLandingStrategy.php', 'DifferentialGitSVNIDField' => 'applications/differential/customfield/DifferentialGitSVNIDField.php', 'DifferentialHarbormasterField' => 'applications/differential/customfield/DifferentialHarbormasterField.php', 'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php', 'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php', 'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php', 'DifferentialHostedMercurialLandingStrategy' => 'applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php', 'DifferentialHovercardEventListener' => 'applications/differential/event/DifferentialHovercardEventListener.php', 'DifferentialHunk' => 'applications/differential/storage/DifferentialHunk.php', 'DifferentialHunkParser' => 'applications/differential/parser/DifferentialHunkParser.php', 'DifferentialHunkParserTestCase' => 'applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php', 'DifferentialHunkQuery' => 'applications/differential/query/DifferentialHunkQuery.php', 'DifferentialHunkTestCase' => 'applications/differential/storage/__tests__/DifferentialHunkTestCase.php', 'DifferentialInlineComment' => 'applications/differential/storage/DifferentialInlineComment.php', 'DifferentialInlineCommentEditController' => 'applications/differential/controller/DifferentialInlineCommentEditController.php', 'DifferentialInlineCommentPreviewController' => 'applications/differential/controller/DifferentialInlineCommentPreviewController.php', 'DifferentialInlineCommentQuery' => 'applications/differential/query/DifferentialInlineCommentQuery.php', 'DifferentialJIRAIssuesField' => 'applications/differential/customfield/DifferentialJIRAIssuesField.php', 'DifferentialLandingActionMenuEventListener' => 'applications/differential/landing/DifferentialLandingActionMenuEventListener.php', 'DifferentialLandingStrategy' => 'applications/differential/landing/DifferentialLandingStrategy.php', 'DifferentialLegacyHunk' => 'applications/differential/storage/DifferentialLegacyHunk.php', 'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php', 'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php', 'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php', 'DifferentialLocalCommitsView' => 'applications/differential/view/DifferentialLocalCommitsView.php', 'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php', 'DifferentialModernHunk' => 'applications/differential/storage/DifferentialModernHunk.php', 'DifferentialNextStepField' => 'applications/differential/customfield/DifferentialNextStepField.php', 'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php', 'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php', 'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php', 'DifferentialPathField' => 'applications/differential/customfield/DifferentialPathField.php', 'DifferentialPrimaryPaneView' => 'applications/differential/view/DifferentialPrimaryPaneView.php', 'DifferentialProjectReviewersField' => 'applications/differential/customfield/DifferentialProjectReviewersField.php', 'DifferentialProjectsField' => 'applications/differential/customfield/DifferentialProjectsField.php', 'DifferentialQueryConduitAPIMethod' => 'applications/differential/conduit/DifferentialQueryConduitAPIMethod.php', 'DifferentialQueryDiffsConduitAPIMethod' => 'applications/differential/conduit/DifferentialQueryDiffsConduitAPIMethod.php', 'DifferentialRawDiffRenderer' => 'applications/differential/render/DifferentialRawDiffRenderer.php', 'DifferentialReleephRequestFieldSpecification' => 'applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php', 'DifferentialRemarkupRule' => 'applications/differential/remarkup/DifferentialRemarkupRule.php', 'DifferentialReplyHandler' => 'applications/differential/mail/DifferentialReplyHandler.php', 'DifferentialRepositoryField' => 'applications/differential/customfield/DifferentialRepositoryField.php', 'DifferentialRepositoryLookup' => 'applications/differential/query/DifferentialRepositoryLookup.php', 'DifferentialRequiredSignaturesField' => 'applications/differential/customfield/DifferentialRequiredSignaturesField.php', 'DifferentialRevertPlanField' => 'applications/differential/customfield/DifferentialRevertPlanField.php', 'DifferentialReviewedByField' => 'applications/differential/customfield/DifferentialReviewedByField.php', 'DifferentialReviewer' => 'applications/differential/storage/DifferentialReviewer.php', 'DifferentialReviewerForRevisionEdgeType' => 'applications/differential/edge/DifferentialReviewerForRevisionEdgeType.php', 'DifferentialReviewerStatus' => 'applications/differential/constants/DifferentialReviewerStatus.php', 'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingReviewersHeraldAction.php', 'DifferentialReviewersAddBlockingSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddBlockingSelfHeraldAction.php', 'DifferentialReviewersAddReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddReviewersHeraldAction.php', 'DifferentialReviewersAddSelfHeraldAction' => 'applications/differential/herald/DifferentialReviewersAddSelfHeraldAction.php', 'DifferentialReviewersField' => 'applications/differential/customfield/DifferentialReviewersField.php', 'DifferentialReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersHeraldAction.php', 'DifferentialReviewersView' => 'applications/differential/view/DifferentialReviewersView.php', 'DifferentialRevision' => 'applications/differential/storage/DifferentialRevision.php', 'DifferentialRevisionAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialRevisionAffectedFilesHeraldField.php', 'DifferentialRevisionAuthorHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorHeraldField.php', 'DifferentialRevisionAuthorProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php', 'DifferentialRevisionCloseDetailsController' => 'applications/differential/controller/DifferentialRevisionCloseDetailsController.php', 'DifferentialRevisionContentAddedHeraldField' => 'applications/differential/herald/DifferentialRevisionContentAddedHeraldField.php', 'DifferentialRevisionContentHeraldField' => 'applications/differential/herald/DifferentialRevisionContentHeraldField.php', 'DifferentialRevisionContentRemovedHeraldField' => 'applications/differential/herald/DifferentialRevisionContentRemovedHeraldField.php', 'DifferentialRevisionControlSystem' => 'applications/differential/constants/DifferentialRevisionControlSystem.php', 'DifferentialRevisionDependedOnByRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependedOnByRevisionEdgeType.php', 'DifferentialRevisionDependsOnRevisionEdgeType' => 'applications/differential/edge/DifferentialRevisionDependsOnRevisionEdgeType.php', 'DifferentialRevisionDetailView' => 'applications/differential/view/DifferentialRevisionDetailView.php', 'DifferentialRevisionEditController' => 'applications/differential/controller/DifferentialRevisionEditController.php', 'DifferentialRevisionHasCommitEdgeType' => 'applications/differential/edge/DifferentialRevisionHasCommitEdgeType.php', 'DifferentialRevisionHasReviewerEdgeType' => 'applications/differential/edge/DifferentialRevisionHasReviewerEdgeType.php', 'DifferentialRevisionHasTaskEdgeType' => 'applications/differential/edge/DifferentialRevisionHasTaskEdgeType.php', 'DifferentialRevisionHeraldField' => 'applications/differential/herald/DifferentialRevisionHeraldField.php', 'DifferentialRevisionHeraldFieldGroup' => 'applications/differential/herald/DifferentialRevisionHeraldFieldGroup.php', 'DifferentialRevisionIDField' => 'applications/differential/customfield/DifferentialRevisionIDField.php', 'DifferentialRevisionLandController' => 'applications/differential/controller/DifferentialRevisionLandController.php', 'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php', 'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php', 'DifferentialRevisionMailReceiver' => 'applications/differential/mail/DifferentialRevisionMailReceiver.php', 'DifferentialRevisionOperationController' => 'applications/differential/controller/DifferentialRevisionOperationController.php', 'DifferentialRevisionPHIDType' => 'applications/differential/phid/DifferentialRevisionPHIDType.php', 'DifferentialRevisionPackageHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageHeraldField.php', 'DifferentialRevisionPackageOwnerHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageOwnerHeraldField.php', 'DifferentialRevisionQuery' => 'applications/differential/query/DifferentialRevisionQuery.php', 'DifferentialRevisionRepositoryHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryHeraldField.php', 'DifferentialRevisionRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryProjectsHeraldField.php', 'DifferentialRevisionReviewersHeraldField' => 'applications/differential/herald/DifferentialRevisionReviewersHeraldField.php', 'DifferentialRevisionSearchEngine' => 'applications/differential/query/DifferentialRevisionSearchEngine.php', 'DifferentialRevisionStatus' => 'applications/differential/constants/DifferentialRevisionStatus.php', 'DifferentialRevisionSummaryHeraldField' => 'applications/differential/herald/DifferentialRevisionSummaryHeraldField.php', 'DifferentialRevisionTitleHeraldField' => 'applications/differential/herald/DifferentialRevisionTitleHeraldField.php', 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/DifferentialRevisionUpdateHistoryView.php', 'DifferentialRevisionViewController' => 'applications/differential/controller/DifferentialRevisionViewController.php', 'DifferentialSchemaSpec' => 'applications/differential/storage/DifferentialSchemaSpec.php', 'DifferentialSearchIndexer' => 'applications/differential/search/DifferentialSearchIndexer.php', 'DifferentialSetDiffPropertyConduitAPIMethod' => 'applications/differential/conduit/DifferentialSetDiffPropertyConduitAPIMethod.php', 'DifferentialStoredCustomField' => 'applications/differential/customfield/DifferentialStoredCustomField.php', 'DifferentialSubscribersField' => 'applications/differential/customfield/DifferentialSubscribersField.php', 'DifferentialSummaryField' => 'applications/differential/customfield/DifferentialSummaryField.php', 'DifferentialTestPlanField' => 'applications/differential/customfield/DifferentialTestPlanField.php', 'DifferentialTitleField' => 'applications/differential/customfield/DifferentialTitleField.php', 'DifferentialTransaction' => 'applications/differential/storage/DifferentialTransaction.php', 'DifferentialTransactionComment' => 'applications/differential/storage/DifferentialTransactionComment.php', 'DifferentialTransactionEditor' => 'applications/differential/editor/DifferentialTransactionEditor.php', 'DifferentialTransactionQuery' => 'applications/differential/query/DifferentialTransactionQuery.php', 'DifferentialTransactionView' => 'applications/differential/view/DifferentialTransactionView.php', 'DifferentialUnitField' => 'applications/differential/customfield/DifferentialUnitField.php', 'DifferentialUnitStatus' => 'applications/differential/constants/DifferentialUnitStatus.php', 'DifferentialUnitTestResult' => 'applications/differential/constants/DifferentialUnitTestResult.php', 'DifferentialUpdateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php', 'DifferentialViewPolicyField' => 'applications/differential/customfield/DifferentialViewPolicyField.php', 'DiffusionAuditorDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorDatasource.php', 'DiffusionAuditorFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorFunctionDatasource.php', 'DiffusionAuditorsAddAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddAuditorsHeraldAction.php', 'DiffusionAuditorsAddSelfHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php', 'DiffusionAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsHeraldAction.php', 'DiffusionBlockHeraldAction' => 'applications/diffusion/herald/DiffusionBlockHeraldAction.php', 'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php', 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', 'DiffusionBranchTableView' => 'applications/diffusion/view/DiffusionBranchTableView.php', 'DiffusionBrowseController' => 'applications/diffusion/controller/DiffusionBrowseController.php', 'DiffusionBrowseDirectoryController' => 'applications/diffusion/controller/DiffusionBrowseDirectoryController.php', 'DiffusionBrowseFileController' => 'applications/diffusion/controller/DiffusionBrowseFileController.php', 'DiffusionBrowseMainController' => 'applications/diffusion/controller/DiffusionBrowseMainController.php', 'DiffusionBrowseQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php', 'DiffusionBrowseResultSet' => 'applications/diffusion/data/DiffusionBrowseResultSet.php', 'DiffusionBrowseSearchController' => 'applications/diffusion/controller/DiffusionBrowseSearchController.php', 'DiffusionBrowseTableView' => 'applications/diffusion/view/DiffusionBrowseTableView.php', 'DiffusionCachedResolveRefsQuery' => 'applications/diffusion/query/DiffusionCachedResolveRefsQuery.php', 'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php', 'DiffusionChangeHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionChangeHeraldFieldGroup.php', 'DiffusionCommitAffectedFilesHeraldField' => 'applications/diffusion/herald/DiffusionCommitAffectedFilesHeraldField.php', 'DiffusionCommitAuthorHeraldField' => 'applications/diffusion/herald/DiffusionCommitAuthorHeraldField.php', 'DiffusionCommitAutocloseHeraldField' => 'applications/diffusion/herald/DiffusionCommitAutocloseHeraldField.php', 'DiffusionCommitBranchesController' => 'applications/diffusion/controller/DiffusionCommitBranchesController.php', 'DiffusionCommitBranchesHeraldField' => 'applications/diffusion/herald/DiffusionCommitBranchesHeraldField.php', 'DiffusionCommitCommitterHeraldField' => 'applications/diffusion/herald/DiffusionCommitCommitterHeraldField.php', 'DiffusionCommitController' => 'applications/diffusion/controller/DiffusionCommitController.php', 'DiffusionCommitDiffContentAddedHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffContentAddedHeraldField.php', 'DiffusionCommitDiffContentHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffContentHeraldField.php', 'DiffusionCommitDiffContentRemovedHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffContentRemovedHeraldField.php', 'DiffusionCommitDiffEnormousHeraldField' => 'applications/diffusion/herald/DiffusionCommitDiffEnormousHeraldField.php', 'DiffusionCommitEditController' => 'applications/diffusion/controller/DiffusionCommitEditController.php', 'DiffusionCommitHasRevisionEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasRevisionEdgeType.php', 'DiffusionCommitHasTaskEdgeType' => 'applications/diffusion/edge/DiffusionCommitHasTaskEdgeType.php', 'DiffusionCommitHash' => 'applications/diffusion/data/DiffusionCommitHash.php', 'DiffusionCommitHeraldField' => 'applications/diffusion/herald/DiffusionCommitHeraldField.php', 'DiffusionCommitHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionCommitHeraldFieldGroup.php', 'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php', 'DiffusionCommitHookRejectException' => 'applications/diffusion/exception/DiffusionCommitHookRejectException.php', 'DiffusionCommitMessageHeraldField' => 'applications/diffusion/herald/DiffusionCommitMessageHeraldField.php', 'DiffusionCommitPackageAuditHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageAuditHeraldField.php', 'DiffusionCommitPackageHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageHeraldField.php', 'DiffusionCommitPackageOwnerHeraldField' => 'applications/diffusion/herald/DiffusionCommitPackageOwnerHeraldField.php', 'DiffusionCommitParentsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCommitParentsQueryConduitAPIMethod.php', 'DiffusionCommitQuery' => 'applications/diffusion/query/DiffusionCommitQuery.php', 'DiffusionCommitRef' => 'applications/diffusion/data/DiffusionCommitRef.php', 'DiffusionCommitRemarkupRule' => 'applications/diffusion/remarkup/DiffusionCommitRemarkupRule.php', 'DiffusionCommitRemarkupRuleTestCase' => 'applications/diffusion/remarkup/__tests__/DiffusionCommitRemarkupRuleTestCase.php', 'DiffusionCommitRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionCommitRepositoryHeraldField.php', 'DiffusionCommitRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionCommitRepositoryProjectsHeraldField.php', 'DiffusionCommitRevertedByCommitEdgeType' => 'applications/diffusion/edge/DiffusionCommitRevertedByCommitEdgeType.php', 'DiffusionCommitRevertsCommitEdgeType' => 'applications/diffusion/edge/DiffusionCommitRevertsCommitEdgeType.php', 'DiffusionCommitReviewerHeraldField' => 'applications/diffusion/herald/DiffusionCommitReviewerHeraldField.php', 'DiffusionCommitRevisionAcceptedHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionAcceptedHeraldField.php', 'DiffusionCommitRevisionHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionHeraldField.php', 'DiffusionCommitRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php', 'DiffusionCommitRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionSubscribersHeraldField.php', 'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php', 'DiffusionConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionConduitAPIMethod.php', 'DiffusionController' => 'applications/diffusion/controller/DiffusionController.php', 'DiffusionCreateCommentConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCreateCommentConduitAPIMethod.php', 'DiffusionCreateRepositoriesCapability' => 'applications/diffusion/capability/DiffusionCreateRepositoriesCapability.php', 'DiffusionDefaultEditCapability' => 'applications/diffusion/capability/DiffusionDefaultEditCapability.php', 'DiffusionDefaultPushCapability' => 'applications/diffusion/capability/DiffusionDefaultPushCapability.php', 'DiffusionDefaultViewCapability' => 'applications/diffusion/capability/DiffusionDefaultViewCapability.php', 'DiffusionDiffController' => 'applications/diffusion/controller/DiffusionDiffController.php', 'DiffusionDiffInlineCommentQuery' => 'applications/diffusion/query/DiffusionDiffInlineCommentQuery.php', 'DiffusionDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionDiffQueryConduitAPIMethod.php', 'DiffusionDoorkeeperCommitFeedStoryPublisher' => 'applications/diffusion/doorkeeper/DiffusionDoorkeeperCommitFeedStoryPublisher.php', 'DiffusionEmptyResultView' => 'applications/diffusion/view/DiffusionEmptyResultView.php', 'DiffusionExistsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionExistsQueryConduitAPIMethod.php', 'DiffusionExternalController' => 'applications/diffusion/controller/DiffusionExternalController.php', 'DiffusionExternalSymbolQuery' => 'applications/diffusion/symbol/DiffusionExternalSymbolQuery.php', 'DiffusionExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionExternalSymbolsSource.php', 'DiffusionFileContent' => 'applications/diffusion/data/DiffusionFileContent.php', 'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionFileContentQuery.php', 'DiffusionFileContentQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFileContentQueryConduitAPIMethod.php', 'DiffusionFindSymbolsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionFindSymbolsConduitAPIMethod.php', 'DiffusionGetCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetCommitsConduitAPIMethod.php', 'DiffusionGetLintMessagesConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetLintMessagesConduitAPIMethod.php', 'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionGetRecentCommitsByPathConduitAPIMethod.php', 'DiffusionGitBranch' => 'applications/diffusion/data/DiffusionGitBranch.php', 'DiffusionGitBranchTestCase' => 'applications/diffusion/data/__tests__/DiffusionGitBranchTestCase.php', 'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php', 'DiffusionGitFileContentQueryTestCase' => 'applications/diffusion/query/__tests__/DiffusionGitFileContentQueryTestCase.php', 'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php', 'DiffusionGitReceivePackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitReceivePackSSHWorkflow.php', 'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php', 'DiffusionGitResponse' => 'applications/diffusion/response/DiffusionGitResponse.php', 'DiffusionGitSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitSSHWorkflow.php', 'DiffusionGitUploadPackSSHWorkflow' => 'applications/diffusion/ssh/DiffusionGitUploadPackSSHWorkflow.php', 'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php', 'DiffusionHistoryQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php', 'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php', 'DiffusionHovercardEventListener' => 'applications/diffusion/events/DiffusionHovercardEventListener.php', 'DiffusionInlineCommentController' => 'applications/diffusion/controller/DiffusionInlineCommentController.php', 'DiffusionInlineCommentPreviewController' => 'applications/diffusion/controller/DiffusionInlineCommentPreviewController.php', 'DiffusionLastModifiedController' => 'applications/diffusion/controller/DiffusionLastModifiedController.php', 'DiffusionLastModifiedQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLastModifiedQueryConduitAPIMethod.php', 'DiffusionLintController' => 'applications/diffusion/controller/DiffusionLintController.php', 'DiffusionLintCountQuery' => 'applications/diffusion/query/DiffusionLintCountQuery.php', 'DiffusionLintDetailsController' => 'applications/diffusion/controller/DiffusionLintDetailsController.php', 'DiffusionLintSaveRunner' => 'applications/diffusion/DiffusionLintSaveRunner.php', 'DiffusionLookSoonConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionLookSoonConduitAPIMethod.php', 'DiffusionLowLevelCommitFieldsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelCommitFieldsQuery.php', 'DiffusionLowLevelCommitQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelCommitQuery.php', 'DiffusionLowLevelGitRefQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelGitRefQuery.php', 'DiffusionLowLevelMercurialBranchesQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialBranchesQuery.php', 'DiffusionLowLevelMercurialPathsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php', 'DiffusionLowLevelMercurialPathsQueryTests' => 'applications/diffusion/query/lowlevel/__tests__/DiffusionLowLevelMercurialPathsQueryTests.php', 'DiffusionLowLevelParentsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php', 'DiffusionLowLevelQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelQuery.php', 'DiffusionLowLevelResolveRefsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php', 'DiffusionMercurialFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php', 'DiffusionMercurialRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php', 'DiffusionMercurialRequest' => 'applications/diffusion/request/DiffusionMercurialRequest.php', 'DiffusionMercurialResponse' => 'applications/diffusion/response/DiffusionMercurialResponse.php', 'DiffusionMercurialSSHWorkflow' => 'applications/diffusion/ssh/DiffusionMercurialSSHWorkflow.php', 'DiffusionMercurialServeSSHWorkflow' => 'applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php', 'DiffusionMercurialWireClientSSHProtocolChannel' => 'applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php', 'DiffusionMercurialWireProtocol' => 'applications/diffusion/protocol/DiffusionMercurialWireProtocol.php', 'DiffusionMercurialWireProtocolTests' => 'applications/diffusion/protocol/__tests__/DiffusionMercurialWireProtocolTests.php', 'DiffusionMercurialWireSSHTestCase' => 'applications/diffusion/ssh/__tests__/DiffusionMercurialWireSSHTestCase.php', 'DiffusionMergedCommitsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionMergedCommitsQueryConduitAPIMethod.php', 'DiffusionMirrorDeleteController' => 'applications/diffusion/controller/DiffusionMirrorDeleteController.php', 'DiffusionMirrorEditController' => 'applications/diffusion/controller/DiffusionMirrorEditController.php', 'DiffusionPathChange' => 'applications/diffusion/data/DiffusionPathChange.php', 'DiffusionPathChangeQuery' => 'applications/diffusion/query/pathchange/DiffusionPathChangeQuery.php', 'DiffusionPathCompleteController' => 'applications/diffusion/controller/DiffusionPathCompleteController.php', 'DiffusionPathIDQuery' => 'applications/diffusion/query/pathid/DiffusionPathIDQuery.php', 'DiffusionPathQuery' => 'applications/diffusion/query/DiffusionPathQuery.php', 'DiffusionPathQueryTestCase' => 'applications/diffusion/query/pathid/__tests__/DiffusionPathQueryTestCase.php', 'DiffusionPathTreeController' => 'applications/diffusion/controller/DiffusionPathTreeController.php', 'DiffusionPathValidateController' => 'applications/diffusion/controller/DiffusionPathValidateController.php', 'DiffusionPhpExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionPhpExternalSymbolsSource.php', 'DiffusionPreCommitContentAffectedFilesHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAffectedFilesHeraldField.php', 'DiffusionPreCommitContentAuthorHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAuthorHeraldField.php', 'DiffusionPreCommitContentAuthorRawHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentAuthorRawHeraldField.php', 'DiffusionPreCommitContentBranchesHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentBranchesHeraldField.php', 'DiffusionPreCommitContentCommitterHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentCommitterHeraldField.php', 'DiffusionPreCommitContentCommitterRawHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentCommitterRawHeraldField.php', 'DiffusionPreCommitContentDiffContentAddedHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentDiffContentAddedHeraldField.php', 'DiffusionPreCommitContentDiffContentHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentDiffContentHeraldField.php', 'DiffusionPreCommitContentDiffContentRemovedHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentDiffContentRemovedHeraldField.php', 'DiffusionPreCommitContentDiffEnormousHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentDiffEnormousHeraldField.php', 'DiffusionPreCommitContentHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentHeraldField.php', 'DiffusionPreCommitContentMergeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentMergeHeraldField.php', 'DiffusionPreCommitContentMessageHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentMessageHeraldField.php', 'DiffusionPreCommitContentPusherHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherHeraldField.php', 'DiffusionPreCommitContentPusherIsCommitterHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherIsCommitterHeraldField.php', 'DiffusionPreCommitContentPusherProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherProjectsHeraldField.php', 'DiffusionPreCommitContentRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRepositoryHeraldField.php', 'DiffusionPreCommitContentRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRepositoryProjectsHeraldField.php', 'DiffusionPreCommitContentRevisionAcceptedHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionAcceptedHeraldField.php', 'DiffusionPreCommitContentRevisionHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionHeraldField.php', 'DiffusionPreCommitContentRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionReviewersHeraldField.php', 'DiffusionPreCommitContentRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentRevisionSubscribersHeraldField.php', 'DiffusionPreCommitRefChangeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefChangeHeraldField.php', 'DiffusionPreCommitRefHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefHeraldField.php', 'DiffusionPreCommitRefHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionPreCommitRefHeraldFieldGroup.php', 'DiffusionPreCommitRefNameHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefNameHeraldField.php', 'DiffusionPreCommitRefPusherHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefPusherHeraldField.php', 'DiffusionPreCommitRefPusherProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefPusherProjectsHeraldField.php', 'DiffusionPreCommitRefRepositoryHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefRepositoryHeraldField.php', 'DiffusionPreCommitRefRepositoryProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefRepositoryProjectsHeraldField.php', 'DiffusionPreCommitRefTypeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitRefTypeHeraldField.php', 'DiffusionPushCapability' => 'applications/diffusion/capability/DiffusionPushCapability.php', 'DiffusionPushEventViewController' => 'applications/diffusion/controller/DiffusionPushEventViewController.php', 'DiffusionPushLogController' => 'applications/diffusion/controller/DiffusionPushLogController.php', 'DiffusionPushLogListController' => 'applications/diffusion/controller/DiffusionPushLogListController.php', 'DiffusionPushLogListView' => 'applications/diffusion/view/DiffusionPushLogListView.php', 'DiffusionPythonExternalSymbolsSource' => 'applications/diffusion/symbol/DiffusionPythonExternalSymbolsSource.php', 'DiffusionQuery' => 'applications/diffusion/query/DiffusionQuery.php', 'DiffusionQueryCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php', 'DiffusionQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php', 'DiffusionQueryPathsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php', 'DiffusionRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionRawDiffQuery.php', 'DiffusionRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRawDiffQueryConduitAPIMethod.php', 'DiffusionReadmeView' => 'applications/diffusion/view/DiffusionReadmeView.php', 'DiffusionRefNotFoundException' => 'applications/diffusion/exception/DiffusionRefNotFoundException.php', 'DiffusionRefTableController' => 'applications/diffusion/controller/DiffusionRefTableController.php', 'DiffusionRefsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRefsQueryConduitAPIMethod.php', 'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php', 'DiffusionRepositoryByIDRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryByIDRemarkupRule.php', 'DiffusionRepositoryController' => 'applications/diffusion/controller/DiffusionRepositoryController.php', 'DiffusionRepositoryCreateController' => 'applications/diffusion/controller/DiffusionRepositoryCreateController.php', 'DiffusionRepositoryDatasource' => 'applications/diffusion/typeahead/DiffusionRepositoryDatasource.php', 'DiffusionRepositoryDefaultController' => 'applications/diffusion/controller/DiffusionRepositoryDefaultController.php', 'DiffusionRepositoryEditActionsController' => 'applications/diffusion/controller/DiffusionRepositoryEditActionsController.php', 'DiffusionRepositoryEditActivateController' => 'applications/diffusion/controller/DiffusionRepositoryEditActivateController.php', 'DiffusionRepositoryEditAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryEditAutomationController.php', 'DiffusionRepositoryEditBasicController' => 'applications/diffusion/controller/DiffusionRepositoryEditBasicController.php', 'DiffusionRepositoryEditBranchesController' => 'applications/diffusion/controller/DiffusionRepositoryEditBranchesController.php', 'DiffusionRepositoryEditController' => 'applications/diffusion/controller/DiffusionRepositoryEditController.php', 'DiffusionRepositoryEditDangerousController' => 'applications/diffusion/controller/DiffusionRepositoryEditDangerousController.php', 'DiffusionRepositoryEditDeleteController' => 'applications/diffusion/controller/DiffusionRepositoryEditDeleteController.php', 'DiffusionRepositoryEditEncodingController' => 'applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php', 'DiffusionRepositoryEditHostingController' => 'applications/diffusion/controller/DiffusionRepositoryEditHostingController.php', 'DiffusionRepositoryEditMainController' => 'applications/diffusion/controller/DiffusionRepositoryEditMainController.php', 'DiffusionRepositoryEditStagingController' => 'applications/diffusion/controller/DiffusionRepositoryEditStagingController.php', 'DiffusionRepositoryEditStorageController' => 'applications/diffusion/controller/DiffusionRepositoryEditStorageController.php', 'DiffusionRepositoryEditSubversionController' => 'applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php', 'DiffusionRepositoryEditUpdateController' => 'applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php', 'DiffusionRepositoryListController' => 'applications/diffusion/controller/DiffusionRepositoryListController.php', 'DiffusionRepositoryNewController' => 'applications/diffusion/controller/DiffusionRepositoryNewController.php', 'DiffusionRepositoryPath' => 'applications/diffusion/data/DiffusionRepositoryPath.php', 'DiffusionRepositoryRef' => 'applications/diffusion/data/DiffusionRepositoryRef.php', 'DiffusionRepositoryRemarkupRule' => 'applications/diffusion/remarkup/DiffusionRepositoryRemarkupRule.php', 'DiffusionRepositorySymbolsController' => 'applications/diffusion/controller/DiffusionRepositorySymbolsController.php', 'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php', 'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php', 'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php', 'DiffusionResolveRefsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionResolveRefsConduitAPIMethod.php', 'DiffusionResolveUserQuery' => 'applications/diffusion/query/DiffusionResolveUserQuery.php', 'DiffusionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSSHWorkflow.php', 'DiffusionSearchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionSearchQueryConduitAPIMethod.php', 'DiffusionServeController' => 'applications/diffusion/controller/DiffusionServeController.php', 'DiffusionSetPasswordSettingsPanel' => 'applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php', 'DiffusionSetupException' => 'applications/diffusion/exception/DiffusionSetupException.php', 'DiffusionSubversionSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSubversionSSHWorkflow.php', 'DiffusionSubversionServeSSHWorkflow' => 'applications/diffusion/ssh/DiffusionSubversionServeSSHWorkflow.php', 'DiffusionSubversionWireProtocol' => 'applications/diffusion/protocol/DiffusionSubversionWireProtocol.php', 'DiffusionSubversionWireProtocolTestCase' => 'applications/diffusion/protocol/__tests__/DiffusionSubversionWireProtocolTestCase.php', 'DiffusionSvnFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionSvnFileContentQuery.php', 'DiffusionSvnRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionSvnRawDiffQuery.php', 'DiffusionSvnRequest' => 'applications/diffusion/request/DiffusionSvnRequest.php', 'DiffusionSymbolController' => 'applications/diffusion/controller/DiffusionSymbolController.php', 'DiffusionSymbolDatasource' => 'applications/diffusion/typeahead/DiffusionSymbolDatasource.php', 'DiffusionSymbolQuery' => 'applications/diffusion/query/DiffusionSymbolQuery.php', 'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php', 'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php', 'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php', 'DiffusionURITestCase' => 'applications/diffusion/request/__tests__/DiffusionURITestCase.php', 'DiffusionUpdateCoverageConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php', 'DiffusionView' => 'applications/diffusion/view/DiffusionView.php', 'DivinerArticleAtomizer' => 'applications/diviner/atomizer/DivinerArticleAtomizer.php', 'DivinerAtom' => 'applications/diviner/atom/DivinerAtom.php', 'DivinerAtomCache' => 'applications/diviner/cache/DivinerAtomCache.php', 'DivinerAtomController' => 'applications/diviner/controller/DivinerAtomController.php', 'DivinerAtomListController' => 'applications/diviner/controller/DivinerAtomListController.php', 'DivinerAtomPHIDType' => 'applications/diviner/phid/DivinerAtomPHIDType.php', 'DivinerAtomQuery' => 'applications/diviner/query/DivinerAtomQuery.php', 'DivinerAtomRef' => 'applications/diviner/atom/DivinerAtomRef.php', 'DivinerAtomSearchEngine' => 'applications/diviner/query/DivinerAtomSearchEngine.php', 'DivinerAtomSearchIndexer' => 'applications/diviner/search/DivinerAtomSearchIndexer.php', 'DivinerAtomizeWorkflow' => 'applications/diviner/workflow/DivinerAtomizeWorkflow.php', 'DivinerAtomizer' => 'applications/diviner/atomizer/DivinerAtomizer.php', 'DivinerBookController' => 'applications/diviner/controller/DivinerBookController.php', 'DivinerBookDatasource' => 'applications/diviner/typeahead/DivinerBookDatasource.php', 'DivinerBookEditController' => 'applications/diviner/controller/DivinerBookEditController.php', 'DivinerBookItemView' => 'applications/diviner/view/DivinerBookItemView.php', 'DivinerBookPHIDType' => 'applications/diviner/phid/DivinerBookPHIDType.php', 'DivinerBookQuery' => 'applications/diviner/query/DivinerBookQuery.php', 'DivinerBookSearchIndexer' => 'applications/diviner/search/DivinerBookSearchIndexer.php', 'DivinerController' => 'applications/diviner/controller/DivinerController.php', 'DivinerDAO' => 'applications/diviner/storage/DivinerDAO.php', 'DivinerDefaultEditCapability' => 'applications/diviner/capability/DivinerDefaultEditCapability.php', 'DivinerDefaultRenderer' => 'applications/diviner/renderer/DivinerDefaultRenderer.php', 'DivinerDefaultViewCapability' => 'applications/diviner/capability/DivinerDefaultViewCapability.php', 'DivinerDiskCache' => 'applications/diviner/cache/DivinerDiskCache.php', 'DivinerFileAtomizer' => 'applications/diviner/atomizer/DivinerFileAtomizer.php', 'DivinerFindController' => 'applications/diviner/controller/DivinerFindController.php', 'DivinerGenerateWorkflow' => 'applications/diviner/workflow/DivinerGenerateWorkflow.php', 'DivinerLiveAtom' => 'applications/diviner/storage/DivinerLiveAtom.php', 'DivinerLiveBook' => 'applications/diviner/storage/DivinerLiveBook.php', 'DivinerLiveBookEditor' => 'applications/diviner/editor/DivinerLiveBookEditor.php', 'DivinerLiveBookTransaction' => 'applications/diviner/storage/DivinerLiveBookTransaction.php', 'DivinerLiveBookTransactionQuery' => 'applications/diviner/query/DivinerLiveBookTransactionQuery.php', 'DivinerLivePublisher' => 'applications/diviner/publisher/DivinerLivePublisher.php', 'DivinerLiveSymbol' => 'applications/diviner/storage/DivinerLiveSymbol.php', 'DivinerMainController' => 'applications/diviner/controller/DivinerMainController.php', 'DivinerPHPAtomizer' => 'applications/diviner/atomizer/DivinerPHPAtomizer.php', 'DivinerParameterTableView' => 'applications/diviner/view/DivinerParameterTableView.php', 'DivinerPublishCache' => 'applications/diviner/cache/DivinerPublishCache.php', 'DivinerPublisher' => 'applications/diviner/publisher/DivinerPublisher.php', 'DivinerRenderer' => 'applications/diviner/renderer/DivinerRenderer.php', 'DivinerReturnTableView' => 'applications/diviner/view/DivinerReturnTableView.php', 'DivinerSchemaSpec' => 'applications/diviner/storage/DivinerSchemaSpec.php', 'DivinerSectionView' => 'applications/diviner/view/DivinerSectionView.php', 'DivinerStaticPublisher' => 'applications/diviner/publisher/DivinerStaticPublisher.php', 'DivinerSymbolRemarkupRule' => 'applications/diviner/markup/DivinerSymbolRemarkupRule.php', 'DivinerWorkflow' => 'applications/diviner/workflow/DivinerWorkflow.php', 'DoorkeeperAsanaFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperAsanaFeedWorker.php', 'DoorkeeperAsanaRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperAsanaRemarkupRule.php', 'DoorkeeperBridge' => 'applications/doorkeeper/bridge/DoorkeeperBridge.php', 'DoorkeeperBridgeAsana' => 'applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php', 'DoorkeeperBridgeJIRA' => 'applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php', 'DoorkeeperBridgeJIRATestCase' => 'applications/doorkeeper/bridge/__tests__/DoorkeeperBridgeJIRATestCase.php', 'DoorkeeperDAO' => 'applications/doorkeeper/storage/DoorkeeperDAO.php', 'DoorkeeperExternalObject' => 'applications/doorkeeper/storage/DoorkeeperExternalObject.php', 'DoorkeeperExternalObjectQuery' => 'applications/doorkeeper/query/DoorkeeperExternalObjectQuery.php', 'DoorkeeperFeedStoryPublisher' => 'applications/doorkeeper/engine/DoorkeeperFeedStoryPublisher.php', 'DoorkeeperFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperFeedWorker.php', 'DoorkeeperImportEngine' => 'applications/doorkeeper/engine/DoorkeeperImportEngine.php', 'DoorkeeperJIRAFeedWorker' => 'applications/doorkeeper/worker/DoorkeeperJIRAFeedWorker.php', 'DoorkeeperJIRARemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperJIRARemarkupRule.php', 'DoorkeeperMissingLinkException' => 'applications/doorkeeper/exception/DoorkeeperMissingLinkException.php', 'DoorkeeperObjectRef' => 'applications/doorkeeper/engine/DoorkeeperObjectRef.php', 'DoorkeeperRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperRemarkupRule.php', 'DoorkeeperSchemaSpec' => 'applications/doorkeeper/storage/DoorkeeperSchemaSpec.php', 'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php', 'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php', 'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php', 'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php', 'DrydockAuthorization' => 'applications/drydock/storage/DrydockAuthorization.php', 'DrydockAuthorizationAuthorizeController' => 'applications/drydock/controller/DrydockAuthorizationAuthorizeController.php', 'DrydockAuthorizationListController' => 'applications/drydock/controller/DrydockAuthorizationListController.php', 'DrydockAuthorizationListView' => 'applications/drydock/view/DrydockAuthorizationListView.php', 'DrydockAuthorizationPHIDType' => 'applications/drydock/phid/DrydockAuthorizationPHIDType.php', 'DrydockAuthorizationQuery' => 'applications/drydock/query/DrydockAuthorizationQuery.php', 'DrydockAuthorizationSearchEngine' => 'applications/drydock/query/DrydockAuthorizationSearchEngine.php', 'DrydockAuthorizationViewController' => 'applications/drydock/controller/DrydockAuthorizationViewController.php', 'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php', 'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php', 'DrydockBlueprintCoreCustomField' => 'applications/drydock/customfield/DrydockBlueprintCoreCustomField.php', 'DrydockBlueprintCreateController' => 'applications/drydock/controller/DrydockBlueprintCreateController.php', 'DrydockBlueprintCustomField' => 'applications/drydock/customfield/DrydockBlueprintCustomField.php', 'DrydockBlueprintDatasource' => 'applications/drydock/typeahead/DrydockBlueprintDatasource.php', 'DrydockBlueprintDisableController' => 'applications/drydock/controller/DrydockBlueprintDisableController.php', 'DrydockBlueprintEditController' => 'applications/drydock/controller/DrydockBlueprintEditController.php', 'DrydockBlueprintEditor' => 'applications/drydock/editor/DrydockBlueprintEditor.php', 'DrydockBlueprintImplementation' => 'applications/drydock/blueprint/DrydockBlueprintImplementation.php', 'DrydockBlueprintImplementationTestCase' => 'applications/drydock/blueprint/__tests__/DrydockBlueprintImplementationTestCase.php', 'DrydockBlueprintListController' => 'applications/drydock/controller/DrydockBlueprintListController.php', 'DrydockBlueprintPHIDType' => 'applications/drydock/phid/DrydockBlueprintPHIDType.php', 'DrydockBlueprintQuery' => 'applications/drydock/query/DrydockBlueprintQuery.php', 'DrydockBlueprintSearchEngine' => 'applications/drydock/query/DrydockBlueprintSearchEngine.php', 'DrydockBlueprintTransaction' => 'applications/drydock/storage/DrydockBlueprintTransaction.php', 'DrydockBlueprintTransactionQuery' => 'applications/drydock/query/DrydockBlueprintTransactionQuery.php', 'DrydockBlueprintViewController' => 'applications/drydock/controller/DrydockBlueprintViewController.php', 'DrydockCommand' => 'applications/drydock/storage/DrydockCommand.php', 'DrydockCommandInterface' => 'applications/drydock/interface/command/DrydockCommandInterface.php', 'DrydockCommandQuery' => 'applications/drydock/query/DrydockCommandQuery.php', 'DrydockConsoleController' => 'applications/drydock/controller/DrydockConsoleController.php', 'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php', 'DrydockController' => 'applications/drydock/controller/DrydockController.php', 'DrydockCreateBlueprintsCapability' => 'applications/drydock/capability/DrydockCreateBlueprintsCapability.php', 'DrydockDAO' => 'applications/drydock/storage/DrydockDAO.php', 'DrydockDefaultEditCapability' => 'applications/drydock/capability/DrydockDefaultEditCapability.php', 'DrydockDefaultViewCapability' => 'applications/drydock/capability/DrydockDefaultViewCapability.php', 'DrydockFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockFilesystemInterface.php', 'DrydockInterface' => 'applications/drydock/interface/DrydockInterface.php', 'DrydockLandRepositoryOperation' => 'applications/drydock/operation/DrydockLandRepositoryOperation.php', 'DrydockLease' => 'applications/drydock/storage/DrydockLease.php', 'DrydockLeaseAcquiredLogType' => 'applications/drydock/logtype/DrydockLeaseAcquiredLogType.php', 'DrydockLeaseActivatedLogType' => 'applications/drydock/logtype/DrydockLeaseActivatedLogType.php', 'DrydockLeaseActivationFailureLogType' => 'applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php', 'DrydockLeaseActivationYieldLogType' => 'applications/drydock/logtype/DrydockLeaseActivationYieldLogType.php', 'DrydockLeaseController' => 'applications/drydock/controller/DrydockLeaseController.php', 'DrydockLeaseDatasource' => 'applications/drydock/typeahead/DrydockLeaseDatasource.php', 'DrydockLeaseDestroyedLogType' => 'applications/drydock/logtype/DrydockLeaseDestroyedLogType.php', 'DrydockLeaseListController' => 'applications/drydock/controller/DrydockLeaseListController.php', 'DrydockLeaseListView' => 'applications/drydock/view/DrydockLeaseListView.php', 'DrydockLeaseNoAuthorizationsLogType' => 'applications/drydock/logtype/DrydockLeaseNoAuthorizationsLogType.php', 'DrydockLeaseNoBlueprintsLogType' => 'applications/drydock/logtype/DrydockLeaseNoBlueprintsLogType.php', 'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php', 'DrydockLeaseQuery' => 'applications/drydock/query/DrydockLeaseQuery.php', 'DrydockLeaseQueuedLogType' => 'applications/drydock/logtype/DrydockLeaseQueuedLogType.php', 'DrydockLeaseReleaseController' => 'applications/drydock/controller/DrydockLeaseReleaseController.php', 'DrydockLeaseReleasedLogType' => 'applications/drydock/logtype/DrydockLeaseReleasedLogType.php', 'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php', 'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php', 'DrydockLeaseUpdateWorker' => 'applications/drydock/worker/DrydockLeaseUpdateWorker.php', 'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php', 'DrydockLeaseWaitingForResourcesLogType' => 'applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php', 'DrydockLog' => 'applications/drydock/storage/DrydockLog.php', 'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php', 'DrydockLogGarbageCollector' => 'applications/drydock/garbagecollector/DrydockLogGarbageCollector.php', 'DrydockLogListController' => 'applications/drydock/controller/DrydockLogListController.php', 'DrydockLogListView' => 'applications/drydock/view/DrydockLogListView.php', 'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php', 'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php', 'DrydockLogType' => 'applications/drydock/logtype/DrydockLogType.php', 'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php', 'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php', 'DrydockManagementReleaseLeaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseLeaseWorkflow.php', 'DrydockManagementReleaseResourceWorkflow' => 'applications/drydock/management/DrydockManagementReleaseResourceWorkflow.php', 'DrydockManagementUpdateLeaseWorkflow' => 'applications/drydock/management/DrydockManagementUpdateLeaseWorkflow.php', 'DrydockManagementUpdateResourceWorkflow' => 'applications/drydock/management/DrydockManagementUpdateResourceWorkflow.php', 'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php', 'DrydockObjectAuthorizationView' => 'applications/drydock/view/DrydockObjectAuthorizationView.php', 'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php', 'DrydockRepositoryOperation' => 'applications/drydock/storage/DrydockRepositoryOperation.php', 'DrydockRepositoryOperationListController' => 'applications/drydock/controller/DrydockRepositoryOperationListController.php', 'DrydockRepositoryOperationPHIDType' => 'applications/drydock/phid/DrydockRepositoryOperationPHIDType.php', 'DrydockRepositoryOperationQuery' => 'applications/drydock/query/DrydockRepositoryOperationQuery.php', 'DrydockRepositoryOperationSearchEngine' => 'applications/drydock/query/DrydockRepositoryOperationSearchEngine.php', 'DrydockRepositoryOperationStatusController' => 'applications/drydock/controller/DrydockRepositoryOperationStatusController.php', 'DrydockRepositoryOperationStatusView' => 'applications/drydock/view/DrydockRepositoryOperationStatusView.php', 'DrydockRepositoryOperationType' => 'applications/drydock/operation/DrydockRepositoryOperationType.php', 'DrydockRepositoryOperationUpdateWorker' => 'applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php', 'DrydockRepositoryOperationViewController' => 'applications/drydock/controller/DrydockRepositoryOperationViewController.php', 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', 'DrydockResourceActivationFailureLogType' => 'applications/drydock/logtype/DrydockResourceActivationFailureLogType.php', 'DrydockResourceActivationYieldLogType' => 'applications/drydock/logtype/DrydockResourceActivationYieldLogType.php', 'DrydockResourceController' => 'applications/drydock/controller/DrydockResourceController.php', 'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php', 'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php', 'DrydockResourceListView' => 'applications/drydock/view/DrydockResourceListView.php', 'DrydockResourcePHIDType' => 'applications/drydock/phid/DrydockResourcePHIDType.php', 'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php', 'DrydockResourceReleaseController' => 'applications/drydock/controller/DrydockResourceReleaseController.php', 'DrydockResourceSearchEngine' => 'applications/drydock/query/DrydockResourceSearchEngine.php', 'DrydockResourceStatus' => 'applications/drydock/constants/DrydockResourceStatus.php', 'DrydockResourceUpdateWorker' => 'applications/drydock/worker/DrydockResourceUpdateWorker.php', 'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php', 'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php', 'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php', 'DrydockSlotLock' => 'applications/drydock/storage/DrydockSlotLock.php', 'DrydockSlotLockException' => 'applications/drydock/exception/DrydockSlotLockException.php', 'DrydockSlotLockFailureLogType' => 'applications/drydock/logtype/DrydockSlotLockFailureLogType.php', 'DrydockTestRepositoryOperation' => 'applications/drydock/operation/DrydockTestRepositoryOperation.php', 'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php', 'DrydockWorker' => 'applications/drydock/worker/DrydockWorker.php', 'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php', 'FeedConduitAPIMethod' => 'applications/feed/conduit/FeedConduitAPIMethod.php', 'FeedPublishConduitAPIMethod' => 'applications/feed/conduit/FeedPublishConduitAPIMethod.php', 'FeedPublisherHTTPWorker' => 'applications/feed/worker/FeedPublisherHTTPWorker.php', 'FeedPublisherWorker' => 'applications/feed/worker/FeedPublisherWorker.php', 'FeedPushWorker' => 'applications/feed/worker/FeedPushWorker.php', 'FeedQueryConduitAPIMethod' => 'applications/feed/conduit/FeedQueryConduitAPIMethod.php', 'FeedStoryNotificationGarbageCollector' => 'applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php', 'FileAllocateConduitAPIMethod' => 'applications/files/conduit/FileAllocateConduitAPIMethod.php', 'FileConduitAPIMethod' => 'applications/files/conduit/FileConduitAPIMethod.php', 'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php', 'FileDownloadConduitAPIMethod' => 'applications/files/conduit/FileDownloadConduitAPIMethod.php', 'FileInfoConduitAPIMethod' => 'applications/files/conduit/FileInfoConduitAPIMethod.php', 'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php', 'FileQueryChunksConduitAPIMethod' => 'applications/files/conduit/FileQueryChunksConduitAPIMethod.php', 'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php', 'FileUploadChunkConduitAPIMethod' => 'applications/files/conduit/FileUploadChunkConduitAPIMethod.php', 'FileUploadConduitAPIMethod' => 'applications/files/conduit/FileUploadConduitAPIMethod.php', 'FileUploadHashConduitAPIMethod' => 'applications/files/conduit/FileUploadHashConduitAPIMethod.php', 'FilesDefaultViewCapability' => 'applications/files/capability/FilesDefaultViewCapability.php', 'FlagConduitAPIMethod' => 'applications/flag/conduit/FlagConduitAPIMethod.php', 'FlagDeleteConduitAPIMethod' => 'applications/flag/conduit/FlagDeleteConduitAPIMethod.php', 'FlagEditConduitAPIMethod' => 'applications/flag/conduit/FlagEditConduitAPIMethod.php', 'FlagQueryConduitAPIMethod' => 'applications/flag/conduit/FlagQueryConduitAPIMethod.php', 'FundBacker' => 'applications/fund/storage/FundBacker.php', 'FundBackerCart' => 'applications/fund/phortune/FundBackerCart.php', 'FundBackerEditor' => 'applications/fund/editor/FundBackerEditor.php', 'FundBackerListController' => 'applications/fund/controller/FundBackerListController.php', 'FundBackerPHIDType' => 'applications/fund/phid/FundBackerPHIDType.php', 'FundBackerProduct' => 'applications/fund/phortune/FundBackerProduct.php', 'FundBackerQuery' => 'applications/fund/query/FundBackerQuery.php', 'FundBackerSearchEngine' => 'applications/fund/query/FundBackerSearchEngine.php', 'FundBackerTransaction' => 'applications/fund/storage/FundBackerTransaction.php', 'FundBackerTransactionQuery' => 'applications/fund/query/FundBackerTransactionQuery.php', 'FundController' => 'applications/fund/controller/FundController.php', 'FundCreateInitiativesCapability' => 'applications/fund/capability/FundCreateInitiativesCapability.php', 'FundDAO' => 'applications/fund/storage/FundDAO.php', 'FundDefaultViewCapability' => 'applications/fund/capability/FundDefaultViewCapability.php', 'FundInitiative' => 'applications/fund/storage/FundInitiative.php', 'FundInitiativeBackController' => 'applications/fund/controller/FundInitiativeBackController.php', 'FundInitiativeCloseController' => 'applications/fund/controller/FundInitiativeCloseController.php', 'FundInitiativeEditController' => 'applications/fund/controller/FundInitiativeEditController.php', 'FundInitiativeEditor' => 'applications/fund/editor/FundInitiativeEditor.php', 'FundInitiativeIndexer' => 'applications/fund/search/FundInitiativeIndexer.php', 'FundInitiativeListController' => 'applications/fund/controller/FundInitiativeListController.php', 'FundInitiativePHIDType' => 'applications/fund/phid/FundInitiativePHIDType.php', 'FundInitiativeQuery' => 'applications/fund/query/FundInitiativeQuery.php', 'FundInitiativeRemarkupRule' => 'applications/fund/remarkup/FundInitiativeRemarkupRule.php', 'FundInitiativeReplyHandler' => 'applications/fund/mail/FundInitiativeReplyHandler.php', 'FundInitiativeSearchEngine' => 'applications/fund/query/FundInitiativeSearchEngine.php', 'FundInitiativeTransaction' => 'applications/fund/storage/FundInitiativeTransaction.php', 'FundInitiativeTransactionQuery' => 'applications/fund/query/FundInitiativeTransactionQuery.php', 'FundInitiativeViewController' => 'applications/fund/controller/FundInitiativeViewController.php', 'FundSchemaSpec' => 'applications/fund/storage/FundSchemaSpec.php', 'HarbormasterArcLintBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php', 'HarbormasterArcUnitBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php', 'HarbormasterArtifact' => 'applications/harbormaster/artifact/HarbormasterArtifact.php', 'HarbormasterAutotargetsTestCase' => 'applications/harbormaster/__tests__/HarbormasterAutotargetsTestCase.php', 'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php', 'HarbormasterBuildAbortedException' => 'applications/harbormaster/exception/HarbormasterBuildAbortedException.php', 'HarbormasterBuildActionController' => 'applications/harbormaster/controller/HarbormasterBuildActionController.php', 'HarbormasterBuildArcanistAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildArcanistAutoplan.php', 'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php', 'HarbormasterBuildArtifactPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php', 'HarbormasterBuildArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildArtifactQuery.php', 'HarbormasterBuildAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildAutoplan.php', 'HarbormasterBuildCommand' => 'applications/harbormaster/storage/HarbormasterBuildCommand.php', 'HarbormasterBuildDependencyDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildDependencyDatasource.php', 'HarbormasterBuildEngine' => 'applications/harbormaster/engine/HarbormasterBuildEngine.php', 'HarbormasterBuildFailureException' => 'applications/harbormaster/exception/HarbormasterBuildFailureException.php', 'HarbormasterBuildGraph' => 'applications/harbormaster/engine/HarbormasterBuildGraph.php', 'HarbormasterBuildLintMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php', 'HarbormasterBuildLog' => 'applications/harbormaster/storage/build/HarbormasterBuildLog.php', 'HarbormasterBuildLogPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildLogPHIDType.php', 'HarbormasterBuildLogQuery' => 'applications/harbormaster/query/HarbormasterBuildLogQuery.php', 'HarbormasterBuildMessage' => 'applications/harbormaster/storage/HarbormasterBuildMessage.php', 'HarbormasterBuildMessageQuery' => 'applications/harbormaster/query/HarbormasterBuildMessageQuery.php', 'HarbormasterBuildPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPHIDType.php', 'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php', 'HarbormasterBuildPlanDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildPlanDatasource.php', 'HarbormasterBuildPlanDefaultEditCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultEditCapability.php', 'HarbormasterBuildPlanDefaultViewCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php', 'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php', 'HarbormasterBuildPlanPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPlanPHIDType.php', 'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php', 'HarbormasterBuildPlanSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php', 'HarbormasterBuildPlanTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php', 'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php', 'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php', 'HarbormasterBuildRequest' => 'applications/harbormaster/engine/HarbormasterBuildRequest.php', 'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php', 'HarbormasterBuildStepCoreCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCoreCustomField.php', 'HarbormasterBuildStepCustomField' => 'applications/harbormaster/customfield/HarbormasterBuildStepCustomField.php', 'HarbormasterBuildStepEditor' => 'applications/harbormaster/editor/HarbormasterBuildStepEditor.php', 'HarbormasterBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuildStepGroup.php', 'HarbormasterBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterBuildStepImplementation.php', 'HarbormasterBuildStepImplementationTestCase' => 'applications/harbormaster/step/__tests__/HarbormasterBuildStepImplementationTestCase.php', 'HarbormasterBuildStepPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildStepPHIDType.php', 'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php', 'HarbormasterBuildStepTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStepTransaction.php', 'HarbormasterBuildStepTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildStepTransactionQuery.php', 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php', 'HarbormasterBuildTargetPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildTargetPHIDType.php', 'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php', 'HarbormasterBuildTransaction' => 'applications/harbormaster/storage/HarbormasterBuildTransaction.php', 'HarbormasterBuildTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php', 'HarbormasterBuildTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildTransactionQuery.php', 'HarbormasterBuildUnitMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php', 'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php', 'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php', 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', 'HarbormasterBuildableActionController' => 'applications/harbormaster/controller/HarbormasterBuildableActionController.php', 'HarbormasterBuildableAdapterInterface' => 'applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php', 'HarbormasterBuildableInterface' => 'applications/harbormaster/interface/HarbormasterBuildableInterface.php', 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', 'HarbormasterBuildablePHIDType' => 'applications/harbormaster/phid/HarbormasterBuildablePHIDType.php', 'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php', 'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php', 'HarbormasterBuildableTransaction' => 'applications/harbormaster/storage/HarbormasterBuildableTransaction.php', 'HarbormasterBuildableTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php', 'HarbormasterBuildableTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildableTransactionQuery.php', 'HarbormasterBuildableViewController' => 'applications/harbormaster/controller/HarbormasterBuildableViewController.php', 'HarbormasterBuiltinBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php', 'HarbormasterCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php', 'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php', 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 'HarbormasterCreateArtifactConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterCreateArtifactConduitAPIMethod.php', 'HarbormasterCreatePlansCapability' => 'applications/harbormaster/capability/HarbormasterCreatePlansCapability.php', 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', 'HarbormasterDrydockBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterDrydockBuildStepGroup.php', 'HarbormasterDrydockCommandBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterDrydockCommandBuildStepImplementation.php', 'HarbormasterDrydockLeaseArtifact' => 'applications/harbormaster/artifact/HarbormasterDrydockLeaseArtifact.php', 'HarbormasterExecFuture' => 'applications/harbormaster/future/HarbormasterExecFuture.php', 'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php', 'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php', 'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php', 'HarbormasterHostArtifact' => 'applications/harbormaster/artifact/HarbormasterHostArtifact.php', 'HarbormasterLeaseHostBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseHostBuildStepImplementation.php', 'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php', 'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php', 'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php', 'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php', 'HarbormasterManagementUpdateWorkflow' => 'applications/harbormaster/management/HarbormasterManagementUpdateWorkflow.php', 'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php', 'HarbormasterMessageType' => 'applications/harbormaster/engine/HarbormasterMessageType.php', 'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php', 'HarbormasterOtherBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterOtherBuildStepGroup.php', 'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php', 'HarbormasterPlanDisableController' => 'applications/harbormaster/controller/HarbormasterPlanDisableController.php', 'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php', 'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php', 'HarbormasterPlanRunController' => 'applications/harbormaster/controller/HarbormasterPlanRunController.php', 'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php', 'HarbormasterPrototypeBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterPrototypeBuildStepGroup.php', 'HarbormasterPublishFragmentBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php', 'HarbormasterQueryAutotargetsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryAutotargetsConduitAPIMethod.php', 'HarbormasterQueryBuildablesConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php', 'HarbormasterQueryBuildsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php', 'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php', 'HarbormasterRunBuildPlansHeraldAction' => 'applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php', 'HarbormasterSchemaSpec' => 'applications/harbormaster/storage/HarbormasterSchemaSpec.php', 'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php', 'HarbormasterSendMessageConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php', 'HarbormasterSleepBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterSleepBuildStepImplementation.php', 'HarbormasterStepAddController' => 'applications/harbormaster/controller/HarbormasterStepAddController.php', 'HarbormasterStepDeleteController' => 'applications/harbormaster/controller/HarbormasterStepDeleteController.php', 'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php', 'HarbormasterStepViewController' => 'applications/harbormaster/controller/HarbormasterStepViewController.php', 'HarbormasterTargetEngine' => 'applications/harbormaster/engine/HarbormasterTargetEngine.php', 'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php', 'HarbormasterTestBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterTestBuildStepGroup.php', 'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php', 'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php', 'HarbormasterURIArtifact' => 'applications/harbormaster/artifact/HarbormasterURIArtifact.php', 'HarbormasterUnitMessagesController' => 'applications/harbormaster/controller/HarbormasterUnitMessagesController.php', 'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php', 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', 'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php', 'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php', 'HarbormasterWorkingCopyArtifact' => 'applications/harbormaster/artifact/HarbormasterWorkingCopyArtifact.php', 'HeraldAction' => 'applications/herald/action/HeraldAction.php', 'HeraldActionGroup' => 'applications/herald/action/HeraldActionGroup.php', 'HeraldActionRecord' => 'applications/herald/storage/HeraldActionRecord.php', 'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php', 'HeraldAlwaysField' => 'applications/herald/field/HeraldAlwaysField.php', 'HeraldAnotherRuleField' => 'applications/herald/field/HeraldAnotherRuleField.php', 'HeraldApplicationActionGroup' => 'applications/herald/action/HeraldApplicationActionGroup.php', 'HeraldApplyTranscript' => 'applications/herald/storage/transcript/HeraldApplyTranscript.php', 'HeraldBasicFieldGroup' => 'applications/herald/field/HeraldBasicFieldGroup.php', 'HeraldCommitAdapter' => 'applications/diffusion/herald/HeraldCommitAdapter.php', 'HeraldCondition' => 'applications/herald/storage/HeraldCondition.php', 'HeraldConditionTranscript' => 'applications/herald/storage/transcript/HeraldConditionTranscript.php', 'HeraldContentSourceField' => 'applications/herald/field/HeraldContentSourceField.php', 'HeraldController' => 'applications/herald/controller/HeraldController.php', 'HeraldDAO' => 'applications/herald/storage/HeraldDAO.php', 'HeraldDifferentialAdapter' => 'applications/differential/herald/HeraldDifferentialAdapter.php', 'HeraldDifferentialDiffAdapter' => 'applications/differential/herald/HeraldDifferentialDiffAdapter.php', 'HeraldDifferentialRevisionAdapter' => 'applications/differential/herald/HeraldDifferentialRevisionAdapter.php', 'HeraldDisableController' => 'applications/herald/controller/HeraldDisableController.php', 'HeraldDoNothingAction' => 'applications/herald/action/HeraldDoNothingAction.php', 'HeraldEditFieldGroup' => 'applications/herald/field/HeraldEditFieldGroup.php', 'HeraldEffect' => 'applications/herald/engine/HeraldEffect.php', 'HeraldEmptyFieldValue' => 'applications/herald/value/HeraldEmptyFieldValue.php', 'HeraldEngine' => 'applications/herald/engine/HeraldEngine.php', 'HeraldField' => 'applications/herald/field/HeraldField.php', 'HeraldFieldGroup' => 'applications/herald/field/HeraldFieldGroup.php', 'HeraldFieldTestCase' => 'applications/herald/field/__tests__/HeraldFieldTestCase.php', 'HeraldFieldValue' => 'applications/herald/value/HeraldFieldValue.php', 'HeraldGroup' => 'applications/herald/group/HeraldGroup.php', 'HeraldInvalidActionException' => 'applications/herald/engine/exception/HeraldInvalidActionException.php', 'HeraldInvalidConditionException' => 'applications/herald/engine/exception/HeraldInvalidConditionException.php', 'HeraldManageGlobalRulesCapability' => 'applications/herald/capability/HeraldManageGlobalRulesCapability.php', 'HeraldManiphestTaskAdapter' => 'applications/maniphest/herald/HeraldManiphestTaskAdapter.php', 'HeraldNewController' => 'applications/herald/controller/HeraldNewController.php', 'HeraldNewObjectField' => 'applications/herald/field/HeraldNewObjectField.php', 'HeraldNotifyActionGroup' => 'applications/herald/action/HeraldNotifyActionGroup.php', 'HeraldObjectTranscript' => 'applications/herald/storage/transcript/HeraldObjectTranscript.php', 'HeraldPholioMockAdapter' => 'applications/pholio/herald/HeraldPholioMockAdapter.php', 'HeraldPonderQuestionAdapter' => 'applications/ponder/herald/HeraldPonderQuestionAdapter.php', 'HeraldPreCommitAdapter' => 'applications/diffusion/herald/HeraldPreCommitAdapter.php', 'HeraldPreCommitContentAdapter' => 'applications/diffusion/herald/HeraldPreCommitContentAdapter.php', 'HeraldPreCommitRefAdapter' => 'applications/diffusion/herald/HeraldPreCommitRefAdapter.php', 'HeraldPreventActionGroup' => 'applications/herald/action/HeraldPreventActionGroup.php', 'HeraldProjectsField' => 'applications/project/herald/HeraldProjectsField.php', 'HeraldRecursiveConditionsException' => 'applications/herald/engine/exception/HeraldRecursiveConditionsException.php', 'HeraldRelatedFieldGroup' => 'applications/herald/field/HeraldRelatedFieldGroup.php', 'HeraldRemarkupRule' => 'applications/herald/remarkup/HeraldRemarkupRule.php', 'HeraldRepetitionPolicyConfig' => 'applications/herald/config/HeraldRepetitionPolicyConfig.php', 'HeraldRule' => 'applications/herald/storage/HeraldRule.php', 'HeraldRuleController' => 'applications/herald/controller/HeraldRuleController.php', 'HeraldRuleEditor' => 'applications/herald/editor/HeraldRuleEditor.php', 'HeraldRuleListController' => 'applications/herald/controller/HeraldRuleListController.php', 'HeraldRulePHIDType' => 'applications/herald/phid/HeraldRulePHIDType.php', 'HeraldRuleQuery' => 'applications/herald/query/HeraldRuleQuery.php', 'HeraldRuleSearchEngine' => 'applications/herald/query/HeraldRuleSearchEngine.php', 'HeraldRuleTestCase' => 'applications/herald/storage/__tests__/HeraldRuleTestCase.php', 'HeraldRuleTransaction' => 'applications/herald/storage/HeraldRuleTransaction.php', 'HeraldRuleTransactionComment' => 'applications/herald/storage/HeraldRuleTransactionComment.php', 'HeraldRuleTranscript' => 'applications/herald/storage/transcript/HeraldRuleTranscript.php', 'HeraldRuleTypeConfig' => 'applications/herald/config/HeraldRuleTypeConfig.php', 'HeraldRuleViewController' => 'applications/herald/controller/HeraldRuleViewController.php', 'HeraldSchemaSpec' => 'applications/herald/storage/HeraldSchemaSpec.php', 'HeraldSelectFieldValue' => 'applications/herald/value/HeraldSelectFieldValue.php', 'HeraldSpaceField' => 'applications/spaces/herald/HeraldSpaceField.php', 'HeraldSubscribersField' => 'applications/subscriptions/herald/HeraldSubscribersField.php', 'HeraldSupportActionGroup' => 'applications/herald/action/HeraldSupportActionGroup.php', 'HeraldSupportFieldGroup' => 'applications/herald/field/HeraldSupportFieldGroup.php', 'HeraldTestConsoleController' => 'applications/herald/controller/HeraldTestConsoleController.php', 'HeraldTextFieldValue' => 'applications/herald/value/HeraldTextFieldValue.php', 'HeraldTokenizerFieldValue' => 'applications/herald/value/HeraldTokenizerFieldValue.php', 'HeraldTransactionQuery' => 'applications/herald/query/HeraldTransactionQuery.php', 'HeraldTranscript' => 'applications/herald/storage/transcript/HeraldTranscript.php', 'HeraldTranscriptController' => 'applications/herald/controller/HeraldTranscriptController.php', 'HeraldTranscriptGarbageCollector' => 'applications/herald/garbagecollector/HeraldTranscriptGarbageCollector.php', 'HeraldTranscriptListController' => 'applications/herald/controller/HeraldTranscriptListController.php', 'HeraldTranscriptQuery' => 'applications/herald/query/HeraldTranscriptQuery.php', 'HeraldTranscriptSearchEngine' => 'applications/herald/query/HeraldTranscriptSearchEngine.php', 'HeraldTranscriptTestCase' => 'applications/herald/storage/__tests__/HeraldTranscriptTestCase.php', 'HeraldUtilityActionGroup' => 'applications/herald/action/HeraldUtilityActionGroup.php', 'Javelin' => 'infrastructure/javelin/Javelin.php', 'JavelinReactorUIExample' => 'applications/uiexample/examples/JavelinReactorUIExample.php', 'JavelinUIExample' => 'applications/uiexample/examples/JavelinUIExample.php', 'JavelinViewExampleServerView' => 'applications/uiexample/examples/JavelinViewExampleServerView.php', 'JavelinViewUIExample' => 'applications/uiexample/examples/JavelinViewUIExample.php', 'LegalpadController' => 'applications/legalpad/controller/LegalpadController.php', 'LegalpadCreateDocumentsCapability' => 'applications/legalpad/capability/LegalpadCreateDocumentsCapability.php', 'LegalpadDAO' => 'applications/legalpad/storage/LegalpadDAO.php', 'LegalpadDefaultEditCapability' => 'applications/legalpad/capability/LegalpadDefaultEditCapability.php', 'LegalpadDefaultViewCapability' => 'applications/legalpad/capability/LegalpadDefaultViewCapability.php', 'LegalpadDocument' => 'applications/legalpad/storage/LegalpadDocument.php', 'LegalpadDocumentBody' => 'applications/legalpad/storage/LegalpadDocumentBody.php', 'LegalpadDocumentCommentController' => 'applications/legalpad/controller/LegalpadDocumentCommentController.php', 'LegalpadDocumentDatasource' => 'applications/legalpad/typeahead/LegalpadDocumentDatasource.php', 'LegalpadDocumentDoneController' => 'applications/legalpad/controller/LegalpadDocumentDoneController.php', 'LegalpadDocumentEditController' => 'applications/legalpad/controller/LegalpadDocumentEditController.php', 'LegalpadDocumentEditor' => 'applications/legalpad/editor/LegalpadDocumentEditor.php', 'LegalpadDocumentListController' => 'applications/legalpad/controller/LegalpadDocumentListController.php', 'LegalpadDocumentManageController' => 'applications/legalpad/controller/LegalpadDocumentManageController.php', 'LegalpadDocumentQuery' => 'applications/legalpad/query/LegalpadDocumentQuery.php', 'LegalpadDocumentRemarkupRule' => 'applications/legalpad/remarkup/LegalpadDocumentRemarkupRule.php', 'LegalpadDocumentSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSearchEngine.php', 'LegalpadDocumentSignController' => 'applications/legalpad/controller/LegalpadDocumentSignController.php', 'LegalpadDocumentSignature' => 'applications/legalpad/storage/LegalpadDocumentSignature.php', 'LegalpadDocumentSignatureAddController' => 'applications/legalpad/controller/LegalpadDocumentSignatureAddController.php', 'LegalpadDocumentSignatureListController' => 'applications/legalpad/controller/LegalpadDocumentSignatureListController.php', 'LegalpadDocumentSignatureQuery' => 'applications/legalpad/query/LegalpadDocumentSignatureQuery.php', 'LegalpadDocumentSignatureSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php', 'LegalpadDocumentSignatureVerificationController' => 'applications/legalpad/controller/LegalpadDocumentSignatureVerificationController.php', 'LegalpadDocumentSignatureViewController' => 'applications/legalpad/controller/LegalpadDocumentSignatureViewController.php', 'LegalpadMailReceiver' => 'applications/legalpad/mail/LegalpadMailReceiver.php', 'LegalpadObjectNeedsSignatureEdgeType' => 'applications/legalpad/edge/LegalpadObjectNeedsSignatureEdgeType.php', 'LegalpadReplyHandler' => 'applications/legalpad/mail/LegalpadReplyHandler.php', 'LegalpadRequireSignatureHeraldAction' => 'applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php', 'LegalpadSchemaSpec' => 'applications/legalpad/storage/LegalpadSchemaSpec.php', 'LegalpadSignatureNeededByObjectEdgeType' => 'applications/legalpad/edge/LegalpadSignatureNeededByObjectEdgeType.php', 'LegalpadTransaction' => 'applications/legalpad/storage/LegalpadTransaction.php', 'LegalpadTransactionComment' => 'applications/legalpad/storage/LegalpadTransactionComment.php', 'LegalpadTransactionQuery' => 'applications/legalpad/query/LegalpadTransactionQuery.php', 'LegalpadTransactionView' => 'applications/legalpad/view/LegalpadTransactionView.php', 'LiskChunkTestCase' => 'infrastructure/storage/lisk/__tests__/LiskChunkTestCase.php', 'LiskDAO' => 'infrastructure/storage/lisk/LiskDAO.php', 'LiskDAOSet' => 'infrastructure/storage/lisk/LiskDAOSet.php', 'LiskDAOTestCase' => 'infrastructure/storage/lisk/__tests__/LiskDAOTestCase.php', 'LiskEphemeralObjectException' => 'infrastructure/storage/lisk/LiskEphemeralObjectException.php', 'LiskFixtureTestCase' => 'infrastructure/storage/lisk/__tests__/LiskFixtureTestCase.php', 'LiskIsolationTestCase' => 'infrastructure/storage/lisk/__tests__/LiskIsolationTestCase.php', 'LiskIsolationTestDAO' => 'infrastructure/storage/lisk/__tests__/LiskIsolationTestDAO.php', 'LiskIsolationTestDAOException' => 'infrastructure/storage/lisk/__tests__/LiskIsolationTestDAOException.php', 'LiskMigrationIterator' => 'infrastructure/storage/lisk/LiskMigrationIterator.php', 'LiskRawMigrationIterator' => 'infrastructure/storage/lisk/LiskRawMigrationIterator.php', 'MacroConduitAPIMethod' => 'applications/macro/conduit/MacroConduitAPIMethod.php', 'MacroCreateMemeConduitAPIMethod' => 'applications/macro/conduit/MacroCreateMemeConduitAPIMethod.php', 'MacroQueryConduitAPIMethod' => 'applications/macro/conduit/MacroQueryConduitAPIMethod.php', 'ManiphestAssignEmailCommand' => 'applications/maniphest/command/ManiphestAssignEmailCommand.php', 'ManiphestAssigneeDatasource' => 'applications/maniphest/typeahead/ManiphestAssigneeDatasource.php', 'ManiphestBatchEditController' => 'applications/maniphest/controller/ManiphestBatchEditController.php', 'ManiphestBulkEditCapability' => 'applications/maniphest/capability/ManiphestBulkEditCapability.php', 'ManiphestClaimEmailCommand' => 'applications/maniphest/command/ManiphestClaimEmailCommand.php', 'ManiphestCloseEmailCommand' => 'applications/maniphest/command/ManiphestCloseEmailCommand.php', 'ManiphestConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestConduitAPIMethod.php', 'ManiphestConfiguredCustomField' => 'applications/maniphest/field/ManiphestConfiguredCustomField.php', 'ManiphestConstants' => 'applications/maniphest/constants/ManiphestConstants.php', 'ManiphestController' => 'applications/maniphest/controller/ManiphestController.php', 'ManiphestCreateMailReceiver' => 'applications/maniphest/mail/ManiphestCreateMailReceiver.php', 'ManiphestCreateTaskConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestCreateTaskConduitAPIMethod.php', 'ManiphestCustomField' => 'applications/maniphest/field/ManiphestCustomField.php', 'ManiphestCustomFieldNumericIndex' => 'applications/maniphest/storage/ManiphestCustomFieldNumericIndex.php', 'ManiphestCustomFieldStatusParser' => 'applications/maniphest/field/parser/ManiphestCustomFieldStatusParser.php', 'ManiphestCustomFieldStatusParserTestCase' => 'applications/maniphest/field/parser/__tests__/ManiphestCustomFieldStatusParserTestCase.php', 'ManiphestCustomFieldStorage' => 'applications/maniphest/storage/ManiphestCustomFieldStorage.php', 'ManiphestCustomFieldStringIndex' => 'applications/maniphest/storage/ManiphestCustomFieldStringIndex.php', 'ManiphestDAO' => 'applications/maniphest/storage/ManiphestDAO.php', 'ManiphestDefaultEditCapability' => 'applications/maniphest/capability/ManiphestDefaultEditCapability.php', 'ManiphestDefaultViewCapability' => 'applications/maniphest/capability/ManiphestDefaultViewCapability.php', 'ManiphestEditAssignCapability' => 'applications/maniphest/capability/ManiphestEditAssignCapability.php', 'ManiphestEditPoliciesCapability' => 'applications/maniphest/capability/ManiphestEditPoliciesCapability.php', 'ManiphestEditPriorityCapability' => 'applications/maniphest/capability/ManiphestEditPriorityCapability.php', 'ManiphestEditProjectsCapability' => 'applications/maniphest/capability/ManiphestEditProjectsCapability.php', 'ManiphestEditStatusCapability' => 'applications/maniphest/capability/ManiphestEditStatusCapability.php', 'ManiphestEmailCommand' => 'applications/maniphest/command/ManiphestEmailCommand.php', 'ManiphestExcelDefaultFormat' => 'applications/maniphest/export/ManiphestExcelDefaultFormat.php', 'ManiphestExcelFormat' => 'applications/maniphest/export/ManiphestExcelFormat.php', 'ManiphestExcelFormatTestCase' => 'applications/maniphest/export/__tests__/ManiphestExcelFormatTestCase.php', 'ManiphestExportController' => 'applications/maniphest/controller/ManiphestExportController.php', 'ManiphestGetTaskTransactionsConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestGetTaskTransactionsConduitAPIMethod.php', 'ManiphestHovercardEventListener' => 'applications/maniphest/event/ManiphestHovercardEventListener.php', 'ManiphestInfoConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php', 'ManiphestNameIndex' => 'applications/maniphest/storage/ManiphestNameIndex.php', 'ManiphestNameIndexEventListener' => 'applications/maniphest/event/ManiphestNameIndexEventListener.php', 'ManiphestPriorityEmailCommand' => 'applications/maniphest/command/ManiphestPriorityEmailCommand.php', 'ManiphestQueryConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php', 'ManiphestQueryStatusesConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestQueryStatusesConduitAPIMethod.php', 'ManiphestRemarkupRule' => 'applications/maniphest/remarkup/ManiphestRemarkupRule.php', 'ManiphestReplyHandler' => 'applications/maniphest/mail/ManiphestReplyHandler.php', 'ManiphestReportController' => 'applications/maniphest/controller/ManiphestReportController.php', 'ManiphestSchemaSpec' => 'applications/maniphest/storage/ManiphestSchemaSpec.php', 'ManiphestSearchIndexer' => 'applications/maniphest/search/ManiphestSearchIndexer.php', 'ManiphestStatusConfigOptionType' => 'applications/maniphest/config/ManiphestStatusConfigOptionType.php', 'ManiphestStatusEmailCommand' => 'applications/maniphest/command/ManiphestStatusEmailCommand.php', 'ManiphestSubpriorityController' => 'applications/maniphest/controller/ManiphestSubpriorityController.php', 'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php', 'ManiphestTaskAssignHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignHeraldAction.php', 'ManiphestTaskAssignOtherHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignOtherHeraldAction.php', 'ManiphestTaskAssignSelfHeraldAction' => 'applications/maniphest/herald/ManiphestTaskAssignSelfHeraldAction.php', 'ManiphestTaskAssigneeHeraldField' => 'applications/maniphest/herald/ManiphestTaskAssigneeHeraldField.php', 'ManiphestTaskAuthorHeraldField' => 'applications/maniphest/herald/ManiphestTaskAuthorHeraldField.php', 'ManiphestTaskAuthorPolicyRule' => 'applications/maniphest/policyrule/ManiphestTaskAuthorPolicyRule.php', 'ManiphestTaskClosedStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskClosedStatusDatasource.php', 'ManiphestTaskDependedOnByTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependedOnByTaskEdgeType.php', 'ManiphestTaskDependsOnTaskEdgeType' => 'applications/maniphest/edge/ManiphestTaskDependsOnTaskEdgeType.php', 'ManiphestTaskDescriptionHeraldField' => 'applications/maniphest/herald/ManiphestTaskDescriptionHeraldField.php', 'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php', 'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php', 'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php', 'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php', 'ManiphestTaskHasMockEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasMockEdgeType.php', 'ManiphestTaskHasRevisionEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasRevisionEdgeType.php', 'ManiphestTaskHeraldField' => 'applications/maniphest/herald/ManiphestTaskHeraldField.php', 'ManiphestTaskHeraldFieldGroup' => 'applications/maniphest/herald/ManiphestTaskHeraldFieldGroup.php', 'ManiphestTaskListController' => 'applications/maniphest/controller/ManiphestTaskListController.php', 'ManiphestTaskListView' => 'applications/maniphest/view/ManiphestTaskListView.php', 'ManiphestTaskMailReceiver' => 'applications/maniphest/mail/ManiphestTaskMailReceiver.php', 'ManiphestTaskOpenStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskOpenStatusDatasource.php', 'ManiphestTaskPHIDType' => 'applications/maniphest/phid/ManiphestTaskPHIDType.php', 'ManiphestTaskPriority' => 'applications/maniphest/constants/ManiphestTaskPriority.php', 'ManiphestTaskPriorityDatasource' => 'applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php', 'ManiphestTaskPriorityHeraldField' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldField.php', 'ManiphestTaskQuery' => 'applications/maniphest/query/ManiphestTaskQuery.php', 'ManiphestTaskResultListView' => 'applications/maniphest/view/ManiphestTaskResultListView.php', 'ManiphestTaskSearchEngine' => 'applications/maniphest/query/ManiphestTaskSearchEngine.php', 'ManiphestTaskStatus' => 'applications/maniphest/constants/ManiphestTaskStatus.php', 'ManiphestTaskStatusDatasource' => 'applications/maniphest/typeahead/ManiphestTaskStatusDatasource.php', 'ManiphestTaskStatusFunctionDatasource' => 'applications/maniphest/typeahead/ManiphestTaskStatusFunctionDatasource.php', 'ManiphestTaskStatusHeraldField' => 'applications/maniphest/herald/ManiphestTaskStatusHeraldField.php', 'ManiphestTaskStatusTestCase' => 'applications/maniphest/constants/__tests__/ManiphestTaskStatusTestCase.php', 'ManiphestTaskTestCase' => 'applications/maniphest/__tests__/ManiphestTaskTestCase.php', 'ManiphestTaskTitleHeraldField' => 'applications/maniphest/herald/ManiphestTaskTitleHeraldField.php', 'ManiphestTransaction' => 'applications/maniphest/storage/ManiphestTransaction.php', 'ManiphestTransactionComment' => 'applications/maniphest/storage/ManiphestTransactionComment.php', 'ManiphestTransactionEditor' => 'applications/maniphest/editor/ManiphestTransactionEditor.php', 'ManiphestTransactionPreviewController' => 'applications/maniphest/controller/ManiphestTransactionPreviewController.php', 'ManiphestTransactionQuery' => 'applications/maniphest/query/ManiphestTransactionQuery.php', 'ManiphestTransactionSaveController' => 'applications/maniphest/controller/ManiphestTransactionSaveController.php', 'ManiphestUpdateConduitAPIMethod' => 'applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php', 'ManiphestView' => 'applications/maniphest/view/ManiphestView.php', 'MetaMTAEmailTransactionCommand' => 'applications/metamta/command/MetaMTAEmailTransactionCommand.php', 'MetaMTAEmailTransactionCommandTestCase' => 'applications/metamta/command/__tests__/MetaMTAEmailTransactionCommandTestCase.php', 'MetaMTAMailReceivedGarbageCollector' => 'applications/metamta/garbagecollector/MetaMTAMailReceivedGarbageCollector.php', 'MetaMTAMailSentGarbageCollector' => 'applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php', 'MetaMTAReceivedMailStatus' => 'applications/metamta/constants/MetaMTAReceivedMailStatus.php', 'MultimeterContext' => 'applications/multimeter/storage/MultimeterContext.php', 'MultimeterControl' => 'applications/multimeter/data/MultimeterControl.php', 'MultimeterController' => 'applications/multimeter/controller/MultimeterController.php', 'MultimeterDAO' => 'applications/multimeter/storage/MultimeterDAO.php', 'MultimeterDimension' => 'applications/multimeter/storage/MultimeterDimension.php', 'MultimeterEvent' => 'applications/multimeter/storage/MultimeterEvent.php', 'MultimeterEventGarbageCollector' => 'applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php', 'MultimeterHost' => 'applications/multimeter/storage/MultimeterHost.php', 'MultimeterLabel' => 'applications/multimeter/storage/MultimeterLabel.php', 'MultimeterSampleController' => 'applications/multimeter/controller/MultimeterSampleController.php', 'MultimeterViewer' => 'applications/multimeter/storage/MultimeterViewer.php', 'NuanceConduitAPIMethod' => 'applications/nuance/conduit/NuanceConduitAPIMethod.php', 'NuanceConsoleController' => 'applications/nuance/controller/NuanceConsoleController.php', 'NuanceController' => 'applications/nuance/controller/NuanceController.php', 'NuanceCreateItemConduitAPIMethod' => 'applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php', 'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php', 'NuanceItem' => 'applications/nuance/storage/NuanceItem.php', 'NuanceItemEditController' => 'applications/nuance/controller/NuanceItemEditController.php', 'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php', 'NuanceItemPHIDType' => 'applications/nuance/phid/NuanceItemPHIDType.php', 'NuanceItemQuery' => 'applications/nuance/query/NuanceItemQuery.php', 'NuanceItemTransaction' => 'applications/nuance/storage/NuanceItemTransaction.php', 'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php', 'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php', 'NuanceItemViewController' => 'applications/nuance/controller/NuanceItemViewController.php', 'NuancePhabricatorFormSourceDefinition' => 'applications/nuance/source/NuancePhabricatorFormSourceDefinition.php', 'NuanceQuery' => 'applications/nuance/query/NuanceQuery.php', 'NuanceQueue' => 'applications/nuance/storage/NuanceQueue.php', 'NuanceQueueDatasource' => 'applications/nuance/typeahead/NuanceQueueDatasource.php', 'NuanceQueueEditController' => 'applications/nuance/controller/NuanceQueueEditController.php', 'NuanceQueueEditor' => 'applications/nuance/editor/NuanceQueueEditor.php', 'NuanceQueueListController' => 'applications/nuance/controller/NuanceQueueListController.php', 'NuanceQueuePHIDType' => 'applications/nuance/phid/NuanceQueuePHIDType.php', 'NuanceQueueQuery' => 'applications/nuance/query/NuanceQueueQuery.php', 'NuanceQueueSearchEngine' => 'applications/nuance/query/NuanceQueueSearchEngine.php', 'NuanceQueueTransaction' => 'applications/nuance/storage/NuanceQueueTransaction.php', 'NuanceQueueTransactionComment' => 'applications/nuance/storage/NuanceQueueTransactionComment.php', 'NuanceQueueTransactionQuery' => 'applications/nuance/query/NuanceQueueTransactionQuery.php', 'NuanceQueueViewController' => 'applications/nuance/controller/NuanceQueueViewController.php', 'NuanceRequestor' => 'applications/nuance/storage/NuanceRequestor.php', 'NuanceRequestorEditController' => 'applications/nuance/controller/NuanceRequestorEditController.php', 'NuanceRequestorEditor' => 'applications/nuance/editor/NuanceRequestorEditor.php', 'NuanceRequestorPHIDType' => 'applications/nuance/phid/NuanceRequestorPHIDType.php', 'NuanceRequestorQuery' => 'applications/nuance/query/NuanceRequestorQuery.php', 'NuanceRequestorSource' => 'applications/nuance/storage/NuanceRequestorSource.php', 'NuanceRequestorTransaction' => 'applications/nuance/storage/NuanceRequestorTransaction.php', 'NuanceRequestorTransactionComment' => 'applications/nuance/storage/NuanceRequestorTransactionComment.php', 'NuanceRequestorTransactionQuery' => 'applications/nuance/query/NuanceRequestorTransactionQuery.php', 'NuanceRequestorViewController' => 'applications/nuance/controller/NuanceRequestorViewController.php', 'NuanceSchemaSpec' => 'applications/nuance/storage/NuanceSchemaSpec.php', 'NuanceSource' => 'applications/nuance/storage/NuanceSource.php', 'NuanceSourceActionController' => 'applications/nuance/controller/NuanceSourceActionController.php', 'NuanceSourceCreateController' => 'applications/nuance/controller/NuanceSourceCreateController.php', 'NuanceSourceDefaultEditCapability' => 'applications/nuance/capability/NuanceSourceDefaultEditCapability.php', 'NuanceSourceDefaultViewCapability' => 'applications/nuance/capability/NuanceSourceDefaultViewCapability.php', 'NuanceSourceDefinition' => 'applications/nuance/source/NuanceSourceDefinition.php', 'NuanceSourceDefinitionTestCase' => 'applications/nuance/source/__tests__/NuanceSourceDefinitionTestCase.php', 'NuanceSourceEditController' => 'applications/nuance/controller/NuanceSourceEditController.php', 'NuanceSourceEditor' => 'applications/nuance/editor/NuanceSourceEditor.php', 'NuanceSourceListController' => 'applications/nuance/controller/NuanceSourceListController.php', 'NuanceSourceManageCapability' => 'applications/nuance/capability/NuanceSourceManageCapability.php', 'NuanceSourcePHIDType' => 'applications/nuance/phid/NuanceSourcePHIDType.php', 'NuanceSourceQuery' => 'applications/nuance/query/NuanceSourceQuery.php', 'NuanceSourceSearchEngine' => 'applications/nuance/query/NuanceSourceSearchEngine.php', 'NuanceSourceTransaction' => 'applications/nuance/storage/NuanceSourceTransaction.php', 'NuanceSourceTransactionComment' => 'applications/nuance/storage/NuanceSourceTransactionComment.php', 'NuanceSourceTransactionQuery' => 'applications/nuance/query/NuanceSourceTransactionQuery.php', 'NuanceSourceViewController' => 'applications/nuance/controller/NuanceSourceViewController.php', 'NuanceTransaction' => 'applications/nuance/storage/NuanceTransaction.php', 'OwnersConduitAPIMethod' => 'applications/owners/conduit/OwnersConduitAPIMethod.php', + 'OwnersEditConduitAPIMethod' => 'applications/owners/conduit/OwnersEditConduitAPIMethod.php', 'OwnersPackageReplyHandler' => 'applications/owners/mail/OwnersPackageReplyHandler.php', 'OwnersQueryConduitAPIMethod' => 'applications/owners/conduit/OwnersQueryConduitAPIMethod.php', 'PHIDConduitAPIMethod' => 'applications/phid/conduit/PHIDConduitAPIMethod.php', 'PHIDInfoConduitAPIMethod' => 'applications/phid/conduit/PHIDInfoConduitAPIMethod.php', 'PHIDLookupConduitAPIMethod' => 'applications/phid/conduit/PHIDLookupConduitAPIMethod.php', 'PHIDQueryConduitAPIMethod' => 'applications/phid/conduit/PHIDQueryConduitAPIMethod.php', 'PHUI' => 'view/phui/PHUI.php', 'PHUIActionPanelExample' => 'applications/uiexample/examples/PHUIActionPanelExample.php', 'PHUIActionPanelView' => 'view/phui/PHUIActionPanelView.php', 'PHUIApplicationMenuView' => 'view/layout/PHUIApplicationMenuView.php', 'PHUIBadgeBoxView' => 'view/phui/PHUIBadgeBoxView.php', 'PHUIBadgeExample' => 'applications/uiexample/examples/PHUIBadgeExample.php', 'PHUIBadgeMiniView' => 'view/phui/PHUIBadgeMiniView.php', 'PHUIBadgeView' => 'view/phui/PHUIBadgeView.php', 'PHUIBoxExample' => 'applications/uiexample/examples/PHUIBoxExample.php', 'PHUIBoxView' => 'view/phui/PHUIBoxView.php', 'PHUIButtonBarExample' => 'applications/uiexample/examples/PHUIButtonBarExample.php', 'PHUIButtonBarView' => 'view/phui/PHUIButtonBarView.php', 'PHUIButtonExample' => 'applications/uiexample/examples/PHUIButtonExample.php', 'PHUIButtonView' => 'view/phui/PHUIButtonView.php', 'PHUICalendarDayView' => 'view/phui/calendar/PHUICalendarDayView.php', 'PHUICalendarListView' => 'view/phui/calendar/PHUICalendarListView.php', 'PHUICalendarMonthView' => 'view/phui/calendar/PHUICalendarMonthView.php', 'PHUICalendarWidgetView' => 'view/phui/calendar/PHUICalendarWidgetView.php', 'PHUIColorPalletteExample' => 'applications/uiexample/examples/PHUIColorPalletteExample.php', 'PHUICrumbView' => 'view/phui/PHUICrumbView.php', 'PHUICrumbsView' => 'view/phui/PHUICrumbsView.php', 'PHUIDiffInlineCommentDetailView' => 'infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php', 'PHUIDiffInlineCommentEditView' => 'infrastructure/diff/view/PHUIDiffInlineCommentEditView.php', 'PHUIDiffInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php', 'PHUIDiffInlineCommentTableScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentTableScaffold.php', 'PHUIDiffInlineCommentUndoView' => 'infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php', 'PHUIDiffInlineCommentView' => 'infrastructure/diff/view/PHUIDiffInlineCommentView.php', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php', 'PHUIDiffRevealIconView' => 'infrastructure/diff/view/PHUIDiffRevealIconView.php', 'PHUIDiffTableOfContentsItemView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsItemView.php', 'PHUIDiffTableOfContentsListView' => 'infrastructure/diff/view/PHUIDiffTableOfContentsListView.php', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php', 'PHUIDocumentExample' => 'applications/uiexample/examples/PHUIDocumentExample.php', + 'PHUIDocumentSummaryView' => 'view/phui/PHUIDocumentSummaryView.php', 'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php', 'PHUIDocumentViewPro' => 'view/phui/PHUIDocumentViewPro.php', 'PHUIFeedStoryExample' => 'applications/uiexample/examples/PHUIFeedStoryExample.php', 'PHUIFeedStoryView' => 'view/phui/PHUIFeedStoryView.php', 'PHUIFormDividerControl' => 'view/form/control/PHUIFormDividerControl.php', 'PHUIFormFreeformDateControl' => 'view/form/control/PHUIFormFreeformDateControl.php', 'PHUIFormInsetView' => 'view/form/PHUIFormInsetView.php', 'PHUIFormLayoutView' => 'view/form/PHUIFormLayoutView.php', 'PHUIFormMultiSubmitControl' => 'view/form/control/PHUIFormMultiSubmitControl.php', 'PHUIFormPageView' => 'view/form/PHUIFormPageView.php', 'PHUIHandleListView' => 'applications/phid/view/PHUIHandleListView.php', 'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php', 'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php', 'PHUIHeaderView' => 'view/phui/PHUIHeaderView.php', 'PHUIIconExample' => 'applications/uiexample/examples/PHUIIconExample.php', 'PHUIIconView' => 'view/phui/PHUIIconView.php', 'PHUIImageMaskExample' => 'applications/uiexample/examples/PHUIImageMaskExample.php', 'PHUIImageMaskView' => 'view/phui/PHUIImageMaskView.php', 'PHUIInfoExample' => 'applications/uiexample/examples/PHUIInfoExample.php', 'PHUIInfoPanelExample' => 'applications/uiexample/examples/PHUIInfoPanelExample.php', 'PHUIInfoPanelView' => 'view/phui/PHUIInfoPanelView.php', 'PHUIInfoView' => 'view/form/PHUIInfoView.php', 'PHUIListExample' => 'applications/uiexample/examples/PHUIListExample.php', 'PHUIListItemView' => 'view/phui/PHUIListItemView.php', 'PHUIListView' => 'view/phui/PHUIListView.php', 'PHUIListViewTestCase' => 'view/layout/__tests__/PHUIListViewTestCase.php', 'PHUIObjectBoxView' => 'view/phui/PHUIObjectBoxView.php', 'PHUIObjectItemListExample' => 'applications/uiexample/examples/PHUIObjectItemListExample.php', 'PHUIObjectItemListView' => 'view/phui/PHUIObjectItemListView.php', 'PHUIObjectItemView' => 'view/phui/PHUIObjectItemView.php', 'PHUIPagedFormView' => 'view/form/PHUIPagedFormView.php', 'PHUIPagerView' => 'view/phui/PHUIPagerView.php', 'PHUIPinboardItemView' => 'view/phui/PHUIPinboardItemView.php', 'PHUIPinboardView' => 'view/phui/PHUIPinboardView.php', 'PHUIPropertyGroupView' => 'view/phui/PHUIPropertyGroupView.php', 'PHUIPropertyListExample' => 'applications/uiexample/examples/PHUIPropertyListExample.php', 'PHUIPropertyListView' => 'view/phui/PHUIPropertyListView.php', 'PHUIRemarkupPreviewPanel' => 'view/phui/PHUIRemarkupPreviewPanel.php', 'PHUIRemarkupView' => 'infrastructure/markup/view/PHUIRemarkupView.php', 'PHUISpacesNamespaceContextView' => 'applications/spaces/view/PHUISpacesNamespaceContextView.php', 'PHUIStatusItemView' => 'view/phui/PHUIStatusItemView.php', 'PHUIStatusListView' => 'view/phui/PHUIStatusListView.php', 'PHUITagExample' => 'applications/uiexample/examples/PHUITagExample.php', 'PHUITagView' => 'view/phui/PHUITagView.php', 'PHUITextExample' => 'applications/uiexample/examples/PHUITextExample.php', 'PHUITextView' => 'view/phui/PHUITextView.php', 'PHUITimelineEventView' => 'view/phui/PHUITimelineEventView.php', 'PHUITimelineExample' => 'applications/uiexample/examples/PHUITimelineExample.php', 'PHUITimelineView' => 'view/phui/PHUITimelineView.php', 'PHUITwoColumnView' => 'view/phui/PHUITwoColumnView.php', 'PHUITypeaheadExample' => 'applications/uiexample/examples/PHUITypeaheadExample.php', 'PHUIWorkboardView' => 'view/phui/PHUIWorkboardView.php', 'PHUIWorkpanelView' => 'view/phui/PHUIWorkpanelView.php', 'PassphraseAbstractKey' => 'applications/passphrase/keys/PassphraseAbstractKey.php', 'PassphraseConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseConduitAPIMethod.php', 'PassphraseController' => 'applications/passphrase/controller/PassphraseController.php', 'PassphraseCredential' => 'applications/passphrase/storage/PassphraseCredential.php', 'PassphraseCredentialAuthorPolicyRule' => 'applications/passphrase/policyrule/PassphraseCredentialAuthorPolicyRule.php', 'PassphraseCredentialConduitController' => 'applications/passphrase/controller/PassphraseCredentialConduitController.php', 'PassphraseCredentialControl' => 'applications/passphrase/view/PassphraseCredentialControl.php', 'PassphraseCredentialCreateController' => 'applications/passphrase/controller/PassphraseCredentialCreateController.php', 'PassphraseCredentialDestroyController' => 'applications/passphrase/controller/PassphraseCredentialDestroyController.php', 'PassphraseCredentialEditController' => 'applications/passphrase/controller/PassphraseCredentialEditController.php', 'PassphraseCredentialListController' => 'applications/passphrase/controller/PassphraseCredentialListController.php', 'PassphraseCredentialLockController' => 'applications/passphrase/controller/PassphraseCredentialLockController.php', 'PassphraseCredentialPHIDType' => 'applications/passphrase/phid/PassphraseCredentialPHIDType.php', 'PassphraseCredentialPublicController' => 'applications/passphrase/controller/PassphraseCredentialPublicController.php', 'PassphraseCredentialQuery' => 'applications/passphrase/query/PassphraseCredentialQuery.php', 'PassphraseCredentialRevealController' => 'applications/passphrase/controller/PassphraseCredentialRevealController.php', 'PassphraseCredentialSearchEngine' => 'applications/passphrase/query/PassphraseCredentialSearchEngine.php', 'PassphraseCredentialTransaction' => 'applications/passphrase/storage/PassphraseCredentialTransaction.php', 'PassphraseCredentialTransactionEditor' => 'applications/passphrase/editor/PassphraseCredentialTransactionEditor.php', 'PassphraseCredentialTransactionQuery' => 'applications/passphrase/query/PassphraseCredentialTransactionQuery.php', 'PassphraseCredentialType' => 'applications/passphrase/credentialtype/PassphraseCredentialType.php', 'PassphraseCredentialTypeTestCase' => 'applications/passphrase/credentialtype/__tests__/PassphraseCredentialTypeTestCase.php', 'PassphraseCredentialViewController' => 'applications/passphrase/controller/PassphraseCredentialViewController.php', 'PassphraseDAO' => 'applications/passphrase/storage/PassphraseDAO.php', 'PassphraseDefaultEditCapability' => 'applications/passphrase/capability/PassphraseDefaultEditCapability.php', 'PassphraseDefaultViewCapability' => 'applications/passphrase/capability/PassphraseDefaultViewCapability.php', 'PassphraseNoteCredentialType' => 'applications/passphrase/credentialtype/PassphraseNoteCredentialType.php', 'PassphrasePasswordCredentialType' => 'applications/passphrase/credentialtype/PassphrasePasswordCredentialType.php', 'PassphrasePasswordKey' => 'applications/passphrase/keys/PassphrasePasswordKey.php', 'PassphraseQueryConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseQueryConduitAPIMethod.php', 'PassphraseRemarkupRule' => 'applications/passphrase/remarkup/PassphraseRemarkupRule.php', 'PassphraseSSHGeneratedKeyCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHGeneratedKeyCredentialType.php', 'PassphraseSSHKey' => 'applications/passphrase/keys/PassphraseSSHKey.php', 'PassphraseSSHPrivateKeyCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHPrivateKeyCredentialType.php', 'PassphraseSSHPrivateKeyFileCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHPrivateKeyFileCredentialType.php', 'PassphraseSSHPrivateKeyTextCredentialType' => 'applications/passphrase/credentialtype/PassphraseSSHPrivateKeyTextCredentialType.php', 'PassphraseSchemaSpec' => 'applications/passphrase/storage/PassphraseSchemaSpec.php', 'PassphraseSearchIndexer' => 'applications/passphrase/search/PassphraseSearchIndexer.php', 'PassphraseSecret' => 'applications/passphrase/storage/PassphraseSecret.php', 'PasteConduitAPIMethod' => 'applications/paste/conduit/PasteConduitAPIMethod.php', 'PasteCreateConduitAPIMethod' => 'applications/paste/conduit/PasteCreateConduitAPIMethod.php', 'PasteCreateMailReceiver' => 'applications/paste/mail/PasteCreateMailReceiver.php', 'PasteDefaultEditCapability' => 'applications/paste/capability/PasteDefaultEditCapability.php', 'PasteDefaultViewCapability' => 'applications/paste/capability/PasteDefaultViewCapability.php', 'PasteEditConduitAPIMethod' => 'applications/paste/conduit/PasteEditConduitAPIMethod.php', 'PasteEmbedView' => 'applications/paste/view/PasteEmbedView.php', 'PasteInfoConduitAPIMethod' => 'applications/paste/conduit/PasteInfoConduitAPIMethod.php', 'PasteMailReceiver' => 'applications/paste/mail/PasteMailReceiver.php', 'PasteQueryConduitAPIMethod' => 'applications/paste/conduit/PasteQueryConduitAPIMethod.php', 'PasteReplyHandler' => 'applications/paste/mail/PasteReplyHandler.php', 'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php', 'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php', 'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php', 'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php', 'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php', 'PhabricatorAccessControlTestCase' => 'applications/base/controller/__tests__/PhabricatorAccessControlTestCase.php', 'PhabricatorAccessLog' => 'infrastructure/log/PhabricatorAccessLog.php', 'PhabricatorAccessLogConfigOptions' => 'applications/config/option/PhabricatorAccessLogConfigOptions.php', 'PhabricatorAccountSettingsPanel' => 'applications/settings/panel/PhabricatorAccountSettingsPanel.php', 'PhabricatorActionListView' => 'view/layout/PhabricatorActionListView.php', 'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php', 'PhabricatorActivitySettingsPanel' => 'applications/settings/panel/PhabricatorActivitySettingsPanel.php', 'PhabricatorAdministratorsPolicyRule' => 'applications/people/policyrule/PhabricatorAdministratorsPolicyRule.php', 'PhabricatorAjaxRequestExceptionHandler' => 'aphront/handler/PhabricatorAjaxRequestExceptionHandler.php', 'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php', 'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php', 'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php', 'PhabricatorAphlictManagementDebugWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementDebugWorkflow.php', 'PhabricatorAphlictManagementRestartWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementRestartWorkflow.php', 'PhabricatorAphlictManagementStartWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStartWorkflow.php', 'PhabricatorAphlictManagementStatusWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStatusWorkflow.php', 'PhabricatorAphlictManagementStopWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStopWorkflow.php', 'PhabricatorAphlictManagementWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementWorkflow.php', 'PhabricatorAphlictSetupCheck' => 'applications/notification/setup/PhabricatorAphlictSetupCheck.php', 'PhabricatorAphrontBarUIExample' => 'applications/uiexample/examples/PhabricatorAphrontBarUIExample.php', 'PhabricatorAphrontViewTestCase' => 'view/__tests__/PhabricatorAphrontViewTestCase.php', 'PhabricatorAppSearchEngine' => 'applications/meta/query/PhabricatorAppSearchEngine.php', 'PhabricatorApplication' => 'applications/base/PhabricatorApplication.php', 'PhabricatorApplicationApplicationPHIDType' => 'applications/meta/phid/PhabricatorApplicationApplicationPHIDType.php', 'PhabricatorApplicationConfigOptions' => 'applications/config/option/PhabricatorApplicationConfigOptions.php', 'PhabricatorApplicationConfigurationPanel' => 'applications/meta/panel/PhabricatorApplicationConfigurationPanel.php', 'PhabricatorApplicationConfigurationPanelTestCase' => 'applications/meta/panel/__tests__/PhabricatorApplicationConfigurationPanelTestCase.php', 'PhabricatorApplicationDatasource' => 'applications/meta/typeahead/PhabricatorApplicationDatasource.php', 'PhabricatorApplicationDetailViewController' => 'applications/meta/controller/PhabricatorApplicationDetailViewController.php', 'PhabricatorApplicationEditController' => 'applications/meta/controller/PhabricatorApplicationEditController.php', 'PhabricatorApplicationEditHTTPParameterHelpView' => 'applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php', 'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php', 'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php', 'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php', 'PhabricatorApplicationQuery' => 'applications/meta/query/PhabricatorApplicationQuery.php', 'PhabricatorApplicationSearchController' => 'applications/search/controller/PhabricatorApplicationSearchController.php', 'PhabricatorApplicationSearchEngine' => 'applications/search/engine/PhabricatorApplicationSearchEngine.php', 'PhabricatorApplicationSearchEngineTestCase' => 'applications/search/engine/__tests__/PhabricatorApplicationSearchEngineTestCase.php', 'PhabricatorApplicationSearchResultView' => 'applications/search/view/PhabricatorApplicationSearchResultView.php', 'PhabricatorApplicationStatusView' => 'applications/meta/view/PhabricatorApplicationStatusView.php', 'PhabricatorApplicationTestCase' => 'applications/base/__tests__/PhabricatorApplicationTestCase.php', 'PhabricatorApplicationTransaction' => 'applications/transactions/storage/PhabricatorApplicationTransaction.php', 'PhabricatorApplicationTransactionComment' => 'applications/transactions/storage/PhabricatorApplicationTransactionComment.php', 'PhabricatorApplicationTransactionCommentEditController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php', 'PhabricatorApplicationTransactionCommentEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php', 'PhabricatorApplicationTransactionCommentHistoryController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentHistoryController.php', 'PhabricatorApplicationTransactionCommentQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php', 'PhabricatorApplicationTransactionCommentQuoteController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentQuoteController.php', 'PhabricatorApplicationTransactionCommentRawController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentRawController.php', 'PhabricatorApplicationTransactionCommentRemoveController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php', 'PhabricatorApplicationTransactionCommentView' => 'applications/transactions/view/PhabricatorApplicationTransactionCommentView.php', 'PhabricatorApplicationTransactionController' => 'applications/transactions/controller/PhabricatorApplicationTransactionController.php', 'PhabricatorApplicationTransactionDetailController' => 'applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php', 'PhabricatorApplicationTransactionEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionEditor.php', 'PhabricatorApplicationTransactionFeedStory' => 'applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php', 'PhabricatorApplicationTransactionInterface' => 'applications/transactions/interface/PhabricatorApplicationTransactionInterface.php', 'PhabricatorApplicationTransactionNoEffectException' => 'applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php', 'PhabricatorApplicationTransactionNoEffectResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionNoEffectResponse.php', 'PhabricatorApplicationTransactionPublishWorker' => 'applications/transactions/worker/PhabricatorApplicationTransactionPublishWorker.php', 'PhabricatorApplicationTransactionQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionQuery.php', 'PhabricatorApplicationTransactionReplyHandler' => 'applications/transactions/replyhandler/PhabricatorApplicationTransactionReplyHandler.php', 'PhabricatorApplicationTransactionResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionResponse.php', 'PhabricatorApplicationTransactionShowOlderController' => 'applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php', 'PhabricatorApplicationTransactionStructureException' => 'applications/transactions/exception/PhabricatorApplicationTransactionStructureException.php', 'PhabricatorApplicationTransactionTemplatedCommentQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionTemplatedCommentQuery.php', 'PhabricatorApplicationTransactionTextDiffDetailView' => 'applications/transactions/view/PhabricatorApplicationTransactionTextDiffDetailView.php', 'PhabricatorApplicationTransactionTransactionPHIDType' => 'applications/transactions/phid/PhabricatorApplicationTransactionTransactionPHIDType.php', 'PhabricatorApplicationTransactionValidationError' => 'applications/transactions/error/PhabricatorApplicationTransactionValidationError.php', 'PhabricatorApplicationTransactionValidationException' => 'applications/transactions/exception/PhabricatorApplicationTransactionValidationException.php', 'PhabricatorApplicationTransactionValidationResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionValidationResponse.php', 'PhabricatorApplicationTransactionValueController' => 'applications/transactions/controller/PhabricatorApplicationTransactionValueController.php', 'PhabricatorApplicationTransactionView' => 'applications/transactions/view/PhabricatorApplicationTransactionView.php', 'PhabricatorApplicationUninstallController' => 'applications/meta/controller/PhabricatorApplicationUninstallController.php', 'PhabricatorApplicationsApplication' => 'applications/meta/application/PhabricatorApplicationsApplication.php', 'PhabricatorApplicationsController' => 'applications/meta/controller/PhabricatorApplicationsController.php', 'PhabricatorApplicationsListController' => 'applications/meta/controller/PhabricatorApplicationsListController.php', 'PhabricatorAsanaAuthProvider' => 'applications/auth/provider/PhabricatorAsanaAuthProvider.php', 'PhabricatorAsanaConfigOptions' => 'applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php', 'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorAsanaSubtaskHasObjectEdgeType.php', 'PhabricatorAsanaTaskHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorAsanaTaskHasObjectEdgeType.php', 'PhabricatorAuditActionConstants' => 'applications/audit/constants/PhabricatorAuditActionConstants.php', 'PhabricatorAuditAddCommentController' => 'applications/audit/controller/PhabricatorAuditAddCommentController.php', 'PhabricatorAuditApplication' => 'applications/audit/application/PhabricatorAuditApplication.php', 'PhabricatorAuditCommentEditor' => 'applications/audit/editor/PhabricatorAuditCommentEditor.php', 'PhabricatorAuditCommitStatusConstants' => 'applications/audit/constants/PhabricatorAuditCommitStatusConstants.php', 'PhabricatorAuditController' => 'applications/audit/controller/PhabricatorAuditController.php', 'PhabricatorAuditEditor' => 'applications/audit/editor/PhabricatorAuditEditor.php', 'PhabricatorAuditInlineComment' => 'applications/audit/storage/PhabricatorAuditInlineComment.php', 'PhabricatorAuditListController' => 'applications/audit/controller/PhabricatorAuditListController.php', 'PhabricatorAuditListView' => 'applications/audit/view/PhabricatorAuditListView.php', 'PhabricatorAuditMailReceiver' => 'applications/audit/mail/PhabricatorAuditMailReceiver.php', 'PhabricatorAuditManagementDeleteWorkflow' => 'applications/audit/management/PhabricatorAuditManagementDeleteWorkflow.php', 'PhabricatorAuditManagementWorkflow' => 'applications/audit/management/PhabricatorAuditManagementWorkflow.php', 'PhabricatorAuditPreviewController' => 'applications/audit/controller/PhabricatorAuditPreviewController.php', 'PhabricatorAuditReplyHandler' => 'applications/audit/mail/PhabricatorAuditReplyHandler.php', 'PhabricatorAuditStatusConstants' => 'applications/audit/constants/PhabricatorAuditStatusConstants.php', 'PhabricatorAuditTransaction' => 'applications/audit/storage/PhabricatorAuditTransaction.php', 'PhabricatorAuditTransactionComment' => 'applications/audit/storage/PhabricatorAuditTransactionComment.php', 'PhabricatorAuditTransactionQuery' => 'applications/audit/query/PhabricatorAuditTransactionQuery.php', 'PhabricatorAuditTransactionView' => 'applications/audit/view/PhabricatorAuditTransactionView.php', 'PhabricatorAuthAccountView' => 'applications/auth/view/PhabricatorAuthAccountView.php', 'PhabricatorAuthApplication' => 'applications/auth/application/PhabricatorAuthApplication.php', 'PhabricatorAuthAuthFactorPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthFactorPHIDType.php', 'PhabricatorAuthAuthProviderPHIDType' => 'applications/auth/phid/PhabricatorAuthAuthProviderPHIDType.php', 'PhabricatorAuthConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthConduitAPIMethod.php', 'PhabricatorAuthConfirmLinkController' => 'applications/auth/controller/PhabricatorAuthConfirmLinkController.php', 'PhabricatorAuthController' => 'applications/auth/controller/PhabricatorAuthController.php', 'PhabricatorAuthDAO' => 'applications/auth/storage/PhabricatorAuthDAO.php', 'PhabricatorAuthDisableController' => 'applications/auth/controller/config/PhabricatorAuthDisableController.php', 'PhabricatorAuthDowngradeSessionController' => 'applications/auth/controller/PhabricatorAuthDowngradeSessionController.php', 'PhabricatorAuthEditController' => 'applications/auth/controller/config/PhabricatorAuthEditController.php', 'PhabricatorAuthFactor' => 'applications/auth/factor/PhabricatorAuthFactor.php', 'PhabricatorAuthFactorConfig' => 'applications/auth/storage/PhabricatorAuthFactorConfig.php', 'PhabricatorAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthFactorTestCase.php', 'PhabricatorAuthFinishController' => 'applications/auth/controller/PhabricatorAuthFinishController.php', 'PhabricatorAuthHighSecurityRequiredException' => 'applications/auth/exception/PhabricatorAuthHighSecurityRequiredException.php', 'PhabricatorAuthHighSecurityToken' => 'applications/auth/data/PhabricatorAuthHighSecurityToken.php', 'PhabricatorAuthInvite' => 'applications/auth/storage/PhabricatorAuthInvite.php', 'PhabricatorAuthInviteAccountException' => 'applications/auth/exception/PhabricatorAuthInviteAccountException.php', 'PhabricatorAuthInviteAction' => 'applications/auth/data/PhabricatorAuthInviteAction.php', 'PhabricatorAuthInviteActionTableView' => 'applications/auth/view/PhabricatorAuthInviteActionTableView.php', 'PhabricatorAuthInviteController' => 'applications/auth/controller/PhabricatorAuthInviteController.php', 'PhabricatorAuthInviteDialogException' => 'applications/auth/exception/PhabricatorAuthInviteDialogException.php', 'PhabricatorAuthInviteEngine' => 'applications/auth/engine/PhabricatorAuthInviteEngine.php', 'PhabricatorAuthInviteException' => 'applications/auth/exception/PhabricatorAuthInviteException.php', 'PhabricatorAuthInviteInvalidException' => 'applications/auth/exception/PhabricatorAuthInviteInvalidException.php', 'PhabricatorAuthInviteLoginException' => 'applications/auth/exception/PhabricatorAuthInviteLoginException.php', 'PhabricatorAuthInvitePHIDType' => 'applications/auth/phid/PhabricatorAuthInvitePHIDType.php', 'PhabricatorAuthInviteQuery' => 'applications/auth/query/PhabricatorAuthInviteQuery.php', 'PhabricatorAuthInviteRegisteredException' => 'applications/auth/exception/PhabricatorAuthInviteRegisteredException.php', 'PhabricatorAuthInviteSearchEngine' => 'applications/auth/query/PhabricatorAuthInviteSearchEngine.php', 'PhabricatorAuthInviteTestCase' => 'applications/auth/factor/__tests__/PhabricatorAuthInviteTestCase.php', 'PhabricatorAuthInviteVerifyException' => 'applications/auth/exception/PhabricatorAuthInviteVerifyException.php', 'PhabricatorAuthInviteWorker' => 'applications/auth/worker/PhabricatorAuthInviteWorker.php', 'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php', 'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php', 'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php', 'PhabricatorAuthLoginHandler' => 'applications/auth/handler/PhabricatorAuthLoginHandler.php', 'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php', 'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php', 'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php', 'PhabricatorAuthManagementRecoverWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php', 'PhabricatorAuthManagementRefreshWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php', 'PhabricatorAuthManagementStripWorkflow' => 'applications/auth/management/PhabricatorAuthManagementStripWorkflow.php', 'PhabricatorAuthManagementTrustOAuthClientWorkflow' => 'applications/auth/management/PhabricatorAuthManagementTrustOAuthClientWorkflow.php', 'PhabricatorAuthManagementUnlimitWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUnlimitWorkflow.php', 'PhabricatorAuthManagementUntrustOAuthClientWorkflow' => 'applications/auth/management/PhabricatorAuthManagementUntrustOAuthClientWorkflow.php', 'PhabricatorAuthManagementVerifyWorkflow' => 'applications/auth/management/PhabricatorAuthManagementVerifyWorkflow.php', 'PhabricatorAuthManagementWorkflow' => 'applications/auth/management/PhabricatorAuthManagementWorkflow.php', 'PhabricatorAuthNeedsApprovalController' => 'applications/auth/controller/PhabricatorAuthNeedsApprovalController.php', 'PhabricatorAuthNeedsMultiFactorController' => 'applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php', 'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php', 'PhabricatorAuthOldOAuthRedirectController' => 'applications/auth/controller/PhabricatorAuthOldOAuthRedirectController.php', 'PhabricatorAuthOneTimeLoginController' => 'applications/auth/controller/PhabricatorAuthOneTimeLoginController.php', 'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php', 'PhabricatorAuthProviderConfig' => 'applications/auth/storage/PhabricatorAuthProviderConfig.php', 'PhabricatorAuthProviderConfigController' => 'applications/auth/controller/config/PhabricatorAuthProviderConfigController.php', 'PhabricatorAuthProviderConfigEditor' => 'applications/auth/editor/PhabricatorAuthProviderConfigEditor.php', 'PhabricatorAuthProviderConfigQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigQuery.php', 'PhabricatorAuthProviderConfigTransaction' => 'applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php', 'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php', 'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'applications/auth/conduit/PhabricatorAuthQueryPublicKeysConduitAPIMethod.php', 'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php', 'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php', 'PhabricatorAuthSSHKey' => 'applications/auth/storage/PhabricatorAuthSSHKey.php', 'PhabricatorAuthSSHKeyController' => 'applications/auth/controller/PhabricatorAuthSSHKeyController.php', 'PhabricatorAuthSSHKeyDeleteController' => 'applications/auth/controller/PhabricatorAuthSSHKeyDeleteController.php', 'PhabricatorAuthSSHKeyEditController' => 'applications/auth/controller/PhabricatorAuthSSHKeyEditController.php', 'PhabricatorAuthSSHKeyGenerateController' => 'applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php', 'PhabricatorAuthSSHKeyQuery' => 'applications/auth/query/PhabricatorAuthSSHKeyQuery.php', 'PhabricatorAuthSSHKeyTableView' => 'applications/auth/view/PhabricatorAuthSSHKeyTableView.php', 'PhabricatorAuthSSHPublicKey' => 'applications/auth/sshkey/PhabricatorAuthSSHPublicKey.php', 'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php', 'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php', 'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php', 'PhabricatorAuthSessionQuery' => 'applications/auth/query/PhabricatorAuthSessionQuery.php', 'PhabricatorAuthSetupCheck' => 'applications/config/check/PhabricatorAuthSetupCheck.php', 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', 'PhabricatorAuthTemporaryToken' => 'applications/auth/storage/PhabricatorAuthTemporaryToken.php', 'PhabricatorAuthTemporaryTokenGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthTemporaryTokenGarbageCollector.php', 'PhabricatorAuthTemporaryTokenQuery' => 'applications/auth/query/PhabricatorAuthTemporaryTokenQuery.php', 'PhabricatorAuthTerminateSessionController' => 'applications/auth/controller/PhabricatorAuthTerminateSessionController.php', 'PhabricatorAuthTryFactorAction' => 'applications/auth/action/PhabricatorAuthTryFactorAction.php', 'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php', 'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php', 'PhabricatorAuthenticationConfigOptions' => 'applications/config/option/PhabricatorAuthenticationConfigOptions.php', 'PhabricatorAutoEventListener' => 'infrastructure/events/PhabricatorAutoEventListener.php', 'PhabricatorBadgeHasRecipientEdgeType' => 'applications/badges/edge/PhabricatorBadgeHasRecipientEdgeType.php', 'PhabricatorBadgesApplication' => 'applications/badges/application/PhabricatorBadgesApplication.php', 'PhabricatorBadgesBadge' => 'applications/badges/storage/PhabricatorBadgesBadge.php', 'PhabricatorBadgesCommentController' => 'applications/badges/controller/PhabricatorBadgesCommentController.php', 'PhabricatorBadgesController' => 'applications/badges/controller/PhabricatorBadgesController.php', 'PhabricatorBadgesCreateCapability' => 'applications/badges/capability/PhabricatorBadgesCreateCapability.php', 'PhabricatorBadgesDAO' => 'applications/badges/storage/PhabricatorBadgesDAO.php', 'PhabricatorBadgesDefaultEditCapability' => 'applications/badges/capability/PhabricatorBadgesDefaultEditCapability.php', 'PhabricatorBadgesEditController' => 'applications/badges/controller/PhabricatorBadgesEditController.php', 'PhabricatorBadgesEditIconController' => 'applications/badges/controller/PhabricatorBadgesEditIconController.php', 'PhabricatorBadgesEditRecipientsController' => 'applications/badges/controller/PhabricatorBadgesEditRecipientsController.php', 'PhabricatorBadgesEditor' => 'applications/badges/editor/PhabricatorBadgesEditor.php', 'PhabricatorBadgesIcon' => 'applications/badges/icon/PhabricatorBadgesIcon.php', 'PhabricatorBadgesListController' => 'applications/badges/controller/PhabricatorBadgesListController.php', 'PhabricatorBadgesMailReceiver' => 'applications/badges/mail/PhabricatorBadgesMailReceiver.php', 'PhabricatorBadgesPHIDType' => 'applications/badges/phid/PhabricatorBadgesPHIDType.php', 'PhabricatorBadgesQuery' => 'applications/badges/query/PhabricatorBadgesQuery.php', 'PhabricatorBadgesRecipientsListView' => 'applications/badges/view/PhabricatorBadgesRecipientsListView.php', 'PhabricatorBadgesRemoveRecipientsController' => 'applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php', 'PhabricatorBadgesReplyHandler' => 'applications/badges/mail/PhabricatorBadgesReplyHandler.php', 'PhabricatorBadgesSchemaSpec' => 'applications/badges/storage/PhabricatorBadgesSchemaSpec.php', 'PhabricatorBadgesSearchEngine' => 'applications/badges/query/PhabricatorBadgesSearchEngine.php', 'PhabricatorBadgesTransaction' => 'applications/badges/storage/PhabricatorBadgesTransaction.php', 'PhabricatorBadgesTransactionComment' => 'applications/badges/storage/PhabricatorBadgesTransactionComment.php', 'PhabricatorBadgesTransactionQuery' => 'applications/badges/query/PhabricatorBadgesTransactionQuery.php', 'PhabricatorBadgesViewController' => 'applications/badges/controller/PhabricatorBadgesViewController.php', 'PhabricatorBarePageUIExample' => 'applications/uiexample/examples/PhabricatorBarePageUIExample.php', 'PhabricatorBarePageView' => 'view/page/PhabricatorBarePageView.php', 'PhabricatorBaseURISetupCheck' => 'applications/config/check/PhabricatorBaseURISetupCheck.php', 'PhabricatorBcryptPasswordHasher' => 'infrastructure/util/password/PhabricatorBcryptPasswordHasher.php', 'PhabricatorBinariesSetupCheck' => 'applications/config/check/PhabricatorBinariesSetupCheck.php', 'PhabricatorBitbucketAuthProvider' => 'applications/auth/provider/PhabricatorBitbucketAuthProvider.php', 'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php', 'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php', 'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php', 'PhabricatorBotFeedNotificationHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php', 'PhabricatorBotFlowdockProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php', 'PhabricatorBotHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotHandler.php', 'PhabricatorBotLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php', 'PhabricatorBotMacroHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php', 'PhabricatorBotMessage' => 'infrastructure/daemon/bot/PhabricatorBotMessage.php', 'PhabricatorBotObjectNameHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php', 'PhabricatorBotSymbolHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php', 'PhabricatorBotTarget' => 'infrastructure/daemon/bot/target/PhabricatorBotTarget.php', 'PhabricatorBotUser' => 'infrastructure/daemon/bot/target/PhabricatorBotUser.php', 'PhabricatorBotWhatsNewHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php', 'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php', 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', 'PhabricatorBusyUIExample' => 'applications/uiexample/examples/PhabricatorBusyUIExample.php', 'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php', 'PhabricatorCacheGeneralGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php', 'PhabricatorCacheManagementPurgeWorkflow' => 'applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php', 'PhabricatorCacheManagementWorkflow' => 'applications/cache/management/PhabricatorCacheManagementWorkflow.php', 'PhabricatorCacheMarkupGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheMarkupGarbageCollector.php', 'PhabricatorCacheSchemaSpec' => 'applications/cache/storage/PhabricatorCacheSchemaSpec.php', 'PhabricatorCacheSetupCheck' => 'applications/config/check/PhabricatorCacheSetupCheck.php', 'PhabricatorCacheSpec' => 'applications/cache/spec/PhabricatorCacheSpec.php', 'PhabricatorCacheTTLGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php', 'PhabricatorCaches' => 'applications/cache/PhabricatorCaches.php', 'PhabricatorCachesTestCase' => 'applications/cache/__tests__/PhabricatorCachesTestCase.php', 'PhabricatorCalendarApplication' => 'applications/calendar/application/PhabricatorCalendarApplication.php', 'PhabricatorCalendarController' => 'applications/calendar/controller/PhabricatorCalendarController.php', 'PhabricatorCalendarDAO' => 'applications/calendar/storage/PhabricatorCalendarDAO.php', 'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php', 'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php', 'PhabricatorCalendarEventCommentController' => 'applications/calendar/controller/PhabricatorCalendarEventCommentController.php', 'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php', 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', 'PhabricatorCalendarEventEditIconController' => 'applications/calendar/controller/PhabricatorCalendarEventEditIconController.php', 'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php', 'PhabricatorCalendarEventEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventEmailCommand.php', 'PhabricatorCalendarEventInvitee' => 'applications/calendar/storage/PhabricatorCalendarEventInvitee.php', 'PhabricatorCalendarEventInviteeQuery' => 'applications/calendar/query/PhabricatorCalendarEventInviteeQuery.php', 'PhabricatorCalendarEventJoinController' => 'applications/calendar/controller/PhabricatorCalendarEventJoinController.php', 'PhabricatorCalendarEventListController' => 'applications/calendar/controller/PhabricatorCalendarEventListController.php', 'PhabricatorCalendarEventMailReceiver' => 'applications/calendar/mail/PhabricatorCalendarEventMailReceiver.php', 'PhabricatorCalendarEventPHIDType' => 'applications/calendar/phid/PhabricatorCalendarEventPHIDType.php', 'PhabricatorCalendarEventQuery' => 'applications/calendar/query/PhabricatorCalendarEventQuery.php', 'PhabricatorCalendarEventRSVPEmailCommand' => 'applications/calendar/command/PhabricatorCalendarEventRSVPEmailCommand.php', 'PhabricatorCalendarEventSearchEngine' => 'applications/calendar/query/PhabricatorCalendarEventSearchEngine.php', 'PhabricatorCalendarEventSearchIndexer' => 'applications/calendar/search/PhabricatorCalendarEventSearchIndexer.php', 'PhabricatorCalendarEventTransaction' => 'applications/calendar/storage/PhabricatorCalendarEventTransaction.php', 'PhabricatorCalendarEventTransactionComment' => 'applications/calendar/storage/PhabricatorCalendarEventTransactionComment.php', 'PhabricatorCalendarEventTransactionQuery' => 'applications/calendar/query/PhabricatorCalendarEventTransactionQuery.php', 'PhabricatorCalendarEventViewController' => 'applications/calendar/controller/PhabricatorCalendarEventViewController.php', 'PhabricatorCalendarHoliday' => 'applications/calendar/storage/PhabricatorCalendarHoliday.php', 'PhabricatorCalendarHolidayTestCase' => 'applications/calendar/storage/__tests__/PhabricatorCalendarHolidayTestCase.php', 'PhabricatorCalendarIcon' => 'applications/calendar/icon/PhabricatorCalendarIcon.php', 'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php', 'PhabricatorCalendarReplyHandler' => 'applications/calendar/mail/PhabricatorCalendarReplyHandler.php', 'PhabricatorCalendarSchemaSpec' => 'applications/calendar/storage/PhabricatorCalendarSchemaSpec.php', 'PhabricatorCampfireProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php', 'PhabricatorCelerityApplication' => 'applications/celerity/application/PhabricatorCelerityApplication.php', 'PhabricatorCelerityTestCase' => '__tests__/PhabricatorCelerityTestCase.php', 'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php', 'PhabricatorChangesetResponse' => 'infrastructure/diff/PhabricatorChangesetResponse.php', 'PhabricatorChatLogApplication' => 'applications/chatlog/application/PhabricatorChatLogApplication.php', 'PhabricatorChatLogChannel' => 'applications/chatlog/storage/PhabricatorChatLogChannel.php', 'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/PhabricatorChatLogChannelListController.php', 'PhabricatorChatLogChannelLogController' => 'applications/chatlog/controller/PhabricatorChatLogChannelLogController.php', 'PhabricatorChatLogChannelQuery' => 'applications/chatlog/query/PhabricatorChatLogChannelQuery.php', 'PhabricatorChatLogController' => 'applications/chatlog/controller/PhabricatorChatLogController.php', 'PhabricatorChatLogDAO' => 'applications/chatlog/storage/PhabricatorChatLogDAO.php', 'PhabricatorChatLogEvent' => 'applications/chatlog/storage/PhabricatorChatLogEvent.php', 'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php', 'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php', 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', + 'PhabricatorCommentEditEngineExtension' => 'applications/transactions/editengineextension/PhabricatorCommentEditEngineExtension.php', 'PhabricatorCommentEditField' => 'applications/transactions/editfield/PhabricatorCommentEditField.php', 'PhabricatorCommentEditType' => 'applications/transactions/edittype/PhabricatorCommentEditType.php', 'PhabricatorCommitBranchesField' => 'applications/repository/customfield/PhabricatorCommitBranchesField.php', 'PhabricatorCommitCustomField' => 'applications/repository/customfield/PhabricatorCommitCustomField.php', 'PhabricatorCommitMergedCommitsField' => 'applications/repository/customfield/PhabricatorCommitMergedCommitsField.php', 'PhabricatorCommitRepositoryField' => 'applications/repository/customfield/PhabricatorCommitRepositoryField.php', 'PhabricatorCommitSearchEngine' => 'applications/audit/query/PhabricatorCommitSearchEngine.php', 'PhabricatorCommitTagsField' => 'applications/repository/customfield/PhabricatorCommitTagsField.php', 'PhabricatorCommonPasswords' => 'applications/auth/constants/PhabricatorCommonPasswords.php', 'PhabricatorConduitAPIController' => 'applications/conduit/controller/PhabricatorConduitAPIController.php', 'PhabricatorConduitApplication' => 'applications/conduit/application/PhabricatorConduitApplication.php', 'PhabricatorConduitCertificateToken' => 'applications/conduit/storage/PhabricatorConduitCertificateToken.php', 'PhabricatorConduitConnectionLog' => 'applications/conduit/storage/PhabricatorConduitConnectionLog.php', 'PhabricatorConduitConsoleController' => 'applications/conduit/controller/PhabricatorConduitConsoleController.php', 'PhabricatorConduitController' => 'applications/conduit/controller/PhabricatorConduitController.php', 'PhabricatorConduitDAO' => 'applications/conduit/storage/PhabricatorConduitDAO.php', 'PhabricatorConduitListController' => 'applications/conduit/controller/PhabricatorConduitListController.php', 'PhabricatorConduitLogController' => 'applications/conduit/controller/PhabricatorConduitLogController.php', 'PhabricatorConduitLogQuery' => 'applications/conduit/query/PhabricatorConduitLogQuery.php', 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php', 'PhabricatorConduitMethodQuery' => 'applications/conduit/query/PhabricatorConduitMethodQuery.php', 'PhabricatorConduitRequestExceptionHandler' => 'aphront/handler/PhabricatorConduitRequestExceptionHandler.php', 'PhabricatorConduitSearchEngine' => 'applications/conduit/query/PhabricatorConduitSearchEngine.php', 'PhabricatorConduitTestCase' => '__tests__/PhabricatorConduitTestCase.php', 'PhabricatorConduitToken' => 'applications/conduit/storage/PhabricatorConduitToken.php', 'PhabricatorConduitTokenController' => 'applications/conduit/controller/PhabricatorConduitTokenController.php', 'PhabricatorConduitTokenEditController' => 'applications/conduit/controller/PhabricatorConduitTokenEditController.php', 'PhabricatorConduitTokenHandshakeController' => 'applications/conduit/controller/PhabricatorConduitTokenHandshakeController.php', 'PhabricatorConduitTokenQuery' => 'applications/conduit/query/PhabricatorConduitTokenQuery.php', 'PhabricatorConduitTokenTerminateController' => 'applications/conduit/controller/PhabricatorConduitTokenTerminateController.php', 'PhabricatorConduitTokensSettingsPanel' => 'applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php', 'PhabricatorConfigAllController' => 'applications/config/controller/PhabricatorConfigAllController.php', 'PhabricatorConfigApplication' => 'applications/config/application/PhabricatorConfigApplication.php', 'PhabricatorConfigCacheController' => 'applications/config/controller/PhabricatorConfigCacheController.php', 'PhabricatorConfigCollectorsModule' => 'applications/config/module/PhabricatorConfigCollectorsModule.php', 'PhabricatorConfigColumnSchema' => 'applications/config/schema/PhabricatorConfigColumnSchema.php', 'PhabricatorConfigConfigPHIDType' => 'applications/config/phid/PhabricatorConfigConfigPHIDType.php', 'PhabricatorConfigController' => 'applications/config/controller/PhabricatorConfigController.php', 'PhabricatorConfigCoreSchemaSpec' => 'applications/config/schema/PhabricatorConfigCoreSchemaSpec.php', 'PhabricatorConfigDatabaseController' => 'applications/config/controller/PhabricatorConfigDatabaseController.php', 'PhabricatorConfigDatabaseIssueController' => 'applications/config/controller/PhabricatorConfigDatabaseIssueController.php', 'PhabricatorConfigDatabaseSchema' => 'applications/config/schema/PhabricatorConfigDatabaseSchema.php', 'PhabricatorConfigDatabaseSource' => 'infrastructure/env/PhabricatorConfigDatabaseSource.php', 'PhabricatorConfigDatabaseStatusController' => 'applications/config/controller/PhabricatorConfigDatabaseStatusController.php', 'PhabricatorConfigDefaultSource' => 'infrastructure/env/PhabricatorConfigDefaultSource.php', 'PhabricatorConfigDictionarySource' => 'infrastructure/env/PhabricatorConfigDictionarySource.php', 'PhabricatorConfigEdgeModule' => 'applications/config/module/PhabricatorConfigEdgeModule.php', 'PhabricatorConfigEditController' => 'applications/config/controller/PhabricatorConfigEditController.php', 'PhabricatorConfigEditor' => 'applications/config/editor/PhabricatorConfigEditor.php', 'PhabricatorConfigEntry' => 'applications/config/storage/PhabricatorConfigEntry.php', 'PhabricatorConfigEntryDAO' => 'applications/config/storage/PhabricatorConfigEntryDAO.php', 'PhabricatorConfigEntryQuery' => 'applications/config/query/PhabricatorConfigEntryQuery.php', 'PhabricatorConfigFileSource' => 'infrastructure/env/PhabricatorConfigFileSource.php', 'PhabricatorConfigGroupController' => 'applications/config/controller/PhabricatorConfigGroupController.php', 'PhabricatorConfigHTTPParameterTypesModule' => 'applications/config/module/PhabricatorConfigHTTPParameterTypesModule.php', 'PhabricatorConfigHistoryController' => 'applications/config/controller/PhabricatorConfigHistoryController.php', 'PhabricatorConfigIgnoreController' => 'applications/config/controller/PhabricatorConfigIgnoreController.php', 'PhabricatorConfigIssueListController' => 'applications/config/controller/PhabricatorConfigIssueListController.php', 'PhabricatorConfigIssueViewController' => 'applications/config/controller/PhabricatorConfigIssueViewController.php', 'PhabricatorConfigJSON' => 'applications/config/json/PhabricatorConfigJSON.php', 'PhabricatorConfigJSONOptionType' => 'applications/config/custom/PhabricatorConfigJSONOptionType.php', 'PhabricatorConfigKeySchema' => 'applications/config/schema/PhabricatorConfigKeySchema.php', 'PhabricatorConfigListController' => 'applications/config/controller/PhabricatorConfigListController.php', 'PhabricatorConfigLocalSource' => 'infrastructure/env/PhabricatorConfigLocalSource.php', 'PhabricatorConfigManagementDeleteWorkflow' => 'applications/config/management/PhabricatorConfigManagementDeleteWorkflow.php', 'PhabricatorConfigManagementGetWorkflow' => 'applications/config/management/PhabricatorConfigManagementGetWorkflow.php', 'PhabricatorConfigManagementListWorkflow' => 'applications/config/management/PhabricatorConfigManagementListWorkflow.php', 'PhabricatorConfigManagementMigrateWorkflow' => 'applications/config/management/PhabricatorConfigManagementMigrateWorkflow.php', 'PhabricatorConfigManagementSetWorkflow' => 'applications/config/management/PhabricatorConfigManagementSetWorkflow.php', 'PhabricatorConfigManagementWorkflow' => 'applications/config/management/PhabricatorConfigManagementWorkflow.php', 'PhabricatorConfigModule' => 'applications/config/module/PhabricatorConfigModule.php', 'PhabricatorConfigModuleController' => 'applications/config/controller/PhabricatorConfigModuleController.php', 'PhabricatorConfigOption' => 'applications/config/option/PhabricatorConfigOption.php', 'PhabricatorConfigOptionType' => 'applications/config/custom/PhabricatorConfigOptionType.php', 'PhabricatorConfigPHIDModule' => 'applications/config/module/PhabricatorConfigPHIDModule.php', 'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php', 'PhabricatorConfigPurgeCacheController' => 'applications/config/controller/PhabricatorConfigPurgeCacheController.php', 'PhabricatorConfigRequestExceptionHandlerModule' => 'applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php', 'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php', 'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php', 'PhabricatorConfigSchemaSpec' => 'applications/config/schema/PhabricatorConfigSchemaSpec.php', 'PhabricatorConfigServerSchema' => 'applications/config/schema/PhabricatorConfigServerSchema.php', 'PhabricatorConfigSiteModule' => 'applications/config/module/PhabricatorConfigSiteModule.php', 'PhabricatorConfigSiteSource' => 'infrastructure/env/PhabricatorConfigSiteSource.php', 'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php', 'PhabricatorConfigStackSource' => 'infrastructure/env/PhabricatorConfigStackSource.php', 'PhabricatorConfigStorageSchema' => 'applications/config/schema/PhabricatorConfigStorageSchema.php', 'PhabricatorConfigTableSchema' => 'applications/config/schema/PhabricatorConfigTableSchema.php', 'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php', 'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php', 'PhabricatorConfigValidationException' => 'applications/config/exception/PhabricatorConfigValidationException.php', 'PhabricatorConfigVersionsModule' => 'applications/config/module/PhabricatorConfigVersionsModule.php', 'PhabricatorConfigWelcomeController' => 'applications/config/controller/PhabricatorConfigWelcomeController.php', 'PhabricatorConpherenceApplication' => 'applications/conpherence/application/PhabricatorConpherenceApplication.php', 'PhabricatorConpherencePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorConpherencePreferencesSettingsPanel.php', 'PhabricatorConpherenceThreadPHIDType' => 'applications/conpherence/phid/PhabricatorConpherenceThreadPHIDType.php', 'PhabricatorConsoleApplication' => 'applications/console/application/PhabricatorConsoleApplication.php', 'PhabricatorContentSource' => 'applications/metamta/contentsource/PhabricatorContentSource.php', 'PhabricatorContentSourceView' => 'applications/metamta/contentsource/PhabricatorContentSourceView.php', 'PhabricatorContributedToObjectEdgeType' => 'applications/transactions/edges/PhabricatorContributedToObjectEdgeType.php', 'PhabricatorController' => 'applications/base/controller/PhabricatorController.php', 'PhabricatorCookies' => 'applications/auth/constants/PhabricatorCookies.php', 'PhabricatorCoreConfigOptions' => 'applications/config/option/PhabricatorCoreConfigOptions.php', 'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php', 'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php', 'PhabricatorCountdownCommentController' => 'applications/countdown/controller/PhabricatorCountdownCommentController.php', 'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php', 'PhabricatorCountdownCountdownPHIDType' => 'applications/countdown/phid/PhabricatorCountdownCountdownPHIDType.php', 'PhabricatorCountdownDAO' => 'applications/countdown/storage/PhabricatorCountdownDAO.php', 'PhabricatorCountdownDefaultEditCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultEditCapability.php', 'PhabricatorCountdownDefaultViewCapability' => 'applications/countdown/capability/PhabricatorCountdownDefaultViewCapability.php', 'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/PhabricatorCountdownDeleteController.php', 'PhabricatorCountdownEditController' => 'applications/countdown/controller/PhabricatorCountdownEditController.php', 'PhabricatorCountdownEditor' => 'applications/countdown/editor/PhabricatorCountdownEditor.php', 'PhabricatorCountdownListController' => 'applications/countdown/controller/PhabricatorCountdownListController.php', 'PhabricatorCountdownMailReceiver' => 'applications/countdown/mail/PhabricatorCountdownMailReceiver.php', 'PhabricatorCountdownQuery' => 'applications/countdown/query/PhabricatorCountdownQuery.php', 'PhabricatorCountdownRemarkupRule' => 'applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php', 'PhabricatorCountdownReplyHandler' => 'applications/countdown/mail/PhabricatorCountdownReplyHandler.php', 'PhabricatorCountdownSchemaSpec' => 'applications/countdown/storage/PhabricatorCountdownSchemaSpec.php', 'PhabricatorCountdownSearchEngine' => 'applications/countdown/query/PhabricatorCountdownSearchEngine.php', 'PhabricatorCountdownTransaction' => 'applications/countdown/storage/PhabricatorCountdownTransaction.php', 'PhabricatorCountdownTransactionComment' => 'applications/countdown/storage/PhabricatorCountdownTransactionComment.php', 'PhabricatorCountdownTransactionQuery' => 'applications/countdown/query/PhabricatorCountdownTransactionQuery.php', 'PhabricatorCountdownView' => 'applications/countdown/view/PhabricatorCountdownView.php', 'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php', 'PhabricatorCredentialsUsedByObjectEdgeType' => 'applications/passphrase/edge/PhabricatorCredentialsUsedByObjectEdgeType.php', 'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php', 'PhabricatorCustomField' => 'infrastructure/customfield/field/PhabricatorCustomField.php', 'PhabricatorCustomFieldAttachment' => 'infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php', 'PhabricatorCustomFieldConfigOptionType' => 'infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php', 'PhabricatorCustomFieldDataNotAvailableException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldDataNotAvailableException.php', + 'PhabricatorCustomFieldEditEngineExtension' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditEngineExtension.php', + 'PhabricatorCustomFieldEditField' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php', + 'PhabricatorCustomFieldEditType' => 'infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php', 'PhabricatorCustomFieldHeraldField' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldField.php', 'PhabricatorCustomFieldHeraldFieldGroup' => 'infrastructure/customfield/herald/PhabricatorCustomFieldHeraldFieldGroup.php', 'PhabricatorCustomFieldImplementationIncompleteException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldImplementationIncompleteException.php', 'PhabricatorCustomFieldIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldIndexStorage.php', 'PhabricatorCustomFieldInterface' => 'infrastructure/customfield/interface/PhabricatorCustomFieldInterface.php', 'PhabricatorCustomFieldList' => 'infrastructure/customfield/field/PhabricatorCustomFieldList.php', 'PhabricatorCustomFieldMonogramParser' => 'infrastructure/customfield/parser/PhabricatorCustomFieldMonogramParser.php', 'PhabricatorCustomFieldNotAttachedException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldNotAttachedException.php', 'PhabricatorCustomFieldNotProxyException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldNotProxyException.php', 'PhabricatorCustomFieldNumericIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldNumericIndexStorage.php', 'PhabricatorCustomFieldStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldStorage.php', 'PhabricatorCustomFieldStringIndexStorage' => 'infrastructure/customfield/storage/PhabricatorCustomFieldStringIndexStorage.php', 'PhabricatorCustomHeaderConfigType' => 'applications/config/custom/PhabricatorCustomHeaderConfigType.php', 'PhabricatorDaemon' => 'infrastructure/daemon/PhabricatorDaemon.php', 'PhabricatorDaemonBulkJobListController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobListController.php', 'PhabricatorDaemonBulkJobMonitorController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobMonitorController.php', 'PhabricatorDaemonBulkJobViewController' => 'applications/daemon/controller/PhabricatorDaemonBulkJobViewController.php', 'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/PhabricatorDaemonConsoleController.php', 'PhabricatorDaemonController' => 'applications/daemon/controller/PhabricatorDaemonController.php', 'PhabricatorDaemonDAO' => 'applications/daemon/storage/PhabricatorDaemonDAO.php', 'PhabricatorDaemonEventListener' => 'applications/daemon/event/PhabricatorDaemonEventListener.php', 'PhabricatorDaemonLog' => 'applications/daemon/storage/PhabricatorDaemonLog.php', 'PhabricatorDaemonLogEvent' => 'applications/daemon/storage/PhabricatorDaemonLogEvent.php', 'PhabricatorDaemonLogEventGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php', 'PhabricatorDaemonLogEventViewController' => 'applications/daemon/controller/PhabricatorDaemonLogEventViewController.php', 'PhabricatorDaemonLogEventsView' => 'applications/daemon/view/PhabricatorDaemonLogEventsView.php', 'PhabricatorDaemonLogGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php', 'PhabricatorDaemonLogListController' => 'applications/daemon/controller/PhabricatorDaemonLogListController.php', 'PhabricatorDaemonLogListView' => 'applications/daemon/view/PhabricatorDaemonLogListView.php', 'PhabricatorDaemonLogQuery' => 'applications/daemon/query/PhabricatorDaemonLogQuery.php', 'PhabricatorDaemonLogViewController' => 'applications/daemon/controller/PhabricatorDaemonLogViewController.php', 'PhabricatorDaemonManagementDebugWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementDebugWorkflow.php', 'PhabricatorDaemonManagementLaunchWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementLaunchWorkflow.php', 'PhabricatorDaemonManagementListWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementListWorkflow.php', 'PhabricatorDaemonManagementLogWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementLogWorkflow.php', 'PhabricatorDaemonManagementReloadWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementReloadWorkflow.php', 'PhabricatorDaemonManagementRestartWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementRestartWorkflow.php', 'PhabricatorDaemonManagementStartWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementStartWorkflow.php', 'PhabricatorDaemonManagementStatusWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementStatusWorkflow.php', 'PhabricatorDaemonManagementStopWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementStopWorkflow.php', 'PhabricatorDaemonManagementWorkflow' => 'applications/daemon/management/PhabricatorDaemonManagementWorkflow.php', 'PhabricatorDaemonOverseerModule' => 'infrastructure/daemon/overseer/PhabricatorDaemonOverseerModule.php', 'PhabricatorDaemonReference' => 'infrastructure/daemon/control/PhabricatorDaemonReference.php', 'PhabricatorDaemonTaskGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonTaskGarbageCollector.php', 'PhabricatorDaemonTasksTableView' => 'applications/daemon/view/PhabricatorDaemonTasksTableView.php', 'PhabricatorDaemonsApplication' => 'applications/daemon/application/PhabricatorDaemonsApplication.php', 'PhabricatorDaemonsSetupCheck' => 'applications/config/check/PhabricatorDaemonsSetupCheck.php', 'PhabricatorDailyRoutineTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorDailyRoutineTriggerClock.php', 'PhabricatorDashboard' => 'applications/dashboard/storage/PhabricatorDashboard.php', 'PhabricatorDashboardAddPanelController' => 'applications/dashboard/controller/PhabricatorDashboardAddPanelController.php', 'PhabricatorDashboardApplication' => 'applications/dashboard/application/PhabricatorDashboardApplication.php', 'PhabricatorDashboardController' => 'applications/dashboard/controller/PhabricatorDashboardController.php', 'PhabricatorDashboardCopyController' => 'applications/dashboard/controller/PhabricatorDashboardCopyController.php', 'PhabricatorDashboardDAO' => 'applications/dashboard/storage/PhabricatorDashboardDAO.php', 'PhabricatorDashboardDashboardHasPanelEdgeType' => 'applications/dashboard/edge/PhabricatorDashboardDashboardHasPanelEdgeType.php', 'PhabricatorDashboardDashboardPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardDashboardPHIDType.php', 'PhabricatorDashboardEditController' => 'applications/dashboard/controller/PhabricatorDashboardEditController.php', 'PhabricatorDashboardHistoryController' => 'applications/dashboard/controller/PhabricatorDashboardHistoryController.php', 'PhabricatorDashboardInstall' => 'applications/dashboard/storage/PhabricatorDashboardInstall.php', 'PhabricatorDashboardInstallController' => 'applications/dashboard/controller/PhabricatorDashboardInstallController.php', 'PhabricatorDashboardLayoutConfig' => 'applications/dashboard/layoutconfig/PhabricatorDashboardLayoutConfig.php', 'PhabricatorDashboardListController' => 'applications/dashboard/controller/PhabricatorDashboardListController.php', 'PhabricatorDashboardManageController' => 'applications/dashboard/controller/PhabricatorDashboardManageController.php', 'PhabricatorDashboardMovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardMovePanelController.php', 'PhabricatorDashboardPanel' => 'applications/dashboard/storage/PhabricatorDashboardPanel.php', 'PhabricatorDashboardPanelArchiveController' => 'applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php', 'PhabricatorDashboardPanelCoreCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php', 'PhabricatorDashboardPanelCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php', 'PhabricatorDashboardPanelEditController' => 'applications/dashboard/controller/PhabricatorDashboardPanelEditController.php', 'PhabricatorDashboardPanelHasDashboardEdgeType' => 'applications/dashboard/edge/PhabricatorDashboardPanelHasDashboardEdgeType.php', 'PhabricatorDashboardPanelListController' => 'applications/dashboard/controller/PhabricatorDashboardPanelListController.php', 'PhabricatorDashboardPanelPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php', 'PhabricatorDashboardPanelQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelQuery.php', 'PhabricatorDashboardPanelRenderController' => 'applications/dashboard/controller/PhabricatorDashboardPanelRenderController.php', 'PhabricatorDashboardPanelRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php', 'PhabricatorDashboardPanelSearchApplicationCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php', 'PhabricatorDashboardPanelSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php', 'PhabricatorDashboardPanelSearchQueryCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php', 'PhabricatorDashboardPanelTabsCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelTabsCustomField.php', 'PhabricatorDashboardPanelTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPanelTransaction.php', 'PhabricatorDashboardPanelTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php', 'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php', 'PhabricatorDashboardPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardPanelType.php', 'PhabricatorDashboardPanelViewController' => 'applications/dashboard/controller/PhabricatorDashboardPanelViewController.php', 'PhabricatorDashboardQuery' => 'applications/dashboard/query/PhabricatorDashboardQuery.php', 'PhabricatorDashboardQueryPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php', 'PhabricatorDashboardRemarkupRule' => 'applications/dashboard/remarkup/PhabricatorDashboardRemarkupRule.php', 'PhabricatorDashboardRemovePanelController' => 'applications/dashboard/controller/PhabricatorDashboardRemovePanelController.php', 'PhabricatorDashboardRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php', 'PhabricatorDashboardSchemaSpec' => 'applications/dashboard/storage/PhabricatorDashboardSchemaSpec.php', 'PhabricatorDashboardSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardSearchEngine.php', 'PhabricatorDashboardTabsPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php', 'PhabricatorDashboardTextPanelType' => 'applications/dashboard/paneltype/PhabricatorDashboardTextPanelType.php', 'PhabricatorDashboardTransaction' => 'applications/dashboard/storage/PhabricatorDashboardTransaction.php', 'PhabricatorDashboardTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardTransactionEditor.php', 'PhabricatorDashboardTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardTransactionQuery.php', 'PhabricatorDashboardUninstallController' => 'applications/dashboard/controller/PhabricatorDashboardUninstallController.php', 'PhabricatorDashboardViewController' => 'applications/dashboard/controller/PhabricatorDashboardViewController.php', 'PhabricatorDataCacheSpec' => 'applications/cache/spec/PhabricatorDataCacheSpec.php', 'PhabricatorDataNotAttachedException' => 'infrastructure/storage/lisk/PhabricatorDataNotAttachedException.php', 'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php', 'PhabricatorDatasourceEditField' => 'applications/transactions/editfield/PhabricatorDatasourceEditField.php', 'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php', 'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php', 'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php', 'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php', 'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php', 'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php', 'PhabricatorDeveloperConfigOptions' => 'applications/config/option/PhabricatorDeveloperConfigOptions.php', 'PhabricatorDeveloperPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDeveloperPreferencesSettingsPanel.php', 'PhabricatorDiffInlineCommentQuery' => 'infrastructure/diff/query/PhabricatorDiffInlineCommentQuery.php', 'PhabricatorDiffPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDiffPreferencesSettingsPanel.php', 'PhabricatorDifferenceEngine' => 'infrastructure/diff/PhabricatorDifferenceEngine.php', 'PhabricatorDifferentialApplication' => 'applications/differential/application/PhabricatorDifferentialApplication.php', 'PhabricatorDifferentialConfigOptions' => 'applications/differential/config/PhabricatorDifferentialConfigOptions.php', 'PhabricatorDifferentialRevisionTestDataGenerator' => 'applications/differential/lipsum/PhabricatorDifferentialRevisionTestDataGenerator.php', 'PhabricatorDiffusionApplication' => 'applications/diffusion/application/PhabricatorDiffusionApplication.php', 'PhabricatorDiffusionConfigOptions' => 'applications/diffusion/config/PhabricatorDiffusionConfigOptions.php', 'PhabricatorDisabledUserController' => 'applications/auth/controller/PhabricatorDisabledUserController.php', 'PhabricatorDisplayPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDisplayPreferencesSettingsPanel.php', 'PhabricatorDisqusAuthProvider' => 'applications/auth/provider/PhabricatorDisqusAuthProvider.php', 'PhabricatorDivinerApplication' => 'applications/diviner/application/PhabricatorDivinerApplication.php', 'PhabricatorDoorkeeperApplication' => 'applications/doorkeeper/application/PhabricatorDoorkeeperApplication.php', 'PhabricatorDraft' => 'applications/draft/storage/PhabricatorDraft.php', 'PhabricatorDraftDAO' => 'applications/draft/storage/PhabricatorDraftDAO.php', 'PhabricatorDrydockApplication' => 'applications/drydock/application/PhabricatorDrydockApplication.php', 'PhabricatorEdgeConfig' => 'infrastructure/edges/constants/PhabricatorEdgeConfig.php', 'PhabricatorEdgeConstants' => 'infrastructure/edges/constants/PhabricatorEdgeConstants.php', 'PhabricatorEdgeCycleException' => 'infrastructure/edges/exception/PhabricatorEdgeCycleException.php', 'PhabricatorEdgeEditType' => 'applications/transactions/edittype/PhabricatorEdgeEditType.php', 'PhabricatorEdgeEditor' => 'infrastructure/edges/editor/PhabricatorEdgeEditor.php', 'PhabricatorEdgeGraph' => 'infrastructure/edges/util/PhabricatorEdgeGraph.php', 'PhabricatorEdgeQuery' => 'infrastructure/edges/query/PhabricatorEdgeQuery.php', 'PhabricatorEdgeTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeTestCase.php', 'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php', 'PhabricatorEdgeTypeTestCase' => 'infrastructure/edges/type/__tests__/PhabricatorEdgeTypeTestCase.php', 'PhabricatorEditEngine' => 'applications/transactions/editengine/PhabricatorEditEngine.php', 'PhabricatorEditEngineAPIMethod' => 'applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php', 'PhabricatorEditEngineConfiguration' => 'applications/transactions/storage/PhabricatorEditEngineConfiguration.php', + 'PhabricatorEditEngineConfigurationDefaultCreateController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultCreateController.php', 'PhabricatorEditEngineConfigurationDefaultsController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultsController.php', + 'PhabricatorEditEngineConfigurationDisableController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationDisableController.php', 'PhabricatorEditEngineConfigurationEditController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationEditController.php', 'PhabricatorEditEngineConfigurationEditEngine' => 'applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php', 'PhabricatorEditEngineConfigurationEditor' => 'applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php', 'PhabricatorEditEngineConfigurationListController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php', 'PhabricatorEditEngineConfigurationLockController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationLockController.php', 'PhabricatorEditEngineConfigurationPHIDType' => 'applications/transactions/phid/PhabricatorEditEngineConfigurationPHIDType.php', 'PhabricatorEditEngineConfigurationQuery' => 'applications/transactions/query/PhabricatorEditEngineConfigurationQuery.php', 'PhabricatorEditEngineConfigurationReorderController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationReorderController.php', 'PhabricatorEditEngineConfigurationSaveController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationSaveController.php', 'PhabricatorEditEngineConfigurationSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php', 'PhabricatorEditEngineConfigurationTransaction' => 'applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php', 'PhabricatorEditEngineConfigurationTransactionQuery' => 'applications/transactions/query/PhabricatorEditEngineConfigurationTransactionQuery.php', 'PhabricatorEditEngineConfigurationViewController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php', 'PhabricatorEditEngineController' => 'applications/transactions/controller/PhabricatorEditEngineController.php', + 'PhabricatorEditEngineExtension' => 'applications/transactions/editengineextension/PhabricatorEditEngineExtension.php', + 'PhabricatorEditEngineExtensionModule' => 'applications/transactions/editengineextension/PhabricatorEditEngineExtensionModule.php', 'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php', 'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php', 'PhabricatorEditEngineSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineSearchEngine.php', 'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php', 'PhabricatorEditType' => 'applications/transactions/edittype/PhabricatorEditType.php', 'PhabricatorEditor' => 'infrastructure/PhabricatorEditor.php', 'PhabricatorElasticSearchEngine' => 'applications/search/engine/PhabricatorElasticSearchEngine.php', 'PhabricatorElasticSearchSetupCheck' => 'applications/config/check/PhabricatorElasticSearchSetupCheck.php', 'PhabricatorEmailAddressesSettingsPanel' => 'applications/settings/panel/PhabricatorEmailAddressesSettingsPanel.php', 'PhabricatorEmailFormatSettingsPanel' => 'applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php', 'PhabricatorEmailLoginController' => 'applications/auth/controller/PhabricatorEmailLoginController.php', 'PhabricatorEmailPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorEmailPreferencesSettingsPanel.php', 'PhabricatorEmailVerificationController' => 'applications/auth/controller/PhabricatorEmailVerificationController.php', 'PhabricatorEmbedFileRemarkupRule' => 'applications/files/markup/PhabricatorEmbedFileRemarkupRule.php', 'PhabricatorEmojiRemarkupRule' => 'applications/macro/markup/PhabricatorEmojiRemarkupRule.php', 'PhabricatorEmptyQueryException' => 'infrastructure/query/PhabricatorEmptyQueryException.php', 'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php', 'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php', 'PhabricatorEvent' => 'infrastructure/events/PhabricatorEvent.php', 'PhabricatorEventEngine' => 'infrastructure/events/PhabricatorEventEngine.php', 'PhabricatorEventListener' => 'infrastructure/events/PhabricatorEventListener.php', 'PhabricatorEventType' => 'infrastructure/events/constant/PhabricatorEventType.php', 'PhabricatorExampleEventListener' => 'infrastructure/events/PhabricatorExampleEventListener.php', 'PhabricatorExtendedPolicyInterface' => 'applications/policy/interface/PhabricatorExtendedPolicyInterface.php', 'PhabricatorExtendingPhabricatorConfigOptions' => 'applications/config/option/PhabricatorExtendingPhabricatorConfigOptions.php', 'PhabricatorExtensionsSetupCheck' => 'applications/config/check/PhabricatorExtensionsSetupCheck.php', 'PhabricatorExternalAccount' => 'applications/people/storage/PhabricatorExternalAccount.php', 'PhabricatorExternalAccountQuery' => 'applications/auth/query/PhabricatorExternalAccountQuery.php', 'PhabricatorExternalAccountsSettingsPanel' => 'applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php', 'PhabricatorExtraConfigSetupCheck' => 'applications/config/check/PhabricatorExtraConfigSetupCheck.php', 'PhabricatorFacebookAuthProvider' => 'applications/auth/provider/PhabricatorFacebookAuthProvider.php', 'PhabricatorFactAggregate' => 'applications/fact/storage/PhabricatorFactAggregate.php', 'PhabricatorFactApplication' => 'applications/fact/application/PhabricatorFactApplication.php', 'PhabricatorFactChartController' => 'applications/fact/controller/PhabricatorFactChartController.php', 'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php', 'PhabricatorFactCountEngine' => 'applications/fact/engine/PhabricatorFactCountEngine.php', 'PhabricatorFactCursor' => 'applications/fact/storage/PhabricatorFactCursor.php', 'PhabricatorFactDAO' => 'applications/fact/storage/PhabricatorFactDAO.php', 'PhabricatorFactDaemon' => 'applications/fact/daemon/PhabricatorFactDaemon.php', 'PhabricatorFactEngine' => 'applications/fact/engine/PhabricatorFactEngine.php', 'PhabricatorFactEngineTestCase' => 'applications/fact/engine/__tests__/PhabricatorFactEngineTestCase.php', 'PhabricatorFactHomeController' => 'applications/fact/controller/PhabricatorFactHomeController.php', 'PhabricatorFactLastUpdatedEngine' => 'applications/fact/engine/PhabricatorFactLastUpdatedEngine.php', 'PhabricatorFactManagementAnalyzeWorkflow' => 'applications/fact/management/PhabricatorFactManagementAnalyzeWorkflow.php', 'PhabricatorFactManagementCursorsWorkflow' => 'applications/fact/management/PhabricatorFactManagementCursorsWorkflow.php', 'PhabricatorFactManagementDestroyWorkflow' => 'applications/fact/management/PhabricatorFactManagementDestroyWorkflow.php', 'PhabricatorFactManagementListWorkflow' => 'applications/fact/management/PhabricatorFactManagementListWorkflow.php', 'PhabricatorFactManagementStatusWorkflow' => 'applications/fact/management/PhabricatorFactManagementStatusWorkflow.php', 'PhabricatorFactManagementWorkflow' => 'applications/fact/management/PhabricatorFactManagementWorkflow.php', 'PhabricatorFactRaw' => 'applications/fact/storage/PhabricatorFactRaw.php', 'PhabricatorFactSimpleSpec' => 'applications/fact/spec/PhabricatorFactSimpleSpec.php', 'PhabricatorFactSpec' => 'applications/fact/spec/PhabricatorFactSpec.php', 'PhabricatorFactUpdateIterator' => 'applications/fact/extract/PhabricatorFactUpdateIterator.php', 'PhabricatorFeedApplication' => 'applications/feed/application/PhabricatorFeedApplication.php', 'PhabricatorFeedBuilder' => 'applications/feed/builder/PhabricatorFeedBuilder.php', 'PhabricatorFeedConfigOptions' => 'applications/feed/config/PhabricatorFeedConfigOptions.php', 'PhabricatorFeedController' => 'applications/feed/controller/PhabricatorFeedController.php', 'PhabricatorFeedDAO' => 'applications/feed/storage/PhabricatorFeedDAO.php', 'PhabricatorFeedDetailController' => 'applications/feed/controller/PhabricatorFeedDetailController.php', 'PhabricatorFeedListController' => 'applications/feed/controller/PhabricatorFeedListController.php', 'PhabricatorFeedManagementRepublishWorkflow' => 'applications/feed/management/PhabricatorFeedManagementRepublishWorkflow.php', 'PhabricatorFeedManagementWorkflow' => 'applications/feed/management/PhabricatorFeedManagementWorkflow.php', 'PhabricatorFeedQuery' => 'applications/feed/query/PhabricatorFeedQuery.php', 'PhabricatorFeedSearchEngine' => 'applications/feed/query/PhabricatorFeedSearchEngine.php', 'PhabricatorFeedStory' => 'applications/feed/story/PhabricatorFeedStory.php', 'PhabricatorFeedStoryData' => 'applications/feed/storage/PhabricatorFeedStoryData.php', 'PhabricatorFeedStoryNotification' => 'applications/notification/storage/PhabricatorFeedStoryNotification.php', 'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php', 'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php', 'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php', 'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php', 'PhabricatorFileChunk' => 'applications/files/storage/PhabricatorFileChunk.php', 'PhabricatorFileChunkIterator' => 'applications/files/engine/PhabricatorFileChunkIterator.php', 'PhabricatorFileChunkQuery' => 'applications/files/query/PhabricatorFileChunkQuery.php', 'PhabricatorFileCommentController' => 'applications/files/controller/PhabricatorFileCommentController.php', 'PhabricatorFileComposeController' => 'applications/files/controller/PhabricatorFileComposeController.php', 'PhabricatorFileController' => 'applications/files/controller/PhabricatorFileController.php', 'PhabricatorFileDAO' => 'applications/files/storage/PhabricatorFileDAO.php', 'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php', 'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php', 'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php', 'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php', 'PhabricatorFileEditor' => 'applications/files/editor/PhabricatorFileEditor.php', 'PhabricatorFileFilePHIDType' => 'applications/files/phid/PhabricatorFileFilePHIDType.php', 'PhabricatorFileHasObjectEdgeType' => 'applications/files/edge/PhabricatorFileHasObjectEdgeType.php', 'PhabricatorFileImageMacro' => 'applications/macro/storage/PhabricatorFileImageMacro.php', 'PhabricatorFileImageTransform' => 'applications/files/transform/PhabricatorFileImageTransform.php', 'PhabricatorFileInfoController' => 'applications/files/controller/PhabricatorFileInfoController.php', 'PhabricatorFileLinkListView' => 'view/layout/PhabricatorFileLinkListView.php', 'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php', 'PhabricatorFileListController' => 'applications/files/controller/PhabricatorFileListController.php', 'PhabricatorFileQuery' => 'applications/files/query/PhabricatorFileQuery.php', 'PhabricatorFileSchemaSpec' => 'applications/files/storage/PhabricatorFileSchemaSpec.php', 'PhabricatorFileSearchEngine' => 'applications/files/query/PhabricatorFileSearchEngine.php', 'PhabricatorFileStorageBlob' => 'applications/files/storage/PhabricatorFileStorageBlob.php', 'PhabricatorFileStorageConfigurationException' => 'applications/files/exception/PhabricatorFileStorageConfigurationException.php', 'PhabricatorFileStorageEngine' => 'applications/files/engine/PhabricatorFileStorageEngine.php', 'PhabricatorFileStorageEngineTestCase' => 'applications/files/engine/__tests__/PhabricatorFileStorageEngineTestCase.php', 'PhabricatorFileTemporaryGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php', 'PhabricatorFileTestCase' => 'applications/files/storage/__tests__/PhabricatorFileTestCase.php', 'PhabricatorFileTestDataGenerator' => 'applications/files/lipsum/PhabricatorFileTestDataGenerator.php', 'PhabricatorFileThumbnailTransform' => 'applications/files/transform/PhabricatorFileThumbnailTransform.php', 'PhabricatorFileTransaction' => 'applications/files/storage/PhabricatorFileTransaction.php', 'PhabricatorFileTransactionComment' => 'applications/files/storage/PhabricatorFileTransactionComment.php', 'PhabricatorFileTransactionQuery' => 'applications/files/query/PhabricatorFileTransactionQuery.php', 'PhabricatorFileTransform' => 'applications/files/transform/PhabricatorFileTransform.php', 'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php', 'PhabricatorFileTransformListController' => 'applications/files/controller/PhabricatorFileTransformListController.php', 'PhabricatorFileTransformTestCase' => 'applications/files/transform/__tests__/PhabricatorFileTransformTestCase.php', 'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php', 'PhabricatorFileUploadDialogController' => 'applications/files/controller/PhabricatorFileUploadDialogController.php', 'PhabricatorFileUploadException' => 'applications/files/exception/PhabricatorFileUploadException.php', 'PhabricatorFileinfoSetupCheck' => 'applications/config/check/PhabricatorFileinfoSetupCheck.php', 'PhabricatorFilesApplication' => 'applications/files/application/PhabricatorFilesApplication.php', 'PhabricatorFilesApplicationStorageEnginePanel' => 'applications/files/applicationpanel/PhabricatorFilesApplicationStorageEnginePanel.php', 'PhabricatorFilesConfigOptions' => 'applications/files/config/PhabricatorFilesConfigOptions.php', 'PhabricatorFilesManagementCatWorkflow' => 'applications/files/management/PhabricatorFilesManagementCatWorkflow.php', 'PhabricatorFilesManagementCompactWorkflow' => 'applications/files/management/PhabricatorFilesManagementCompactWorkflow.php', 'PhabricatorFilesManagementEnginesWorkflow' => 'applications/files/management/PhabricatorFilesManagementEnginesWorkflow.php', 'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php', 'PhabricatorFilesManagementPurgeWorkflow' => 'applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php', 'PhabricatorFilesManagementRebuildWorkflow' => 'applications/files/management/PhabricatorFilesManagementRebuildWorkflow.php', 'PhabricatorFilesManagementWorkflow' => 'applications/files/management/PhabricatorFilesManagementWorkflow.php', 'PhabricatorFilesOutboundRequestAction' => 'applications/files/action/PhabricatorFilesOutboundRequestAction.php', 'PhabricatorFlag' => 'applications/flag/storage/PhabricatorFlag.php', 'PhabricatorFlagAddFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php', 'PhabricatorFlagColor' => 'applications/flag/constants/PhabricatorFlagColor.php', 'PhabricatorFlagConstants' => 'applications/flag/constants/PhabricatorFlagConstants.php', 'PhabricatorFlagController' => 'applications/flag/controller/PhabricatorFlagController.php', 'PhabricatorFlagDAO' => 'applications/flag/storage/PhabricatorFlagDAO.php', 'PhabricatorFlagDeleteController' => 'applications/flag/controller/PhabricatorFlagDeleteController.php', 'PhabricatorFlagEditController' => 'applications/flag/controller/PhabricatorFlagEditController.php', 'PhabricatorFlagListController' => 'applications/flag/controller/PhabricatorFlagListController.php', 'PhabricatorFlagQuery' => 'applications/flag/query/PhabricatorFlagQuery.php', 'PhabricatorFlagSearchEngine' => 'applications/flag/query/PhabricatorFlagSearchEngine.php', 'PhabricatorFlagSelectControl' => 'applications/flag/view/PhabricatorFlagSelectControl.php', 'PhabricatorFlaggableInterface' => 'applications/flag/interface/PhabricatorFlaggableInterface.php', 'PhabricatorFlagsApplication' => 'applications/flag/application/PhabricatorFlagsApplication.php', 'PhabricatorFlagsUIEventListener' => 'applications/flag/events/PhabricatorFlagsUIEventListener.php', 'PhabricatorFundApplication' => 'applications/fund/application/PhabricatorFundApplication.php', 'PhabricatorGDSetupCheck' => 'applications/config/check/PhabricatorGDSetupCheck.php', 'PhabricatorGarbageCollector' => 'infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php', 'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementCollectWorkflow.php', 'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementSetPolicyWorkflow.php', 'PhabricatorGarbageCollectorManagementWorkflow' => 'infrastructure/daemon/garbagecollector/management/PhabricatorGarbageCollectorManagementWorkflow.php', 'PhabricatorGestureUIExample' => 'applications/uiexample/examples/PhabricatorGestureUIExample.php', 'PhabricatorGitGraphStream' => 'applications/repository/daemon/PhabricatorGitGraphStream.php', 'PhabricatorGitHubAuthProvider' => 'applications/auth/provider/PhabricatorGitHubAuthProvider.php', 'PhabricatorGlobalLock' => 'infrastructure/util/PhabricatorGlobalLock.php', 'PhabricatorGlobalUploadTargetView' => 'applications/files/view/PhabricatorGlobalUploadTargetView.php', 'PhabricatorGoogleAuthProvider' => 'applications/auth/provider/PhabricatorGoogleAuthProvider.php', 'PhabricatorHTTPParameterTypeTableView' => 'applications/config/view/PhabricatorHTTPParameterTypeTableView.php', 'PhabricatorHandleList' => 'applications/phid/handle/pool/PhabricatorHandleList.php', 'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/PhabricatorHandleObjectSelectorDataView.php', 'PhabricatorHandlePool' => 'applications/phid/handle/pool/PhabricatorHandlePool.php', 'PhabricatorHandlePoolTestCase' => 'applications/phid/handle/pool/__tests__/PhabricatorHandlePoolTestCase.php', 'PhabricatorHandleQuery' => 'applications/phid/query/PhabricatorHandleQuery.php', 'PhabricatorHarbormasterApplication' => 'applications/harbormaster/application/PhabricatorHarbormasterApplication.php', 'PhabricatorHarbormasterConfigOptions' => 'applications/harbormaster/config/PhabricatorHarbormasterConfigOptions.php', 'PhabricatorHash' => 'infrastructure/util/PhabricatorHash.php', 'PhabricatorHashTestCase' => 'infrastructure/util/__tests__/PhabricatorHashTestCase.php', 'PhabricatorHelpApplication' => 'applications/help/application/PhabricatorHelpApplication.php', 'PhabricatorHelpController' => 'applications/help/controller/PhabricatorHelpController.php', 'PhabricatorHelpDocumentationController' => 'applications/help/controller/PhabricatorHelpDocumentationController.php', 'PhabricatorHelpEditorProtocolController' => 'applications/help/controller/PhabricatorHelpEditorProtocolController.php', 'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php', 'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php', 'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php', 'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php', 'PhabricatorHomeController' => 'applications/home/controller/PhabricatorHomeController.php', 'PhabricatorHomeMainController' => 'applications/home/controller/PhabricatorHomeMainController.php', 'PhabricatorHomePreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorHomePreferencesSettingsPanel.php', 'PhabricatorHomeQuickCreateController' => 'applications/home/controller/PhabricatorHomeQuickCreateController.php', 'PhabricatorHovercardUIExample' => 'applications/uiexample/examples/PhabricatorHovercardUIExample.php', 'PhabricatorHovercardView' => 'view/widget/hovercard/PhabricatorHovercardView.php', 'PhabricatorHunksManagementMigrateWorkflow' => 'applications/differential/management/PhabricatorHunksManagementMigrateWorkflow.php', 'PhabricatorHunksManagementWorkflow' => 'applications/differential/management/PhabricatorHunksManagementWorkflow.php', 'PhabricatorIRCProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php', 'PhabricatorIconRemarkupRule' => 'applications/macro/markup/PhabricatorIconRemarkupRule.php', 'PhabricatorImageMacroRemarkupRule' => 'applications/macro/markup/PhabricatorImageMacroRemarkupRule.php', 'PhabricatorImageTransformer' => 'applications/files/PhabricatorImageTransformer.php', 'PhabricatorImagemagickSetupCheck' => 'applications/config/check/PhabricatorImagemagickSetupCheck.php', 'PhabricatorInfrastructureTestCase' => '__tests__/PhabricatorInfrastructureTestCase.php', 'PhabricatorInlineCommentController' => 'infrastructure/diff/PhabricatorInlineCommentController.php', 'PhabricatorInlineCommentInterface' => 'infrastructure/diff/interface/PhabricatorInlineCommentInterface.php', 'PhabricatorInlineCommentPreviewController' => 'infrastructure/diff/PhabricatorInlineCommentPreviewController.php', 'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php', 'PhabricatorInstructionsEditField' => 'applications/transactions/editfield/PhabricatorInstructionsEditField.php', 'PhabricatorInternationalizationManagementExtractWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php', 'PhabricatorInternationalizationManagementWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementWorkflow.php', 'PhabricatorInvalidConfigSetupCheck' => 'applications/config/check/PhabricatorInvalidConfigSetupCheck.php', 'PhabricatorIteratedMD5PasswordHasher' => 'infrastructure/util/password/PhabricatorIteratedMD5PasswordHasher.php', 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorIteratedMD5PasswordHasherTestCase.php', 'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php', 'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php', 'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php', 'PhabricatorLDAPAuthProvider' => 'applications/auth/provider/PhabricatorLDAPAuthProvider.php', 'PhabricatorLegalpadApplication' => 'applications/legalpad/application/PhabricatorLegalpadApplication.php', 'PhabricatorLegalpadConfigOptions' => 'applications/legalpad/config/PhabricatorLegalpadConfigOptions.php', 'PhabricatorLegalpadDocumentPHIDType' => 'applications/legalpad/phid/PhabricatorLegalpadDocumentPHIDType.php', 'PhabricatorLegalpadSignaturePolicyRule' => 'applications/legalpad/policyrule/PhabricatorLegalpadSignaturePolicyRule.php', 'PhabricatorLibraryTestCase' => '__tests__/PhabricatorLibraryTestCase.php', 'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php', 'PhabricatorLipsumGenerateWorkflow' => 'applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php', 'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php', 'PhabricatorLipsumMondrianArtist' => 'applications/lipsum/image/PhabricatorLipsumMondrianArtist.php', 'PhabricatorLiskDAO' => 'infrastructure/storage/lisk/PhabricatorLiskDAO.php', 'PhabricatorLiskSerializer' => 'infrastructure/storage/lisk/PhabricatorLiskSerializer.php', 'PhabricatorListFilterUIExample' => 'applications/uiexample/examples/PhabricatorListFilterUIExample.php', 'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php', 'PhabricatorLocalTimeTestCase' => 'view/__tests__/PhabricatorLocalTimeTestCase.php', 'PhabricatorLocaleScopeGuard' => 'infrastructure/internationalization/scope/PhabricatorLocaleScopeGuard.php', 'PhabricatorLocaleScopeGuardTestCase' => 'infrastructure/internationalization/scope/__tests__/PhabricatorLocaleScopeGuardTestCase.php', 'PhabricatorLogTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorLogTriggerAction.php', 'PhabricatorLogoutController' => 'applications/auth/controller/PhabricatorLogoutController.php', 'PhabricatorLunarPhasePolicyRule' => 'applications/policy/rule/PhabricatorLunarPhasePolicyRule.php', 'PhabricatorMacroApplication' => 'applications/macro/application/PhabricatorMacroApplication.php', 'PhabricatorMacroAudioController' => 'applications/macro/controller/PhabricatorMacroAudioController.php', 'PhabricatorMacroCommentController' => 'applications/macro/controller/PhabricatorMacroCommentController.php', 'PhabricatorMacroConfigOptions' => 'applications/macro/config/PhabricatorMacroConfigOptions.php', 'PhabricatorMacroController' => 'applications/macro/controller/PhabricatorMacroController.php', 'PhabricatorMacroDatasource' => 'applications/macro/typeahead/PhabricatorMacroDatasource.php', 'PhabricatorMacroDisableController' => 'applications/macro/controller/PhabricatorMacroDisableController.php', 'PhabricatorMacroEditController' => 'applications/macro/controller/PhabricatorMacroEditController.php', 'PhabricatorMacroEditor' => 'applications/macro/editor/PhabricatorMacroEditor.php', 'PhabricatorMacroListController' => 'applications/macro/controller/PhabricatorMacroListController.php', 'PhabricatorMacroMacroPHIDType' => 'applications/macro/phid/PhabricatorMacroMacroPHIDType.php', 'PhabricatorMacroMailReceiver' => 'applications/macro/mail/PhabricatorMacroMailReceiver.php', 'PhabricatorMacroManageCapability' => 'applications/macro/capability/PhabricatorMacroManageCapability.php', 'PhabricatorMacroMemeController' => 'applications/macro/controller/PhabricatorMacroMemeController.php', 'PhabricatorMacroMemeDialogController' => 'applications/macro/controller/PhabricatorMacroMemeDialogController.php', 'PhabricatorMacroQuery' => 'applications/macro/query/PhabricatorMacroQuery.php', 'PhabricatorMacroReplyHandler' => 'applications/macro/mail/PhabricatorMacroReplyHandler.php', 'PhabricatorMacroSearchEngine' => 'applications/macro/query/PhabricatorMacroSearchEngine.php', 'PhabricatorMacroTransaction' => 'applications/macro/storage/PhabricatorMacroTransaction.php', 'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php', 'PhabricatorMacroTransactionQuery' => 'applications/macro/query/PhabricatorMacroTransactionQuery.php', 'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php', 'PhabricatorMailEmailHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailHeraldField.php', 'PhabricatorMailEmailHeraldFieldGroup' => 'applications/metamta/herald/PhabricatorMailEmailHeraldFieldGroup.php', 'PhabricatorMailEmailSubjectHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailSubjectHeraldField.php', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php', 'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php', 'PhabricatorMailImplementationMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php', 'PhabricatorMailImplementationPHPMailerAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php', 'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php', 'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php', 'PhabricatorMailManagementListInboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementListInboundWorkflow.php', 'PhabricatorMailManagementListOutboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementListOutboundWorkflow.php', 'PhabricatorMailManagementReceiveTestWorkflow' => 'applications/metamta/management/PhabricatorMailManagementReceiveTestWorkflow.php', 'PhabricatorMailManagementResendWorkflow' => 'applications/metamta/management/PhabricatorMailManagementResendWorkflow.php', 'PhabricatorMailManagementSendTestWorkflow' => 'applications/metamta/management/PhabricatorMailManagementSendTestWorkflow.php', 'PhabricatorMailManagementShowInboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementShowInboundWorkflow.php', 'PhabricatorMailManagementShowOutboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php', 'PhabricatorMailManagementVolumeWorkflow' => 'applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php', 'PhabricatorMailManagementWorkflow' => 'applications/metamta/management/PhabricatorMailManagementWorkflow.php', 'PhabricatorMailOutboundMailHeraldAdapter' => 'applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php', 'PhabricatorMailOutboundRoutingHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php', 'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php', 'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php', 'PhabricatorMailOutboundStatus' => 'applications/metamta/constants/PhabricatorMailOutboundStatus.php', 'PhabricatorMailReceiver' => 'applications/metamta/receiver/PhabricatorMailReceiver.php', 'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php', 'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php', 'PhabricatorMailRoutingRule' => 'applications/metamta/constants/PhabricatorMailRoutingRule.php', 'PhabricatorMailSetupCheck' => 'applications/config/check/PhabricatorMailSetupCheck.php', 'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php', 'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php', 'PhabricatorMainMenuSearchView' => 'view/page/menu/PhabricatorMainMenuSearchView.php', 'PhabricatorMainMenuView' => 'view/page/menu/PhabricatorMainMenuView.php', 'PhabricatorManagementWorkflow' => 'infrastructure/management/PhabricatorManagementWorkflow.php', 'PhabricatorManiphestApplication' => 'applications/maniphest/application/PhabricatorManiphestApplication.php', 'PhabricatorManiphestConfigOptions' => 'applications/maniphest/config/PhabricatorManiphestConfigOptions.php', 'PhabricatorManiphestTaskTestDataGenerator' => 'applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php', 'PhabricatorMarkupCache' => 'applications/cache/storage/PhabricatorMarkupCache.php', 'PhabricatorMarkupEngine' => 'infrastructure/markup/PhabricatorMarkupEngine.php', 'PhabricatorMarkupInterface' => 'infrastructure/markup/PhabricatorMarkupInterface.php', 'PhabricatorMarkupOneOff' => 'infrastructure/markup/PhabricatorMarkupOneOff.php', 'PhabricatorMarkupPreviewController' => 'infrastructure/markup/PhabricatorMarkupPreviewController.php', 'PhabricatorMemeRemarkupRule' => 'applications/macro/markup/PhabricatorMemeRemarkupRule.php', 'PhabricatorMentionRemarkupRule' => 'applications/people/markup/PhabricatorMentionRemarkupRule.php', 'PhabricatorMentionableInterface' => 'applications/transactions/interface/PhabricatorMentionableInterface.php', 'PhabricatorMercurialGraphStream' => 'applications/repository/daemon/PhabricatorMercurialGraphStream.php', 'PhabricatorMetaMTAActor' => 'applications/metamta/query/PhabricatorMetaMTAActor.php', 'PhabricatorMetaMTAActorQuery' => 'applications/metamta/query/PhabricatorMetaMTAActorQuery.php', 'PhabricatorMetaMTAApplication' => 'applications/metamta/application/PhabricatorMetaMTAApplication.php', 'PhabricatorMetaMTAApplicationEmail' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmail.php', 'PhabricatorMetaMTAApplicationEmailDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAApplicationEmailDatasource.php', 'PhabricatorMetaMTAApplicationEmailEditor' => 'applications/metamta/editor/PhabricatorMetaMTAApplicationEmailEditor.php', 'PhabricatorMetaMTAApplicationEmailHeraldField' => 'applications/metamta/herald/PhabricatorMetaMTAApplicationEmailHeraldField.php', 'PhabricatorMetaMTAApplicationEmailPHIDType' => 'applications/phid/PhabricatorMetaMTAApplicationEmailPHIDType.php', 'PhabricatorMetaMTAApplicationEmailPanel' => 'applications/metamta/applicationpanel/PhabricatorMetaMTAApplicationEmailPanel.php', 'PhabricatorMetaMTAApplicationEmailQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailQuery.php', 'PhabricatorMetaMTAApplicationEmailTransaction' => 'applications/metamta/storage/PhabricatorMetaMTAApplicationEmailTransaction.php', 'PhabricatorMetaMTAApplicationEmailTransactionQuery' => 'applications/metamta/query/PhabricatorMetaMTAApplicationEmailTransactionQuery.php', 'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/PhabricatorMetaMTAAttachment.php', 'PhabricatorMetaMTAConfigOptions' => 'applications/config/option/PhabricatorMetaMTAConfigOptions.php', 'PhabricatorMetaMTAController' => 'applications/metamta/controller/PhabricatorMetaMTAController.php', 'PhabricatorMetaMTADAO' => 'applications/metamta/storage/PhabricatorMetaMTADAO.php', 'PhabricatorMetaMTAEmailBodyParser' => 'applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php', 'PhabricatorMetaMTAEmailBodyParserTestCase' => 'applications/metamta/parser/__tests__/PhabricatorMetaMTAEmailBodyParserTestCase.php', 'PhabricatorMetaMTAEmailHeraldAction' => 'applications/metamta/herald/PhabricatorMetaMTAEmailHeraldAction.php', 'PhabricatorMetaMTAEmailOthersHeraldAction' => 'applications/metamta/herald/PhabricatorMetaMTAEmailOthersHeraldAction.php', 'PhabricatorMetaMTAEmailSelfHeraldAction' => 'applications/metamta/herald/PhabricatorMetaMTAEmailSelfHeraldAction.php', 'PhabricatorMetaMTAErrorMailAction' => 'applications/metamta/action/PhabricatorMetaMTAErrorMailAction.php', 'PhabricatorMetaMTAMail' => 'applications/metamta/storage/PhabricatorMetaMTAMail.php', 'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php', 'PhabricatorMetaMTAMailBodyTestCase' => 'applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php', 'PhabricatorMetaMTAMailHasRecipientEdgeType' => 'applications/metamta/edge/PhabricatorMetaMTAMailHasRecipientEdgeType.php', 'PhabricatorMetaMTAMailListController' => 'applications/metamta/controller/PhabricatorMetaMTAMailListController.php', 'PhabricatorMetaMTAMailPHIDType' => 'applications/metamta/phid/PhabricatorMetaMTAMailPHIDType.php', 'PhabricatorMetaMTAMailQuery' => 'applications/metamta/query/PhabricatorMetaMTAMailQuery.php', 'PhabricatorMetaMTAMailSearchEngine' => 'applications/metamta/query/PhabricatorMetaMTAMailSearchEngine.php', 'PhabricatorMetaMTAMailSection' => 'applications/metamta/view/PhabricatorMetaMTAMailSection.php', 'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php', 'PhabricatorMetaMTAMailViewController' => 'applications/metamta/controller/PhabricatorMetaMTAMailViewController.php', 'PhabricatorMetaMTAMailableDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php', 'PhabricatorMetaMTAMailableFunctionDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableFunctionDatasource.php', 'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php', 'PhabricatorMetaMTAMemberQuery' => 'applications/metamta/query/PhabricatorMetaMTAMemberQuery.php', 'PhabricatorMetaMTAPermanentFailureException' => 'applications/metamta/exception/PhabricatorMetaMTAPermanentFailureException.php', 'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php', 'PhabricatorMetaMTAReceivedMailProcessingException' => 'applications/metamta/exception/PhabricatorMetaMTAReceivedMailProcessingException.php', 'PhabricatorMetaMTAReceivedMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAReceivedMailTestCase.php', 'PhabricatorMetaMTASchemaSpec' => 'applications/metamta/storage/PhabricatorMetaMTASchemaSpec.php', 'PhabricatorMetaMTASendGridReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php', 'PhabricatorMetaMTAWorker' => 'applications/metamta/PhabricatorMetaMTAWorker.php', 'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php', 'PhabricatorMultiColumnUIExample' => 'applications/uiexample/examples/PhabricatorMultiColumnUIExample.php', 'PhabricatorMultiFactorSettingsPanel' => 'applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php', 'PhabricatorMultimeterApplication' => 'applications/multimeter/application/PhabricatorMultimeterApplication.php', 'PhabricatorMustVerifyEmailController' => 'applications/auth/controller/PhabricatorMustVerifyEmailController.php', 'PhabricatorMySQLConfigOptions' => 'applications/config/option/PhabricatorMySQLConfigOptions.php', 'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php', 'PhabricatorMySQLSearchEngine' => 'applications/search/engine/PhabricatorMySQLSearchEngine.php', 'PhabricatorMySQLSetupCheck' => 'applications/config/check/PhabricatorMySQLSetupCheck.php', 'PhabricatorNamedQuery' => 'applications/search/storage/PhabricatorNamedQuery.php', 'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php', 'PhabricatorNavigationRemarkupRule' => 'infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php', 'PhabricatorNeverTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorNeverTriggerClock.php', 'PhabricatorNotificationBuilder' => 'applications/notification/builder/PhabricatorNotificationBuilder.php', 'PhabricatorNotificationClearController' => 'applications/notification/controller/PhabricatorNotificationClearController.php', 'PhabricatorNotificationClient' => 'applications/notification/client/PhabricatorNotificationClient.php', 'PhabricatorNotificationConfigOptions' => 'applications/config/option/PhabricatorNotificationConfigOptions.php', 'PhabricatorNotificationController' => 'applications/notification/controller/PhabricatorNotificationController.php', 'PhabricatorNotificationIndividualController' => 'applications/notification/controller/PhabricatorNotificationIndividualController.php', 'PhabricatorNotificationListController' => 'applications/notification/controller/PhabricatorNotificationListController.php', 'PhabricatorNotificationPanelController' => 'applications/notification/controller/PhabricatorNotificationPanelController.php', 'PhabricatorNotificationQuery' => 'applications/notification/query/PhabricatorNotificationQuery.php', 'PhabricatorNotificationSearchEngine' => 'applications/notification/query/PhabricatorNotificationSearchEngine.php', 'PhabricatorNotificationStatusController' => 'applications/notification/controller/PhabricatorNotificationStatusController.php', 'PhabricatorNotificationStatusView' => 'applications/notification/view/PhabricatorNotificationStatusView.php', 'PhabricatorNotificationTestController' => 'applications/notification/controller/PhabricatorNotificationTestController.php', 'PhabricatorNotificationTestFeedStory' => 'applications/notification/feed/PhabricatorNotificationTestFeedStory.php', 'PhabricatorNotificationUIExample' => 'applications/uiexample/examples/PhabricatorNotificationUIExample.php', 'PhabricatorNotificationsApplication' => 'applications/notification/application/PhabricatorNotificationsApplication.php', 'PhabricatorNuanceApplication' => 'applications/nuance/application/PhabricatorNuanceApplication.php', 'PhabricatorOAuth1AuthProvider' => 'applications/auth/provider/PhabricatorOAuth1AuthProvider.php', 'PhabricatorOAuth2AuthProvider' => 'applications/auth/provider/PhabricatorOAuth2AuthProvider.php', 'PhabricatorOAuthAuthProvider' => 'applications/auth/provider/PhabricatorOAuthAuthProvider.php', 'PhabricatorOAuthClientAuthorization' => 'applications/oauthserver/storage/PhabricatorOAuthClientAuthorization.php', 'PhabricatorOAuthClientAuthorizationQuery' => 'applications/oauthserver/query/PhabricatorOAuthClientAuthorizationQuery.php', 'PhabricatorOAuthClientController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientController.php', 'PhabricatorOAuthClientDeleteController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientDeleteController.php', 'PhabricatorOAuthClientEditController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientEditController.php', 'PhabricatorOAuthClientListController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientListController.php', 'PhabricatorOAuthClientSecretController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientSecretController.php', 'PhabricatorOAuthClientViewController' => 'applications/oauthserver/controller/client/PhabricatorOAuthClientViewController.php', 'PhabricatorOAuthResponse' => 'applications/oauthserver/PhabricatorOAuthResponse.php', 'PhabricatorOAuthServer' => 'applications/oauthserver/PhabricatorOAuthServer.php', 'PhabricatorOAuthServerAccessToken' => 'applications/oauthserver/storage/PhabricatorOAuthServerAccessToken.php', 'PhabricatorOAuthServerApplication' => 'applications/oauthserver/application/PhabricatorOAuthServerApplication.php', 'PhabricatorOAuthServerAuthController' => 'applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php', 'PhabricatorOAuthServerAuthorizationCode' => 'applications/oauthserver/storage/PhabricatorOAuthServerAuthorizationCode.php', 'PhabricatorOAuthServerAuthorizationsSettingsPanel' => 'applications/oauthserver/panel/PhabricatorOAuthServerAuthorizationsSettingsPanel.php', 'PhabricatorOAuthServerClient' => 'applications/oauthserver/storage/PhabricatorOAuthServerClient.php', 'PhabricatorOAuthServerClientAuthorizationPHIDType' => 'applications/oauthserver/phid/PhabricatorOAuthServerClientAuthorizationPHIDType.php', 'PhabricatorOAuthServerClientPHIDType' => 'applications/oauthserver/phid/PhabricatorOAuthServerClientPHIDType.php', 'PhabricatorOAuthServerClientQuery' => 'applications/oauthserver/query/PhabricatorOAuthServerClientQuery.php', 'PhabricatorOAuthServerClientSearchEngine' => 'applications/oauthserver/query/PhabricatorOAuthServerClientSearchEngine.php', 'PhabricatorOAuthServerController' => 'applications/oauthserver/controller/PhabricatorOAuthServerController.php', 'PhabricatorOAuthServerCreateClientsCapability' => 'applications/oauthserver/capability/PhabricatorOAuthServerCreateClientsCapability.php', 'PhabricatorOAuthServerDAO' => 'applications/oauthserver/storage/PhabricatorOAuthServerDAO.php', 'PhabricatorOAuthServerScope' => 'applications/oauthserver/PhabricatorOAuthServerScope.php', 'PhabricatorOAuthServerTestCase' => 'applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php', 'PhabricatorOAuthServerTestController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTestController.php', 'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php', 'PhabricatorObjectHandle' => 'applications/phid/PhabricatorObjectHandle.php', 'PhabricatorObjectHasAsanaSubtaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaSubtaskEdgeType.php', 'PhabricatorObjectHasAsanaTaskEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasAsanaTaskEdgeType.php', 'PhabricatorObjectHasContributorEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasContributorEdgeType.php', 'PhabricatorObjectHasFileEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasFileEdgeType.php', 'PhabricatorObjectHasJiraIssueEdgeType' => 'applications/doorkeeper/edge/PhabricatorObjectHasJiraIssueEdgeType.php', 'PhabricatorObjectHasSubscriberEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasSubscriberEdgeType.php', 'PhabricatorObjectHasUnsubscriberEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasUnsubscriberEdgeType.php', 'PhabricatorObjectHasWatcherEdgeType' => 'applications/transactions/edges/PhabricatorObjectHasWatcherEdgeType.php', 'PhabricatorObjectListQuery' => 'applications/phid/query/PhabricatorObjectListQuery.php', 'PhabricatorObjectListQueryTestCase' => 'applications/phid/query/__tests__/PhabricatorObjectListQueryTestCase.php', 'PhabricatorObjectMailReceiver' => 'applications/metamta/receiver/PhabricatorObjectMailReceiver.php', 'PhabricatorObjectMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorObjectMailReceiverTestCase.php', 'PhabricatorObjectMentionedByObjectEdgeType' => 'applications/transactions/edges/PhabricatorObjectMentionedByObjectEdgeType.php', 'PhabricatorObjectMentionsObjectEdgeType' => 'applications/transactions/edges/PhabricatorObjectMentionsObjectEdgeType.php', 'PhabricatorObjectQuery' => 'applications/phid/query/PhabricatorObjectQuery.php', 'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php', 'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php', 'PhabricatorObjectUsesCredentialsEdgeType' => 'applications/transactions/edges/PhabricatorObjectUsesCredentialsEdgeType.php', 'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php', 'PhabricatorOneTimeTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorOneTimeTriggerClock.php', 'PhabricatorOpcodeCacheSpec' => 'applications/cache/spec/PhabricatorOpcodeCacheSpec.php', 'PhabricatorOwnerPathQuery' => 'applications/owners/query/PhabricatorOwnerPathQuery.php', 'PhabricatorOwnersApplication' => 'applications/owners/application/PhabricatorOwnersApplication.php', 'PhabricatorOwnersConfigOptions' => 'applications/owners/config/PhabricatorOwnersConfigOptions.php', 'PhabricatorOwnersConfiguredCustomField' => 'applications/owners/customfield/PhabricatorOwnersConfiguredCustomField.php', 'PhabricatorOwnersController' => 'applications/owners/controller/PhabricatorOwnersController.php', 'PhabricatorOwnersCustomField' => 'applications/owners/customfield/PhabricatorOwnersCustomField.php', 'PhabricatorOwnersCustomFieldNumericIndex' => 'applications/owners/storage/PhabricatorOwnersCustomFieldNumericIndex.php', 'PhabricatorOwnersCustomFieldStorage' => 'applications/owners/storage/PhabricatorOwnersCustomFieldStorage.php', 'PhabricatorOwnersCustomFieldStringIndex' => 'applications/owners/storage/PhabricatorOwnersCustomFieldStringIndex.php', 'PhabricatorOwnersDAO' => 'applications/owners/storage/PhabricatorOwnersDAO.php', 'PhabricatorOwnersDetailController' => 'applications/owners/controller/PhabricatorOwnersDetailController.php', 'PhabricatorOwnersEditController' => 'applications/owners/controller/PhabricatorOwnersEditController.php', 'PhabricatorOwnersListController' => 'applications/owners/controller/PhabricatorOwnersListController.php', 'PhabricatorOwnersOwner' => 'applications/owners/storage/PhabricatorOwnersOwner.php', 'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php', 'PhabricatorOwnersPackageDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php', + 'PhabricatorOwnersPackageEditEngine' => 'applications/owners/editor/PhabricatorOwnersPackageEditEngine.php', 'PhabricatorOwnersPackageFunctionDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageFunctionDatasource.php', 'PhabricatorOwnersPackageOwnerDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageOwnerDatasource.php', 'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php', 'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php', 'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php', 'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php', 'PhabricatorOwnersPackageTransaction' => 'applications/owners/storage/PhabricatorOwnersPackageTransaction.php', 'PhabricatorOwnersPackageTransactionEditor' => 'applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php', 'PhabricatorOwnersPackageTransactionQuery' => 'applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php', 'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php', 'PhabricatorOwnersPathsController' => 'applications/owners/controller/PhabricatorOwnersPathsController.php', 'PhabricatorOwnersSchemaSpec' => 'applications/owners/storage/PhabricatorOwnersSchemaSpec.php', 'PhabricatorOwnersSearchField' => 'applications/owners/searchfield/PhabricatorOwnersSearchField.php', 'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php', 'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php', 'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php', 'PhabricatorPHIDInterface' => 'applications/phid/interface/PhabricatorPHIDInterface.php', + 'PhabricatorPHIDListEditField' => 'applications/transactions/editfield/PhabricatorPHIDListEditField.php', 'PhabricatorPHIDResolver' => 'applications/phid/resolver/PhabricatorPHIDResolver.php', 'PhabricatorPHIDType' => 'applications/phid/type/PhabricatorPHIDType.php', 'PhabricatorPHIDTypeTestCase' => 'applications/phid/type/__tests__/PhabricatorPHIDTypeTestCase.php', 'PhabricatorPHPASTApplication' => 'applications/phpast/application/PhabricatorPHPASTApplication.php', 'PhabricatorPHPConfigSetupCheck' => 'applications/config/check/PhabricatorPHPConfigSetupCheck.php', 'PhabricatorPHPMailerConfigOptions' => 'applications/config/option/PhabricatorPHPMailerConfigOptions.php', 'PhabricatorPagedFormUIExample' => 'applications/uiexample/examples/PhabricatorPagedFormUIExample.php', 'PhabricatorPagerUIExample' => 'applications/uiexample/examples/PhabricatorPagerUIExample.php', 'PhabricatorPassphraseApplication' => 'applications/passphrase/application/PhabricatorPassphraseApplication.php', 'PhabricatorPasswordAuthProvider' => 'applications/auth/provider/PhabricatorPasswordAuthProvider.php', 'PhabricatorPasswordHasher' => 'infrastructure/util/password/PhabricatorPasswordHasher.php', 'PhabricatorPasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorPasswordHasherTestCase.php', 'PhabricatorPasswordHasherUnavailableException' => 'infrastructure/util/password/PhabricatorPasswordHasherUnavailableException.php', 'PhabricatorPasswordSettingsPanel' => 'applications/settings/panel/PhabricatorPasswordSettingsPanel.php', 'PhabricatorPaste' => 'applications/paste/storage/PhabricatorPaste.php', 'PhabricatorPasteApplication' => 'applications/paste/application/PhabricatorPasteApplication.php', - 'PhabricatorPasteCommentController' => 'applications/paste/controller/PhabricatorPasteCommentController.php', 'PhabricatorPasteConfigOptions' => 'applications/paste/config/PhabricatorPasteConfigOptions.php', 'PhabricatorPasteController' => 'applications/paste/controller/PhabricatorPasteController.php', 'PhabricatorPasteDAO' => 'applications/paste/storage/PhabricatorPasteDAO.php', 'PhabricatorPasteEditController' => 'applications/paste/controller/PhabricatorPasteEditController.php', 'PhabricatorPasteEditEngine' => 'applications/paste/editor/PhabricatorPasteEditEngine.php', 'PhabricatorPasteEditor' => 'applications/paste/editor/PhabricatorPasteEditor.php', 'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php', 'PhabricatorPastePastePHIDType' => 'applications/paste/phid/PhabricatorPastePastePHIDType.php', 'PhabricatorPasteQuery' => 'applications/paste/query/PhabricatorPasteQuery.php', 'PhabricatorPasteRawController' => 'applications/paste/controller/PhabricatorPasteRawController.php', 'PhabricatorPasteRemarkupRule' => 'applications/paste/remarkup/PhabricatorPasteRemarkupRule.php', 'PhabricatorPasteSchemaSpec' => 'applications/paste/storage/PhabricatorPasteSchemaSpec.php', 'PhabricatorPasteSearchEngine' => 'applications/paste/query/PhabricatorPasteSearchEngine.php', 'PhabricatorPasteSnippet' => 'applications/paste/snippet/PhabricatorPasteSnippet.php', 'PhabricatorPasteTestDataGenerator' => 'applications/paste/lipsum/PhabricatorPasteTestDataGenerator.php', 'PhabricatorPasteTransaction' => 'applications/paste/storage/PhabricatorPasteTransaction.php', 'PhabricatorPasteTransactionComment' => 'applications/paste/storage/PhabricatorPasteTransactionComment.php', 'PhabricatorPasteTransactionQuery' => 'applications/paste/query/PhabricatorPasteTransactionQuery.php', 'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php', 'PhabricatorPathSetupCheck' => 'applications/config/check/PhabricatorPathSetupCheck.php', 'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php', 'PhabricatorPeopleApplication' => 'applications/people/application/PhabricatorPeopleApplication.php', 'PhabricatorPeopleApproveController' => 'applications/people/controller/PhabricatorPeopleApproveController.php', 'PhabricatorPeopleCalendarController' => 'applications/people/controller/PhabricatorPeopleCalendarController.php', 'PhabricatorPeopleController' => 'applications/people/controller/PhabricatorPeopleController.php', 'PhabricatorPeopleCreateController' => 'applications/people/controller/PhabricatorPeopleCreateController.php', 'PhabricatorPeopleDatasource' => 'applications/people/typeahead/PhabricatorPeopleDatasource.php', 'PhabricatorPeopleDeleteController' => 'applications/people/controller/PhabricatorPeopleDeleteController.php', 'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php', 'PhabricatorPeopleEmpowerController' => 'applications/people/controller/PhabricatorPeopleEmpowerController.php', 'PhabricatorPeopleExternalPHIDType' => 'applications/people/phid/PhabricatorPeopleExternalPHIDType.php', 'PhabricatorPeopleHovercardEventListener' => 'applications/people/event/PhabricatorPeopleHovercardEventListener.php', 'PhabricatorPeopleInviteController' => 'applications/people/controller/PhabricatorPeopleInviteController.php', 'PhabricatorPeopleInviteListController' => 'applications/people/controller/PhabricatorPeopleInviteListController.php', 'PhabricatorPeopleInviteSendController' => 'applications/people/controller/PhabricatorPeopleInviteSendController.php', 'PhabricatorPeopleLdapController' => 'applications/people/controller/PhabricatorPeopleLdapController.php', 'PhabricatorPeopleListController' => 'applications/people/controller/PhabricatorPeopleListController.php', 'PhabricatorPeopleLogQuery' => 'applications/people/query/PhabricatorPeopleLogQuery.php', 'PhabricatorPeopleLogSearchEngine' => 'applications/people/query/PhabricatorPeopleLogSearchEngine.php', 'PhabricatorPeopleLogsController' => 'applications/people/controller/PhabricatorPeopleLogsController.php', 'PhabricatorPeopleNewController' => 'applications/people/controller/PhabricatorPeopleNewController.php', 'PhabricatorPeopleNoOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleNoOwnerDatasource.php', 'PhabricatorPeopleOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleOwnerDatasource.php', 'PhabricatorPeopleProfileController' => 'applications/people/controller/PhabricatorPeopleProfileController.php', 'PhabricatorPeopleProfileEditController' => 'applications/people/controller/PhabricatorPeopleProfileEditController.php', 'PhabricatorPeopleProfilePictureController' => 'applications/people/controller/PhabricatorPeopleProfilePictureController.php', 'PhabricatorPeopleQuery' => 'applications/people/query/PhabricatorPeopleQuery.php', 'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php', 'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php', 'PhabricatorPeopleTestDataGenerator' => 'applications/people/lipsum/PhabricatorPeopleTestDataGenerator.php', 'PhabricatorPeopleTransactionQuery' => 'applications/people/query/PhabricatorPeopleTransactionQuery.php', 'PhabricatorPeopleUserFunctionDatasource' => 'applications/people/typeahead/PhabricatorPeopleUserFunctionDatasource.php', 'PhabricatorPeopleUserPHIDType' => 'applications/people/phid/PhabricatorPeopleUserPHIDType.php', 'PhabricatorPeopleWelcomeController' => 'applications/people/controller/PhabricatorPeopleWelcomeController.php', 'PhabricatorPersonaAuthProvider' => 'applications/auth/provider/PhabricatorPersonaAuthProvider.php', 'PhabricatorPhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorPhabricatorAuthProvider.php', 'PhabricatorPhameApplication' => 'applications/phame/application/PhabricatorPhameApplication.php', 'PhabricatorPhameBlogPHIDType' => 'applications/phame/phid/PhabricatorPhameBlogPHIDType.php', 'PhabricatorPhameConfigOptions' => 'applications/phame/config/PhabricatorPhameConfigOptions.php', 'PhabricatorPhamePostPHIDType' => 'applications/phame/phid/PhabricatorPhamePostPHIDType.php', 'PhabricatorPhluxApplication' => 'applications/phlux/application/PhabricatorPhluxApplication.php', 'PhabricatorPholioApplication' => 'applications/pholio/application/PhabricatorPholioApplication.php', 'PhabricatorPholioConfigOptions' => 'applications/pholio/config/PhabricatorPholioConfigOptions.php', 'PhabricatorPholioMockTestDataGenerator' => 'applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php', 'PhabricatorPhortuneApplication' => 'applications/phortune/application/PhabricatorPhortuneApplication.php', 'PhabricatorPhortuneManagementInvoiceWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php', 'PhabricatorPhortuneManagementWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementWorkflow.php', 'PhabricatorPhragmentApplication' => 'applications/phragment/application/PhabricatorPhragmentApplication.php', 'PhabricatorPhrequentApplication' => 'applications/phrequent/application/PhabricatorPhrequentApplication.php', 'PhabricatorPhrequentConfigOptions' => 'applications/phrequent/config/PhabricatorPhrequentConfigOptions.php', 'PhabricatorPhrictionApplication' => 'applications/phriction/application/PhabricatorPhrictionApplication.php', 'PhabricatorPhrictionConfigOptions' => 'applications/phriction/config/PhabricatorPhrictionConfigOptions.php', 'PhabricatorPhurlApplication' => 'applications/phurl/application/PhabricatorPhurlApplication.php', 'PhabricatorPhurlConfigOptions' => 'applications/config/option/PhabricatorPhurlConfigOptions.php', 'PhabricatorPhurlController' => 'applications/phurl/controller/PhabricatorPhurlController.php', 'PhabricatorPhurlDAO' => 'applications/phurl/storage/PhabricatorPhurlDAO.php', 'PhabricatorPhurlLinkRemarkupRule' => 'applications/phurl/remarkup/PhabricatorPhurlLinkRemarkupRule.php', 'PhabricatorPhurlRemarkupRule' => 'applications/phurl/remarkup/PhabricatorPhurlRemarkupRule.php', 'PhabricatorPhurlSchemaSpec' => 'applications/phurl/storage/PhabricatorPhurlSchemaSpec.php', 'PhabricatorPhurlShortURLController' => 'applications/phurl/controller/PhabricatorPhurlShortURLController.php', 'PhabricatorPhurlShortURLDefaultController' => 'applications/phurl/controller/PhabricatorPhurlShortURLDefaultController.php', 'PhabricatorPhurlURL' => 'applications/phurl/storage/PhabricatorPhurlURL.php', 'PhabricatorPhurlURLAccessController' => 'applications/phurl/controller/PhabricatorPhurlURLAccessController.php', 'PhabricatorPhurlURLCommentController' => 'applications/phurl/controller/PhabricatorPhurlURLCommentController.php', 'PhabricatorPhurlURLCreateCapability' => 'applications/phurl/capability/PhabricatorPhurlURLCreateCapability.php', 'PhabricatorPhurlURLEditController' => 'applications/phurl/controller/PhabricatorPhurlURLEditController.php', 'PhabricatorPhurlURLEditor' => 'applications/phurl/editor/PhabricatorPhurlURLEditor.php', 'PhabricatorPhurlURLListController' => 'applications/phurl/controller/PhabricatorPhurlURLListController.php', 'PhabricatorPhurlURLPHIDType' => 'applications/phurl/phid/PhabricatorPhurlURLPHIDType.php', 'PhabricatorPhurlURLQuery' => 'applications/phurl/query/PhabricatorPhurlURLQuery.php', 'PhabricatorPhurlURLSearchEngine' => 'applications/phurl/query/PhabricatorPhurlURLSearchEngine.php', 'PhabricatorPhurlURLTransaction' => 'applications/phurl/storage/PhabricatorPhurlURLTransaction.php', 'PhabricatorPhurlURLTransactionComment' => 'applications/phurl/storage/PhabricatorPhurlURLTransactionComment.php', 'PhabricatorPhurlURLTransactionQuery' => 'applications/phurl/query/PhabricatorPhurlURLTransactionQuery.php', 'PhabricatorPhurlURLViewController' => 'applications/phurl/controller/PhabricatorPhurlURLViewController.php', 'PhabricatorPlatformSite' => 'aphront/site/PhabricatorPlatformSite.php', 'PhabricatorPolicies' => 'applications/policy/constants/PhabricatorPolicies.php', 'PhabricatorPolicy' => 'applications/policy/storage/PhabricatorPolicy.php', 'PhabricatorPolicyApplication' => 'applications/policy/application/PhabricatorPolicyApplication.php', 'PhabricatorPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorPolicyAwareQuery.php', 'PhabricatorPolicyAwareTestQuery' => 'applications/policy/__tests__/PhabricatorPolicyAwareTestQuery.php', 'PhabricatorPolicyCanEditCapability' => 'applications/policy/capability/PhabricatorPolicyCanEditCapability.php', 'PhabricatorPolicyCanJoinCapability' => 'applications/policy/capability/PhabricatorPolicyCanJoinCapability.php', 'PhabricatorPolicyCanViewCapability' => 'applications/policy/capability/PhabricatorPolicyCanViewCapability.php', 'PhabricatorPolicyCapability' => 'applications/policy/capability/PhabricatorPolicyCapability.php', 'PhabricatorPolicyCapabilityTestCase' => 'applications/policy/capability/__tests__/PhabricatorPolicyCapabilityTestCase.php', 'PhabricatorPolicyConfigOptions' => 'applications/policy/config/PhabricatorPolicyConfigOptions.php', 'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php', 'PhabricatorPolicyController' => 'applications/policy/controller/PhabricatorPolicyController.php', 'PhabricatorPolicyDAO' => 'applications/policy/storage/PhabricatorPolicyDAO.php', 'PhabricatorPolicyDataTestCase' => 'applications/policy/__tests__/PhabricatorPolicyDataTestCase.php', 'PhabricatorPolicyEditController' => 'applications/policy/controller/PhabricatorPolicyEditController.php', + 'PhabricatorPolicyEditEngineExtension' => 'applications/policy/editor/PhabricatorPolicyEditEngineExtension.php', 'PhabricatorPolicyEditField' => 'applications/transactions/editfield/PhabricatorPolicyEditField.php', 'PhabricatorPolicyException' => 'applications/policy/exception/PhabricatorPolicyException.php', 'PhabricatorPolicyExplainController' => 'applications/policy/controller/PhabricatorPolicyExplainController.php', 'PhabricatorPolicyFilter' => 'applications/policy/filter/PhabricatorPolicyFilter.php', 'PhabricatorPolicyInterface' => 'applications/policy/interface/PhabricatorPolicyInterface.php', 'PhabricatorPolicyManagementShowWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementShowWorkflow.php', 'PhabricatorPolicyManagementUnlockWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementUnlockWorkflow.php', 'PhabricatorPolicyManagementWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementWorkflow.php', 'PhabricatorPolicyPHIDTypePolicy' => 'applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php', 'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php', 'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php', 'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php', 'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php', 'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php', 'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php', 'PhabricatorPonderApplication' => 'applications/ponder/application/PhabricatorPonderApplication.php', 'PhabricatorProject' => 'applications/project/storage/PhabricatorProject.php', 'PhabricatorProjectAddHeraldAction' => 'applications/project/herald/PhabricatorProjectAddHeraldAction.php', 'PhabricatorProjectApplication' => 'applications/project/application/PhabricatorProjectApplication.php', 'PhabricatorProjectArchiveController' => 'applications/project/controller/PhabricatorProjectArchiveController.php', 'PhabricatorProjectBoardController' => 'applications/project/controller/PhabricatorProjectBoardController.php', 'PhabricatorProjectBoardImportController' => 'applications/project/controller/PhabricatorProjectBoardImportController.php', 'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php', 'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php', 'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php', 'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php', 'PhabricatorProjectColumnEditController' => 'applications/project/controller/PhabricatorProjectColumnEditController.php', 'PhabricatorProjectColumnHideController' => 'applications/project/controller/PhabricatorProjectColumnHideController.php', 'PhabricatorProjectColumnPHIDType' => 'applications/project/phid/PhabricatorProjectColumnPHIDType.php', 'PhabricatorProjectColumnPosition' => 'applications/project/storage/PhabricatorProjectColumnPosition.php', 'PhabricatorProjectColumnPositionQuery' => 'applications/project/query/PhabricatorProjectColumnPositionQuery.php', 'PhabricatorProjectColumnQuery' => 'applications/project/query/PhabricatorProjectColumnQuery.php', 'PhabricatorProjectColumnTransaction' => 'applications/project/storage/PhabricatorProjectColumnTransaction.php', 'PhabricatorProjectColumnTransactionEditor' => 'applications/project/editor/PhabricatorProjectColumnTransactionEditor.php', 'PhabricatorProjectColumnTransactionQuery' => 'applications/project/query/PhabricatorProjectColumnTransactionQuery.php', 'PhabricatorProjectConfigOptions' => 'applications/project/config/PhabricatorProjectConfigOptions.php', 'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php', 'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php', 'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php', 'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php', 'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php', 'PhabricatorProjectCustomFieldStringIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldStringIndex.php', 'PhabricatorProjectDAO' => 'applications/project/storage/PhabricatorProjectDAO.php', 'PhabricatorProjectDatasource' => 'applications/project/typeahead/PhabricatorProjectDatasource.php', 'PhabricatorProjectDescriptionField' => 'applications/project/customfield/PhabricatorProjectDescriptionField.php', 'PhabricatorProjectEditDetailsController' => 'applications/project/controller/PhabricatorProjectEditDetailsController.php', 'PhabricatorProjectEditIconController' => 'applications/project/controller/PhabricatorProjectEditIconController.php', 'PhabricatorProjectEditPictureController' => 'applications/project/controller/PhabricatorProjectEditPictureController.php', 'PhabricatorProjectEditorTestCase' => 'applications/project/editor/__tests__/PhabricatorProjectEditorTestCase.php', 'PhabricatorProjectFeedController' => 'applications/project/controller/PhabricatorProjectFeedController.php', 'PhabricatorProjectHeraldAction' => 'applications/project/herald/PhabricatorProjectHeraldAction.php', 'PhabricatorProjectIcon' => 'applications/project/icon/PhabricatorProjectIcon.php', 'PhabricatorProjectInterface' => 'applications/project/interface/PhabricatorProjectInterface.php', 'PhabricatorProjectListController' => 'applications/project/controller/PhabricatorProjectListController.php', 'PhabricatorProjectLogicalAndDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalAndDatasource.php', 'PhabricatorProjectLogicalDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalDatasource.php', 'PhabricatorProjectLogicalOrNotDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalOrNotDatasource.php', 'PhabricatorProjectLogicalUserDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalUserDatasource.php', 'PhabricatorProjectLogicalViewerDatasource' => 'applications/project/typeahead/PhabricatorProjectLogicalViewerDatasource.php', 'PhabricatorProjectMemberOfProjectEdgeType' => 'applications/project/edge/PhabricatorProjectMemberOfProjectEdgeType.php', 'PhabricatorProjectMembersDatasource' => 'applications/project/typeahead/PhabricatorProjectMembersDatasource.php', 'PhabricatorProjectMembersEditController' => 'applications/project/controller/PhabricatorProjectMembersEditController.php', 'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php', 'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php', 'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php', 'PhabricatorProjectObjectHasProjectEdgeType' => 'applications/project/edge/PhabricatorProjectObjectHasProjectEdgeType.php', 'PhabricatorProjectOrUserDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserDatasource.php', 'PhabricatorProjectOrUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectOrUserFunctionDatasource.php', 'PhabricatorProjectPHIDResolver' => 'applications/phid/resolver/PhabricatorProjectPHIDResolver.php', 'PhabricatorProjectProfileController' => 'applications/project/controller/PhabricatorProjectProfileController.php', 'PhabricatorProjectProjectHasMemberEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasMemberEdgeType.php', 'PhabricatorProjectProjectHasObjectEdgeType' => 'applications/project/edge/PhabricatorProjectProjectHasObjectEdgeType.php', 'PhabricatorProjectProjectPHIDType' => 'applications/project/phid/PhabricatorProjectProjectPHIDType.php', 'PhabricatorProjectQuery' => 'applications/project/query/PhabricatorProjectQuery.php', 'PhabricatorProjectRemoveHeraldAction' => 'applications/project/herald/PhabricatorProjectRemoveHeraldAction.php', 'PhabricatorProjectSchemaSpec' => 'applications/project/storage/PhabricatorProjectSchemaSpec.php', 'PhabricatorProjectSearchEngine' => 'applications/project/query/PhabricatorProjectSearchEngine.php', 'PhabricatorProjectSearchField' => 'applications/project/searchfield/PhabricatorProjectSearchField.php', 'PhabricatorProjectSearchIndexer' => 'applications/project/search/PhabricatorProjectSearchIndexer.php', 'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php', 'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php', 'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php', 'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php', 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', 'PhabricatorProjectTransactionQuery' => 'applications/project/query/PhabricatorProjectTransactionQuery.php', 'PhabricatorProjectUIEventListener' => 'applications/project/events/PhabricatorProjectUIEventListener.php', 'PhabricatorProjectUpdateController' => 'applications/project/controller/PhabricatorProjectUpdateController.php', 'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php', 'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php', 'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php', + 'PhabricatorProjectsEditEngineExtension' => 'applications/project/editor/PhabricatorProjectsEditEngineExtension.php', 'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php', 'PhabricatorProjectsPolicyRule' => 'applications/project/policyrule/PhabricatorProjectsPolicyRule.php', 'PhabricatorProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php', 'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php', 'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php', 'PhabricatorQueryConstraint' => 'infrastructure/query/constraint/PhabricatorQueryConstraint.php', 'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php', 'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php', 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', 'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php', 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', 'PhabricatorRecipientHasBadgeEdgeType' => 'applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php', 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php', 'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php', 'PhabricatorReleephApplication' => 'applications/releeph/application/PhabricatorReleephApplication.php', 'PhabricatorReleephApplicationConfigOptions' => 'applications/releeph/config/PhabricatorReleephApplicationConfigOptions.php', 'PhabricatorRemarkupControl' => 'view/form/control/PhabricatorRemarkupControl.php', 'PhabricatorRemarkupCowsayBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupCowsayBlockInterpreter.php', 'PhabricatorRemarkupCustomBlockRule' => 'infrastructure/markup/rule/PhabricatorRemarkupCustomBlockRule.php', 'PhabricatorRemarkupCustomInlineRule' => 'infrastructure/markup/rule/PhabricatorRemarkupCustomInlineRule.php', 'PhabricatorRemarkupEditField' => 'applications/transactions/editfield/PhabricatorRemarkupEditField.php', 'PhabricatorRemarkupFigletBlockInterpreter' => 'infrastructure/markup/interpreter/PhabricatorRemarkupFigletBlockInterpreter.php', 'PhabricatorRemarkupUIExample' => 'applications/uiexample/examples/PhabricatorRemarkupUIExample.php', 'PhabricatorRepositoriesSetupCheck' => 'applications/config/check/PhabricatorRepositoriesSetupCheck.php', 'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php', 'PhabricatorRepositoryAuditRequest' => 'applications/repository/storage/PhabricatorRepositoryAuditRequest.php', 'PhabricatorRepositoryBranch' => 'applications/repository/storage/PhabricatorRepositoryBranch.php', 'PhabricatorRepositoryCommit' => 'applications/repository/storage/PhabricatorRepositoryCommit.php', 'PhabricatorRepositoryCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php', 'PhabricatorRepositoryCommitData' => 'applications/repository/storage/PhabricatorRepositoryCommitData.php', 'PhabricatorRepositoryCommitHeraldWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php', 'PhabricatorRepositoryCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php', 'PhabricatorRepositoryCommitOwnersWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php', 'PhabricatorRepositoryCommitPHIDType' => 'applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php', 'PhabricatorRepositoryCommitParserWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitParserWorker.php', 'PhabricatorRepositoryCommitRef' => 'applications/repository/engine/PhabricatorRepositoryCommitRef.php', 'PhabricatorRepositoryCommitSearchIndexer' => 'applications/repository/search/PhabricatorRepositoryCommitSearchIndexer.php', 'PhabricatorRepositoryConfigOptions' => 'applications/repository/config/PhabricatorRepositoryConfigOptions.php', 'PhabricatorRepositoryDAO' => 'applications/repository/storage/PhabricatorRepositoryDAO.php', 'PhabricatorRepositoryDiscoveryEngine' => 'applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php', 'PhabricatorRepositoryEditor' => 'applications/repository/editor/PhabricatorRepositoryEditor.php', 'PhabricatorRepositoryEngine' => 'applications/repository/engine/PhabricatorRepositoryEngine.php', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryGitCommitChangeParserWorker.php', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryGitCommitMessageParserWorker.php', 'PhabricatorRepositoryGraphCache' => 'applications/repository/graphcache/PhabricatorRepositoryGraphCache.php', 'PhabricatorRepositoryGraphStream' => 'applications/repository/daemon/PhabricatorRepositoryGraphStream.php', 'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php', 'PhabricatorRepositoryManagementDiscoverWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php', 'PhabricatorRepositoryManagementEditWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementEditWorkflow.php', 'PhabricatorRepositoryManagementImportingWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php', 'PhabricatorRepositoryManagementListPathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListPathsWorkflow.php', 'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php', 'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php', 'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php', 'PhabricatorRepositoryManagementMirrorWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php', 'PhabricatorRepositoryManagementMovePathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php', 'PhabricatorRepositoryManagementParentsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php', 'PhabricatorRepositoryManagementPullWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementPullWorkflow.php', 'PhabricatorRepositoryManagementRefsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementRefsWorkflow.php', 'PhabricatorRepositoryManagementReparseWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php', 'PhabricatorRepositoryManagementUpdateWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementUpdateWorkflow.php', 'PhabricatorRepositoryManagementWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementWorkflow.php', 'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryMercurialCommitChangeParserWorker.php', 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryMercurialCommitMessageParserWorker.php', 'PhabricatorRepositoryMirror' => 'applications/repository/storage/PhabricatorRepositoryMirror.php', 'PhabricatorRepositoryMirrorEngine' => 'applications/repository/engine/PhabricatorRepositoryMirrorEngine.php', 'PhabricatorRepositoryMirrorPHIDType' => 'applications/repository/phid/PhabricatorRepositoryMirrorPHIDType.php', 'PhabricatorRepositoryMirrorQuery' => 'applications/repository/query/PhabricatorRepositoryMirrorQuery.php', 'PhabricatorRepositoryParsedChange' => 'applications/repository/data/PhabricatorRepositoryParsedChange.php', 'PhabricatorRepositoryPullEngine' => 'applications/repository/engine/PhabricatorRepositoryPullEngine.php', 'PhabricatorRepositoryPullLocalDaemon' => 'applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php', 'PhabricatorRepositoryPushEvent' => 'applications/repository/storage/PhabricatorRepositoryPushEvent.php', 'PhabricatorRepositoryPushEventPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPushEventPHIDType.php', 'PhabricatorRepositoryPushEventQuery' => 'applications/repository/query/PhabricatorRepositoryPushEventQuery.php', 'PhabricatorRepositoryPushLog' => 'applications/repository/storage/PhabricatorRepositoryPushLog.php', 'PhabricatorRepositoryPushLogPHIDType' => 'applications/repository/phid/PhabricatorRepositoryPushLogPHIDType.php', 'PhabricatorRepositoryPushLogQuery' => 'applications/repository/query/PhabricatorRepositoryPushLogQuery.php', 'PhabricatorRepositoryPushLogSearchEngine' => 'applications/repository/query/PhabricatorRepositoryPushLogSearchEngine.php', 'PhabricatorRepositoryPushMailWorker' => 'applications/repository/worker/PhabricatorRepositoryPushMailWorker.php', 'PhabricatorRepositoryPushReplyHandler' => 'applications/repository/mail/PhabricatorRepositoryPushReplyHandler.php', 'PhabricatorRepositoryQuery' => 'applications/repository/query/PhabricatorRepositoryQuery.php', 'PhabricatorRepositoryRefCursor' => 'applications/repository/storage/PhabricatorRepositoryRefCursor.php', 'PhabricatorRepositoryRefCursorQuery' => 'applications/repository/query/PhabricatorRepositoryRefCursorQuery.php', 'PhabricatorRepositoryRefEngine' => 'applications/repository/engine/PhabricatorRepositoryRefEngine.php', 'PhabricatorRepositoryRepositoryPHIDType' => 'applications/repository/phid/PhabricatorRepositoryRepositoryPHIDType.php', 'PhabricatorRepositorySchemaSpec' => 'applications/repository/storage/PhabricatorRepositorySchemaSpec.php', 'PhabricatorRepositorySearchEngine' => 'applications/repository/query/PhabricatorRepositorySearchEngine.php', 'PhabricatorRepositoryStatusMessage' => 'applications/repository/storage/PhabricatorRepositoryStatusMessage.php', 'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositorySvnCommitChangeParserWorker.php', 'PhabricatorRepositorySvnCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositorySvnCommitMessageParserWorker.php', 'PhabricatorRepositorySymbol' => 'applications/repository/storage/PhabricatorRepositorySymbol.php', 'PhabricatorRepositoryTestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php', 'PhabricatorRepositoryTransaction' => 'applications/repository/storage/PhabricatorRepositoryTransaction.php', 'PhabricatorRepositoryTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryTransactionQuery.php', 'PhabricatorRepositoryType' => 'applications/repository/constants/PhabricatorRepositoryType.php', 'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php', 'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php', 'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php', 'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php', 'PhabricatorRepositoryVersion' => 'applications/repository/constants/PhabricatorRepositoryVersion.php', 'PhabricatorRequestExceptionHandler' => 'aphront/handler/PhabricatorRequestExceptionHandler.php', 'PhabricatorResourceSite' => 'aphront/site/PhabricatorResourceSite.php', 'PhabricatorRobotsController' => 'applications/system/controller/PhabricatorRobotsController.php', 'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php', 'PhabricatorSMS' => 'infrastructure/sms/storage/PhabricatorSMS.php', 'PhabricatorSMSConfigOptions' => 'applications/config/option/PhabricatorSMSConfigOptions.php', 'PhabricatorSMSDAO' => 'infrastructure/sms/storage/PhabricatorSMSDAO.php', 'PhabricatorSMSDemultiplexWorker' => 'infrastructure/sms/worker/PhabricatorSMSDemultiplexWorker.php', 'PhabricatorSMSImplementationAdapter' => 'infrastructure/sms/adapter/PhabricatorSMSImplementationAdapter.php', 'PhabricatorSMSImplementationTestBlackholeAdapter' => 'infrastructure/sms/adapter/PhabricatorSMSImplementationTestBlackholeAdapter.php', 'PhabricatorSMSImplementationTwilioAdapter' => 'infrastructure/sms/adapter/PhabricatorSMSImplementationTwilioAdapter.php', 'PhabricatorSMSManagementListOutboundWorkflow' => 'infrastructure/sms/management/PhabricatorSMSManagementListOutboundWorkflow.php', 'PhabricatorSMSManagementSendTestWorkflow' => 'infrastructure/sms/management/PhabricatorSMSManagementSendTestWorkflow.php', 'PhabricatorSMSManagementShowOutboundWorkflow' => 'infrastructure/sms/management/PhabricatorSMSManagementShowOutboundWorkflow.php', 'PhabricatorSMSManagementWorkflow' => 'infrastructure/sms/management/PhabricatorSMSManagementWorkflow.php', 'PhabricatorSMSSendWorker' => 'infrastructure/sms/worker/PhabricatorSMSSendWorker.php', 'PhabricatorSMSWorker' => 'infrastructure/sms/worker/PhabricatorSMSWorker.php', 'PhabricatorSQLPatchList' => 'infrastructure/storage/patch/PhabricatorSQLPatchList.php', 'PhabricatorSSHKeyGenerator' => 'infrastructure/util/PhabricatorSSHKeyGenerator.php', 'PhabricatorSSHKeysSettingsPanel' => 'applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php', 'PhabricatorSSHLog' => 'infrastructure/log/PhabricatorSSHLog.php', 'PhabricatorSSHPassthruCommand' => 'infrastructure/ssh/PhabricatorSSHPassthruCommand.php', 'PhabricatorSSHPublicKeyInterface' => 'applications/auth/sshkey/PhabricatorSSHPublicKeyInterface.php', 'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php', 'PhabricatorSavedQuery' => 'applications/search/storage/PhabricatorSavedQuery.php', 'PhabricatorSavedQueryQuery' => 'applications/search/query/PhabricatorSavedQueryQuery.php', 'PhabricatorScheduleTaskTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorScheduleTaskTriggerAction.php', 'PhabricatorScopedEnv' => 'infrastructure/env/PhabricatorScopedEnv.php', 'PhabricatorSearchAbstractDocument' => 'applications/search/index/PhabricatorSearchAbstractDocument.php', 'PhabricatorSearchApplication' => 'applications/search/application/PhabricatorSearchApplication.php', 'PhabricatorSearchApplicationSearchEngine' => 'applications/search/query/PhabricatorSearchApplicationSearchEngine.php', 'PhabricatorSearchApplicationStorageEnginePanel' => 'applications/search/applicationpanel/PhabricatorSearchApplicationStorageEnginePanel.php', 'PhabricatorSearchAttachController' => 'applications/search/controller/PhabricatorSearchAttachController.php', 'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php', 'PhabricatorSearchCheckboxesField' => 'applications/search/field/PhabricatorSearchCheckboxesField.php', 'PhabricatorSearchConfigOptions' => 'applications/search/config/PhabricatorSearchConfigOptions.php', 'PhabricatorSearchController' => 'applications/search/controller/PhabricatorSearchController.php', 'PhabricatorSearchCustomFieldProxyField' => 'applications/search/field/PhabricatorSearchCustomFieldProxyField.php', 'PhabricatorSearchDAO' => 'applications/search/storage/PhabricatorSearchDAO.php', 'PhabricatorSearchDatasource' => 'applications/search/typeahead/PhabricatorSearchDatasource.php', 'PhabricatorSearchDatasourceField' => 'applications/search/field/PhabricatorSearchDatasourceField.php', 'PhabricatorSearchDateControlField' => 'applications/search/field/PhabricatorSearchDateControlField.php', 'PhabricatorSearchDateField' => 'applications/search/field/PhabricatorSearchDateField.php', 'PhabricatorSearchDeleteController' => 'applications/search/controller/PhabricatorSearchDeleteController.php', 'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php', 'PhabricatorSearchDocumentField' => 'applications/search/storage/document/PhabricatorSearchDocumentField.php', 'PhabricatorSearchDocumentFieldType' => 'applications/search/constants/PhabricatorSearchDocumentFieldType.php', 'PhabricatorSearchDocumentIndexer' => 'applications/search/index/PhabricatorSearchDocumentIndexer.php', 'PhabricatorSearchDocumentQuery' => 'applications/search/query/PhabricatorSearchDocumentQuery.php', 'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/PhabricatorSearchDocumentRelationship.php', 'PhabricatorSearchDocumentTypeDatasource' => 'applications/search/typeahead/PhabricatorSearchDocumentTypeDatasource.php', 'PhabricatorSearchEditController' => 'applications/search/controller/PhabricatorSearchEditController.php', 'PhabricatorSearchEngine' => 'applications/search/engine/PhabricatorSearchEngine.php', 'PhabricatorSearchEngineTestCase' => 'applications/search/engine/__tests__/PhabricatorSearchEngineTestCase.php', 'PhabricatorSearchField' => 'applications/search/field/PhabricatorSearchField.php', 'PhabricatorSearchHovercardController' => 'applications/search/controller/PhabricatorSearchHovercardController.php', 'PhabricatorSearchIndexer' => 'applications/search/index/PhabricatorSearchIndexer.php', 'PhabricatorSearchManagementIndexWorkflow' => 'applications/search/management/PhabricatorSearchManagementIndexWorkflow.php', 'PhabricatorSearchManagementInitWorkflow' => 'applications/search/management/PhabricatorSearchManagementInitWorkflow.php', 'PhabricatorSearchManagementWorkflow' => 'applications/search/management/PhabricatorSearchManagementWorkflow.php', 'PhabricatorSearchOrderController' => 'applications/search/controller/PhabricatorSearchOrderController.php', 'PhabricatorSearchOrderField' => 'applications/search/field/PhabricatorSearchOrderField.php', 'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php', 'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php', 'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php', 'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php', 'PhabricatorSearchSelectField' => 'applications/search/field/PhabricatorSearchSelectField.php', 'PhabricatorSearchStringListField' => 'applications/search/field/PhabricatorSearchStringListField.php', 'PhabricatorSearchSubscribersField' => 'applications/search/field/PhabricatorSearchSubscribersField.php', 'PhabricatorSearchTextField' => 'applications/search/field/PhabricatorSearchTextField.php', 'PhabricatorSearchThreeStateField' => 'applications/search/field/PhabricatorSearchThreeStateField.php', 'PhabricatorSearchTokenizerField' => 'applications/search/field/PhabricatorSearchTokenizerField.php', 'PhabricatorSearchWorker' => 'applications/search/worker/PhabricatorSearchWorker.php', 'PhabricatorSecurityConfigOptions' => 'applications/config/option/PhabricatorSecurityConfigOptions.php', 'PhabricatorSecuritySetupCheck' => 'applications/config/check/PhabricatorSecuritySetupCheck.php', 'PhabricatorSelectEditField' => 'applications/transactions/editfield/PhabricatorSelectEditField.php', 'PhabricatorSendGridConfigOptions' => 'applications/config/option/PhabricatorSendGridConfigOptions.php', 'PhabricatorSessionsSettingsPanel' => 'applications/settings/panel/PhabricatorSessionsSettingsPanel.php', 'PhabricatorSettingsAddEmailAction' => 'applications/settings/action/PhabricatorSettingsAddEmailAction.php', 'PhabricatorSettingsAdjustController' => 'applications/settings/controller/PhabricatorSettingsAdjustController.php', 'PhabricatorSettingsApplication' => 'applications/settings/application/PhabricatorSettingsApplication.php', 'PhabricatorSettingsMainController' => 'applications/settings/controller/PhabricatorSettingsMainController.php', 'PhabricatorSettingsPanel' => 'applications/settings/panel/PhabricatorSettingsPanel.php', 'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php', 'PhabricatorSetupCheckTestCase' => 'applications/config/check/__tests__/PhabricatorSetupCheckTestCase.php', 'PhabricatorSetupIssue' => 'applications/config/issue/PhabricatorSetupIssue.php', 'PhabricatorSetupIssueUIExample' => 'applications/uiexample/examples/PhabricatorSetupIssueUIExample.php', 'PhabricatorSetupIssueView' => 'applications/config/view/PhabricatorSetupIssueView.php', 'PhabricatorShortSite' => 'aphront/site/PhabricatorShortSite.php', 'PhabricatorSimpleEditType' => 'applications/transactions/edittype/PhabricatorSimpleEditType.php', 'PhabricatorSite' => 'aphront/site/PhabricatorSite.php', 'PhabricatorSlowvoteApplication' => 'applications/slowvote/application/PhabricatorSlowvoteApplication.php', 'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/PhabricatorSlowvoteChoice.php', 'PhabricatorSlowvoteCloseController' => 'applications/slowvote/controller/PhabricatorSlowvoteCloseController.php', 'PhabricatorSlowvoteCommentController' => 'applications/slowvote/controller/PhabricatorSlowvoteCommentController.php', 'PhabricatorSlowvoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteController.php', 'PhabricatorSlowvoteDAO' => 'applications/slowvote/storage/PhabricatorSlowvoteDAO.php', 'PhabricatorSlowvoteDefaultViewCapability' => 'applications/slowvote/capability/PhabricatorSlowvoteDefaultViewCapability.php', 'PhabricatorSlowvoteEditController' => 'applications/slowvote/controller/PhabricatorSlowvoteEditController.php', 'PhabricatorSlowvoteEditor' => 'applications/slowvote/editor/PhabricatorSlowvoteEditor.php', 'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/PhabricatorSlowvoteListController.php', 'PhabricatorSlowvoteMailReceiver' => 'applications/slowvote/mail/PhabricatorSlowvoteMailReceiver.php', 'PhabricatorSlowvoteOption' => 'applications/slowvote/storage/PhabricatorSlowvoteOption.php', 'PhabricatorSlowvotePoll' => 'applications/slowvote/storage/PhabricatorSlowvotePoll.php', 'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/PhabricatorSlowvotePollController.php', 'PhabricatorSlowvotePollPHIDType' => 'applications/slowvote/phid/PhabricatorSlowvotePollPHIDType.php', 'PhabricatorSlowvoteQuery' => 'applications/slowvote/query/PhabricatorSlowvoteQuery.php', 'PhabricatorSlowvoteReplyHandler' => 'applications/slowvote/mail/PhabricatorSlowvoteReplyHandler.php', 'PhabricatorSlowvoteSchemaSpec' => 'applications/slowvote/storage/PhabricatorSlowvoteSchemaSpec.php', 'PhabricatorSlowvoteSearchEngine' => 'applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php', 'PhabricatorSlowvoteTransaction' => 'applications/slowvote/storage/PhabricatorSlowvoteTransaction.php', 'PhabricatorSlowvoteTransactionComment' => 'applications/slowvote/storage/PhabricatorSlowvoteTransactionComment.php', 'PhabricatorSlowvoteTransactionQuery' => 'applications/slowvote/query/PhabricatorSlowvoteTransactionQuery.php', 'PhabricatorSlowvoteVoteController' => 'applications/slowvote/controller/PhabricatorSlowvoteVoteController.php', 'PhabricatorSlug' => 'infrastructure/util/PhabricatorSlug.php', 'PhabricatorSlugTestCase' => 'infrastructure/util/__tests__/PhabricatorSlugTestCase.php', 'PhabricatorSortTableUIExample' => 'applications/uiexample/examples/PhabricatorSortTableUIExample.php', 'PhabricatorSourceCodeView' => 'view/layout/PhabricatorSourceCodeView.php', 'PhabricatorSpaceEditField' => 'applications/transactions/editfield/PhabricatorSpaceEditField.php', 'PhabricatorSpacesApplication' => 'applications/spaces/application/PhabricatorSpacesApplication.php', 'PhabricatorSpacesArchiveController' => 'applications/spaces/controller/PhabricatorSpacesArchiveController.php', 'PhabricatorSpacesCapabilityCreateSpaces' => 'applications/spaces/capability/PhabricatorSpacesCapabilityCreateSpaces.php', 'PhabricatorSpacesCapabilityDefaultEdit' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultEdit.php', 'PhabricatorSpacesCapabilityDefaultView' => 'applications/spaces/capability/PhabricatorSpacesCapabilityDefaultView.php', 'PhabricatorSpacesController' => 'applications/spaces/controller/PhabricatorSpacesController.php', 'PhabricatorSpacesDAO' => 'applications/spaces/storage/PhabricatorSpacesDAO.php', 'PhabricatorSpacesEditController' => 'applications/spaces/controller/PhabricatorSpacesEditController.php', 'PhabricatorSpacesInterface' => 'applications/spaces/interface/PhabricatorSpacesInterface.php', 'PhabricatorSpacesListController' => 'applications/spaces/controller/PhabricatorSpacesListController.php', 'PhabricatorSpacesNamespace' => 'applications/spaces/storage/PhabricatorSpacesNamespace.php', 'PhabricatorSpacesNamespaceDatasource' => 'applications/spaces/typeahead/PhabricatorSpacesNamespaceDatasource.php', 'PhabricatorSpacesNamespaceEditor' => 'applications/spaces/editor/PhabricatorSpacesNamespaceEditor.php', 'PhabricatorSpacesNamespacePHIDType' => 'applications/spaces/phid/PhabricatorSpacesNamespacePHIDType.php', 'PhabricatorSpacesNamespaceQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceQuery.php', 'PhabricatorSpacesNamespaceSearchEngine' => 'applications/spaces/query/PhabricatorSpacesNamespaceSearchEngine.php', 'PhabricatorSpacesNamespaceTransaction' => 'applications/spaces/storage/PhabricatorSpacesNamespaceTransaction.php', 'PhabricatorSpacesNamespaceTransactionQuery' => 'applications/spaces/query/PhabricatorSpacesNamespaceTransactionQuery.php', 'PhabricatorSpacesNoAccessController' => 'applications/spaces/controller/PhabricatorSpacesNoAccessController.php', 'PhabricatorSpacesRemarkupRule' => 'applications/spaces/remarkup/PhabricatorSpacesRemarkupRule.php', 'PhabricatorSpacesSchemaSpec' => 'applications/spaces/storage/PhabricatorSpacesSchemaSpec.php', 'PhabricatorSpacesSearchField' => 'applications/spaces/searchfield/PhabricatorSpacesSearchField.php', 'PhabricatorSpacesTestCase' => 'applications/spaces/__tests__/PhabricatorSpacesTestCase.php', 'PhabricatorSpacesViewController' => 'applications/spaces/controller/PhabricatorSpacesViewController.php', 'PhabricatorStandardCustomField' => 'infrastructure/customfield/standard/PhabricatorStandardCustomField.php', 'PhabricatorStandardCustomFieldBlueprints' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBlueprints.php', 'PhabricatorStandardCustomFieldBool' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php', 'PhabricatorStandardCustomFieldCredential' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldCredential.php', 'PhabricatorStandardCustomFieldDatasource' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDatasource.php', 'PhabricatorStandardCustomFieldDate' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php', 'PhabricatorStandardCustomFieldHeader' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldHeader.php', 'PhabricatorStandardCustomFieldInt' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php', 'PhabricatorStandardCustomFieldInterface' => 'infrastructure/customfield/interface/PhabricatorStandardCustomFieldInterface.php', 'PhabricatorStandardCustomFieldLink' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldLink.php', 'PhabricatorStandardCustomFieldPHIDs' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php', 'PhabricatorStandardCustomFieldRemarkup' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php', 'PhabricatorStandardCustomFieldSelect' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php', 'PhabricatorStandardCustomFieldText' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php', 'PhabricatorStandardCustomFieldTokenizer' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php', 'PhabricatorStandardCustomFieldUsers' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php', 'PhabricatorStandardPageView' => 'view/page/PhabricatorStandardPageView.php', 'PhabricatorStandardSelectCustomFieldDatasource' => 'infrastructure/customfield/datasource/PhabricatorStandardSelectCustomFieldDatasource.php', 'PhabricatorStatusController' => 'applications/system/controller/PhabricatorStatusController.php', 'PhabricatorStatusUIExample' => 'applications/uiexample/examples/PhabricatorStatusUIExample.php', 'PhabricatorStorageFixtureScopeGuard' => 'infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php', 'PhabricatorStorageManagementAPI' => 'infrastructure/storage/management/PhabricatorStorageManagementAPI.php', 'PhabricatorStorageManagementAdjustWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php', 'PhabricatorStorageManagementDatabasesWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php', 'PhabricatorStorageManagementDestroyWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php', 'PhabricatorStorageManagementDumpWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php', 'PhabricatorStorageManagementProbeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php', 'PhabricatorStorageManagementQuickstartWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php', 'PhabricatorStorageManagementRenamespaceWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php', 'PhabricatorStorageManagementShellWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php', 'PhabricatorStorageManagementStatusWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementStatusWorkflow.php', 'PhabricatorStorageManagementUpgradeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php', 'PhabricatorStorageManagementWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php', 'PhabricatorStoragePatch' => 'infrastructure/storage/management/PhabricatorStoragePatch.php', 'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php', 'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php', 'PhabricatorStreamingProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php', 'PhabricatorSubscribableInterface' => 'applications/subscriptions/interface/PhabricatorSubscribableInterface.php', 'PhabricatorSubscribedToObjectEdgeType' => 'applications/transactions/edges/PhabricatorSubscribedToObjectEdgeType.php', 'PhabricatorSubscribersEditField' => 'applications/transactions/editfield/PhabricatorSubscribersEditField.php', 'PhabricatorSubscribersQuery' => 'applications/subscriptions/query/PhabricatorSubscribersQuery.php', 'PhabricatorSubscriptionTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorSubscriptionTriggerClock.php', 'PhabricatorSubscriptionsAddSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php', 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php', 'PhabricatorSubscriptionsApplication' => 'applications/subscriptions/application/PhabricatorSubscriptionsApplication.php', 'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php', + 'PhabricatorSubscriptionsEditEngineExtension' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php', 'PhabricatorSubscriptionsEditor' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php', 'PhabricatorSubscriptionsHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php', 'PhabricatorSubscriptionsListController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsListController.php', 'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php', 'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php', 'PhabricatorSubscriptionsSubscribeEmailCommand' => 'applications/subscriptions/command/PhabricatorSubscriptionsSubscribeEmailCommand.php', 'PhabricatorSubscriptionsSubscribersPolicyRule' => 'applications/subscriptions/policyrule/PhabricatorSubscriptionsSubscribersPolicyRule.php', 'PhabricatorSubscriptionsTransactionController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsTransactionController.php', 'PhabricatorSubscriptionsUIEventListener' => 'applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php', 'PhabricatorSubscriptionsUnsubscribeEmailCommand' => 'applications/subscriptions/command/PhabricatorSubscriptionsUnsubscribeEmailCommand.php', 'PhabricatorSupportApplication' => 'applications/support/application/PhabricatorSupportApplication.php', 'PhabricatorSyntaxHighlighter' => 'infrastructure/markup/PhabricatorSyntaxHighlighter.php', 'PhabricatorSyntaxHighlightingConfigOptions' => 'applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php', 'PhabricatorSystemAction' => 'applications/system/action/PhabricatorSystemAction.php', 'PhabricatorSystemActionEngine' => 'applications/system/engine/PhabricatorSystemActionEngine.php', 'PhabricatorSystemActionGarbageCollector' => 'applications/system/garbagecollector/PhabricatorSystemActionGarbageCollector.php', 'PhabricatorSystemActionLog' => 'applications/system/storage/PhabricatorSystemActionLog.php', 'PhabricatorSystemActionRateLimitException' => 'applications/system/exception/PhabricatorSystemActionRateLimitException.php', 'PhabricatorSystemApplication' => 'applications/system/application/PhabricatorSystemApplication.php', 'PhabricatorSystemDAO' => 'applications/system/storage/PhabricatorSystemDAO.php', 'PhabricatorSystemDestructionGarbageCollector' => 'applications/system/garbagecollector/PhabricatorSystemDestructionGarbageCollector.php', 'PhabricatorSystemDestructionLog' => 'applications/system/storage/PhabricatorSystemDestructionLog.php', 'PhabricatorSystemRemoveDestroyWorkflow' => 'applications/system/management/PhabricatorSystemRemoveDestroyWorkflow.php', 'PhabricatorSystemRemoveLogWorkflow' => 'applications/system/management/PhabricatorSystemRemoveLogWorkflow.php', 'PhabricatorSystemRemoveWorkflow' => 'applications/system/management/PhabricatorSystemRemoveWorkflow.php', 'PhabricatorSystemSelectEncodingController' => 'applications/system/controller/PhabricatorSystemSelectEncodingController.php', 'PhabricatorSystemSelectHighlightController' => 'applications/system/controller/PhabricatorSystemSelectHighlightController.php', 'PhabricatorTOTPAuthFactor' => 'applications/auth/factor/PhabricatorTOTPAuthFactor.php', 'PhabricatorTOTPAuthFactorTestCase' => 'applications/auth/factor/__tests__/PhabricatorTOTPAuthFactorTestCase.php', 'PhabricatorTaskmasterDaemon' => 'infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php', 'PhabricatorTestApplication' => 'applications/base/controller/__tests__/PhabricatorTestApplication.php', 'PhabricatorTestCase' => 'infrastructure/testing/PhabricatorTestCase.php', 'PhabricatorTestController' => 'applications/base/controller/__tests__/PhabricatorTestController.php', 'PhabricatorTestDataGenerator' => 'applications/lipsum/generator/PhabricatorTestDataGenerator.php', 'PhabricatorTestNoCycleEdgeType' => 'applications/transactions/edges/PhabricatorTestNoCycleEdgeType.php', 'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php', 'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php', 'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php', 'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php', 'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php', 'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php', 'PhabricatorTimeTestCase' => 'infrastructure/time/__tests__/PhabricatorTimeTestCase.php', 'PhabricatorTimezoneSetupCheck' => 'applications/config/check/PhabricatorTimezoneSetupCheck.php', 'PhabricatorToken' => 'applications/tokens/storage/PhabricatorToken.php', 'PhabricatorTokenController' => 'applications/tokens/controller/PhabricatorTokenController.php', 'PhabricatorTokenCount' => 'applications/tokens/storage/PhabricatorTokenCount.php', 'PhabricatorTokenCountQuery' => 'applications/tokens/query/PhabricatorTokenCountQuery.php', 'PhabricatorTokenDAO' => 'applications/tokens/storage/PhabricatorTokenDAO.php', 'PhabricatorTokenGiveController' => 'applications/tokens/controller/PhabricatorTokenGiveController.php', 'PhabricatorTokenGiven' => 'applications/tokens/storage/PhabricatorTokenGiven.php', 'PhabricatorTokenGivenController' => 'applications/tokens/controller/PhabricatorTokenGivenController.php', 'PhabricatorTokenGivenEditor' => 'applications/tokens/editor/PhabricatorTokenGivenEditor.php', 'PhabricatorTokenGivenFeedStory' => 'applications/tokens/feed/PhabricatorTokenGivenFeedStory.php', 'PhabricatorTokenGivenQuery' => 'applications/tokens/query/PhabricatorTokenGivenQuery.php', 'PhabricatorTokenLeaderController' => 'applications/tokens/controller/PhabricatorTokenLeaderController.php', 'PhabricatorTokenQuery' => 'applications/tokens/query/PhabricatorTokenQuery.php', 'PhabricatorTokenReceiverInterface' => 'applications/tokens/interface/PhabricatorTokenReceiverInterface.php', 'PhabricatorTokenReceiverQuery' => 'applications/tokens/query/PhabricatorTokenReceiverQuery.php', 'PhabricatorTokenTokenPHIDType' => 'applications/tokens/phid/PhabricatorTokenTokenPHIDType.php', 'PhabricatorTokenUIEventListener' => 'applications/tokens/event/PhabricatorTokenUIEventListener.php', 'PhabricatorTokenizerEditField' => 'applications/transactions/editfield/PhabricatorTokenizerEditField.php', 'PhabricatorTokensApplication' => 'applications/tokens/application/PhabricatorTokensApplication.php', 'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php', 'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php', 'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php', 'PhabricatorTransactionsApplication' => 'applications/transactions/application/PhabricatorTransactionsApplication.php', 'PhabricatorTransformedFile' => 'applications/files/storage/PhabricatorTransformedFile.php', 'PhabricatorTranslationsConfigOptions' => 'applications/config/option/PhabricatorTranslationsConfigOptions.php', 'PhabricatorTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorTriggerAction.php', 'PhabricatorTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorTriggerClock.php', 'PhabricatorTriggerClockTestCase' => 'infrastructure/daemon/workers/clock/__tests__/PhabricatorTriggerClockTestCase.php', 'PhabricatorTriggerDaemon' => 'infrastructure/daemon/workers/PhabricatorTriggerDaemon.php', 'PhabricatorTrivialTestCase' => 'infrastructure/testing/__tests__/PhabricatorTrivialTestCase.php', 'PhabricatorTwitchAuthProvider' => 'applications/auth/provider/PhabricatorTwitchAuthProvider.php', 'PhabricatorTwitterAuthProvider' => 'applications/auth/provider/PhabricatorTwitterAuthProvider.php', 'PhabricatorTypeaheadApplication' => 'applications/typeahead/application/PhabricatorTypeaheadApplication.php', 'PhabricatorTypeaheadCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php', 'PhabricatorTypeaheadDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php', 'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadDatasourceController.php', 'PhabricatorTypeaheadFunctionHelpController' => 'applications/typeahead/controller/PhabricatorTypeaheadFunctionHelpController.php', 'PhabricatorTypeaheadInvalidTokenException' => 'applications/typeahead/exception/PhabricatorTypeaheadInvalidTokenException.php', 'PhabricatorTypeaheadModularDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php', 'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php', 'PhabricatorTypeaheadResult' => 'applications/typeahead/storage/PhabricatorTypeaheadResult.php', 'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php', 'PhabricatorTypeaheadTokenView' => 'applications/typeahead/view/PhabricatorTypeaheadTokenView.php', 'PhabricatorUIConfigOptions' => 'applications/config/option/PhabricatorUIConfigOptions.php', 'PhabricatorUIExample' => 'applications/uiexample/examples/PhabricatorUIExample.php', 'PhabricatorUIExampleRenderController' => 'applications/uiexample/controller/PhabricatorUIExampleRenderController.php', 'PhabricatorUIExamplesApplication' => 'applications/uiexample/application/PhabricatorUIExamplesApplication.php', 'PhabricatorUSEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php', 'PhabricatorUnitsTestCase' => 'view/__tests__/PhabricatorUnitsTestCase.php', 'PhabricatorUnsubscribedFromObjectEdgeType' => 'applications/transactions/edges/PhabricatorUnsubscribedFromObjectEdgeType.php', 'PhabricatorUser' => 'applications/people/storage/PhabricatorUser.php', 'PhabricatorUserBlurbField' => 'applications/people/customfield/PhabricatorUserBlurbField.php', 'PhabricatorUserConfigOptions' => 'applications/people/config/PhabricatorUserConfigOptions.php', 'PhabricatorUserConfiguredCustomField' => 'applications/people/customfield/PhabricatorUserConfiguredCustomField.php', 'PhabricatorUserConfiguredCustomFieldStorage' => 'applications/people/storage/PhabricatorUserConfiguredCustomFieldStorage.php', 'PhabricatorUserCustomField' => 'applications/people/customfield/PhabricatorUserCustomField.php', 'PhabricatorUserCustomFieldNumericIndex' => 'applications/people/storage/PhabricatorUserCustomFieldNumericIndex.php', 'PhabricatorUserCustomFieldStringIndex' => 'applications/people/storage/PhabricatorUserCustomFieldStringIndex.php', 'PhabricatorUserDAO' => 'applications/people/storage/PhabricatorUserDAO.php', 'PhabricatorUserEditor' => 'applications/people/editor/PhabricatorUserEditor.php', 'PhabricatorUserEditorTestCase' => 'applications/people/editor/__tests__/PhabricatorUserEditorTestCase.php', 'PhabricatorUserEmail' => 'applications/people/storage/PhabricatorUserEmail.php', 'PhabricatorUserEmailTestCase' => 'applications/people/storage/__tests__/PhabricatorUserEmailTestCase.php', 'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php', 'PhabricatorUserLogView' => 'applications/people/view/PhabricatorUserLogView.php', 'PhabricatorUserPHIDResolver' => 'applications/phid/resolver/PhabricatorUserPHIDResolver.php', 'PhabricatorUserPreferences' => 'applications/settings/storage/PhabricatorUserPreferences.php', 'PhabricatorUserProfile' => 'applications/people/storage/PhabricatorUserProfile.php', 'PhabricatorUserProfileEditor' => 'applications/people/editor/PhabricatorUserProfileEditor.php', 'PhabricatorUserRealNameField' => 'applications/people/customfield/PhabricatorUserRealNameField.php', 'PhabricatorUserRolesField' => 'applications/people/customfield/PhabricatorUserRolesField.php', 'PhabricatorUserSchemaSpec' => 'applications/people/storage/PhabricatorUserSchemaSpec.php', 'PhabricatorUserSearchIndexer' => 'applications/people/search/PhabricatorUserSearchIndexer.php', 'PhabricatorUserSinceField' => 'applications/people/customfield/PhabricatorUserSinceField.php', 'PhabricatorUserStatusField' => 'applications/people/customfield/PhabricatorUserStatusField.php', 'PhabricatorUserTestCase' => 'applications/people/storage/__tests__/PhabricatorUserTestCase.php', 'PhabricatorUserTitleField' => 'applications/people/customfield/PhabricatorUserTitleField.php', 'PhabricatorUserTransaction' => 'applications/people/storage/PhabricatorUserTransaction.php', 'PhabricatorUsersEditField' => 'applications/transactions/editfield/PhabricatorUsersEditField.php', 'PhabricatorUsersPolicyRule' => 'applications/people/policyrule/PhabricatorUsersPolicyRule.php', 'PhabricatorUsersSearchField' => 'applications/people/searchfield/PhabricatorUsersSearchField.php', 'PhabricatorVCSResponse' => 'applications/repository/response/PhabricatorVCSResponse.php', + 'PhabricatorVersionedDraft' => 'applications/draft/storage/PhabricatorVersionedDraft.php', 'PhabricatorVeryWowEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorVeryWowEnglishTranslation.php', 'PhabricatorViewerDatasource' => 'applications/people/typeahead/PhabricatorViewerDatasource.php', 'PhabricatorWatcherHasObjectEdgeType' => 'applications/transactions/edges/PhabricatorWatcherHasObjectEdgeType.php', 'PhabricatorWordPressAuthProvider' => 'applications/auth/provider/PhabricatorWordPressAuthProvider.php', 'PhabricatorWorker' => 'infrastructure/daemon/workers/PhabricatorWorker.php', 'PhabricatorWorkerActiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php', 'PhabricatorWorkerArchiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php', 'PhabricatorWorkerArchiveTaskQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerArchiveTaskQuery.php', 'PhabricatorWorkerBulkJob' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php', 'PhabricatorWorkerBulkJobCreateWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobCreateWorker.php', 'PhabricatorWorkerBulkJobEditor' => 'infrastructure/daemon/workers/editor/PhabricatorWorkerBulkJobEditor.php', 'PhabricatorWorkerBulkJobPHIDType' => 'infrastructure/daemon/workers/phid/PhabricatorWorkerBulkJobPHIDType.php', 'PhabricatorWorkerBulkJobQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobQuery.php', 'PhabricatorWorkerBulkJobSearchEngine' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobSearchEngine.php', 'PhabricatorWorkerBulkJobTaskWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobTaskWorker.php', 'PhabricatorWorkerBulkJobTestCase' => 'infrastructure/daemon/workers/__tests__/PhabricatorWorkerBulkJobTestCase.php', 'PhabricatorWorkerBulkJobTransaction' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJobTransaction.php', 'PhabricatorWorkerBulkJobTransactionQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerBulkJobTransactionQuery.php', 'PhabricatorWorkerBulkJobType' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobType.php', 'PhabricatorWorkerBulkJobWorker' => 'infrastructure/daemon/workers/bulk/PhabricatorWorkerBulkJobWorker.php', 'PhabricatorWorkerBulkTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerBulkTask.php', 'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerDAO.php', 'PhabricatorWorkerLeaseQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerLeaseQuery.php', 'PhabricatorWorkerManagementCancelWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementCancelWorkflow.php', 'PhabricatorWorkerManagementExecuteWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementExecuteWorkflow.php', 'PhabricatorWorkerManagementFloodWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementFloodWorkflow.php', 'PhabricatorWorkerManagementFreeWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementFreeWorkflow.php', 'PhabricatorWorkerManagementRetryWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementRetryWorkflow.php', 'PhabricatorWorkerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerManagementWorkflow.php', 'PhabricatorWorkerPermanentFailureException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerPermanentFailureException.php', 'PhabricatorWorkerSchemaSpec' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerSchemaSpec.php', 'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTask.php', 'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTaskData.php', 'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/PhabricatorWorkerTaskDetailController.php', 'PhabricatorWorkerTestCase' => 'infrastructure/daemon/workers/__tests__/PhabricatorWorkerTestCase.php', 'PhabricatorWorkerTrigger' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTrigger.php', 'PhabricatorWorkerTriggerEvent' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerTriggerEvent.php', 'PhabricatorWorkerTriggerManagementFireWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php', 'PhabricatorWorkerTriggerManagementWorkflow' => 'infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementWorkflow.php', 'PhabricatorWorkerTriggerPHIDType' => 'infrastructure/daemon/workers/phid/PhabricatorWorkerTriggerPHIDType.php', 'PhabricatorWorkerTriggerQuery' => 'infrastructure/daemon/workers/query/PhabricatorWorkerTriggerQuery.php', 'PhabricatorWorkerYieldException' => 'infrastructure/daemon/workers/exception/PhabricatorWorkerYieldException.php', 'PhabricatorWorkingCopyDiscoveryTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php', 'PhabricatorWorkingCopyPullTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyPullTestCase.php', 'PhabricatorWorkingCopyTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php', 'PhabricatorXHPASTDAO' => 'applications/phpast/storage/PhabricatorXHPASTDAO.php', 'PhabricatorXHPASTParseTree' => 'applications/phpast/storage/PhabricatorXHPASTParseTree.php', 'PhabricatorXHPASTViewController' => 'applications/phpast/controller/PhabricatorXHPASTViewController.php', 'PhabricatorXHPASTViewFrameController' => 'applications/phpast/controller/PhabricatorXHPASTViewFrameController.php', 'PhabricatorXHPASTViewFramesetController' => 'applications/phpast/controller/PhabricatorXHPASTViewFramesetController.php', 'PhabricatorXHPASTViewInputController' => 'applications/phpast/controller/PhabricatorXHPASTViewInputController.php', 'PhabricatorXHPASTViewPanelController' => 'applications/phpast/controller/PhabricatorXHPASTViewPanelController.php', 'PhabricatorXHPASTViewRunController' => 'applications/phpast/controller/PhabricatorXHPASTViewRunController.php', 'PhabricatorXHPASTViewStreamController' => 'applications/phpast/controller/PhabricatorXHPASTViewStreamController.php', 'PhabricatorXHPASTViewTreeController' => 'applications/phpast/controller/PhabricatorXHPASTViewTreeController.php', 'PhabricatorXHProfApplication' => 'applications/xhprof/application/PhabricatorXHProfApplication.php', 'PhabricatorXHProfController' => 'applications/xhprof/controller/PhabricatorXHProfController.php', 'PhabricatorXHProfDAO' => 'applications/xhprof/storage/PhabricatorXHProfDAO.php', 'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/PhabricatorXHProfProfileController.php', 'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/PhabricatorXHProfProfileSymbolView.php', 'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/PhabricatorXHProfProfileTopLevelView.php', 'PhabricatorXHProfProfileView' => 'applications/xhprof/view/PhabricatorXHProfProfileView.php', 'PhabricatorXHProfSample' => 'applications/xhprof/storage/PhabricatorXHProfSample.php', 'PhabricatorXHProfSampleListController' => 'applications/xhprof/controller/PhabricatorXHProfSampleListController.php', 'PhabricatorYoutubeRemarkupRule' => 'infrastructure/markup/rule/PhabricatorYoutubeRemarkupRule.php', 'PhameBasicBlogSkin' => 'applications/phame/skins/PhameBasicBlogSkin.php', 'PhameBasicTemplateBlogSkin' => 'applications/phame/skins/PhameBasicTemplateBlogSkin.php', 'PhameBlog' => 'applications/phame/storage/PhameBlog.php', 'PhameBlogArchiveController' => 'applications/phame/controller/blog/PhameBlogArchiveController.php', 'PhameBlogController' => 'applications/phame/controller/blog/PhameBlogController.php', 'PhameBlogCreateCapability' => 'applications/phame/capability/PhameBlogCreateCapability.php', 'PhameBlogEditController' => 'applications/phame/controller/blog/PhameBlogEditController.php', 'PhameBlogEditor' => 'applications/phame/editor/PhameBlogEditor.php', 'PhameBlogFeedController' => 'applications/phame/controller/blog/PhameBlogFeedController.php', 'PhameBlogListController' => 'applications/phame/controller/blog/PhameBlogListController.php', 'PhameBlogLiveController' => 'applications/phame/controller/blog/PhameBlogLiveController.php', + 'PhameBlogManageController' => 'applications/phame/controller/blog/PhameBlogManageController.php', + 'PhameBlogProfilePictureController' => 'applications/phame/controller/blog/PhameBlogProfilePictureController.php', 'PhameBlogQuery' => 'applications/phame/query/PhameBlogQuery.php', 'PhameBlogReplyHandler' => 'applications/phame/mail/PhameBlogReplyHandler.php', 'PhameBlogSearchEngine' => 'applications/phame/query/PhameBlogSearchEngine.php', 'PhameBlogSite' => 'applications/phame/site/PhameBlogSite.php', 'PhameBlogSkin' => 'applications/phame/skins/PhameBlogSkin.php', 'PhameBlogTransaction' => 'applications/phame/storage/PhameBlogTransaction.php', 'PhameBlogTransactionQuery' => 'applications/phame/query/PhameBlogTransactionQuery.php', 'PhameBlogViewController' => 'applications/phame/controller/blog/PhameBlogViewController.php', 'PhameCelerityResources' => 'applications/phame/celerity/PhameCelerityResources.php', 'PhameConduitAPIMethod' => 'applications/phame/conduit/PhameConduitAPIMethod.php', 'PhameConstants' => 'applications/phame/constants/PhameConstants.php', 'PhameController' => 'applications/phame/controller/PhameController.php', 'PhameCreatePostConduitAPIMethod' => 'applications/phame/conduit/PhameCreatePostConduitAPIMethod.php', 'PhameDAO' => 'applications/phame/storage/PhameDAO.php', + 'PhameDescriptionView' => 'applications/phame/view/PhameDescriptionView.php', + 'PhameHomeController' => 'applications/phame/controller/PhameHomeController.php', 'PhamePost' => 'applications/phame/storage/PhamePost.php', 'PhamePostCommentController' => 'applications/phame/controller/post/PhamePostCommentController.php', 'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php', 'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php', 'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php', 'PhamePostFramedController' => 'applications/phame/controller/post/PhamePostFramedController.php', + 'PhamePostHistoryController' => 'applications/phame/controller/post/PhamePostHistoryController.php', 'PhamePostListController' => 'applications/phame/controller/post/PhamePostListController.php', + 'PhamePostListView' => 'applications/phame/view/PhamePostListView.php', 'PhamePostMailReceiver' => 'applications/phame/mail/PhamePostMailReceiver.php', 'PhamePostNewController' => 'applications/phame/controller/post/PhamePostNewController.php', 'PhamePostNotLiveController' => 'applications/phame/controller/post/PhamePostNotLiveController.php', + 'PhamePostPreviewController' => 'applications/phame/controller/post/PhamePostPreviewController.php', 'PhamePostPublishController' => 'applications/phame/controller/post/PhamePostPublishController.php', 'PhamePostQuery' => 'applications/phame/query/PhamePostQuery.php', 'PhamePostReplyHandler' => 'applications/phame/mail/PhamePostReplyHandler.php', 'PhamePostSearchEngine' => 'applications/phame/query/PhamePostSearchEngine.php', 'PhamePostTransaction' => 'applications/phame/storage/PhamePostTransaction.php', 'PhamePostTransactionComment' => 'applications/phame/storage/PhamePostTransactionComment.php', 'PhamePostTransactionQuery' => 'applications/phame/query/PhamePostTransactionQuery.php', 'PhamePostUnpublishController' => 'applications/phame/controller/post/PhamePostUnpublishController.php', 'PhamePostView' => 'applications/phame/view/PhamePostView.php', 'PhamePostViewController' => 'applications/phame/controller/post/PhamePostViewController.php', 'PhameQueryConduitAPIMethod' => 'applications/phame/conduit/PhameQueryConduitAPIMethod.php', 'PhameQueryPostsConduitAPIMethod' => 'applications/phame/conduit/PhameQueryPostsConduitAPIMethod.php', 'PhameResourceController' => 'applications/phame/controller/PhameResourceController.php', 'PhameSchemaSpec' => 'applications/phame/storage/PhameSchemaSpec.php', 'PhameSite' => 'applications/phame/site/PhameSite.php', 'PhameSkinSpecification' => 'applications/phame/skins/PhameSkinSpecification.php', 'PhluxController' => 'applications/phlux/controller/PhluxController.php', 'PhluxDAO' => 'applications/phlux/storage/PhluxDAO.php', 'PhluxEditController' => 'applications/phlux/controller/PhluxEditController.php', 'PhluxListController' => 'applications/phlux/controller/PhluxListController.php', 'PhluxTransaction' => 'applications/phlux/storage/PhluxTransaction.php', 'PhluxTransactionQuery' => 'applications/phlux/query/PhluxTransactionQuery.php', 'PhluxVariable' => 'applications/phlux/storage/PhluxVariable.php', 'PhluxVariableEditor' => 'applications/phlux/editor/PhluxVariableEditor.php', 'PhluxVariablePHIDType' => 'applications/phlux/phid/PhluxVariablePHIDType.php', 'PhluxVariableQuery' => 'applications/phlux/query/PhluxVariableQuery.php', 'PhluxViewController' => 'applications/phlux/controller/PhluxViewController.php', 'PholioActionMenuEventListener' => 'applications/pholio/event/PholioActionMenuEventListener.php', 'PholioController' => 'applications/pholio/controller/PholioController.php', 'PholioDAO' => 'applications/pholio/storage/PholioDAO.php', 'PholioDefaultEditCapability' => 'applications/pholio/capability/PholioDefaultEditCapability.php', 'PholioDefaultViewCapability' => 'applications/pholio/capability/PholioDefaultViewCapability.php', 'PholioImage' => 'applications/pholio/storage/PholioImage.php', 'PholioImagePHIDType' => 'applications/pholio/phid/PholioImagePHIDType.php', 'PholioImageQuery' => 'applications/pholio/query/PholioImageQuery.php', 'PholioImageUploadController' => 'applications/pholio/controller/PholioImageUploadController.php', 'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php', 'PholioInlineListController' => 'applications/pholio/controller/PholioInlineListController.php', 'PholioMock' => 'applications/pholio/storage/PholioMock.php', 'PholioMockAuthorHeraldField' => 'applications/pholio/herald/PholioMockAuthorHeraldField.php', 'PholioMockCommentController' => 'applications/pholio/controller/PholioMockCommentController.php', 'PholioMockDescriptionHeraldField' => 'applications/pholio/herald/PholioMockDescriptionHeraldField.php', 'PholioMockEditController' => 'applications/pholio/controller/PholioMockEditController.php', 'PholioMockEditor' => 'applications/pholio/editor/PholioMockEditor.php', 'PholioMockEmbedView' => 'applications/pholio/view/PholioMockEmbedView.php', 'PholioMockHasTaskEdgeType' => 'applications/pholio/edge/PholioMockHasTaskEdgeType.php', 'PholioMockHeraldField' => 'applications/pholio/herald/PholioMockHeraldField.php', 'PholioMockHeraldFieldGroup' => 'applications/pholio/herald/PholioMockHeraldFieldGroup.php', 'PholioMockImagesView' => 'applications/pholio/view/PholioMockImagesView.php', 'PholioMockListController' => 'applications/pholio/controller/PholioMockListController.php', 'PholioMockMailReceiver' => 'applications/pholio/mail/PholioMockMailReceiver.php', 'PholioMockNameHeraldField' => 'applications/pholio/herald/PholioMockNameHeraldField.php', 'PholioMockPHIDType' => 'applications/pholio/phid/PholioMockPHIDType.php', 'PholioMockQuery' => 'applications/pholio/query/PholioMockQuery.php', 'PholioMockSearchEngine' => 'applications/pholio/query/PholioMockSearchEngine.php', 'PholioMockThumbGridView' => 'applications/pholio/view/PholioMockThumbGridView.php', 'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php', 'PholioRemarkupRule' => 'applications/pholio/remarkup/PholioRemarkupRule.php', 'PholioReplyHandler' => 'applications/pholio/mail/PholioReplyHandler.php', 'PholioSchemaSpec' => 'applications/pholio/storage/PholioSchemaSpec.php', 'PholioSearchIndexer' => 'applications/pholio/search/PholioSearchIndexer.php', 'PholioTransaction' => 'applications/pholio/storage/PholioTransaction.php', 'PholioTransactionComment' => 'applications/pholio/storage/PholioTransactionComment.php', 'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php', 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', 'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php', 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', 'PhortuneAccountEditController' => 'applications/phortune/controller/PhortuneAccountEditController.php', 'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php', 'PhortuneAccountHasMemberEdgeType' => 'applications/phortune/edge/PhortuneAccountHasMemberEdgeType.php', 'PhortuneAccountListController' => 'applications/phortune/controller/PhortuneAccountListController.php', 'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php', 'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php', 'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php', 'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php', 'PhortuneAccountViewController' => 'applications/phortune/controller/PhortuneAccountViewController.php', 'PhortuneAdHocCart' => 'applications/phortune/cart/PhortuneAdHocCart.php', 'PhortuneAdHocProduct' => 'applications/phortune/product/PhortuneAdHocProduct.php', 'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php', 'PhortuneCartAcceptController' => 'applications/phortune/controller/PhortuneCartAcceptController.php', 'PhortuneCartCancelController' => 'applications/phortune/controller/PhortuneCartCancelController.php', 'PhortuneCartCheckoutController' => 'applications/phortune/controller/PhortuneCartCheckoutController.php', 'PhortuneCartController' => 'applications/phortune/controller/PhortuneCartController.php', 'PhortuneCartEditor' => 'applications/phortune/editor/PhortuneCartEditor.php', 'PhortuneCartImplementation' => 'applications/phortune/cart/PhortuneCartImplementation.php', 'PhortuneCartListController' => 'applications/phortune/controller/PhortuneCartListController.php', 'PhortuneCartPHIDType' => 'applications/phortune/phid/PhortuneCartPHIDType.php', 'PhortuneCartQuery' => 'applications/phortune/query/PhortuneCartQuery.php', 'PhortuneCartReplyHandler' => 'applications/phortune/mail/PhortuneCartReplyHandler.php', 'PhortuneCartSearchEngine' => 'applications/phortune/query/PhortuneCartSearchEngine.php', 'PhortuneCartTransaction' => 'applications/phortune/storage/PhortuneCartTransaction.php', 'PhortuneCartTransactionQuery' => 'applications/phortune/query/PhortuneCartTransactionQuery.php', 'PhortuneCartUpdateController' => 'applications/phortune/controller/PhortuneCartUpdateController.php', 'PhortuneCartViewController' => 'applications/phortune/controller/PhortuneCartViewController.php', 'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php', 'PhortuneChargeListController' => 'applications/phortune/controller/PhortuneChargeListController.php', 'PhortuneChargePHIDType' => 'applications/phortune/phid/PhortuneChargePHIDType.php', 'PhortuneChargeQuery' => 'applications/phortune/query/PhortuneChargeQuery.php', 'PhortuneChargeSearchEngine' => 'applications/phortune/query/PhortuneChargeSearchEngine.php', 'PhortuneChargeTableView' => 'applications/phortune/view/PhortuneChargeTableView.php', 'PhortuneConstants' => 'applications/phortune/constants/PhortuneConstants.php', 'PhortuneController' => 'applications/phortune/controller/PhortuneController.php', 'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php', 'PhortuneCurrency' => 'applications/phortune/currency/PhortuneCurrency.php', 'PhortuneCurrencySerializer' => 'applications/phortune/currency/PhortuneCurrencySerializer.php', 'PhortuneCurrencyTestCase' => 'applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php', 'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php', 'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php', 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', 'PhortuneMemberHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMemberHasAccountEdgeType.php', 'PhortuneMemberHasMerchantEdgeType' => 'applications/phortune/edge/PhortuneMemberHasMerchantEdgeType.php', 'PhortuneMerchant' => 'applications/phortune/storage/PhortuneMerchant.php', 'PhortuneMerchantCapability' => 'applications/phortune/capability/PhortuneMerchantCapability.php', 'PhortuneMerchantController' => 'applications/phortune/controller/PhortuneMerchantController.php', 'PhortuneMerchantEditController' => 'applications/phortune/controller/PhortuneMerchantEditController.php', 'PhortuneMerchantEditor' => 'applications/phortune/editor/PhortuneMerchantEditor.php', 'PhortuneMerchantHasMemberEdgeType' => 'applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php', 'PhortuneMerchantInvoiceCreateController' => 'applications/phortune/controller/PhortuneMerchantInvoiceCreateController.php', 'PhortuneMerchantListController' => 'applications/phortune/controller/PhortuneMerchantListController.php', 'PhortuneMerchantPHIDType' => 'applications/phortune/phid/PhortuneMerchantPHIDType.php', 'PhortuneMerchantQuery' => 'applications/phortune/query/PhortuneMerchantQuery.php', 'PhortuneMerchantSearchEngine' => 'applications/phortune/query/PhortuneMerchantSearchEngine.php', 'PhortuneMerchantTransaction' => 'applications/phortune/storage/PhortuneMerchantTransaction.php', 'PhortuneMerchantTransactionQuery' => 'applications/phortune/query/PhortuneMerchantTransactionQuery.php', 'PhortuneMerchantViewController' => 'applications/phortune/controller/PhortuneMerchantViewController.php', 'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php', 'PhortuneOrderTableView' => 'applications/phortune/view/PhortuneOrderTableView.php', 'PhortunePayPalPaymentProvider' => 'applications/phortune/provider/PhortunePayPalPaymentProvider.php', 'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php', 'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/PhortunePaymentMethodCreateController.php', 'PhortunePaymentMethodDisableController' => 'applications/phortune/controller/PhortunePaymentMethodDisableController.php', 'PhortunePaymentMethodEditController' => 'applications/phortune/controller/PhortunePaymentMethodEditController.php', 'PhortunePaymentMethodPHIDType' => 'applications/phortune/phid/PhortunePaymentMethodPHIDType.php', 'PhortunePaymentMethodQuery' => 'applications/phortune/query/PhortunePaymentMethodQuery.php', 'PhortunePaymentProvider' => 'applications/phortune/provider/PhortunePaymentProvider.php', 'PhortunePaymentProviderConfig' => 'applications/phortune/storage/PhortunePaymentProviderConfig.php', 'PhortunePaymentProviderConfigEditor' => 'applications/phortune/editor/PhortunePaymentProviderConfigEditor.php', 'PhortunePaymentProviderConfigQuery' => 'applications/phortune/query/PhortunePaymentProviderConfigQuery.php', 'PhortunePaymentProviderConfigTransaction' => 'applications/phortune/storage/PhortunePaymentProviderConfigTransaction.php', 'PhortunePaymentProviderConfigTransactionQuery' => 'applications/phortune/query/PhortunePaymentProviderConfigTransactionQuery.php', 'PhortunePaymentProviderPHIDType' => 'applications/phortune/phid/PhortunePaymentProviderPHIDType.php', 'PhortunePaymentProviderTestCase' => 'applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php', 'PhortuneProduct' => 'applications/phortune/storage/PhortuneProduct.php', 'PhortuneProductImplementation' => 'applications/phortune/product/PhortuneProductImplementation.php', 'PhortuneProductListController' => 'applications/phortune/controller/PhortuneProductListController.php', 'PhortuneProductPHIDType' => 'applications/phortune/phid/PhortuneProductPHIDType.php', 'PhortuneProductQuery' => 'applications/phortune/query/PhortuneProductQuery.php', 'PhortuneProductViewController' => 'applications/phortune/controller/PhortuneProductViewController.php', 'PhortuneProviderActionController' => 'applications/phortune/controller/PhortuneProviderActionController.php', 'PhortuneProviderDisableController' => 'applications/phortune/controller/PhortuneProviderDisableController.php', 'PhortuneProviderEditController' => 'applications/phortune/controller/PhortuneProviderEditController.php', 'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php', 'PhortunePurchasePHIDType' => 'applications/phortune/phid/PhortunePurchasePHIDType.php', 'PhortunePurchaseQuery' => 'applications/phortune/query/PhortunePurchaseQuery.php', 'PhortuneSchemaSpec' => 'applications/phortune/storage/PhortuneSchemaSpec.php', 'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php', 'PhortuneSubscription' => 'applications/phortune/storage/PhortuneSubscription.php', 'PhortuneSubscriptionCart' => 'applications/phortune/cart/PhortuneSubscriptionCart.php', 'PhortuneSubscriptionEditController' => 'applications/phortune/controller/PhortuneSubscriptionEditController.php', 'PhortuneSubscriptionImplementation' => 'applications/phortune/subscription/PhortuneSubscriptionImplementation.php', 'PhortuneSubscriptionListController' => 'applications/phortune/controller/PhortuneSubscriptionListController.php', 'PhortuneSubscriptionPHIDType' => 'applications/phortune/phid/PhortuneSubscriptionPHIDType.php', 'PhortuneSubscriptionProduct' => 'applications/phortune/product/PhortuneSubscriptionProduct.php', 'PhortuneSubscriptionQuery' => 'applications/phortune/query/PhortuneSubscriptionQuery.php', 'PhortuneSubscriptionSearchEngine' => 'applications/phortune/query/PhortuneSubscriptionSearchEngine.php', 'PhortuneSubscriptionTableView' => 'applications/phortune/view/PhortuneSubscriptionTableView.php', 'PhortuneSubscriptionViewController' => 'applications/phortune/controller/PhortuneSubscriptionViewController.php', 'PhortuneSubscriptionWorker' => 'applications/phortune/worker/PhortuneSubscriptionWorker.php', 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', 'PhortuneWePayPaymentProvider' => 'applications/phortune/provider/PhortuneWePayPaymentProvider.php', 'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php', 'PhragmentCanCreateCapability' => 'applications/phragment/capability/PhragmentCanCreateCapability.php', 'PhragmentConduitAPIMethod' => 'applications/phragment/conduit/PhragmentConduitAPIMethod.php', 'PhragmentController' => 'applications/phragment/controller/PhragmentController.php', 'PhragmentCreateController' => 'applications/phragment/controller/PhragmentCreateController.php', 'PhragmentDAO' => 'applications/phragment/storage/PhragmentDAO.php', 'PhragmentFragment' => 'applications/phragment/storage/PhragmentFragment.php', 'PhragmentFragmentPHIDType' => 'applications/phragment/phid/PhragmentFragmentPHIDType.php', 'PhragmentFragmentQuery' => 'applications/phragment/query/PhragmentFragmentQuery.php', 'PhragmentFragmentVersion' => 'applications/phragment/storage/PhragmentFragmentVersion.php', 'PhragmentFragmentVersionPHIDType' => 'applications/phragment/phid/PhragmentFragmentVersionPHIDType.php', 'PhragmentFragmentVersionQuery' => 'applications/phragment/query/PhragmentFragmentVersionQuery.php', 'PhragmentGetPatchConduitAPIMethod' => 'applications/phragment/conduit/PhragmentGetPatchConduitAPIMethod.php', 'PhragmentHistoryController' => 'applications/phragment/controller/PhragmentHistoryController.php', 'PhragmentPatchController' => 'applications/phragment/controller/PhragmentPatchController.php', 'PhragmentPatchUtil' => 'applications/phragment/util/PhragmentPatchUtil.php', 'PhragmentPolicyController' => 'applications/phragment/controller/PhragmentPolicyController.php', 'PhragmentQueryFragmentsConduitAPIMethod' => 'applications/phragment/conduit/PhragmentQueryFragmentsConduitAPIMethod.php', 'PhragmentRevertController' => 'applications/phragment/controller/PhragmentRevertController.php', 'PhragmentSchemaSpec' => 'applications/phragment/storage/PhragmentSchemaSpec.php', 'PhragmentSnapshot' => 'applications/phragment/storage/PhragmentSnapshot.php', 'PhragmentSnapshotChild' => 'applications/phragment/storage/PhragmentSnapshotChild.php', 'PhragmentSnapshotChildQuery' => 'applications/phragment/query/PhragmentSnapshotChildQuery.php', 'PhragmentSnapshotCreateController' => 'applications/phragment/controller/PhragmentSnapshotCreateController.php', 'PhragmentSnapshotDeleteController' => 'applications/phragment/controller/PhragmentSnapshotDeleteController.php', 'PhragmentSnapshotPHIDType' => 'applications/phragment/phid/PhragmentSnapshotPHIDType.php', 'PhragmentSnapshotPromoteController' => 'applications/phragment/controller/PhragmentSnapshotPromoteController.php', 'PhragmentSnapshotQuery' => 'applications/phragment/query/PhragmentSnapshotQuery.php', 'PhragmentSnapshotViewController' => 'applications/phragment/controller/PhragmentSnapshotViewController.php', 'PhragmentUpdateController' => 'applications/phragment/controller/PhragmentUpdateController.php', 'PhragmentVersionController' => 'applications/phragment/controller/PhragmentVersionController.php', 'PhragmentZIPController' => 'applications/phragment/controller/PhragmentZIPController.php', 'PhrequentConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentConduitAPIMethod.php', 'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php', 'PhrequentDAO' => 'applications/phrequent/storage/PhrequentDAO.php', 'PhrequentListController' => 'applications/phrequent/controller/PhrequentListController.php', 'PhrequentPopConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentPopConduitAPIMethod.php', 'PhrequentPushConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentPushConduitAPIMethod.php', 'PhrequentSearchEngine' => 'applications/phrequent/query/PhrequentSearchEngine.php', 'PhrequentTimeBlock' => 'applications/phrequent/storage/PhrequentTimeBlock.php', 'PhrequentTimeBlockTestCase' => 'applications/phrequent/storage/__tests__/PhrequentTimeBlockTestCase.php', 'PhrequentTimeSlices' => 'applications/phrequent/storage/PhrequentTimeSlices.php', 'PhrequentTrackController' => 'applications/phrequent/controller/PhrequentTrackController.php', 'PhrequentTrackableInterface' => 'applications/phrequent/interface/PhrequentTrackableInterface.php', 'PhrequentTrackingConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentTrackingConduitAPIMethod.php', 'PhrequentTrackingEditor' => 'applications/phrequent/editor/PhrequentTrackingEditor.php', 'PhrequentUIEventListener' => 'applications/phrequent/event/PhrequentUIEventListener.php', 'PhrequentUserTime' => 'applications/phrequent/storage/PhrequentUserTime.php', 'PhrequentUserTimeQuery' => 'applications/phrequent/query/PhrequentUserTimeQuery.php', 'PhrictionChangeType' => 'applications/phriction/constants/PhrictionChangeType.php', 'PhrictionConduitAPIMethod' => 'applications/phriction/conduit/PhrictionConduitAPIMethod.php', 'PhrictionConstants' => 'applications/phriction/constants/PhrictionConstants.php', 'PhrictionContent' => 'applications/phriction/storage/PhrictionContent.php', 'PhrictionController' => 'applications/phriction/controller/PhrictionController.php', 'PhrictionCreateConduitAPIMethod' => 'applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php', 'PhrictionDAO' => 'applications/phriction/storage/PhrictionDAO.php', 'PhrictionDeleteController' => 'applications/phriction/controller/PhrictionDeleteController.php', 'PhrictionDiffController' => 'applications/phriction/controller/PhrictionDiffController.php', 'PhrictionDocument' => 'applications/phriction/storage/PhrictionDocument.php', 'PhrictionDocumentAuthorHeraldField' => 'applications/phriction/herald/PhrictionDocumentAuthorHeraldField.php', 'PhrictionDocumentContentHeraldField' => 'applications/phriction/herald/PhrictionDocumentContentHeraldField.php', 'PhrictionDocumentController' => 'applications/phriction/controller/PhrictionDocumentController.php', 'PhrictionDocumentHeraldAdapter' => 'applications/phriction/herald/PhrictionDocumentHeraldAdapter.php', 'PhrictionDocumentHeraldField' => 'applications/phriction/herald/PhrictionDocumentHeraldField.php', 'PhrictionDocumentHeraldFieldGroup' => 'applications/phriction/herald/PhrictionDocumentHeraldFieldGroup.php', 'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php', 'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php', 'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php', 'PhrictionDocumentStatus' => 'applications/phriction/constants/PhrictionDocumentStatus.php', 'PhrictionDocumentTitleHeraldField' => 'applications/phriction/herald/PhrictionDocumentTitleHeraldField.php', 'PhrictionEditConduitAPIMethod' => 'applications/phriction/conduit/PhrictionEditConduitAPIMethod.php', 'PhrictionEditController' => 'applications/phriction/controller/PhrictionEditController.php', 'PhrictionHistoryConduitAPIMethod' => 'applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php', 'PhrictionHistoryController' => 'applications/phriction/controller/PhrictionHistoryController.php', 'PhrictionInfoConduitAPIMethod' => 'applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php', 'PhrictionListController' => 'applications/phriction/controller/PhrictionListController.php', 'PhrictionMoveController' => 'applications/phriction/controller/PhrictionMoveController.php', 'PhrictionNewController' => 'applications/phriction/controller/PhrictionNewController.php', 'PhrictionRemarkupRule' => 'applications/phriction/markup/PhrictionRemarkupRule.php', 'PhrictionReplyHandler' => 'applications/phriction/mail/PhrictionReplyHandler.php', 'PhrictionSchemaSpec' => 'applications/phriction/storage/PhrictionSchemaSpec.php', 'PhrictionSearchEngine' => 'applications/phriction/query/PhrictionSearchEngine.php', 'PhrictionSearchIndexer' => 'applications/phriction/search/PhrictionSearchIndexer.php', 'PhrictionTransaction' => 'applications/phriction/storage/PhrictionTransaction.php', 'PhrictionTransactionComment' => 'applications/phriction/storage/PhrictionTransactionComment.php', 'PhrictionTransactionEditor' => 'applications/phriction/editor/PhrictionTransactionEditor.php', 'PhrictionTransactionQuery' => 'applications/phriction/query/PhrictionTransactionQuery.php', 'PolicyLockOptionType' => 'applications/policy/config/PolicyLockOptionType.php', 'PonderAddAnswerView' => 'applications/ponder/view/PonderAddAnswerView.php', 'PonderAnswer' => 'applications/ponder/storage/PonderAnswer.php', 'PonderAnswerCommentController' => 'applications/ponder/controller/PonderAnswerCommentController.php', 'PonderAnswerEditController' => 'applications/ponder/controller/PonderAnswerEditController.php', 'PonderAnswerEditor' => 'applications/ponder/editor/PonderAnswerEditor.php', 'PonderAnswerHasVotingUserEdgeType' => 'applications/ponder/edge/PonderAnswerHasVotingUserEdgeType.php', 'PonderAnswerHistoryController' => 'applications/ponder/controller/PonderAnswerHistoryController.php', 'PonderAnswerMailReceiver' => 'applications/ponder/mail/PonderAnswerMailReceiver.php', 'PonderAnswerPHIDType' => 'applications/ponder/phid/PonderAnswerPHIDType.php', 'PonderAnswerQuery' => 'applications/ponder/query/PonderAnswerQuery.php', 'PonderAnswerReplyHandler' => 'applications/ponder/mail/PonderAnswerReplyHandler.php', 'PonderAnswerSaveController' => 'applications/ponder/controller/PonderAnswerSaveController.php', 'PonderAnswerStatus' => 'applications/ponder/constants/PonderAnswerStatus.php', 'PonderAnswerTransaction' => 'applications/ponder/storage/PonderAnswerTransaction.php', 'PonderAnswerTransactionComment' => 'applications/ponder/storage/PonderAnswerTransactionComment.php', 'PonderAnswerTransactionQuery' => 'applications/ponder/query/PonderAnswerTransactionQuery.php', 'PonderAnswerView' => 'applications/ponder/view/PonderAnswerView.php', 'PonderConstants' => 'applications/ponder/constants/PonderConstants.php', 'PonderController' => 'applications/ponder/controller/PonderController.php', 'PonderDAO' => 'applications/ponder/storage/PonderDAO.php', 'PonderDefaultViewCapability' => 'applications/ponder/capability/PonderDefaultViewCapability.php', 'PonderEditor' => 'applications/ponder/editor/PonderEditor.php', 'PonderFooterView' => 'applications/ponder/view/PonderFooterView.php', 'PonderHelpfulSaveController' => 'applications/ponder/controller/PonderHelpfulSaveController.php', 'PonderModerateCapability' => 'applications/ponder/capability/PonderModerateCapability.php', 'PonderQuestion' => 'applications/ponder/storage/PonderQuestion.php', 'PonderQuestionCommentController' => 'applications/ponder/controller/PonderQuestionCommentController.php', 'PonderQuestionEditController' => 'applications/ponder/controller/PonderQuestionEditController.php', 'PonderQuestionEditor' => 'applications/ponder/editor/PonderQuestionEditor.php', 'PonderQuestionHistoryController' => 'applications/ponder/controller/PonderQuestionHistoryController.php', 'PonderQuestionListController' => 'applications/ponder/controller/PonderQuestionListController.php', 'PonderQuestionMailReceiver' => 'applications/ponder/mail/PonderQuestionMailReceiver.php', 'PonderQuestionPHIDType' => 'applications/ponder/phid/PonderQuestionPHIDType.php', 'PonderQuestionQuery' => 'applications/ponder/query/PonderQuestionQuery.php', 'PonderQuestionReplyHandler' => 'applications/ponder/mail/PonderQuestionReplyHandler.php', 'PonderQuestionSearchEngine' => 'applications/ponder/query/PonderQuestionSearchEngine.php', 'PonderQuestionStatus' => 'applications/ponder/constants/PonderQuestionStatus.php', 'PonderQuestionStatusController' => 'applications/ponder/controller/PonderQuestionStatusController.php', 'PonderQuestionTransaction' => 'applications/ponder/storage/PonderQuestionTransaction.php', 'PonderQuestionTransactionComment' => 'applications/ponder/storage/PonderQuestionTransactionComment.php', 'PonderQuestionTransactionQuery' => 'applications/ponder/query/PonderQuestionTransactionQuery.php', 'PonderQuestionViewController' => 'applications/ponder/controller/PonderQuestionViewController.php', 'PonderRemarkupRule' => 'applications/ponder/remarkup/PonderRemarkupRule.php', 'PonderSchemaSpec' => 'applications/ponder/storage/PonderSchemaSpec.php', 'PonderSearchIndexer' => 'applications/ponder/search/PonderSearchIndexer.php', 'PonderVotableInterface' => 'applications/ponder/storage/PonderVotableInterface.php', 'PonderVote' => 'applications/ponder/constants/PonderVote.php', 'PonderVoteEditor' => 'applications/ponder/editor/PonderVoteEditor.php', 'PonderVotingUserHasAnswerEdgeType' => 'applications/ponder/edge/PonderVotingUserHasAnswerEdgeType.php', 'ProjectAddProjectsEmailCommand' => 'applications/project/command/ProjectAddProjectsEmailCommand.php', 'ProjectBoardTaskCard' => 'applications/project/view/ProjectBoardTaskCard.php', 'ProjectCanLockProjectsCapability' => 'applications/project/capability/ProjectCanLockProjectsCapability.php', 'ProjectConduitAPIMethod' => 'applications/project/conduit/ProjectConduitAPIMethod.php', 'ProjectCreateConduitAPIMethod' => 'applications/project/conduit/ProjectCreateConduitAPIMethod.php', 'ProjectCreateProjectsCapability' => 'applications/project/capability/ProjectCreateProjectsCapability.php', 'ProjectDefaultEditCapability' => 'applications/project/capability/ProjectDefaultEditCapability.php', 'ProjectDefaultJoinCapability' => 'applications/project/capability/ProjectDefaultJoinCapability.php', 'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php', 'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php', 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', 'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php', 'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php', 'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php', 'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php', 'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php', 'ReleephBranchAccessController' => 'applications/releeph/controller/branch/ReleephBranchAccessController.php', 'ReleephBranchCommitFieldSpecification' => 'applications/releeph/field/specification/ReleephBranchCommitFieldSpecification.php', 'ReleephBranchController' => 'applications/releeph/controller/branch/ReleephBranchController.php', 'ReleephBranchCreateController' => 'applications/releeph/controller/branch/ReleephBranchCreateController.php', 'ReleephBranchEditController' => 'applications/releeph/controller/branch/ReleephBranchEditController.php', 'ReleephBranchEditor' => 'applications/releeph/editor/ReleephBranchEditor.php', 'ReleephBranchHistoryController' => 'applications/releeph/controller/branch/ReleephBranchHistoryController.php', 'ReleephBranchNamePreviewController' => 'applications/releeph/controller/branch/ReleephBranchNamePreviewController.php', 'ReleephBranchPHIDType' => 'applications/releeph/phid/ReleephBranchPHIDType.php', 'ReleephBranchPreviewView' => 'applications/releeph/view/branch/ReleephBranchPreviewView.php', 'ReleephBranchQuery' => 'applications/releeph/query/ReleephBranchQuery.php', 'ReleephBranchSearchEngine' => 'applications/releeph/query/ReleephBranchSearchEngine.php', 'ReleephBranchTemplate' => 'applications/releeph/view/branch/ReleephBranchTemplate.php', 'ReleephBranchTransaction' => 'applications/releeph/storage/ReleephBranchTransaction.php', 'ReleephBranchTransactionQuery' => 'applications/releeph/query/ReleephBranchTransactionQuery.php', 'ReleephBranchViewController' => 'applications/releeph/controller/branch/ReleephBranchViewController.php', 'ReleephCommitFinder' => 'applications/releeph/commitfinder/ReleephCommitFinder.php', 'ReleephCommitFinderException' => 'applications/releeph/commitfinder/ReleephCommitFinderException.php', 'ReleephCommitMessageFieldSpecification' => 'applications/releeph/field/specification/ReleephCommitMessageFieldSpecification.php', 'ReleephConduitAPIMethod' => 'applications/releeph/conduit/ReleephConduitAPIMethod.php', 'ReleephController' => 'applications/releeph/controller/ReleephController.php', 'ReleephDAO' => 'applications/releeph/storage/ReleephDAO.php', 'ReleephDefaultFieldSelector' => 'applications/releeph/field/selector/ReleephDefaultFieldSelector.php', 'ReleephDependsOnFieldSpecification' => 'applications/releeph/field/specification/ReleephDependsOnFieldSpecification.php', 'ReleephDiffChurnFieldSpecification' => 'applications/releeph/field/specification/ReleephDiffChurnFieldSpecification.php', 'ReleephDiffMessageFieldSpecification' => 'applications/releeph/field/specification/ReleephDiffMessageFieldSpecification.php', 'ReleephDiffSizeFieldSpecification' => 'applications/releeph/field/specification/ReleephDiffSizeFieldSpecification.php', 'ReleephFieldParseException' => 'applications/releeph/field/exception/ReleephFieldParseException.php', 'ReleephFieldSelector' => 'applications/releeph/field/selector/ReleephFieldSelector.php', 'ReleephFieldSpecification' => 'applications/releeph/field/specification/ReleephFieldSpecification.php', 'ReleephGetBranchesConduitAPIMethod' => 'applications/releeph/conduit/ReleephGetBranchesConduitAPIMethod.php', 'ReleephIntentFieldSpecification' => 'applications/releeph/field/specification/ReleephIntentFieldSpecification.php', 'ReleephLevelFieldSpecification' => 'applications/releeph/field/specification/ReleephLevelFieldSpecification.php', 'ReleephOriginalCommitFieldSpecification' => 'applications/releeph/field/specification/ReleephOriginalCommitFieldSpecification.php', 'ReleephProductActionController' => 'applications/releeph/controller/product/ReleephProductActionController.php', 'ReleephProductController' => 'applications/releeph/controller/product/ReleephProductController.php', 'ReleephProductCreateController' => 'applications/releeph/controller/product/ReleephProductCreateController.php', 'ReleephProductEditController' => 'applications/releeph/controller/product/ReleephProductEditController.php', 'ReleephProductEditor' => 'applications/releeph/editor/ReleephProductEditor.php', 'ReleephProductHistoryController' => 'applications/releeph/controller/product/ReleephProductHistoryController.php', 'ReleephProductListController' => 'applications/releeph/controller/product/ReleephProductListController.php', 'ReleephProductPHIDType' => 'applications/releeph/phid/ReleephProductPHIDType.php', 'ReleephProductQuery' => 'applications/releeph/query/ReleephProductQuery.php', 'ReleephProductSearchEngine' => 'applications/releeph/query/ReleephProductSearchEngine.php', 'ReleephProductTransaction' => 'applications/releeph/storage/ReleephProductTransaction.php', 'ReleephProductTransactionQuery' => 'applications/releeph/query/ReleephProductTransactionQuery.php', 'ReleephProductViewController' => 'applications/releeph/controller/product/ReleephProductViewController.php', 'ReleephProject' => 'applications/releeph/storage/ReleephProject.php', 'ReleephQueryBranchesConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryBranchesConduitAPIMethod.php', 'ReleephQueryProductsConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryProductsConduitAPIMethod.php', 'ReleephQueryRequestsConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryRequestsConduitAPIMethod.php', 'ReleephReasonFieldSpecification' => 'applications/releeph/field/specification/ReleephReasonFieldSpecification.php', 'ReleephRequest' => 'applications/releeph/storage/ReleephRequest.php', 'ReleephRequestActionController' => 'applications/releeph/controller/request/ReleephRequestActionController.php', 'ReleephRequestCommentController' => 'applications/releeph/controller/request/ReleephRequestCommentController.php', 'ReleephRequestConduitAPIMethod' => 'applications/releeph/conduit/ReleephRequestConduitAPIMethod.php', 'ReleephRequestController' => 'applications/releeph/controller/request/ReleephRequestController.php', 'ReleephRequestDifferentialCreateController' => 'applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php', 'ReleephRequestEditController' => 'applications/releeph/controller/request/ReleephRequestEditController.php', 'ReleephRequestMailReceiver' => 'applications/releeph/mail/ReleephRequestMailReceiver.php', 'ReleephRequestPHIDType' => 'applications/releeph/phid/ReleephRequestPHIDType.php', 'ReleephRequestQuery' => 'applications/releeph/query/ReleephRequestQuery.php', 'ReleephRequestReplyHandler' => 'applications/releeph/mail/ReleephRequestReplyHandler.php', 'ReleephRequestSearchEngine' => 'applications/releeph/query/ReleephRequestSearchEngine.php', 'ReleephRequestStatus' => 'applications/releeph/constants/ReleephRequestStatus.php', 'ReleephRequestTransaction' => 'applications/releeph/storage/ReleephRequestTransaction.php', 'ReleephRequestTransactionComment' => 'applications/releeph/storage/ReleephRequestTransactionComment.php', 'ReleephRequestTransactionQuery' => 'applications/releeph/query/ReleephRequestTransactionQuery.php', 'ReleephRequestTransactionalEditor' => 'applications/releeph/editor/ReleephRequestTransactionalEditor.php', 'ReleephRequestTypeaheadControl' => 'applications/releeph/view/request/ReleephRequestTypeaheadControl.php', 'ReleephRequestTypeaheadController' => 'applications/releeph/controller/request/ReleephRequestTypeaheadController.php', 'ReleephRequestView' => 'applications/releeph/view/ReleephRequestView.php', 'ReleephRequestViewController' => 'applications/releeph/controller/request/ReleephRequestViewController.php', 'ReleephRequestorFieldSpecification' => 'applications/releeph/field/specification/ReleephRequestorFieldSpecification.php', 'ReleephRevisionFieldSpecification' => 'applications/releeph/field/specification/ReleephRevisionFieldSpecification.php', 'ReleephSeverityFieldSpecification' => 'applications/releeph/field/specification/ReleephSeverityFieldSpecification.php', 'ReleephSummaryFieldSpecification' => 'applications/releeph/field/specification/ReleephSummaryFieldSpecification.php', 'ReleephWorkCanPushConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkCanPushConduitAPIMethod.php', 'ReleephWorkGetAuthorInfoConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkGetAuthorInfoConduitAPIMethod.php', 'ReleephWorkGetBranchCommitMessageConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkGetBranchCommitMessageConduitAPIMethod.php', 'ReleephWorkGetBranchConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkGetBranchConduitAPIMethod.php', 'ReleephWorkGetCommitMessageConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkGetCommitMessageConduitAPIMethod.php', 'ReleephWorkNextRequestConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkNextRequestConduitAPIMethod.php', 'ReleephWorkRecordConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkRecordConduitAPIMethod.php', 'ReleephWorkRecordPickStatusConduitAPIMethod' => 'applications/releeph/conduit/work/ReleephWorkRecordPickStatusConduitAPIMethod.php', 'RemarkupProcessConduitAPIMethod' => 'applications/remarkup/conduit/RemarkupProcessConduitAPIMethod.php', 'RepositoryConduitAPIMethod' => 'applications/repository/conduit/RepositoryConduitAPIMethod.php', 'RepositoryCreateConduitAPIMethod' => 'applications/repository/conduit/RepositoryCreateConduitAPIMethod.php', 'RepositoryQueryConduitAPIMethod' => 'applications/repository/conduit/RepositoryQueryConduitAPIMethod.php', 'ShellLogView' => 'applications/harbormaster/view/ShellLogView.php', 'SlowvoteConduitAPIMethod' => 'applications/slowvote/conduit/SlowvoteConduitAPIMethod.php', 'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php', 'SlowvoteInfoConduitAPIMethod' => 'applications/slowvote/conduit/SlowvoteInfoConduitAPIMethod.php', 'SlowvoteRemarkupRule' => 'applications/slowvote/remarkup/SlowvoteRemarkupRule.php', 'SubscriptionListDialogBuilder' => 'applications/subscriptions/view/SubscriptionListDialogBuilder.php', 'SubscriptionListStringBuilder' => 'applications/subscriptions/view/SubscriptionListStringBuilder.php', 'TokenConduitAPIMethod' => 'applications/tokens/conduit/TokenConduitAPIMethod.php', 'TokenGiveConduitAPIMethod' => 'applications/tokens/conduit/TokenGiveConduitAPIMethod.php', 'TokenGivenConduitAPIMethod' => 'applications/tokens/conduit/TokenGivenConduitAPIMethod.php', 'TokenQueryConduitAPIMethod' => 'applications/tokens/conduit/TokenQueryConduitAPIMethod.php', 'UserConduitAPIMethod' => 'applications/people/conduit/UserConduitAPIMethod.php', 'UserDisableConduitAPIMethod' => 'applications/people/conduit/UserDisableConduitAPIMethod.php', 'UserEnableConduitAPIMethod' => 'applications/people/conduit/UserEnableConduitAPIMethod.php', 'UserFindConduitAPIMethod' => 'applications/people/conduit/UserFindConduitAPIMethod.php', 'UserQueryConduitAPIMethod' => 'applications/people/conduit/UserQueryConduitAPIMethod.php', 'UserWhoAmIConduitAPIMethod' => 'applications/people/conduit/UserWhoAmIConduitAPIMethod.php', ), 'function' => array( 'celerity_generate_unique_node_id' => 'applications/celerity/api.php', 'celerity_get_resource_uri' => 'applications/celerity/api.php', 'javelin_tag' => 'infrastructure/javelin/markup.php', 'phabricator_date' => 'view/viewutils.php', 'phabricator_datetime' => 'view/viewutils.php', 'phabricator_form' => 'infrastructure/javelin/markup.php', 'phabricator_format_local_time' => 'view/viewutils.php', 'phabricator_relative_date' => 'view/viewutils.php', 'phabricator_time' => 'view/viewutils.php', 'phid_get_subtype' => 'applications/phid/utils.php', 'phid_get_type' => 'applications/phid/utils.php', 'phid_group_by_type' => 'applications/phid/utils.php', 'require_celerity_resource' => 'applications/celerity/api.php', ), 'xmap' => array( 'AlmanacAddress' => 'Phobject', 'AlmanacBinding' => array( 'AlmanacDAO', 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorApplicationTransactionInterface', 'AlmanacPropertyInterface', 'PhabricatorDestructibleInterface', ), 'AlmanacBindingEditController' => 'AlmanacServiceController', 'AlmanacBindingEditor' => 'PhabricatorApplicationTransactionEditor', 'AlmanacBindingPHIDType' => 'PhabricatorPHIDType', 'AlmanacBindingQuery' => 'AlmanacQuery', 'AlmanacBindingTableView' => 'AphrontView', 'AlmanacBindingTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacBindingViewController' => 'AlmanacServiceController', 'AlmanacClusterDatabaseServiceType' => 'AlmanacClusterServiceType', 'AlmanacClusterRepositoryServiceType' => 'AlmanacClusterServiceType', 'AlmanacClusterServiceType' => 'AlmanacServiceType', 'AlmanacConduitAPIMethod' => 'ConduitAPIMethod', 'AlmanacConsoleController' => 'AlmanacController', 'AlmanacController' => 'PhabricatorController', 'AlmanacCoreCustomField' => array( 'AlmanacCustomField', 'PhabricatorStandardCustomFieldInterface', ), 'AlmanacCreateClusterServicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacCreateDevicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacCreateNetworksCapability' => 'PhabricatorPolicyCapability', 'AlmanacCreateServicesCapability' => 'PhabricatorPolicyCapability', 'AlmanacCustomField' => 'PhabricatorCustomField', 'AlmanacCustomServiceType' => 'AlmanacServiceType', 'AlmanacDAO' => 'PhabricatorLiskDAO', 'AlmanacDevice' => array( 'AlmanacDAO', 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorProjectInterface', 'PhabricatorSSHPublicKeyInterface', 'AlmanacPropertyInterface', 'PhabricatorDestructibleInterface', ), 'AlmanacDeviceController' => 'AlmanacController', 'AlmanacDeviceEditController' => 'AlmanacDeviceController', 'AlmanacDeviceEditor' => 'PhabricatorApplicationTransactionEditor', 'AlmanacDeviceListController' => 'AlmanacDeviceController', 'AlmanacDevicePHIDType' => 'PhabricatorPHIDType', 'AlmanacDeviceQuery' => 'AlmanacQuery', 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'AlmanacDeviceTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacDeviceViewController' => 'AlmanacDeviceController', 'AlmanacDrydockPoolServiceType' => 'AlmanacServiceType', 'AlmanacInterface' => array( 'AlmanacDAO', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), 'AlmanacInterfaceDatasource' => 'PhabricatorTypeaheadDatasource', 'AlmanacInterfaceEditController' => 'AlmanacDeviceController', 'AlmanacInterfacePHIDType' => 'PhabricatorPHIDType', 'AlmanacInterfaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'AlmanacInterfaceTableView' => 'AphrontView', 'AlmanacKeys' => 'Phobject', 'AlmanacManagementLockWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementRegisterWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementTrustKeyWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementUnlockWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementUntrustKeyWorkflow' => 'AlmanacManagementWorkflow', 'AlmanacManagementWorkflow' => 'PhabricatorManagementWorkflow', 'AlmanacNames' => 'Phobject', 'AlmanacNamesTestCase' => 'PhabricatorTestCase', 'AlmanacNetwork' => array( 'AlmanacDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), 'AlmanacNetworkController' => 'AlmanacController', 'AlmanacNetworkEditController' => 'AlmanacNetworkController', 'AlmanacNetworkEditor' => 'PhabricatorApplicationTransactionEditor', 'AlmanacNetworkListController' => 'AlmanacNetworkController', 'AlmanacNetworkPHIDType' => 'PhabricatorPHIDType', 'AlmanacNetworkQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'AlmanacNetworkSearchEngine' => 'PhabricatorApplicationSearchEngine', 'AlmanacNetworkTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacNetworkTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacNetworkViewController' => 'AlmanacNetworkController', 'AlmanacProperty' => array( 'PhabricatorCustomFieldStorage', 'PhabricatorPolicyInterface', ), 'AlmanacPropertyController' => 'AlmanacController', 'AlmanacPropertyDeleteController' => 'AlmanacDeviceController', 'AlmanacPropertyEditController' => 'AlmanacDeviceController', 'AlmanacPropertyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'AlmanacQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'AlmanacQueryDevicesConduitAPIMethod' => 'AlmanacConduitAPIMethod', 'AlmanacQueryServicesConduitAPIMethod' => 'AlmanacConduitAPIMethod', 'AlmanacSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'AlmanacService' => array( 'AlmanacDAO', 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorProjectInterface', 'AlmanacPropertyInterface', 'PhabricatorDestructibleInterface', ), 'AlmanacServiceController' => 'AlmanacController', 'AlmanacServiceDatasource' => 'PhabricatorTypeaheadDatasource', 'AlmanacServiceEditController' => 'AlmanacServiceController', 'AlmanacServiceEditor' => 'PhabricatorApplicationTransactionEditor', 'AlmanacServiceListController' => 'AlmanacServiceController', 'AlmanacServicePHIDType' => 'PhabricatorPHIDType', 'AlmanacServiceQuery' => 'AlmanacQuery', 'AlmanacServiceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'AlmanacServiceTransaction' => 'PhabricatorApplicationTransaction', 'AlmanacServiceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacServiceType' => 'Phobject', 'AlmanacServiceTypeTestCase' => 'PhabricatorTestCase', 'AlmanacServiceViewController' => 'AlmanacServiceController', 'AphlictDropdownDataQuery' => 'Phobject', 'Aphront304Response' => 'AphrontResponse', 'Aphront400Response' => 'AphrontResponse', 'Aphront403Response' => 'AphrontHTMLResponse', 'Aphront404Response' => 'AphrontHTMLResponse', 'AphrontAjaxResponse' => 'AphrontResponse', 'AphrontApplicationConfiguration' => 'Phobject', 'AphrontBarView' => 'AphrontView', + 'AphrontBoolHTTPParameterType' => 'AphrontHTTPParameterType', 'AphrontCSRFException' => 'AphrontException', 'AphrontCalendarEventView' => 'AphrontView', 'AphrontController' => 'Phobject', 'AphrontCursorPagerView' => 'AphrontView', 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', 'AphrontDialogResponse' => 'AphrontResponse', 'AphrontDialogView' => array( 'AphrontView', 'AphrontResponseProducerInterface', ), 'AphrontException' => 'Exception', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormChooseButtonControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', 'AphrontFormDateControl' => 'AphrontFormControl', 'AphrontFormDateControlValue' => 'Phobject', 'AphrontFormDividerControl' => 'AphrontFormControl', 'AphrontFormFileControl' => 'AphrontFormControl', 'AphrontFormMarkupControl' => 'AphrontFormControl', 'AphrontFormPasswordControl' => 'AphrontFormControl', 'AphrontFormPolicyControl' => 'AphrontFormControl', 'AphrontFormRadioButtonControl' => 'AphrontFormControl', 'AphrontFormRecaptchaControl' => 'AphrontFormControl', 'AphrontFormSelectControl' => 'AphrontFormControl', 'AphrontFormStaticControl' => 'AphrontFormControl', 'AphrontFormSubmitControl' => 'AphrontFormControl', 'AphrontFormTextAreaControl' => 'AphrontFormControl', 'AphrontFormTextControl' => 'AphrontFormControl', 'AphrontFormTextWithSubmitControl' => 'AphrontFormControl', 'AphrontFormTokenizerControl' => 'AphrontFormControl', 'AphrontFormTypeaheadControl' => 'AphrontFormControl', 'AphrontFormView' => 'AphrontView', 'AphrontGlyphBarView' => 'AphrontBarView', 'AphrontHTMLResponse' => 'AphrontResponse', 'AphrontHTTPParameterType' => 'Phobject', 'AphrontHTTPProxyResponse' => 'AphrontResponse', 'AphrontHTTPSink' => 'Phobject', 'AphrontHTTPSinkTestCase' => 'PhabricatorTestCase', + 'AphrontIntHTTPParameterType' => 'AphrontHTTPParameterType', 'AphrontIsolatedDatabaseConnectionTestCase' => 'PhabricatorTestCase', 'AphrontIsolatedHTTPSink' => 'AphrontHTTPSink', 'AphrontJSONResponse' => 'AphrontResponse', 'AphrontJavelinView' => 'AphrontView', 'AphrontKeyboardShortcutsAvailableView' => 'AphrontView', 'AphrontListFilterView' => 'AphrontView', + 'AphrontListHTTPParameterType' => 'AphrontHTTPParameterType', 'AphrontMalformedRequestException' => 'AphrontException', 'AphrontMoreView' => 'AphrontView', 'AphrontMultiColumnView' => 'AphrontView', 'AphrontMySQLDatabaseConnectionTestCase' => 'PhabricatorTestCase', 'AphrontNullView' => 'AphrontView', 'AphrontPHIDHTTPParameterType' => 'AphrontHTTPParameterType', - 'AphrontPHIDListHTTPParameterType' => 'AphrontHTTPParameterType', + 'AphrontPHIDListHTTPParameterType' => 'AphrontListHTTPParameterType', 'AphrontPHPHTTPSink' => 'AphrontHTTPSink', 'AphrontPageView' => 'AphrontView', 'AphrontPlainTextResponse' => 'AphrontResponse', 'AphrontProgressBarView' => 'AphrontBarView', - 'AphrontProjectListHTTPParameterType' => 'AphrontHTTPParameterType', + 'AphrontProjectListHTTPParameterType' => 'AphrontListHTTPParameterType', 'AphrontProxyResponse' => array( 'AphrontResponse', 'AphrontResponseProducerInterface', ), 'AphrontRedirectResponse' => 'AphrontResponse', 'AphrontRedirectResponseTestCase' => 'PhabricatorTestCase', 'AphrontReloadResponse' => 'AphrontRedirectResponse', 'AphrontRequest' => 'Phobject', 'AphrontRequestExceptionHandler' => 'Phobject', 'AphrontRequestTestCase' => 'PhabricatorTestCase', 'AphrontResponse' => 'Phobject', 'AphrontRoutingMap' => 'Phobject', 'AphrontRoutingResult' => 'Phobject', 'AphrontSelectHTTPParameterType' => 'AphrontHTTPParameterType', 'AphrontSideNavFilterView' => 'AphrontView', 'AphrontSite' => 'Phobject', 'AphrontStackTraceView' => 'AphrontView', 'AphrontStandaloneHTMLResponse' => 'AphrontHTMLResponse', 'AphrontStringHTTPParameterType' => 'AphrontHTTPParameterType', - 'AphrontStringListHTTPParameterType' => 'AphrontHTTPParameterType', + 'AphrontStringListHTTPParameterType' => 'AphrontListHTTPParameterType', 'AphrontTableView' => 'AphrontView', 'AphrontTagView' => 'AphrontView', 'AphrontTokenizerTemplateView' => 'AphrontView', 'AphrontTypeaheadTemplateView' => 'AphrontView', 'AphrontUnhandledExceptionResponse' => 'AphrontStandaloneHTMLResponse', - 'AphrontUserListHTTPParameterType' => 'AphrontHTTPParameterType', + 'AphrontUserListHTTPParameterType' => 'AphrontListHTTPParameterType', 'AphrontView' => array( 'Phobject', 'PhutilSafeHTMLProducerInterface', ), 'AphrontWebpageResponse' => 'AphrontHTMLResponse', 'ArcanistConduitAPIMethod' => 'ConduitAPIMethod', 'AuditConduitAPIMethod' => 'ConduitAPIMethod', 'AuditQueryConduitAPIMethod' => 'AuditConduitAPIMethod', 'AuthManageProvidersCapability' => 'PhabricatorPolicyCapability', 'CalendarTimeUtil' => 'Phobject', 'CalendarTimeUtilTestCase' => 'PhabricatorTestCase', 'CelerityAPI' => 'Phobject', 'CelerityDefaultPostprocessor' => 'CelerityPostprocessor', 'CelerityHighContrastPostprocessor' => 'CelerityPostprocessor', 'CelerityLargeFontPostprocessor' => 'CelerityPostprocessor', 'CelerityManagementMapWorkflow' => 'CelerityManagementWorkflow', 'CelerityManagementWorkflow' => 'PhabricatorManagementWorkflow', 'CelerityPhabricatorResourceController' => 'CelerityResourceController', 'CelerityPhabricatorResources' => 'CelerityResourcesOnDisk', 'CelerityPhysicalResources' => 'CelerityResources', 'CelerityPhysicalResourcesTestCase' => 'PhabricatorTestCase', 'CelerityPostprocessor' => 'Phobject', 'CelerityPostprocessorTestCase' => 'PhabricatorTestCase', 'CelerityResourceController' => 'PhabricatorController', 'CelerityResourceGraph' => 'AbstractDirectedGraph', 'CelerityResourceMap' => 'Phobject', 'CelerityResourceMapGenerator' => 'Phobject', 'CelerityResourceTransformer' => 'Phobject', 'CelerityResourceTransformerTestCase' => 'PhabricatorTestCase', 'CelerityResources' => 'Phobject', 'CelerityResourcesOnDisk' => 'CelerityPhysicalResources', 'CeleritySpriteGenerator' => 'Phobject', 'CelerityStaticResourceResponse' => 'Phobject', 'ChatLogConduitAPIMethod' => 'ConduitAPIMethod', 'ChatLogQueryConduitAPIMethod' => 'ChatLogConduitAPIMethod', 'ChatLogRecordConduitAPIMethod' => 'ChatLogConduitAPIMethod', 'ConduitAPIMethod' => array( 'Phobject', 'PhabricatorPolicyInterface', ), 'ConduitAPIMethodTestCase' => 'PhabricatorTestCase', 'ConduitAPIRequest' => 'Phobject', 'ConduitAPIResponse' => 'Phobject', 'ConduitApplicationNotInstalledException' => 'ConduitMethodNotFoundException', 'ConduitCall' => 'Phobject', 'ConduitCallTestCase' => 'PhabricatorTestCase', 'ConduitConnectConduitAPIMethod' => 'ConduitAPIMethod', 'ConduitConnectionGarbageCollector' => 'PhabricatorGarbageCollector', 'ConduitDeprecatedCallSetupCheck' => 'PhabricatorSetupCheck', 'ConduitException' => 'Exception', 'ConduitGetCapabilitiesConduitAPIMethod' => 'ConduitAPIMethod', 'ConduitGetCertificateConduitAPIMethod' => 'ConduitAPIMethod', 'ConduitLogGarbageCollector' => 'PhabricatorGarbageCollector', 'ConduitMethodDoesNotExistException' => 'ConduitMethodNotFoundException', 'ConduitMethodNotFoundException' => 'ConduitException', 'ConduitPingConduitAPIMethod' => 'ConduitAPIMethod', 'ConduitQueryConduitAPIMethod' => 'ConduitAPIMethod', 'ConduitSSHWorkflow' => 'PhabricatorSSHWorkflow', 'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector', 'ConpherenceColumnViewController' => 'ConpherenceController', 'ConpherenceConduitAPIMethod' => 'ConduitAPIMethod', 'ConpherenceConfigOptions' => 'PhabricatorApplicationConfigOptions', 'ConpherenceConstants' => 'Phobject', 'ConpherenceController' => 'PhabricatorController', 'ConpherenceCreateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceCreateThreadMailReceiver' => 'PhabricatorMailReceiver', 'ConpherenceDAO' => 'PhabricatorLiskDAO', 'ConpherenceDurableColumnView' => 'AphrontTagView', 'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor', 'ConpherenceFormDragAndDropUploadControl' => 'AphrontFormControl', 'ConpherenceFulltextQuery' => 'PhabricatorOffsetPagedQuery', 'ConpherenceHovercardEventListener' => 'PhabricatorEventListener', 'ConpherenceImageData' => 'ConpherenceConstants', 'ConpherenceIndex' => 'ConpherenceDAO', 'ConpherenceLayoutView' => 'AphrontView', 'ConpherenceListController' => 'ConpherenceController', 'ConpherenceMenuItemView' => 'AphrontTagView', 'ConpherenceNewRoomController' => 'ConpherenceController', 'ConpherenceNotificationPanelController' => 'ConpherenceController', 'ConpherenceParticipant' => 'ConpherenceDAO', 'ConpherenceParticipantCountQuery' => 'PhabricatorOffsetPagedQuery', 'ConpherenceParticipantQuery' => 'PhabricatorOffsetPagedQuery', 'ConpherenceParticipationStatus' => 'ConpherenceConstants', 'ConpherencePeopleWidgetView' => 'ConpherenceWidgetView', 'ConpherencePicCropControl' => 'AphrontFormControl', 'ConpherenceQueryThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceQueryTransactionConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceReplyHandler' => 'PhabricatorMailReplyHandler', 'ConpherenceRoomListController' => 'ConpherenceController', 'ConpherenceRoomTestCase' => 'ConpherenceTestCase', 'ConpherenceSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'ConpherenceSettings' => 'ConpherenceConstants', 'ConpherenceTestCase' => 'PhabricatorTestCase', 'ConpherenceThread' => array( 'ConpherenceDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorMentionableInterface', 'PhabricatorDestructibleInterface', ), 'ConpherenceThreadIndexer' => 'PhabricatorSearchDocumentIndexer', 'ConpherenceThreadListView' => 'AphrontView', 'ConpherenceThreadMailReceiver' => 'PhabricatorObjectMailReceiver', 'ConpherenceThreadMembersPolicyRule' => 'PhabricatorPolicyRule', 'ConpherenceThreadQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ConpherenceThreadRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ConpherenceThreadSearchEngine' => 'PhabricatorApplicationSearchEngine', 'ConpherenceTransaction' => 'PhabricatorApplicationTransaction', 'ConpherenceTransactionComment' => 'PhabricatorApplicationTransactionComment', 'ConpherenceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'ConpherenceTransactionRenderer' => 'Phobject', 'ConpherenceTransactionView' => 'AphrontView', 'ConpherenceUpdateActions' => 'ConpherenceConstants', 'ConpherenceUpdateController' => 'ConpherenceController', 'ConpherenceUpdateThreadConduitAPIMethod' => 'ConpherenceConduitAPIMethod', 'ConpherenceViewController' => 'ConpherenceController', 'ConpherenceWidgetConfigConstants' => 'ConpherenceConstants', 'ConpherenceWidgetController' => 'ConpherenceController', 'ConpherenceWidgetView' => 'AphrontView', 'DarkConsoleController' => 'PhabricatorController', 'DarkConsoleCore' => 'Phobject', 'DarkConsoleDataController' => 'PhabricatorController', 'DarkConsoleErrorLogPlugin' => 'DarkConsolePlugin', 'DarkConsoleErrorLogPluginAPI' => 'Phobject', 'DarkConsoleEventPlugin' => 'DarkConsolePlugin', 'DarkConsoleEventPluginAPI' => 'PhabricatorEventListener', 'DarkConsolePlugin' => 'Phobject', 'DarkConsoleRequestPlugin' => 'DarkConsolePlugin', 'DarkConsoleServicesPlugin' => 'DarkConsolePlugin', 'DarkConsoleStartupPlugin' => 'DarkConsolePlugin', 'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin', 'DarkConsoleXHProfPluginAPI' => 'Phobject', 'DefaultDatabaseConfigurationProvider' => array( 'Phobject', 'DatabaseConfigurationProvider', ), 'DifferentialAction' => 'Phobject', 'DifferentialActionEmailCommand' => 'MetaMTAEmailTransactionCommand', 'DifferentialActionMenuEventListener' => 'PhabricatorEventListener', 'DifferentialAddCommentView' => 'AphrontView', 'DifferentialAdjustmentMapTestCase' => 'PhutilTestCase', 'DifferentialAffectedPath' => 'DifferentialDAO', 'DifferentialApplyPatchField' => 'DifferentialCustomField', 'DifferentialAsanaRepresentationField' => 'DifferentialCustomField', 'DifferentialAuditorsField' => 'DifferentialStoredCustomField', 'DifferentialAuthorField' => 'DifferentialCustomField', 'DifferentialBlameRevisionField' => 'DifferentialStoredCustomField', 'DifferentialBlockHeraldAction' => 'HeraldAction', 'DifferentialBranchField' => 'DifferentialCustomField', 'DifferentialChangeHeraldFieldGroup' => 'HeraldFieldGroup', 'DifferentialChangeType' => 'Phobject', 'DifferentialChangesSinceLastUpdateField' => 'DifferentialCustomField', 'DifferentialChangeset' => array( 'DifferentialDAO', 'PhabricatorPolicyInterface', ), 'DifferentialChangesetDetailView' => 'AphrontView', 'DifferentialChangesetFileTreeSideNavBuilder' => 'Phobject', 'DifferentialChangesetHTMLRenderer' => 'DifferentialChangesetRenderer', 'DifferentialChangesetListView' => 'AphrontView', 'DifferentialChangesetOneUpRenderer' => 'DifferentialChangesetHTMLRenderer', 'DifferentialChangesetOneUpTestRenderer' => 'DifferentialChangesetTestRenderer', 'DifferentialChangesetParser' => 'Phobject', 'DifferentialChangesetParserTestCase' => 'PhabricatorTestCase', 'DifferentialChangesetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DifferentialChangesetRenderer' => 'Phobject', 'DifferentialChangesetTestRenderer' => 'DifferentialChangesetRenderer', 'DifferentialChangesetTwoUpRenderer' => 'DifferentialChangesetHTMLRenderer', 'DifferentialChangesetTwoUpTestRenderer' => 'DifferentialChangesetTestRenderer', 'DifferentialChangesetViewController' => 'DifferentialController', 'DifferentialCloseConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialCommentPreviewController' => 'DifferentialController', 'DifferentialCommentSaveController' => 'DifferentialController', 'DifferentialCommitMessageParser' => 'Phobject', 'DifferentialCommitMessageParserTestCase' => 'PhabricatorTestCase', 'DifferentialCommitsField' => 'DifferentialCustomField', 'DifferentialConduitAPIMethod' => 'ConduitAPIMethod', 'DifferentialConflictsField' => 'DifferentialCustomField', 'DifferentialController' => 'PhabricatorController', 'DifferentialCoreCustomField' => 'DifferentialCustomField', 'DifferentialCreateCommentConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialCreateDiffConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialCreateInlineConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialCreateMailReceiver' => 'PhabricatorMailReceiver', 'DifferentialCreateRawDiffConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialCreateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialCustomField' => 'PhabricatorCustomField', 'DifferentialCustomFieldDependsOnParser' => 'PhabricatorCustomFieldMonogramParser', 'DifferentialCustomFieldDependsOnParserTestCase' => 'PhabricatorTestCase', 'DifferentialCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 'DifferentialCustomFieldRevertsParser' => 'PhabricatorCustomFieldMonogramParser', 'DifferentialCustomFieldRevertsParserTestCase' => 'PhabricatorTestCase', 'DifferentialCustomFieldStorage' => 'PhabricatorCustomFieldStorage', 'DifferentialCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 'DifferentialDAO' => 'PhabricatorLiskDAO', 'DifferentialDefaultViewCapability' => 'PhabricatorPolicyCapability', 'DifferentialDependenciesField' => 'DifferentialCustomField', 'DifferentialDependsOnField' => 'DifferentialCustomField', 'DifferentialDiff' => array( 'DifferentialDAO', 'PhabricatorPolicyInterface', 'HarbormasterBuildableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', ), 'DifferentialDiffAffectedFilesHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffAuthorHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffAuthorProjectsHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffContentAddedHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffContentHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffContentRemovedHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffCreateController' => 'DifferentialController', 'DifferentialDiffEditor' => 'PhabricatorApplicationTransactionEditor', 'DifferentialDiffHeraldField' => 'HeraldField', 'DifferentialDiffHeraldFieldGroup' => 'HeraldFieldGroup', 'DifferentialDiffInlineCommentQuery' => 'PhabricatorDiffInlineCommentQuery', 'DifferentialDiffPHIDType' => 'PhabricatorPHIDType', 'DifferentialDiffProperty' => 'DifferentialDAO', 'DifferentialDiffQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DifferentialDiffRepositoryHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffRepositoryProjectsHeraldField' => 'DifferentialDiffHeraldField', 'DifferentialDiffTestCase' => 'PhutilTestCase', 'DifferentialDiffTransaction' => 'PhabricatorApplicationTransaction', 'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher', 'DifferentialDraft' => 'DifferentialDAO', 'DifferentialEditPolicyField' => 'DifferentialCoreCustomField', 'DifferentialFieldParseException' => 'Exception', 'DifferentialFieldValidationException' => 'Exception', 'DifferentialFindConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetAllDiffsConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetCommitPathsConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetDiffConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetRawDiffConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetRevisionCommentsConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGetWorkingCopy' => 'Phobject', 'DifferentialGitHubLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialGitSVNIDField' => 'DifferentialCustomField', 'DifferentialHarbormasterField' => 'DifferentialCustomField', 'DifferentialHiddenComment' => 'DifferentialDAO', 'DifferentialHostField' => 'DifferentialCustomField', 'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialHostedMercurialLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialHovercardEventListener' => 'PhabricatorEventListener', 'DifferentialHunk' => array( 'DifferentialDAO', 'PhabricatorPolicyInterface', ), 'DifferentialHunkParser' => 'Phobject', 'DifferentialHunkParserTestCase' => 'PhabricatorTestCase', 'DifferentialHunkQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DifferentialHunkTestCase' => 'PhutilTestCase', 'DifferentialInlineComment' => array( 'Phobject', 'PhabricatorInlineCommentInterface', ), 'DifferentialInlineCommentEditController' => 'PhabricatorInlineCommentController', 'DifferentialInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController', 'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery', 'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField', 'DifferentialLandingActionMenuEventListener' => 'PhabricatorEventListener', 'DifferentialLandingStrategy' => 'Phobject', 'DifferentialLegacyHunk' => 'DifferentialHunk', 'DifferentialLineAdjustmentMap' => 'Phobject', 'DifferentialLintField' => 'DifferentialHarbormasterField', 'DifferentialLintStatus' => 'Phobject', 'DifferentialLocalCommitsView' => 'AphrontView', 'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField', 'DifferentialModernHunk' => 'DifferentialHunk', 'DifferentialNextStepField' => 'DifferentialCustomField', 'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector', 'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialParseRenderTestCase' => 'PhabricatorTestCase', 'DifferentialPathField' => 'DifferentialCustomField', 'DifferentialPrimaryPaneView' => 'AphrontView', 'DifferentialProjectReviewersField' => 'DifferentialCustomField', 'DifferentialProjectsField' => 'DifferentialCoreCustomField', 'DifferentialQueryConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialQueryDiffsConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialRawDiffRenderer' => 'Phobject', 'DifferentialReleephRequestFieldSpecification' => 'Phobject', 'DifferentialRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'DifferentialReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'DifferentialRepositoryField' => 'DifferentialCoreCustomField', 'DifferentialRepositoryLookup' => 'Phobject', 'DifferentialRequiredSignaturesField' => 'DifferentialCoreCustomField', 'DifferentialRevertPlanField' => 'DifferentialStoredCustomField', 'DifferentialReviewedByField' => 'DifferentialCoreCustomField', 'DifferentialReviewer' => 'Phobject', 'DifferentialReviewerForRevisionEdgeType' => 'PhabricatorEdgeType', 'DifferentialReviewerStatus' => 'Phobject', 'DifferentialReviewersAddBlockingReviewersHeraldAction' => 'DifferentialReviewersHeraldAction', 'DifferentialReviewersAddBlockingSelfHeraldAction' => 'DifferentialReviewersHeraldAction', 'DifferentialReviewersAddReviewersHeraldAction' => 'DifferentialReviewersHeraldAction', 'DifferentialReviewersAddSelfHeraldAction' => 'DifferentialReviewersHeraldAction', 'DifferentialReviewersField' => 'DifferentialCoreCustomField', 'DifferentialReviewersHeraldAction' => 'HeraldAction', 'DifferentialReviewersView' => 'AphrontView', 'DifferentialRevision' => array( 'DifferentialDAO', 'PhabricatorTokenReceiverInterface', 'PhabricatorPolicyInterface', 'PhabricatorExtendedPolicyInterface', 'PhabricatorFlaggableInterface', 'PhrequentTrackableInterface', 'HarbormasterBuildableInterface', 'PhabricatorSubscribableInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorMentionableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorProjectInterface', ), 'DifferentialRevisionAffectedFilesHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionAuthorHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionAuthorProjectsHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionCloseDetailsController' => 'DifferentialController', 'DifferentialRevisionContentAddedHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionContentHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionContentRemovedHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionControlSystem' => 'Phobject', 'DifferentialRevisionDependedOnByRevisionEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionDependsOnRevisionEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionHasCommitEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionHasReviewerEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionHasTaskEdgeType' => 'PhabricatorEdgeType', 'DifferentialRevisionHeraldField' => 'HeraldField', 'DifferentialRevisionHeraldFieldGroup' => 'HeraldFieldGroup', 'DifferentialRevisionIDField' => 'DifferentialCustomField', 'DifferentialRevisionLandController' => 'DifferentialController', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionListView' => 'AphrontView', 'DifferentialRevisionMailReceiver' => 'PhabricatorObjectMailReceiver', 'DifferentialRevisionOperationController' => 'DifferentialController', 'DifferentialRevisionPHIDType' => 'PhabricatorPHIDType', 'DifferentialRevisionPackageHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionPackageOwnerHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DifferentialRevisionRepositoryHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionRepositoryProjectsHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionReviewersHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DifferentialRevisionStatus' => 'Phobject', 'DifferentialRevisionSummaryHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionTitleHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'DifferentialSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DifferentialSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'DifferentialSetDiffPropertyConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialStoredCustomField' => 'DifferentialCustomField', 'DifferentialSubscribersField' => 'DifferentialCoreCustomField', 'DifferentialSummaryField' => 'DifferentialCoreCustomField', 'DifferentialTestPlanField' => 'DifferentialCoreCustomField', 'DifferentialTitleField' => 'DifferentialCoreCustomField', 'DifferentialTransaction' => 'PhabricatorApplicationTransaction', 'DifferentialTransactionComment' => 'PhabricatorApplicationTransactionComment', 'DifferentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'DifferentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DifferentialTransactionView' => 'PhabricatorApplicationTransactionView', 'DifferentialUnitField' => 'DifferentialHarbormasterField', 'DifferentialUnitStatus' => 'Phobject', 'DifferentialUnitTestResult' => 'Phobject', 'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialViewPolicyField' => 'DifferentialCoreCustomField', 'DiffusionAuditorDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionAuditorFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionAuditorsAddAuditorsHeraldAction' => 'DiffusionAuditorsHeraldAction', 'DiffusionAuditorsAddSelfHeraldAction' => 'DiffusionAuditorsHeraldAction', 'DiffusionAuditorsHeraldAction' => 'HeraldAction', 'DiffusionBlockHeraldAction' => 'HeraldAction', 'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBranchTableController' => 'DiffusionController', 'DiffusionBranchTableView' => 'DiffusionView', 'DiffusionBrowseController' => 'DiffusionController', 'DiffusionBrowseDirectoryController' => 'DiffusionBrowseController', 'DiffusionBrowseFileController' => 'DiffusionBrowseController', 'DiffusionBrowseMainController' => 'DiffusionBrowseController', 'DiffusionBrowseQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBrowseResultSet' => 'Phobject', 'DiffusionBrowseSearchController' => 'DiffusionBrowseController', 'DiffusionBrowseTableView' => 'DiffusionView', 'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery', 'DiffusionChangeController' => 'DiffusionController', 'DiffusionChangeHeraldFieldGroup' => 'HeraldFieldGroup', 'DiffusionCommitAffectedFilesHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitAuthorHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitAutocloseHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitBranchesController' => 'DiffusionController', 'DiffusionCommitBranchesHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitCommitterHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitController' => 'DiffusionController', 'DiffusionCommitDiffContentAddedHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitDiffContentHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitDiffContentRemovedHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitDiffEnormousHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitEditController' => 'DiffusionController', 'DiffusionCommitHasRevisionEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitHasTaskEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitHash' => 'Phobject', 'DiffusionCommitHeraldField' => 'HeraldField', 'DiffusionCommitHeraldFieldGroup' => 'HeraldFieldGroup', 'DiffusionCommitHookEngine' => 'Phobject', 'DiffusionCommitHookRejectException' => 'Exception', 'DiffusionCommitMessageHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitPackageAuditHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitPackageHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitPackageOwnerHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitParentsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionCommitQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DiffusionCommitRef' => 'Phobject', 'DiffusionCommitRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'DiffusionCommitRemarkupRuleTestCase' => 'PhabricatorTestCase', 'DiffusionCommitRepositoryHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRepositoryProjectsHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevertedByCommitEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitRevertsCommitEdgeType' => 'PhabricatorEdgeType', 'DiffusionCommitReviewerHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionAcceptedHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionReviewersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitRevisionSubscribersHeraldField' => 'DiffusionCommitHeraldField', 'DiffusionCommitTagsController' => 'DiffusionController', 'DiffusionConduitAPIMethod' => 'ConduitAPIMethod', 'DiffusionController' => 'PhabricatorController', 'DiffusionCreateCommentConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionCreateRepositoriesCapability' => 'PhabricatorPolicyCapability', 'DiffusionDefaultEditCapability' => 'PhabricatorPolicyCapability', 'DiffusionDefaultPushCapability' => 'PhabricatorPolicyCapability', 'DiffusionDefaultViewCapability' => 'PhabricatorPolicyCapability', 'DiffusionDiffController' => 'DiffusionController', 'DiffusionDiffInlineCommentQuery' => 'PhabricatorDiffInlineCommentQuery', 'DiffusionDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionDoorkeeperCommitFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher', 'DiffusionEmptyResultView' => 'DiffusionView', 'DiffusionExistsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionExternalController' => 'DiffusionController', 'DiffusionExternalSymbolQuery' => 'Phobject', 'DiffusionExternalSymbolsSource' => 'Phobject', 'DiffusionFileContent' => 'Phobject', 'DiffusionFileContentQuery' => 'DiffusionQuery', 'DiffusionFileContentQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionFindSymbolsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionGetCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionGetLintMessagesConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionGetRecentCommitsByPathConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionGitBranch' => 'Phobject', 'DiffusionGitBranchTestCase' => 'PhabricatorTestCase', 'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionGitFileContentQueryTestCase' => 'PhabricatorTestCase', 'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionGitReceivePackSSHWorkflow' => 'DiffusionGitSSHWorkflow', 'DiffusionGitRequest' => 'DiffusionRequest', 'DiffusionGitResponse' => 'AphrontResponse', 'DiffusionGitSSHWorkflow' => 'DiffusionSSHWorkflow', 'DiffusionGitUploadPackSSHWorkflow' => 'DiffusionGitSSHWorkflow', 'DiffusionHistoryController' => 'DiffusionController', 'DiffusionHistoryQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionHistoryTableView' => 'DiffusionView', 'DiffusionHovercardEventListener' => 'PhabricatorEventListener', 'DiffusionInlineCommentController' => 'PhabricatorInlineCommentController', 'DiffusionInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController', 'DiffusionLastModifiedController' => 'DiffusionController', 'DiffusionLastModifiedQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionLintController' => 'DiffusionController', 'DiffusionLintCountQuery' => 'PhabricatorQuery', 'DiffusionLintDetailsController' => 'DiffusionController', 'DiffusionLintSaveRunner' => 'Phobject', 'DiffusionLookSoonConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionLowLevelCommitFieldsQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelCommitQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelGitRefQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelMercurialBranchesQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelMercurialPathsQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelMercurialPathsQueryTests' => 'PhabricatorTestCase', 'DiffusionLowLevelParentsQuery' => 'DiffusionLowLevelQuery', 'DiffusionLowLevelQuery' => 'Phobject', 'DiffusionLowLevelResolveRefsQuery' => 'DiffusionLowLevelQuery', 'DiffusionMercurialFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionMercurialRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionMercurialRequest' => 'DiffusionRequest', 'DiffusionMercurialResponse' => 'AphrontResponse', 'DiffusionMercurialSSHWorkflow' => 'DiffusionSSHWorkflow', 'DiffusionMercurialServeSSHWorkflow' => 'DiffusionMercurialSSHWorkflow', 'DiffusionMercurialWireClientSSHProtocolChannel' => 'PhutilProtocolChannel', 'DiffusionMercurialWireProtocol' => 'Phobject', 'DiffusionMercurialWireProtocolTests' => 'PhabricatorTestCase', 'DiffusionMercurialWireSSHTestCase' => 'PhabricatorTestCase', 'DiffusionMergedCommitsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionMirrorDeleteController' => 'DiffusionController', 'DiffusionMirrorEditController' => 'DiffusionController', 'DiffusionPathChange' => 'Phobject', 'DiffusionPathChangeQuery' => 'Phobject', 'DiffusionPathCompleteController' => 'DiffusionController', 'DiffusionPathIDQuery' => 'Phobject', 'DiffusionPathQuery' => 'Phobject', 'DiffusionPathQueryTestCase' => 'PhabricatorTestCase', 'DiffusionPathTreeController' => 'DiffusionController', 'DiffusionPathValidateController' => 'DiffusionController', 'DiffusionPhpExternalSymbolsSource' => 'DiffusionExternalSymbolsSource', 'DiffusionPreCommitContentAffectedFilesHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentAuthorHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentAuthorRawHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentBranchesHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentCommitterHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentCommitterRawHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentDiffContentAddedHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentDiffContentHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentDiffContentRemovedHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentDiffEnormousHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentHeraldField' => 'HeraldField', 'DiffusionPreCommitContentMergeHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentMessageHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentPusherHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentPusherIsCommitterHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentPusherProjectsHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRepositoryHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRepositoryProjectsHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRevisionAcceptedHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRevisionHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRevisionReviewersHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentRevisionSubscribersHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitRefChangeHeraldField' => 'DiffusionPreCommitRefHeraldField', 'DiffusionPreCommitRefHeraldField' => 'HeraldField', 'DiffusionPreCommitRefHeraldFieldGroup' => 'HeraldFieldGroup', 'DiffusionPreCommitRefNameHeraldField' => 'DiffusionPreCommitRefHeraldField', 'DiffusionPreCommitRefPusherHeraldField' => 'DiffusionPreCommitRefHeraldField', 'DiffusionPreCommitRefPusherProjectsHeraldField' => 'DiffusionPreCommitRefHeraldField', 'DiffusionPreCommitRefRepositoryHeraldField' => 'DiffusionPreCommitRefHeraldField', 'DiffusionPreCommitRefRepositoryProjectsHeraldField' => 'DiffusionPreCommitRefHeraldField', 'DiffusionPreCommitRefTypeHeraldField' => 'DiffusionPreCommitRefHeraldField', 'DiffusionPushCapability' => 'PhabricatorPolicyCapability', 'DiffusionPushEventViewController' => 'DiffusionPushLogController', 'DiffusionPushLogController' => 'DiffusionController', 'DiffusionPushLogListController' => 'DiffusionPushLogController', 'DiffusionPushLogListView' => 'AphrontView', 'DiffusionPythonExternalSymbolsSource' => 'DiffusionExternalSymbolsSource', 'DiffusionQuery' => 'PhabricatorQuery', 'DiffusionQueryCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryPathsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionRawDiffQuery' => 'DiffusionQuery', 'DiffusionRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionReadmeView' => 'DiffusionView', 'DiffusionRefNotFoundException' => 'Exception', 'DiffusionRefTableController' => 'DiffusionController', 'DiffusionRefsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionRenameHistoryQuery' => 'Phobject', 'DiffusionRepositoryByIDRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'DiffusionRepositoryController' => 'DiffusionController', 'DiffusionRepositoryCreateController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryDatasource' => 'PhabricatorTypeaheadDatasource', 'DiffusionRepositoryDefaultController' => 'DiffusionController', 'DiffusionRepositoryEditActionsController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditAutomationController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditBasicController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditBranchesController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditController' => 'DiffusionController', 'DiffusionRepositoryEditDangerousController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditDeleteController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditEncodingController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditHostingController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditMainController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditStagingController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditStorageController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditSubversionController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryListController' => 'DiffusionController', 'DiffusionRepositoryNewController' => 'DiffusionController', 'DiffusionRepositoryPath' => 'Phobject', 'DiffusionRepositoryRef' => 'Phobject', 'DiffusionRepositoryRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'DiffusionRepositorySymbolsController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryTag' => 'Phobject', 'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController', 'DiffusionRequest' => 'Phobject', 'DiffusionResolveRefsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionResolveUserQuery' => 'Phobject', 'DiffusionSSHWorkflow' => 'PhabricatorSSHWorkflow', 'DiffusionSearchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionServeController' => 'DiffusionController', 'DiffusionSetPasswordSettingsPanel' => 'PhabricatorSettingsPanel', 'DiffusionSetupException' => 'Exception', 'DiffusionSubversionSSHWorkflow' => 'DiffusionSSHWorkflow', 'DiffusionSubversionServeSSHWorkflow' => 'DiffusionSubversionSSHWorkflow', 'DiffusionSubversionWireProtocol' => 'Phobject', 'DiffusionSubversionWireProtocolTestCase' => 'PhabricatorTestCase', 'DiffusionSvnFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionSvnRawDiffQuery' => 'DiffusionRawDiffQuery', 'DiffusionSvnRequest' => 'DiffusionRequest', 'DiffusionSymbolController' => 'DiffusionController', 'DiffusionSymbolDatasource' => 'PhabricatorTypeaheadDatasource', 'DiffusionSymbolQuery' => 'PhabricatorOffsetPagedQuery', 'DiffusionTagListController' => 'DiffusionController', 'DiffusionTagListView' => 'DiffusionView', 'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionURITestCase' => 'PhutilTestCase', 'DiffusionUpdateCoverageConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionView' => 'AphrontView', 'DivinerArticleAtomizer' => 'DivinerAtomizer', 'DivinerAtom' => 'Phobject', 'DivinerAtomCache' => 'DivinerDiskCache', 'DivinerAtomController' => 'DivinerController', 'DivinerAtomListController' => 'DivinerController', 'DivinerAtomPHIDType' => 'PhabricatorPHIDType', 'DivinerAtomQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DivinerAtomRef' => 'Phobject', 'DivinerAtomSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DivinerAtomSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'DivinerAtomizeWorkflow' => 'DivinerWorkflow', 'DivinerAtomizer' => 'Phobject', 'DivinerBookController' => 'DivinerController', 'DivinerBookDatasource' => 'PhabricatorTypeaheadDatasource', 'DivinerBookEditController' => 'DivinerController', 'DivinerBookItemView' => 'AphrontTagView', 'DivinerBookPHIDType' => 'PhabricatorPHIDType', 'DivinerBookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DivinerBookSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'DivinerController' => 'PhabricatorController', 'DivinerDAO' => 'PhabricatorLiskDAO', 'DivinerDefaultEditCapability' => 'PhabricatorPolicyCapability', 'DivinerDefaultRenderer' => 'DivinerRenderer', 'DivinerDefaultViewCapability' => 'PhabricatorPolicyCapability', 'DivinerDiskCache' => 'Phobject', 'DivinerFileAtomizer' => 'DivinerAtomizer', 'DivinerFindController' => 'DivinerController', 'DivinerGenerateWorkflow' => 'DivinerWorkflow', 'DivinerLiveAtom' => 'DivinerDAO', 'DivinerLiveBook' => array( 'DivinerDAO', 'PhabricatorPolicyInterface', 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', 'PhabricatorApplicationTransactionInterface', ), 'DivinerLiveBookEditor' => 'PhabricatorApplicationTransactionEditor', 'DivinerLiveBookTransaction' => 'PhabricatorApplicationTransaction', 'DivinerLiveBookTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DivinerLivePublisher' => 'DivinerPublisher', 'DivinerLiveSymbol' => array( 'DivinerDAO', 'PhabricatorPolicyInterface', 'PhabricatorMarkupInterface', 'PhabricatorDestructibleInterface', ), 'DivinerMainController' => 'DivinerController', 'DivinerPHPAtomizer' => 'DivinerAtomizer', 'DivinerParameterTableView' => 'AphrontTagView', 'DivinerPublishCache' => 'DivinerDiskCache', 'DivinerPublisher' => 'Phobject', 'DivinerRenderer' => 'Phobject', 'DivinerReturnTableView' => 'AphrontTagView', 'DivinerSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DivinerSectionView' => 'AphrontTagView', 'DivinerStaticPublisher' => 'DivinerPublisher', 'DivinerSymbolRemarkupRule' => 'PhutilRemarkupRule', 'DivinerWorkflow' => 'PhabricatorManagementWorkflow', 'DoorkeeperAsanaFeedWorker' => 'DoorkeeperFeedWorker', 'DoorkeeperAsanaRemarkupRule' => 'DoorkeeperRemarkupRule', 'DoorkeeperBridge' => 'Phobject', 'DoorkeeperBridgeAsana' => 'DoorkeeperBridge', 'DoorkeeperBridgeJIRA' => 'DoorkeeperBridge', 'DoorkeeperBridgeJIRATestCase' => 'PhabricatorTestCase', 'DoorkeeperDAO' => 'PhabricatorLiskDAO', 'DoorkeeperExternalObject' => array( 'DoorkeeperDAO', 'PhabricatorPolicyInterface', ), 'DoorkeeperExternalObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DoorkeeperFeedStoryPublisher' => 'Phobject', 'DoorkeeperFeedWorker' => 'FeedPushWorker', 'DoorkeeperImportEngine' => 'Phobject', 'DoorkeeperJIRAFeedWorker' => 'DoorkeeperFeedWorker', 'DoorkeeperJIRARemarkupRule' => 'DoorkeeperRemarkupRule', 'DoorkeeperMissingLinkException' => 'Exception', 'DoorkeeperObjectRef' => 'Phobject', 'DoorkeeperRemarkupRule' => 'PhutilRemarkupRule', 'DoorkeeperSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DoorkeeperTagView' => 'AphrontView', 'DoorkeeperTagsController' => 'PhabricatorController', 'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation', 'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface', 'DrydockAuthorization' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), 'DrydockAuthorizationAuthorizeController' => 'DrydockController', 'DrydockAuthorizationListController' => 'DrydockController', 'DrydockAuthorizationListView' => 'AphrontView', 'DrydockAuthorizationPHIDType' => 'PhabricatorPHIDType', 'DrydockAuthorizationQuery' => 'DrydockQuery', 'DrydockAuthorizationSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockAuthorizationViewController' => 'DrydockController', 'DrydockBlueprint' => array( 'DrydockDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', ), 'DrydockBlueprintController' => 'DrydockController', 'DrydockBlueprintCoreCustomField' => array( 'DrydockBlueprintCustomField', 'PhabricatorStandardCustomFieldInterface', ), 'DrydockBlueprintCreateController' => 'DrydockBlueprintController', 'DrydockBlueprintCustomField' => 'PhabricatorCustomField', 'DrydockBlueprintDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockBlueprintDisableController' => 'DrydockBlueprintController', 'DrydockBlueprintEditController' => 'DrydockBlueprintController', 'DrydockBlueprintEditor' => 'PhabricatorApplicationTransactionEditor', 'DrydockBlueprintImplementation' => 'Phobject', 'DrydockBlueprintImplementationTestCase' => 'PhabricatorTestCase', 'DrydockBlueprintListController' => 'DrydockBlueprintController', 'DrydockBlueprintPHIDType' => 'PhabricatorPHIDType', 'DrydockBlueprintQuery' => 'DrydockQuery', 'DrydockBlueprintSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockBlueprintTransaction' => 'PhabricatorApplicationTransaction', 'DrydockBlueprintTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DrydockBlueprintViewController' => 'DrydockBlueprintController', 'DrydockCommand' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), 'DrydockCommandInterface' => 'DrydockInterface', 'DrydockCommandQuery' => 'DrydockQuery', 'DrydockConsoleController' => 'DrydockController', 'DrydockConstants' => 'Phobject', 'DrydockController' => 'PhabricatorController', 'DrydockCreateBlueprintsCapability' => 'PhabricatorPolicyCapability', 'DrydockDAO' => 'PhabricatorLiskDAO', 'DrydockDefaultEditCapability' => 'PhabricatorPolicyCapability', 'DrydockDefaultViewCapability' => 'PhabricatorPolicyCapability', 'DrydockFilesystemInterface' => 'DrydockInterface', 'DrydockInterface' => 'Phobject', 'DrydockLandRepositoryOperation' => 'DrydockRepositoryOperationType', 'DrydockLease' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), 'DrydockLeaseAcquiredLogType' => 'DrydockLogType', 'DrydockLeaseActivatedLogType' => 'DrydockLogType', 'DrydockLeaseActivationFailureLogType' => 'DrydockLogType', 'DrydockLeaseActivationYieldLogType' => 'DrydockLogType', 'DrydockLeaseController' => 'DrydockController', 'DrydockLeaseDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockLeaseDestroyedLogType' => 'DrydockLogType', 'DrydockLeaseListController' => 'DrydockLeaseController', 'DrydockLeaseListView' => 'AphrontView', 'DrydockLeaseNoAuthorizationsLogType' => 'DrydockLogType', 'DrydockLeaseNoBlueprintsLogType' => 'DrydockLogType', 'DrydockLeasePHIDType' => 'PhabricatorPHIDType', 'DrydockLeaseQuery' => 'DrydockQuery', 'DrydockLeaseQueuedLogType' => 'DrydockLogType', 'DrydockLeaseReleaseController' => 'DrydockLeaseController', 'DrydockLeaseReleasedLogType' => 'DrydockLogType', 'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockLeaseStatus' => 'DrydockConstants', 'DrydockLeaseUpdateWorker' => 'DrydockWorker', 'DrydockLeaseViewController' => 'DrydockLeaseController', 'DrydockLeaseWaitingForResourcesLogType' => 'DrydockLogType', 'DrydockLog' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), 'DrydockLogController' => 'DrydockController', 'DrydockLogGarbageCollector' => 'PhabricatorGarbageCollector', 'DrydockLogListController' => 'DrydockLogController', 'DrydockLogListView' => 'AphrontView', 'DrydockLogQuery' => 'DrydockQuery', 'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockLogType' => 'Phobject', 'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementReleaseLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementReleaseResourceWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementUpdateLeaseWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementUpdateResourceWorkflow' => 'DrydockManagementWorkflow', 'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow', 'DrydockObjectAuthorizationView' => 'AphrontView', 'DrydockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'DrydockRepositoryOperation' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), 'DrydockRepositoryOperationListController' => 'DrydockController', 'DrydockRepositoryOperationPHIDType' => 'PhabricatorPHIDType', 'DrydockRepositoryOperationQuery' => 'DrydockQuery', 'DrydockRepositoryOperationSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockRepositoryOperationStatusController' => 'DrydockController', 'DrydockRepositoryOperationStatusView' => 'AphrontView', 'DrydockRepositoryOperationType' => 'Phobject', 'DrydockRepositoryOperationUpdateWorker' => 'DrydockWorker', 'DrydockRepositoryOperationViewController' => 'DrydockController', 'DrydockResource' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), 'DrydockResourceActivationFailureLogType' => 'DrydockLogType', 'DrydockResourceActivationYieldLogType' => 'DrydockLogType', 'DrydockResourceController' => 'DrydockController', 'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockResourceListController' => 'DrydockResourceController', 'DrydockResourceListView' => 'AphrontView', 'DrydockResourcePHIDType' => 'PhabricatorPHIDType', 'DrydockResourceQuery' => 'DrydockQuery', 'DrydockResourceReleaseController' => 'DrydockResourceController', 'DrydockResourceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'DrydockResourceStatus' => 'DrydockConstants', 'DrydockResourceUpdateWorker' => 'DrydockWorker', 'DrydockResourceViewController' => 'DrydockResourceController', 'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface', 'DrydockSSHCommandInterface' => 'DrydockCommandInterface', 'DrydockSlotLock' => 'DrydockDAO', 'DrydockSlotLockException' => 'Exception', 'DrydockSlotLockFailureLogType' => 'DrydockLogType', 'DrydockTestRepositoryOperation' => 'DrydockRepositoryOperationType', 'DrydockWebrootInterface' => 'DrydockInterface', 'DrydockWorker' => 'PhabricatorWorker', 'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation', 'FeedConduitAPIMethod' => 'ConduitAPIMethod', 'FeedPublishConduitAPIMethod' => 'FeedConduitAPIMethod', 'FeedPublisherHTTPWorker' => 'FeedPushWorker', 'FeedPublisherWorker' => 'FeedPushWorker', 'FeedPushWorker' => 'PhabricatorWorker', 'FeedQueryConduitAPIMethod' => 'FeedConduitAPIMethod', 'FeedStoryNotificationGarbageCollector' => 'PhabricatorGarbageCollector', 'FileAllocateConduitAPIMethod' => 'FileConduitAPIMethod', 'FileConduitAPIMethod' => 'ConduitAPIMethod', 'FileCreateMailReceiver' => 'PhabricatorMailReceiver', 'FileDownloadConduitAPIMethod' => 'FileConduitAPIMethod', 'FileInfoConduitAPIMethod' => 'FileConduitAPIMethod', 'FileMailReceiver' => 'PhabricatorObjectMailReceiver', 'FileQueryChunksConduitAPIMethod' => 'FileConduitAPIMethod', 'FileReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'FileUploadChunkConduitAPIMethod' => 'FileConduitAPIMethod', 'FileUploadConduitAPIMethod' => 'FileConduitAPIMethod', 'FileUploadHashConduitAPIMethod' => 'FileConduitAPIMethod', 'FilesDefaultViewCapability' => 'PhabricatorPolicyCapability', 'FlagConduitAPIMethod' => 'ConduitAPIMethod', 'FlagDeleteConduitAPIMethod' => 'FlagConduitAPIMethod', 'FlagEditConduitAPIMethod' => 'FlagConduitAPIMethod', 'FlagQueryConduitAPIMethod' => 'FlagConduitAPIMethod', 'FundBacker' => array( 'FundDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', ), 'FundBackerCart' => 'PhortuneCartImplementation', 'FundBackerEditor' => 'PhabricatorApplicationTransactionEditor', 'FundBackerListController' => 'FundController', 'FundBackerPHIDType' => 'PhabricatorPHIDType', 'FundBackerProduct' => 'PhortuneProductImplementation', 'FundBackerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'FundBackerSearchEngine' => 'PhabricatorApplicationSearchEngine', 'FundBackerTransaction' => 'PhabricatorApplicationTransaction', 'FundBackerTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'FundController' => 'PhabricatorController', 'FundCreateInitiativesCapability' => 'PhabricatorPolicyCapability', 'FundDAO' => 'PhabricatorLiskDAO', 'FundDefaultViewCapability' => 'PhabricatorPolicyCapability', 'FundInitiative' => array( 'FundDAO', 'PhabricatorPolicyInterface', 'PhabricatorProjectInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorSubscribableInterface', 'PhabricatorMentionableInterface', 'PhabricatorFlaggableInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorDestructibleInterface', ), 'FundInitiativeBackController' => 'FundController', 'FundInitiativeCloseController' => 'FundController', 'FundInitiativeEditController' => 'FundController', 'FundInitiativeEditor' => 'PhabricatorApplicationTransactionEditor', 'FundInitiativeIndexer' => 'PhabricatorSearchDocumentIndexer', 'FundInitiativeListController' => 'FundController', 'FundInitiativePHIDType' => 'PhabricatorPHIDType', 'FundInitiativeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'FundInitiativeRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'FundInitiativeReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'FundInitiativeSearchEngine' => 'PhabricatorApplicationSearchEngine', 'FundInitiativeTransaction' => 'PhabricatorApplicationTransaction', 'FundInitiativeTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'FundInitiativeViewController' => 'FundController', 'FundSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'HarbormasterArcLintBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterArcUnitBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterArtifact' => 'Phobject', 'HarbormasterAutotargetsTestCase' => 'PhabricatorTestCase', 'HarbormasterBuild' => array( 'HarbormasterDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), 'HarbormasterBuildAbortedException' => 'Exception', 'HarbormasterBuildActionController' => 'HarbormasterController', 'HarbormasterBuildArcanistAutoplan' => 'HarbormasterBuildAutoplan', 'HarbormasterBuildArtifact' => array( 'HarbormasterDAO', 'PhabricatorPolicyInterface', ), 'HarbormasterBuildArtifactPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildAutoplan' => 'Phobject', 'HarbormasterBuildCommand' => 'HarbormasterDAO', 'HarbormasterBuildDependencyDatasource' => 'PhabricatorTypeaheadDatasource', 'HarbormasterBuildEngine' => 'Phobject', 'HarbormasterBuildFailureException' => 'Exception', 'HarbormasterBuildGraph' => 'AbstractDirectedGraph', 'HarbormasterBuildLintMessage' => 'HarbormasterDAO', 'HarbormasterBuildLog' => array( 'HarbormasterDAO', 'PhabricatorPolicyInterface', ), 'HarbormasterBuildLogPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildMessage' => array( 'HarbormasterDAO', 'PhabricatorPolicyInterface', ), 'HarbormasterBuildMessageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildPlan' => array( 'HarbormasterDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorSubscribableInterface', ), 'HarbormasterBuildPlanDatasource' => 'PhabricatorTypeaheadDatasource', 'HarbormasterBuildPlanDefaultEditCapability' => 'PhabricatorPolicyCapability', 'HarbormasterBuildPlanDefaultViewCapability' => 'PhabricatorPolicyCapability', 'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor', 'HarbormasterBuildPlanPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildPlanSearchEngine' => 'PhabricatorApplicationSearchEngine', 'HarbormasterBuildPlanTransaction' => 'PhabricatorApplicationTransaction', 'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildRequest' => 'Phobject', 'HarbormasterBuildStep' => array( 'HarbormasterDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', ), 'HarbormasterBuildStepCoreCustomField' => array( 'HarbormasterBuildStepCustomField', 'PhabricatorStandardCustomFieldInterface', ), 'HarbormasterBuildStepCustomField' => 'PhabricatorCustomField', 'HarbormasterBuildStepEditor' => 'PhabricatorApplicationTransactionEditor', 'HarbormasterBuildStepGroup' => 'Phobject', 'HarbormasterBuildStepImplementation' => 'Phobject', 'HarbormasterBuildStepImplementationTestCase' => 'PhabricatorTestCase', 'HarbormasterBuildStepPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildStepTransaction' => 'PhabricatorApplicationTransaction', 'HarbormasterBuildStepTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'HarbormasterBuildTarget' => array( 'HarbormasterDAO', 'PhabricatorPolicyInterface', ), 'HarbormasterBuildTargetPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildTransaction' => 'PhabricatorApplicationTransaction', 'HarbormasterBuildTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'HarbormasterBuildTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'HarbormasterBuildUnitMessage' => 'HarbormasterDAO', 'HarbormasterBuildViewController' => 'HarbormasterController', 'HarbormasterBuildWorker' => 'HarbormasterWorker', 'HarbormasterBuildable' => array( 'HarbormasterDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'HarbormasterBuildableInterface', ), 'HarbormasterBuildableActionController' => 'HarbormasterController', 'HarbormasterBuildableListController' => 'HarbormasterController', 'HarbormasterBuildablePHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildableSearchEngine' => 'PhabricatorApplicationSearchEngine', 'HarbormasterBuildableTransaction' => 'PhabricatorApplicationTransaction', 'HarbormasterBuildableTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'HarbormasterBuildableTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'HarbormasterBuildableViewController' => 'HarbormasterController', 'HarbormasterBuiltinBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod', 'HarbormasterController' => 'PhabricatorController', 'HarbormasterCreateArtifactConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterCreatePlansCapability' => 'PhabricatorPolicyCapability', 'HarbormasterDAO' => 'PhabricatorLiskDAO', 'HarbormasterDrydockBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterDrydockCommandBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterDrydockLeaseArtifact' => 'HarbormasterArtifact', 'HarbormasterExecFuture' => 'Future', 'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterFileArtifact' => 'HarbormasterArtifact', 'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterHostArtifact' => 'HarbormasterDrydockLeaseArtifact', 'HarbormasterLeaseHostBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterLintMessagesController' => 'HarbormasterController', 'HarbormasterLintPropertyView' => 'AphrontView', 'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow', 'HarbormasterManagementUpdateWorkflow' => 'HarbormasterManagementWorkflow', 'HarbormasterManagementWorkflow' => 'PhabricatorManagementWorkflow', 'HarbormasterMessageType' => 'Phobject', 'HarbormasterObject' => 'HarbormasterDAO', 'HarbormasterOtherBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterPlanController' => 'HarbormasterController', 'HarbormasterPlanDisableController' => 'HarbormasterPlanController', 'HarbormasterPlanEditController' => 'HarbormasterPlanController', 'HarbormasterPlanListController' => 'HarbormasterPlanController', 'HarbormasterPlanRunController' => 'HarbormasterController', 'HarbormasterPlanViewController' => 'HarbormasterPlanController', 'HarbormasterPrototypeBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterPublishFragmentBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterQueryAutotargetsConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterQueryBuildablesConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterQueryBuildsConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'HarbormasterRunBuildPlansHeraldAction' => 'HeraldAction', 'HarbormasterSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'HarbormasterScratchTable' => 'HarbormasterDAO', 'HarbormasterSendMessageConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterSleepBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterStepAddController' => 'HarbormasterController', 'HarbormasterStepDeleteController' => 'HarbormasterController', 'HarbormasterStepEditController' => 'HarbormasterController', 'HarbormasterStepViewController' => 'HarbormasterController', 'HarbormasterTargetEngine' => 'Phobject', 'HarbormasterTargetWorker' => 'HarbormasterWorker', 'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation', 'HarbormasterUIEventListener' => 'PhabricatorEventListener', 'HarbormasterURIArtifact' => 'HarbormasterArtifact', 'HarbormasterUnitMessagesController' => 'HarbormasterController', 'HarbormasterUnitPropertyView' => 'AphrontView', 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterWorker' => 'PhabricatorWorker', 'HarbormasterWorkingCopyArtifact' => 'HarbormasterDrydockLeaseArtifact', 'HeraldAction' => 'Phobject', 'HeraldActionGroup' => 'HeraldGroup', 'HeraldActionRecord' => 'HeraldDAO', 'HeraldAdapter' => 'Phobject', 'HeraldAlwaysField' => 'HeraldField', 'HeraldAnotherRuleField' => 'HeraldField', 'HeraldApplicationActionGroup' => 'HeraldActionGroup', 'HeraldApplyTranscript' => 'Phobject', 'HeraldBasicFieldGroup' => 'HeraldFieldGroup', 'HeraldCommitAdapter' => array( 'HeraldAdapter', 'HarbormasterBuildableAdapterInterface', ), 'HeraldCondition' => 'HeraldDAO', 'HeraldConditionTranscript' => 'Phobject', 'HeraldContentSourceField' => 'HeraldField', 'HeraldController' => 'PhabricatorController', 'HeraldDAO' => 'PhabricatorLiskDAO', 'HeraldDifferentialAdapter' => 'HeraldAdapter', 'HeraldDifferentialDiffAdapter' => 'HeraldDifferentialAdapter', 'HeraldDifferentialRevisionAdapter' => array( 'HeraldDifferentialAdapter', 'HarbormasterBuildableAdapterInterface', ), 'HeraldDisableController' => 'HeraldController', 'HeraldDoNothingAction' => 'HeraldAction', 'HeraldEditFieldGroup' => 'HeraldFieldGroup', 'HeraldEffect' => 'Phobject', 'HeraldEmptyFieldValue' => 'HeraldFieldValue', 'HeraldEngine' => 'Phobject', 'HeraldField' => 'Phobject', 'HeraldFieldGroup' => 'HeraldGroup', 'HeraldFieldTestCase' => 'PhutilTestCase', 'HeraldFieldValue' => 'Phobject', 'HeraldGroup' => 'Phobject', 'HeraldInvalidActionException' => 'Exception', 'HeraldInvalidConditionException' => 'Exception', 'HeraldManageGlobalRulesCapability' => 'PhabricatorPolicyCapability', 'HeraldManiphestTaskAdapter' => 'HeraldAdapter', 'HeraldNewController' => 'HeraldController', 'HeraldNewObjectField' => 'HeraldField', 'HeraldNotifyActionGroup' => 'HeraldActionGroup', 'HeraldObjectTranscript' => 'Phobject', 'HeraldPholioMockAdapter' => 'HeraldAdapter', 'HeraldPonderQuestionAdapter' => 'HeraldAdapter', 'HeraldPreCommitAdapter' => 'HeraldAdapter', 'HeraldPreCommitContentAdapter' => 'HeraldPreCommitAdapter', 'HeraldPreCommitRefAdapter' => 'HeraldPreCommitAdapter', 'HeraldPreventActionGroup' => 'HeraldActionGroup', 'HeraldProjectsField' => 'HeraldField', 'HeraldRecursiveConditionsException' => 'Exception', 'HeraldRelatedFieldGroup' => 'HeraldFieldGroup', 'HeraldRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'HeraldRepetitionPolicyConfig' => 'Phobject', 'HeraldRule' => array( 'HeraldDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFlaggableInterface', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', 'PhabricatorSubscribableInterface', ), 'HeraldRuleController' => 'HeraldController', 'HeraldRuleEditor' => 'PhabricatorApplicationTransactionEditor', 'HeraldRuleListController' => 'HeraldController', 'HeraldRulePHIDType' => 'PhabricatorPHIDType', 'HeraldRuleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HeraldRuleSearchEngine' => 'PhabricatorApplicationSearchEngine', 'HeraldRuleTestCase' => 'PhabricatorTestCase', 'HeraldRuleTransaction' => 'PhabricatorApplicationTransaction', 'HeraldRuleTransactionComment' => 'PhabricatorApplicationTransactionComment', 'HeraldRuleTranscript' => 'Phobject', 'HeraldRuleTypeConfig' => 'Phobject', 'HeraldRuleViewController' => 'HeraldController', 'HeraldSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'HeraldSelectFieldValue' => 'HeraldFieldValue', 'HeraldSpaceField' => 'HeraldField', 'HeraldSubscribersField' => 'HeraldField', 'HeraldSupportActionGroup' => 'HeraldActionGroup', 'HeraldSupportFieldGroup' => 'HeraldFieldGroup', 'HeraldTestConsoleController' => 'HeraldController', 'HeraldTextFieldValue' => 'HeraldFieldValue', 'HeraldTokenizerFieldValue' => 'HeraldFieldValue', 'HeraldTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'HeraldTranscript' => array( 'HeraldDAO', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), 'HeraldTranscriptController' => 'HeraldController', 'HeraldTranscriptGarbageCollector' => 'PhabricatorGarbageCollector', 'HeraldTranscriptListController' => 'HeraldController', 'HeraldTranscriptQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HeraldTranscriptSearchEngine' => 'PhabricatorApplicationSearchEngine', 'HeraldTranscriptTestCase' => 'PhabricatorTestCase', 'HeraldUtilityActionGroup' => 'HeraldActionGroup', 'Javelin' => 'Phobject', 'JavelinReactorUIExample' => 'PhabricatorUIExample', 'JavelinUIExample' => 'PhabricatorUIExample', 'JavelinViewExampleServerView' => 'AphrontView', 'JavelinViewUIExample' => 'PhabricatorUIExample', 'LegalpadController' => 'PhabricatorController', 'LegalpadCreateDocumentsCapability' => 'PhabricatorPolicyCapability', 'LegalpadDAO' => 'PhabricatorLiskDAO', 'LegalpadDefaultEditCapability' => 'PhabricatorPolicyCapability', 'LegalpadDefaultViewCapability' => 'PhabricatorPolicyCapability', 'LegalpadDocument' => array( 'LegalpadDAO', 'PhabricatorPolicyInterface', 'PhabricatorSubscribableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', ), 'LegalpadDocumentBody' => array( 'LegalpadDAO', 'PhabricatorMarkupInterface', ), 'LegalpadDocumentCommentController' => 'LegalpadController', 'LegalpadDocumentDatasource' => 'PhabricatorTypeaheadDatasource', 'LegalpadDocumentDoneController' => 'LegalpadController', 'LegalpadDocumentEditController' => 'LegalpadController', 'LegalpadDocumentEditor' => 'PhabricatorApplicationTransactionEditor', 'LegalpadDocumentListController' => 'LegalpadController', 'LegalpadDocumentManageController' => 'LegalpadController', 'LegalpadDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'LegalpadDocumentRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'LegalpadDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine', 'LegalpadDocumentSignController' => 'LegalpadController', 'LegalpadDocumentSignature' => array( 'LegalpadDAO', 'PhabricatorPolicyInterface', ), 'LegalpadDocumentSignatureAddController' => 'LegalpadController', 'LegalpadDocumentSignatureListController' => 'LegalpadController', 'LegalpadDocumentSignatureQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'LegalpadDocumentSignatureSearchEngine' => 'PhabricatorApplicationSearchEngine', 'LegalpadDocumentSignatureVerificationController' => 'LegalpadController', 'LegalpadDocumentSignatureViewController' => 'LegalpadController', 'LegalpadMailReceiver' => 'PhabricatorObjectMailReceiver', 'LegalpadObjectNeedsSignatureEdgeType' => 'PhabricatorEdgeType', 'LegalpadReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'LegalpadRequireSignatureHeraldAction' => 'HeraldAction', 'LegalpadSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'LegalpadSignatureNeededByObjectEdgeType' => 'PhabricatorEdgeType', 'LegalpadTransaction' => 'PhabricatorApplicationTransaction', 'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment', 'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'LegalpadTransactionView' => 'PhabricatorApplicationTransactionView', 'LiskChunkTestCase' => 'PhabricatorTestCase', 'LiskDAO' => 'Phobject', 'LiskDAOSet' => 'Phobject', 'LiskDAOTestCase' => 'PhabricatorTestCase', 'LiskEphemeralObjectException' => 'Exception', 'LiskFixtureTestCase' => 'PhabricatorTestCase', 'LiskIsolationTestCase' => 'PhabricatorTestCase', 'LiskIsolationTestDAO' => 'LiskDAO', 'LiskIsolationTestDAOException' => 'Exception', 'LiskMigrationIterator' => 'PhutilBufferedIterator', 'LiskRawMigrationIterator' => 'PhutilBufferedIterator', 'MacroConduitAPIMethod' => 'ConduitAPIMethod', 'MacroCreateMemeConduitAPIMethod' => 'MacroConduitAPIMethod', 'MacroQueryConduitAPIMethod' => 'MacroConduitAPIMethod', 'ManiphestAssignEmailCommand' => 'ManiphestEmailCommand', 'ManiphestAssigneeDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'ManiphestBatchEditController' => 'ManiphestController', 'ManiphestBulkEditCapability' => 'PhabricatorPolicyCapability', 'ManiphestClaimEmailCommand' => 'ManiphestEmailCommand', 'ManiphestCloseEmailCommand' => 'ManiphestEmailCommand', 'ManiphestConduitAPIMethod' => 'ConduitAPIMethod', 'ManiphestConfiguredCustomField' => array( 'ManiphestCustomField', 'PhabricatorStandardCustomFieldInterface', ), 'ManiphestConstants' => 'Phobject', 'ManiphestController' => 'PhabricatorController', 'ManiphestCreateMailReceiver' => 'PhabricatorMailReceiver', 'ManiphestCreateTaskConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestCustomField' => 'PhabricatorCustomField', 'ManiphestCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 'ManiphestCustomFieldStatusParser' => 'PhabricatorCustomFieldMonogramParser', 'ManiphestCustomFieldStatusParserTestCase' => 'PhabricatorTestCase', 'ManiphestCustomFieldStorage' => 'PhabricatorCustomFieldStorage', 'ManiphestCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 'ManiphestDAO' => 'PhabricatorLiskDAO', 'ManiphestDefaultEditCapability' => 'PhabricatorPolicyCapability', 'ManiphestDefaultViewCapability' => 'PhabricatorPolicyCapability', 'ManiphestEditAssignCapability' => 'PhabricatorPolicyCapability', 'ManiphestEditPoliciesCapability' => 'PhabricatorPolicyCapability', 'ManiphestEditPriorityCapability' => 'PhabricatorPolicyCapability', 'ManiphestEditProjectsCapability' => 'PhabricatorPolicyCapability', 'ManiphestEditStatusCapability' => 'PhabricatorPolicyCapability', 'ManiphestEmailCommand' => 'MetaMTAEmailTransactionCommand', 'ManiphestExcelDefaultFormat' => 'ManiphestExcelFormat', 'ManiphestExcelFormat' => 'Phobject', 'ManiphestExcelFormatTestCase' => 'PhabricatorTestCase', 'ManiphestExportController' => 'ManiphestController', 'ManiphestGetTaskTransactionsConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestHovercardEventListener' => 'PhabricatorEventListener', 'ManiphestInfoConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestNameIndex' => 'ManiphestDAO', 'ManiphestNameIndexEventListener' => 'PhabricatorEventListener', 'ManiphestPriorityEmailCommand' => 'ManiphestEmailCommand', 'ManiphestQueryConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestQueryStatusesConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ManiphestReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'ManiphestReportController' => 'ManiphestController', 'ManiphestSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'ManiphestSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'ManiphestStatusConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'ManiphestStatusEmailCommand' => 'ManiphestEmailCommand', 'ManiphestSubpriorityController' => 'ManiphestController', 'ManiphestTask' => array( 'ManiphestDAO', 'PhabricatorSubscribableInterface', 'PhabricatorMarkupInterface', 'PhabricatorPolicyInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorFlaggableInterface', 'PhabricatorMentionableInterface', 'PhrequentTrackableInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorDestructibleInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorProjectInterface', 'PhabricatorSpacesInterface', ), 'ManiphestTaskAssignHeraldAction' => 'HeraldAction', 'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction', 'ManiphestTaskAssignSelfHeraldAction' => 'ManiphestTaskAssignHeraldAction', 'ManiphestTaskAssigneeHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskAuthorHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskAuthorPolicyRule' => 'PhabricatorPolicyRule', 'ManiphestTaskClosedStatusDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskDependedOnByTaskEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskDependsOnTaskEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskDescriptionHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskDetailController' => 'ManiphestController', 'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskHasMockEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskHasRevisionEdgeType' => 'PhabricatorEdgeType', 'ManiphestTaskHeraldField' => 'HeraldField', 'ManiphestTaskHeraldFieldGroup' => 'HeraldFieldGroup', 'ManiphestTaskListController' => 'ManiphestController', 'ManiphestTaskListView' => 'ManiphestView', 'ManiphestTaskMailReceiver' => 'PhabricatorObjectMailReceiver', 'ManiphestTaskOpenStatusDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskPHIDType' => 'PhabricatorPHIDType', 'ManiphestTaskPriority' => 'ManiphestConstants', 'ManiphestTaskPriorityDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskPriorityHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ManiphestTaskResultListView' => 'ManiphestView', 'ManiphestTaskSearchEngine' => 'PhabricatorApplicationSearchEngine', 'ManiphestTaskStatus' => 'ManiphestConstants', 'ManiphestTaskStatusDatasource' => 'PhabricatorTypeaheadDatasource', 'ManiphestTaskStatusFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'ManiphestTaskStatusHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTaskStatusTestCase' => 'PhabricatorTestCase', 'ManiphestTaskTestCase' => 'PhabricatorTestCase', 'ManiphestTaskTitleHeraldField' => 'ManiphestTaskHeraldField', 'ManiphestTransaction' => 'PhabricatorApplicationTransaction', 'ManiphestTransactionComment' => 'PhabricatorApplicationTransactionComment', 'ManiphestTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'ManiphestTransactionPreviewController' => 'ManiphestController', 'ManiphestTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'ManiphestTransactionSaveController' => 'ManiphestController', 'ManiphestUpdateConduitAPIMethod' => 'ManiphestConduitAPIMethod', 'ManiphestView' => 'AphrontView', 'MetaMTAEmailTransactionCommand' => 'Phobject', 'MetaMTAEmailTransactionCommandTestCase' => 'PhabricatorTestCase', 'MetaMTAMailReceivedGarbageCollector' => 'PhabricatorGarbageCollector', 'MetaMTAMailSentGarbageCollector' => 'PhabricatorGarbageCollector', 'MetaMTAReceivedMailStatus' => 'Phobject', 'MultimeterContext' => 'MultimeterDimension', 'MultimeterControl' => 'Phobject', 'MultimeterController' => 'PhabricatorController', 'MultimeterDAO' => 'PhabricatorLiskDAO', 'MultimeterDimension' => 'MultimeterDAO', 'MultimeterEvent' => 'MultimeterDAO', 'MultimeterEventGarbageCollector' => 'PhabricatorGarbageCollector', 'MultimeterHost' => 'MultimeterDimension', 'MultimeterLabel' => 'MultimeterDimension', 'MultimeterSampleController' => 'MultimeterController', 'MultimeterViewer' => 'MultimeterDimension', 'NuanceConduitAPIMethod' => 'ConduitAPIMethod', 'NuanceConsoleController' => 'NuanceController', 'NuanceController' => 'PhabricatorController', 'NuanceCreateItemConduitAPIMethod' => 'NuanceConduitAPIMethod', 'NuanceDAO' => 'PhabricatorLiskDAO', 'NuanceItem' => array( 'NuanceDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', ), 'NuanceItemEditController' => 'NuanceController', 'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceItemPHIDType' => 'PhabricatorPHIDType', 'NuanceItemQuery' => 'NuanceQuery', 'NuanceItemTransaction' => 'NuanceTransaction', 'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'NuanceItemViewController' => 'NuanceController', 'NuancePhabricatorFormSourceDefinition' => 'NuanceSourceDefinition', 'NuanceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'NuanceQueue' => array( 'NuanceDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', ), 'NuanceQueueDatasource' => 'PhabricatorTypeaheadDatasource', 'NuanceQueueEditController' => 'NuanceController', 'NuanceQueueEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceQueueListController' => 'NuanceController', 'NuanceQueuePHIDType' => 'PhabricatorPHIDType', 'NuanceQueueQuery' => 'NuanceQuery', 'NuanceQueueSearchEngine' => 'PhabricatorApplicationSearchEngine', 'NuanceQueueTransaction' => 'NuanceTransaction', 'NuanceQueueTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceQueueTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'NuanceQueueViewController' => 'NuanceController', 'NuanceRequestor' => array( 'NuanceDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', ), 'NuanceRequestorEditController' => 'NuanceController', 'NuanceRequestorEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceRequestorPHIDType' => 'PhabricatorPHIDType', 'NuanceRequestorQuery' => 'NuanceQuery', 'NuanceRequestorSource' => 'NuanceDAO', 'NuanceRequestorTransaction' => 'NuanceTransaction', 'NuanceRequestorTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceRequestorTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'NuanceRequestorViewController' => 'NuanceController', 'NuanceSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'NuanceSource' => array( 'NuanceDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), 'NuanceSourceActionController' => 'NuanceController', 'NuanceSourceCreateController' => 'NuanceController', 'NuanceSourceDefaultEditCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceDefaultViewCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceDefinition' => 'Phobject', 'NuanceSourceDefinitionTestCase' => 'PhabricatorTestCase', 'NuanceSourceEditController' => 'NuanceController', 'NuanceSourceEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceSourceListController' => 'NuanceController', 'NuanceSourceManageCapability' => 'PhabricatorPolicyCapability', 'NuanceSourcePHIDType' => 'PhabricatorPHIDType', 'NuanceSourceQuery' => 'NuanceQuery', 'NuanceSourceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'NuanceSourceTransaction' => 'NuanceTransaction', 'NuanceSourceTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceSourceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'NuanceSourceViewController' => 'NuanceController', 'NuanceTransaction' => 'PhabricatorApplicationTransaction', 'OwnersConduitAPIMethod' => 'ConduitAPIMethod', + 'OwnersEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'OwnersPackageReplyHandler' => 'PhabricatorMailReplyHandler', 'OwnersQueryConduitAPIMethod' => 'OwnersConduitAPIMethod', 'PHIDConduitAPIMethod' => 'ConduitAPIMethod', 'PHIDInfoConduitAPIMethod' => 'PHIDConduitAPIMethod', 'PHIDLookupConduitAPIMethod' => 'PHIDConduitAPIMethod', 'PHIDQueryConduitAPIMethod' => 'PHIDConduitAPIMethod', 'PHUI' => 'Phobject', 'PHUIActionPanelExample' => 'PhabricatorUIExample', 'PHUIActionPanelView' => 'AphrontTagView', 'PHUIApplicationMenuView' => 'Phobject', 'PHUIBadgeBoxView' => 'AphrontTagView', 'PHUIBadgeExample' => 'PhabricatorUIExample', 'PHUIBadgeMiniView' => 'AphrontTagView', 'PHUIBadgeView' => 'AphrontTagView', 'PHUIBoxExample' => 'PhabricatorUIExample', 'PHUIBoxView' => 'AphrontTagView', 'PHUIButtonBarExample' => 'PhabricatorUIExample', 'PHUIButtonBarView' => 'AphrontTagView', 'PHUIButtonExample' => 'PhabricatorUIExample', 'PHUIButtonView' => 'AphrontTagView', 'PHUICalendarDayView' => 'AphrontView', 'PHUICalendarListView' => 'AphrontTagView', 'PHUICalendarMonthView' => 'AphrontView', 'PHUICalendarWidgetView' => 'AphrontTagView', 'PHUIColorPalletteExample' => 'PhabricatorUIExample', 'PHUICrumbView' => 'AphrontView', 'PHUICrumbsView' => 'AphrontView', 'PHUIDiffInlineCommentDetailView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentEditView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentRowScaffold' => 'AphrontView', 'PHUIDiffInlineCommentTableScaffold' => 'AphrontView', 'PHUIDiffInlineCommentUndoView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentView' => 'AphrontView', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDiffRevealIconView' => 'AphrontView', 'PHUIDiffTableOfContentsItemView' => 'AphrontView', 'PHUIDiffTableOfContentsListView' => 'AphrontView', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDocumentExample' => 'PhabricatorUIExample', + 'PHUIDocumentSummaryView' => 'AphrontTagView', 'PHUIDocumentView' => 'AphrontTagView', 'PHUIDocumentViewPro' => 'AphrontTagView', 'PHUIFeedStoryExample' => 'PhabricatorUIExample', 'PHUIFeedStoryView' => 'AphrontView', 'PHUIFormDividerControl' => 'AphrontFormControl', 'PHUIFormFreeformDateControl' => 'AphrontFormControl', 'PHUIFormInsetView' => 'AphrontView', 'PHUIFormLayoutView' => 'AphrontView', 'PHUIFormMultiSubmitControl' => 'AphrontFormControl', 'PHUIFormPageView' => 'AphrontView', 'PHUIHandleListView' => 'AphrontTagView', 'PHUIHandleTagListView' => 'AphrontTagView', 'PHUIHandleView' => 'AphrontView', 'PHUIHeaderView' => 'AphrontTagView', 'PHUIIconExample' => 'PhabricatorUIExample', 'PHUIIconView' => 'AphrontTagView', 'PHUIImageMaskExample' => 'PhabricatorUIExample', 'PHUIImageMaskView' => 'AphrontTagView', 'PHUIInfoExample' => 'PhabricatorUIExample', 'PHUIInfoPanelExample' => 'PhabricatorUIExample', 'PHUIInfoPanelView' => 'AphrontView', 'PHUIInfoView' => 'AphrontView', 'PHUIListExample' => 'PhabricatorUIExample', 'PHUIListItemView' => 'AphrontTagView', 'PHUIListView' => 'AphrontTagView', 'PHUIListViewTestCase' => 'PhabricatorTestCase', 'PHUIObjectBoxView' => 'AphrontView', 'PHUIObjectItemListExample' => 'PhabricatorUIExample', 'PHUIObjectItemListView' => 'AphrontTagView', 'PHUIObjectItemView' => 'AphrontTagView', 'PHUIPagedFormView' => 'AphrontView', 'PHUIPagerView' => 'AphrontView', 'PHUIPinboardItemView' => 'AphrontView', 'PHUIPinboardView' => 'AphrontView', 'PHUIPropertyGroupView' => 'AphrontTagView', 'PHUIPropertyListExample' => 'PhabricatorUIExample', 'PHUIPropertyListView' => 'AphrontView', 'PHUIRemarkupPreviewPanel' => 'AphrontTagView', 'PHUIRemarkupView' => 'AphrontView', 'PHUISpacesNamespaceContextView' => 'AphrontView', 'PHUIStatusItemView' => 'AphrontTagView', 'PHUIStatusListView' => 'AphrontTagView', 'PHUITagExample' => 'PhabricatorUIExample', 'PHUITagView' => 'AphrontTagView', 'PHUITextExample' => 'PhabricatorUIExample', 'PHUITextView' => 'AphrontTagView', 'PHUITimelineEventView' => 'AphrontView', 'PHUITimelineExample' => 'PhabricatorUIExample', 'PHUITimelineView' => 'AphrontView', 'PHUITwoColumnView' => 'AphrontTagView', 'PHUITypeaheadExample' => 'PhabricatorUIExample', 'PHUIWorkboardView' => 'AphrontTagView', 'PHUIWorkpanelView' => 'AphrontTagView', 'PassphraseAbstractKey' => 'Phobject', 'PassphraseConduitAPIMethod' => 'ConduitAPIMethod', 'PassphraseController' => 'PhabricatorController', 'PassphraseCredential' => array( 'PassphraseDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorFlaggableInterface', 'PhabricatorSubscribableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', ), 'PassphraseCredentialAuthorPolicyRule' => 'PhabricatorPolicyRule', 'PassphraseCredentialConduitController' => 'PassphraseController', 'PassphraseCredentialControl' => 'AphrontFormControl', 'PassphraseCredentialCreateController' => 'PassphraseController', 'PassphraseCredentialDestroyController' => 'PassphraseController', 'PassphraseCredentialEditController' => 'PassphraseController', 'PassphraseCredentialListController' => 'PassphraseController', 'PassphraseCredentialLockController' => 'PassphraseController', 'PassphraseCredentialPHIDType' => 'PhabricatorPHIDType', 'PassphraseCredentialPublicController' => 'PassphraseController', 'PassphraseCredentialQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PassphraseCredentialRevealController' => 'PassphraseController', 'PassphraseCredentialSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PassphraseCredentialTransaction' => 'PhabricatorApplicationTransaction', 'PassphraseCredentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PassphraseCredentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PassphraseCredentialType' => 'Phobject', 'PassphraseCredentialTypeTestCase' => 'PhabricatorTestCase', 'PassphraseCredentialViewController' => 'PassphraseController', 'PassphraseDAO' => 'PhabricatorLiskDAO', 'PassphraseDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PassphraseDefaultViewCapability' => 'PhabricatorPolicyCapability', 'PassphraseNoteCredentialType' => 'PassphraseCredentialType', 'PassphrasePasswordCredentialType' => 'PassphraseCredentialType', 'PassphrasePasswordKey' => 'PassphraseAbstractKey', 'PassphraseQueryConduitAPIMethod' => 'PassphraseConduitAPIMethod', 'PassphraseRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PassphraseSSHGeneratedKeyCredentialType' => 'PassphraseSSHPrivateKeyCredentialType', 'PassphraseSSHKey' => 'PassphraseAbstractKey', 'PassphraseSSHPrivateKeyCredentialType' => 'PassphraseCredentialType', 'PassphraseSSHPrivateKeyFileCredentialType' => 'PassphraseSSHPrivateKeyCredentialType', 'PassphraseSSHPrivateKeyTextCredentialType' => 'PassphraseSSHPrivateKeyCredentialType', 'PassphraseSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PassphraseSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PassphraseSecret' => 'PassphraseDAO', 'PasteConduitAPIMethod' => 'ConduitAPIMethod', 'PasteCreateConduitAPIMethod' => 'PasteConduitAPIMethod', 'PasteCreateMailReceiver' => 'PhabricatorMailReceiver', 'PasteDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PasteDefaultViewCapability' => 'PhabricatorPolicyCapability', 'PasteEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PasteEmbedView' => 'AphrontView', 'PasteInfoConduitAPIMethod' => 'PasteConduitAPIMethod', 'PasteMailReceiver' => 'PhabricatorObjectMailReceiver', 'PasteQueryConduitAPIMethod' => 'PasteConduitAPIMethod', 'PasteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability', 'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability', 'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorAccessControlTestCase' => 'PhabricatorTestCase', 'PhabricatorAccessLog' => 'Phobject', 'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorAccountSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorActionListView' => 'AphrontView', 'PhabricatorActionView' => 'AphrontView', 'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorAdministratorsPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorAjaxRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorAlmanacApplication' => 'PhabricatorApplication', 'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorAnchorView' => 'AphrontView', 'PhabricatorAphlictManagementDebugWorkflow' => 'PhabricatorAphlictManagementWorkflow', 'PhabricatorAphlictManagementRestartWorkflow' => 'PhabricatorAphlictManagementWorkflow', 'PhabricatorAphlictManagementStartWorkflow' => 'PhabricatorAphlictManagementWorkflow', 'PhabricatorAphlictManagementStatusWorkflow' => 'PhabricatorAphlictManagementWorkflow', 'PhabricatorAphlictManagementStopWorkflow' => 'PhabricatorAphlictManagementWorkflow', 'PhabricatorAphlictManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorAphlictSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorAphrontBarUIExample' => 'PhabricatorUIExample', 'PhabricatorAphrontViewTestCase' => 'PhabricatorTestCase', 'PhabricatorAppSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorApplication' => array( 'Phobject', 'PhabricatorPolicyInterface', ), 'PhabricatorApplicationApplicationPHIDType' => 'PhabricatorPHIDType', 'PhabricatorApplicationConfigOptions' => 'Phobject', 'PhabricatorApplicationConfigurationPanel' => 'Phobject', 'PhabricatorApplicationConfigurationPanelTestCase' => 'PhabricatorTestCase', 'PhabricatorApplicationDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorApplicationDetailViewController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationEditController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationEditHTTPParameterHelpView' => 'AphrontView', 'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationLaunchView' => 'AphrontTagView', 'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorApplicationSearchController' => 'PhabricatorSearchBaseController', 'PhabricatorApplicationSearchEngine' => 'Phobject', 'PhabricatorApplicationSearchEngineTestCase' => 'PhabricatorTestCase', 'PhabricatorApplicationSearchResultView' => 'Phobject', 'PhabricatorApplicationStatusView' => 'AphrontView', 'PhabricatorApplicationTestCase' => 'PhabricatorTestCase', 'PhabricatorApplicationTransaction' => array( 'PhabricatorLiskDAO', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorApplicationTransactionComment' => array( 'PhabricatorLiskDAO', 'PhabricatorMarkupInterface', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorApplicationTransactionCommentEditController' => 'PhabricatorApplicationTransactionController', 'PhabricatorApplicationTransactionCommentEditor' => 'PhabricatorEditor', 'PhabricatorApplicationTransactionCommentHistoryController' => 'PhabricatorApplicationTransactionController', 'PhabricatorApplicationTransactionCommentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorApplicationTransactionCommentQuoteController' => 'PhabricatorApplicationTransactionController', 'PhabricatorApplicationTransactionCommentRawController' => 'PhabricatorApplicationTransactionController', 'PhabricatorApplicationTransactionCommentRemoveController' => 'PhabricatorApplicationTransactionController', 'PhabricatorApplicationTransactionCommentView' => 'AphrontView', 'PhabricatorApplicationTransactionController' => 'PhabricatorController', 'PhabricatorApplicationTransactionDetailController' => 'PhabricatorApplicationTransactionController', 'PhabricatorApplicationTransactionEditor' => 'PhabricatorEditor', 'PhabricatorApplicationTransactionFeedStory' => 'PhabricatorFeedStory', 'PhabricatorApplicationTransactionNoEffectException' => 'Exception', 'PhabricatorApplicationTransactionNoEffectResponse' => 'AphrontProxyResponse', 'PhabricatorApplicationTransactionPublishWorker' => 'PhabricatorWorker', 'PhabricatorApplicationTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorApplicationTransactionReplyHandler' => 'PhabricatorMailReplyHandler', 'PhabricatorApplicationTransactionResponse' => 'AphrontProxyResponse', 'PhabricatorApplicationTransactionShowOlderController' => 'PhabricatorApplicationTransactionController', 'PhabricatorApplicationTransactionStructureException' => 'Exception', 'PhabricatorApplicationTransactionTemplatedCommentQuery' => 'PhabricatorApplicationTransactionCommentQuery', 'PhabricatorApplicationTransactionTextDiffDetailView' => 'AphrontView', 'PhabricatorApplicationTransactionTransactionPHIDType' => 'PhabricatorPHIDType', 'PhabricatorApplicationTransactionValidationError' => 'Phobject', 'PhabricatorApplicationTransactionValidationException' => 'Exception', 'PhabricatorApplicationTransactionValidationResponse' => 'AphrontProxyResponse', 'PhabricatorApplicationTransactionValueController' => 'PhabricatorApplicationTransactionController', 'PhabricatorApplicationTransactionView' => 'AphrontView', 'PhabricatorApplicationUninstallController' => 'PhabricatorApplicationsController', 'PhabricatorApplicationsApplication' => 'PhabricatorApplication', 'PhabricatorApplicationsController' => 'PhabricatorController', 'PhabricatorApplicationsListController' => 'PhabricatorApplicationsController', 'PhabricatorAsanaAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorAsanaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorAsanaTaskHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorAuditActionConstants' => 'Phobject', 'PhabricatorAuditAddCommentController' => 'PhabricatorAuditController', 'PhabricatorAuditApplication' => 'PhabricatorApplication', 'PhabricatorAuditCommentEditor' => 'PhabricatorEditor', 'PhabricatorAuditCommitStatusConstants' => 'Phobject', 'PhabricatorAuditController' => 'PhabricatorController', 'PhabricatorAuditEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorAuditInlineComment' => array( 'Phobject', 'PhabricatorInlineCommentInterface', ), 'PhabricatorAuditListController' => 'PhabricatorAuditController', 'PhabricatorAuditListView' => 'AphrontView', 'PhabricatorAuditMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorAuditManagementDeleteWorkflow' => 'PhabricatorAuditManagementWorkflow', 'PhabricatorAuditManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorAuditPreviewController' => 'PhabricatorAuditController', 'PhabricatorAuditReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorAuditStatusConstants' => 'Phobject', 'PhabricatorAuditTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorAuditTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorAuditTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorAuditTransactionView' => 'PhabricatorApplicationTransactionView', 'PhabricatorAuthAccountView' => 'AphrontView', 'PhabricatorAuthApplication' => 'PhabricatorApplication', 'PhabricatorAuthAuthFactorPHIDType' => 'PhabricatorPHIDType', 'PhabricatorAuthAuthProviderPHIDType' => 'PhabricatorPHIDType', 'PhabricatorAuthConduitAPIMethod' => 'ConduitAPIMethod', 'PhabricatorAuthConfirmLinkController' => 'PhabricatorAuthController', 'PhabricatorAuthController' => 'PhabricatorController', 'PhabricatorAuthDAO' => 'PhabricatorLiskDAO', 'PhabricatorAuthDisableController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthDowngradeSessionController' => 'PhabricatorAuthController', 'PhabricatorAuthEditController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthFactor' => 'Phobject', 'PhabricatorAuthFactorConfig' => 'PhabricatorAuthDAO', 'PhabricatorAuthFactorTestCase' => 'PhabricatorTestCase', 'PhabricatorAuthFinishController' => 'PhabricatorAuthController', 'PhabricatorAuthHighSecurityRequiredException' => 'Exception', 'PhabricatorAuthHighSecurityToken' => 'Phobject', 'PhabricatorAuthInvite' => array( 'PhabricatorUserDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorAuthInviteAccountException' => 'PhabricatorAuthInviteDialogException', 'PhabricatorAuthInviteAction' => 'Phobject', 'PhabricatorAuthInviteActionTableView' => 'AphrontView', 'PhabricatorAuthInviteController' => 'PhabricatorAuthController', 'PhabricatorAuthInviteDialogException' => 'PhabricatorAuthInviteException', 'PhabricatorAuthInviteEngine' => 'Phobject', 'PhabricatorAuthInviteException' => 'Exception', 'PhabricatorAuthInviteInvalidException' => 'PhabricatorAuthInviteDialogException', 'PhabricatorAuthInviteLoginException' => 'PhabricatorAuthInviteDialogException', 'PhabricatorAuthInvitePHIDType' => 'PhabricatorPHIDType', 'PhabricatorAuthInviteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthInviteRegisteredException' => 'PhabricatorAuthInviteException', 'PhabricatorAuthInviteSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorAuthInviteTestCase' => 'PhabricatorTestCase', 'PhabricatorAuthInviteVerifyException' => 'PhabricatorAuthInviteDialogException', 'PhabricatorAuthInviteWorker' => 'PhabricatorWorker', 'PhabricatorAuthLinkController' => 'PhabricatorAuthController', 'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorAuthLoginHandler' => 'Phobject', 'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRecoverWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRefreshWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementStripWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementTrustOAuthClientWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementUnlimitWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementUntrustOAuthClientWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementVerifyWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorAuthNeedsApprovalController' => 'PhabricatorAuthController', 'PhabricatorAuthNeedsMultiFactorController' => 'PhabricatorAuthController', 'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthOldOAuthRedirectController' => 'PhabricatorAuthController', 'PhabricatorAuthOneTimeLoginController' => 'PhabricatorAuthController', 'PhabricatorAuthProvider' => 'Phobject', 'PhabricatorAuthProviderConfig' => array( 'PhabricatorAuthDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), 'PhabricatorAuthProviderConfigController' => 'PhabricatorAuthController', 'PhabricatorAuthProviderConfigEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorAuthProviderConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthProviderConfigTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorAuthQueryPublicKeysConduitAPIMethod' => 'PhabricatorAuthConduitAPIMethod', 'PhabricatorAuthRegisterController' => 'PhabricatorAuthController', 'PhabricatorAuthRevokeTokenController' => 'PhabricatorAuthController', 'PhabricatorAuthSSHKey' => array( 'PhabricatorAuthDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorAuthSSHKeyController' => 'PhabricatorAuthController', 'PhabricatorAuthSSHKeyDeleteController' => 'PhabricatorAuthSSHKeyController', 'PhabricatorAuthSSHKeyEditController' => 'PhabricatorAuthSSHKeyController', 'PhabricatorAuthSSHKeyGenerateController' => 'PhabricatorAuthSSHKeyController', 'PhabricatorAuthSSHKeyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthSSHKeyTableView' => 'AphrontView', 'PhabricatorAuthSSHPublicKey' => 'Phobject', 'PhabricatorAuthSession' => array( 'PhabricatorAuthDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorAuthSessionEngine' => 'Phobject', 'PhabricatorAuthSessionGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorAuthSessionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorAuthStartController' => 'PhabricatorAuthController', 'PhabricatorAuthTemporaryToken' => array( 'PhabricatorAuthDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorAuthTemporaryTokenGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorAuthTemporaryTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthTerminateSessionController' => 'PhabricatorAuthController', 'PhabricatorAuthTryFactorAction' => 'PhabricatorSystemAction', 'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController', 'PhabricatorAuthValidateController' => 'PhabricatorAuthController', 'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorAutoEventListener' => 'PhabricatorEventListener', 'PhabricatorBadgeHasRecipientEdgeType' => 'PhabricatorEdgeType', 'PhabricatorBadgesApplication' => 'PhabricatorApplication', 'PhabricatorBadgesBadge' => array( 'PhabricatorBadgesDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorSubscribableInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorFlaggableInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorBadgesCommentController' => 'PhabricatorBadgesController', 'PhabricatorBadgesController' => 'PhabricatorController', 'PhabricatorBadgesCreateCapability' => 'PhabricatorPolicyCapability', 'PhabricatorBadgesDAO' => 'PhabricatorLiskDAO', 'PhabricatorBadgesDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PhabricatorBadgesEditController' => 'PhabricatorBadgesController', 'PhabricatorBadgesEditIconController' => 'PhabricatorBadgesController', 'PhabricatorBadgesEditRecipientsController' => 'PhabricatorBadgesController', 'PhabricatorBadgesEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorBadgesIcon' => 'Phobject', 'PhabricatorBadgesListController' => 'PhabricatorBadgesController', 'PhabricatorBadgesMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorBadgesPHIDType' => 'PhabricatorPHIDType', 'PhabricatorBadgesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorBadgesRecipientsListView' => 'AphrontTagView', 'PhabricatorBadgesRemoveRecipientsController' => 'PhabricatorBadgesController', 'PhabricatorBadgesReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorBadgesSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorBadgesSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorBadgesTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorBadgesTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorBadgesTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorBadgesViewController' => 'PhabricatorBadgesController', 'PhabricatorBarePageUIExample' => 'PhabricatorUIExample', 'PhabricatorBarePageView' => 'AphrontPageView', 'PhabricatorBaseURISetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorBcryptPasswordHasher' => 'PhabricatorPasswordHasher', 'PhabricatorBinariesSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorBitbucketAuthProvider' => 'PhabricatorOAuth1AuthProvider', 'PhabricatorBot' => 'PhabricatorDaemon', 'PhabricatorBotChannel' => 'PhabricatorBotTarget', 'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler', 'PhabricatorBotFeedNotificationHandler' => 'PhabricatorBotHandler', 'PhabricatorBotFlowdockProtocolAdapter' => 'PhabricatorStreamingProtocolAdapter', 'PhabricatorBotHandler' => 'Phobject', 'PhabricatorBotLogHandler' => 'PhabricatorBotHandler', 'PhabricatorBotMacroHandler' => 'PhabricatorBotHandler', 'PhabricatorBotMessage' => 'Phobject', 'PhabricatorBotObjectNameHandler' => 'PhabricatorBotHandler', 'PhabricatorBotSymbolHandler' => 'PhabricatorBotHandler', 'PhabricatorBotTarget' => 'Phobject', 'PhabricatorBotUser' => 'PhabricatorBotTarget', 'PhabricatorBotWhatsNewHandler' => 'PhabricatorBotHandler', 'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation', 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', 'PhabricatorBusyUIExample' => 'PhabricatorUIExample', 'PhabricatorCacheDAO' => 'PhabricatorLiskDAO', 'PhabricatorCacheGeneralGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorCacheManagementPurgeWorkflow' => 'PhabricatorCacheManagementWorkflow', 'PhabricatorCacheManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorCacheMarkupGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorCacheSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorCacheSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorCacheSpec' => 'Phobject', 'PhabricatorCacheTTLGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorCaches' => 'Phobject', 'PhabricatorCachesTestCase' => 'PhabricatorTestCase', 'PhabricatorCalendarApplication' => 'PhabricatorApplication', 'PhabricatorCalendarController' => 'PhabricatorController', 'PhabricatorCalendarDAO' => 'PhabricatorLiskDAO', 'PhabricatorCalendarEvent' => array( 'PhabricatorCalendarDAO', 'PhabricatorPolicyInterface', 'PhabricatorProjectInterface', 'PhabricatorMarkupInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorSubscribableInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorDestructibleInterface', 'PhabricatorMentionableInterface', 'PhabricatorFlaggableInterface', 'PhabricatorSpacesInterface', ), 'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventCommentController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditIconController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorCalendarEventEmailCommand' => 'MetaMTAEmailTransactionCommand', 'PhabricatorCalendarEventInvitee' => array( 'PhabricatorCalendarDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorCalendarEventInviteeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCalendarEventJoinController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventListController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorCalendarEventPHIDType' => 'PhabricatorPHIDType', 'PhabricatorCalendarEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCalendarEventRSVPEmailCommand' => 'PhabricatorCalendarEventEmailCommand', 'PhabricatorCalendarEventSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorCalendarEventSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorCalendarEventTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorCalendarEventTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorCalendarEventTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorCalendarEventViewController' => 'PhabricatorCalendarController', 'PhabricatorCalendarHoliday' => 'PhabricatorCalendarDAO', 'PhabricatorCalendarHolidayTestCase' => 'PhabricatorTestCase', 'PhabricatorCalendarIcon' => 'Phobject', 'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorCalendarReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorCalendarSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorCampfireProtocolAdapter' => 'PhabricatorStreamingProtocolAdapter', 'PhabricatorCelerityApplication' => 'PhabricatorApplication', 'PhabricatorCelerityTestCase' => 'PhabricatorTestCase', 'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase', 'PhabricatorChangesetResponse' => 'AphrontProxyResponse', 'PhabricatorChatLogApplication' => 'PhabricatorApplication', 'PhabricatorChatLogChannel' => array( 'PhabricatorChatLogDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorChatLogChannelListController' => 'PhabricatorChatLogController', 'PhabricatorChatLogChannelLogController' => 'PhabricatorChatLogController', 'PhabricatorChatLogChannelQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorChatLogController' => 'PhabricatorController', 'PhabricatorChatLogDAO' => 'PhabricatorLiskDAO', 'PhabricatorChatLogEvent' => array( 'PhabricatorChatLogDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorChatLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorChunkedFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorCommentEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorCommentEditField' => 'PhabricatorEditField', 'PhabricatorCommentEditType' => 'PhabricatorEditType', 'PhabricatorCommitBranchesField' => 'PhabricatorCommitCustomField', 'PhabricatorCommitCustomField' => 'PhabricatorCustomField', 'PhabricatorCommitMergedCommitsField' => 'PhabricatorCommitCustomField', 'PhabricatorCommitRepositoryField' => 'PhabricatorCommitCustomField', 'PhabricatorCommitSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorCommitTagsField' => 'PhabricatorCommitCustomField', 'PhabricatorCommonPasswords' => 'Phobject', 'PhabricatorConduitAPIController' => 'PhabricatorConduitController', 'PhabricatorConduitApplication' => 'PhabricatorApplication', 'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO', 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitConsoleController' => 'PhabricatorConduitController', 'PhabricatorConduitController' => 'PhabricatorController', 'PhabricatorConduitDAO' => 'PhabricatorLiskDAO', 'PhabricatorConduitListController' => 'PhabricatorConduitController', 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 'PhabricatorConduitLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorConduitMethodCallLog' => array( 'PhabricatorConduitDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorConduitMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorConduitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorConduitSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorConduitTestCase' => 'PhabricatorTestCase', 'PhabricatorConduitToken' => array( 'PhabricatorConduitDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorConduitTokenController' => 'PhabricatorConduitController', 'PhabricatorConduitTokenEditController' => 'PhabricatorConduitController', 'PhabricatorConduitTokenHandshakeController' => 'PhabricatorConduitController', 'PhabricatorConduitTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorConduitTokenTerminateController' => 'PhabricatorConduitController', 'PhabricatorConduitTokensSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorConfigAllController' => 'PhabricatorConfigController', 'PhabricatorConfigApplication' => 'PhabricatorApplication', 'PhabricatorConfigCacheController' => 'PhabricatorConfigController', 'PhabricatorConfigCollectorsModule' => 'PhabricatorConfigModule', 'PhabricatorConfigColumnSchema' => 'PhabricatorConfigStorageSchema', 'PhabricatorConfigConfigPHIDType' => 'PhabricatorPHIDType', 'PhabricatorConfigController' => 'PhabricatorController', 'PhabricatorConfigCoreSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorConfigDatabaseController' => 'PhabricatorConfigController', 'PhabricatorConfigDatabaseIssueController' => 'PhabricatorConfigDatabaseController', 'PhabricatorConfigDatabaseSchema' => 'PhabricatorConfigStorageSchema', 'PhabricatorConfigDatabaseSource' => 'PhabricatorConfigProxySource', 'PhabricatorConfigDatabaseStatusController' => 'PhabricatorConfigDatabaseController', 'PhabricatorConfigDefaultSource' => 'PhabricatorConfigProxySource', 'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource', 'PhabricatorConfigEdgeModule' => 'PhabricatorConfigModule', 'PhabricatorConfigEditController' => 'PhabricatorConfigController', 'PhabricatorConfigEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorConfigEntry' => array( 'PhabricatorConfigEntryDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), 'PhabricatorConfigEntryDAO' => 'PhabricatorLiskDAO', 'PhabricatorConfigEntryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorConfigFileSource' => 'PhabricatorConfigProxySource', 'PhabricatorConfigGroupController' => 'PhabricatorConfigController', 'PhabricatorConfigHTTPParameterTypesModule' => 'PhabricatorConfigModule', 'PhabricatorConfigHistoryController' => 'PhabricatorConfigController', 'PhabricatorConfigIgnoreController' => 'PhabricatorConfigController', 'PhabricatorConfigIssueListController' => 'PhabricatorConfigController', 'PhabricatorConfigIssueViewController' => 'PhabricatorConfigController', 'PhabricatorConfigJSON' => 'Phobject', 'PhabricatorConfigJSONOptionType' => 'PhabricatorConfigOptionType', 'PhabricatorConfigKeySchema' => 'PhabricatorConfigStorageSchema', 'PhabricatorConfigListController' => 'PhabricatorConfigController', 'PhabricatorConfigLocalSource' => 'PhabricatorConfigProxySource', 'PhabricatorConfigManagementDeleteWorkflow' => 'PhabricatorConfigManagementWorkflow', 'PhabricatorConfigManagementGetWorkflow' => 'PhabricatorConfigManagementWorkflow', 'PhabricatorConfigManagementListWorkflow' => 'PhabricatorConfigManagementWorkflow', 'PhabricatorConfigManagementMigrateWorkflow' => 'PhabricatorConfigManagementWorkflow', 'PhabricatorConfigManagementSetWorkflow' => 'PhabricatorConfigManagementWorkflow', 'PhabricatorConfigManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorConfigModule' => 'Phobject', 'PhabricatorConfigModuleController' => 'PhabricatorConfigController', 'PhabricatorConfigOption' => array( 'Phobject', 'PhabricatorMarkupInterface', ), 'PhabricatorConfigOptionType' => 'Phobject', 'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule', 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', 'PhabricatorConfigPurgeCacheController' => 'PhabricatorConfigController', 'PhabricatorConfigRequestExceptionHandlerModule' => 'PhabricatorConfigModule', 'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse', 'PhabricatorConfigSchemaQuery' => 'Phobject', 'PhabricatorConfigSchemaSpec' => 'Phobject', 'PhabricatorConfigServerSchema' => 'PhabricatorConfigStorageSchema', 'PhabricatorConfigSiteModule' => 'PhabricatorConfigModule', 'PhabricatorConfigSiteSource' => 'PhabricatorConfigProxySource', 'PhabricatorConfigSource' => 'Phobject', 'PhabricatorConfigStackSource' => 'PhabricatorConfigSource', 'PhabricatorConfigStorageSchema' => 'Phobject', 'PhabricatorConfigTableSchema' => 'PhabricatorConfigStorageSchema', 'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorConfigValidationException' => 'Exception', 'PhabricatorConfigVersionsModule' => 'PhabricatorConfigModule', 'PhabricatorConfigWelcomeController' => 'PhabricatorConfigController', 'PhabricatorConpherenceApplication' => 'PhabricatorApplication', 'PhabricatorConpherencePreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorConpherenceThreadPHIDType' => 'PhabricatorPHIDType', 'PhabricatorConsoleApplication' => 'PhabricatorApplication', 'PhabricatorContentSource' => 'Phobject', 'PhabricatorContentSourceView' => 'AphrontView', 'PhabricatorContributedToObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorController' => 'AphrontController', 'PhabricatorCookies' => 'Phobject', 'PhabricatorCoreConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorCountdown' => array( 'PhabricatorCountdownDAO', 'PhabricatorPolicyInterface', 'PhabricatorFlaggableInterface', 'PhabricatorSubscribableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorSpacesInterface', 'PhabricatorProjectInterface', ), 'PhabricatorCountdownApplication' => 'PhabricatorApplication', 'PhabricatorCountdownCommentController' => 'PhabricatorCountdownController', 'PhabricatorCountdownController' => 'PhabricatorController', 'PhabricatorCountdownCountdownPHIDType' => 'PhabricatorPHIDType', 'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO', 'PhabricatorCountdownDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PhabricatorCountdownDefaultViewCapability' => 'PhabricatorPolicyCapability', 'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController', 'PhabricatorCountdownEditController' => 'PhabricatorCountdownController', 'PhabricatorCountdownEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorCountdownListController' => 'PhabricatorCountdownController', 'PhabricatorCountdownMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorCountdownQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCountdownRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorCountdownReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorCountdownSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorCountdownSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorCountdownTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorCountdownTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorCountdownTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorCountdownView' => 'AphrontTagView', 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', 'PhabricatorCredentialsUsedByObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery', 'PhabricatorCustomField' => 'Phobject', 'PhabricatorCustomFieldAttachment' => 'Phobject', 'PhabricatorCustomFieldConfigOptionType' => 'PhabricatorConfigOptionType', 'PhabricatorCustomFieldDataNotAvailableException' => 'Exception', + 'PhabricatorCustomFieldEditEngineExtension' => 'PhabricatorEditEngineExtension', + 'PhabricatorCustomFieldEditField' => 'PhabricatorEditField', + 'PhabricatorCustomFieldEditType' => 'PhabricatorEditType', 'PhabricatorCustomFieldHeraldField' => 'HeraldField', 'PhabricatorCustomFieldHeraldFieldGroup' => 'HeraldFieldGroup', 'PhabricatorCustomFieldImplementationIncompleteException' => 'Exception', 'PhabricatorCustomFieldIndexStorage' => 'PhabricatorLiskDAO', 'PhabricatorCustomFieldList' => 'Phobject', 'PhabricatorCustomFieldMonogramParser' => 'Phobject', 'PhabricatorCustomFieldNotAttachedException' => 'Exception', 'PhabricatorCustomFieldNotProxyException' => 'Exception', 'PhabricatorCustomFieldNumericIndexStorage' => 'PhabricatorCustomFieldIndexStorage', 'PhabricatorCustomFieldStorage' => 'PhabricatorLiskDAO', 'PhabricatorCustomFieldStringIndexStorage' => 'PhabricatorCustomFieldIndexStorage', 'PhabricatorCustomHeaderConfigType' => 'PhabricatorConfigOptionType', 'PhabricatorDaemon' => 'PhutilDaemon', 'PhabricatorDaemonBulkJobListController' => 'PhabricatorDaemonController', 'PhabricatorDaemonBulkJobMonitorController' => 'PhabricatorDaemonController', 'PhabricatorDaemonBulkJobViewController' => 'PhabricatorDaemonController', 'PhabricatorDaemonConsoleController' => 'PhabricatorDaemonController', 'PhabricatorDaemonController' => 'PhabricatorController', 'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO', 'PhabricatorDaemonEventListener' => 'PhabricatorEventListener', 'PhabricatorDaemonLog' => array( 'PhabricatorDaemonDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorDaemonLogEvent' => 'PhabricatorDaemonDAO', 'PhabricatorDaemonLogEventGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorDaemonLogEventViewController' => 'PhabricatorDaemonController', 'PhabricatorDaemonLogEventsView' => 'AphrontView', 'PhabricatorDaemonLogGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorDaemonLogListController' => 'PhabricatorDaemonController', 'PhabricatorDaemonLogListView' => 'AphrontView', 'PhabricatorDaemonLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorDaemonLogViewController' => 'PhabricatorDaemonController', 'PhabricatorDaemonManagementDebugWorkflow' => 'PhabricatorDaemonManagementWorkflow', 'PhabricatorDaemonManagementLaunchWorkflow' => 'PhabricatorDaemonManagementWorkflow', 'PhabricatorDaemonManagementListWorkflow' => 'PhabricatorDaemonManagementWorkflow', 'PhabricatorDaemonManagementLogWorkflow' => 'PhabricatorDaemonManagementWorkflow', 'PhabricatorDaemonManagementReloadWorkflow' => 'PhabricatorDaemonManagementWorkflow', 'PhabricatorDaemonManagementRestartWorkflow' => 'PhabricatorDaemonManagementWorkflow', 'PhabricatorDaemonManagementStartWorkflow' => 'PhabricatorDaemonManagementWorkflow', 'PhabricatorDaemonManagementStatusWorkflow' => 'PhabricatorDaemonManagementWorkflow', 'PhabricatorDaemonManagementStopWorkflow' => 'PhabricatorDaemonManagementWorkflow', 'PhabricatorDaemonManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorDaemonOverseerModule' => 'PhutilDaemonOverseerModule', 'PhabricatorDaemonReference' => 'Phobject', 'PhabricatorDaemonTaskGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorDaemonTasksTableView' => 'AphrontView', 'PhabricatorDaemonsApplication' => 'PhabricatorApplication', 'PhabricatorDaemonsSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorDailyRoutineTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorDashboard' => array( 'PhabricatorDashboardDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorFlaggableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorProjectInterface', ), 'PhabricatorDashboardAddPanelController' => 'PhabricatorDashboardController', 'PhabricatorDashboardApplication' => 'PhabricatorApplication', 'PhabricatorDashboardController' => 'PhabricatorController', 'PhabricatorDashboardCopyController' => 'PhabricatorDashboardController', 'PhabricatorDashboardDAO' => 'PhabricatorLiskDAO', 'PhabricatorDashboardDashboardHasPanelEdgeType' => 'PhabricatorEdgeType', 'PhabricatorDashboardDashboardPHIDType' => 'PhabricatorPHIDType', 'PhabricatorDashboardEditController' => 'PhabricatorDashboardController', 'PhabricatorDashboardHistoryController' => 'PhabricatorDashboardController', 'PhabricatorDashboardInstall' => 'PhabricatorDashboardDAO', 'PhabricatorDashboardInstallController' => 'PhabricatorDashboardController', 'PhabricatorDashboardLayoutConfig' => 'Phobject', 'PhabricatorDashboardListController' => 'PhabricatorDashboardController', 'PhabricatorDashboardManageController' => 'PhabricatorDashboardController', 'PhabricatorDashboardMovePanelController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanel' => array( 'PhabricatorDashboardDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorFlaggableInterface', 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorDashboardPanelArchiveController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelCoreCustomField' => array( 'PhabricatorDashboardPanelCustomField', 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorDashboardPanelCustomField' => 'PhabricatorCustomField', 'PhabricatorDashboardPanelEditController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelHasDashboardEdgeType' => 'PhabricatorEdgeType', 'PhabricatorDashboardPanelListController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelPHIDType' => 'PhabricatorPHIDType', 'PhabricatorDashboardPanelQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorDashboardPanelRenderController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelRenderingEngine' => 'Phobject', 'PhabricatorDashboardPanelSearchApplicationCustomField' => 'PhabricatorStandardCustomField', 'PhabricatorDashboardPanelSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorDashboardPanelSearchQueryCustomField' => 'PhabricatorStandardCustomField', 'PhabricatorDashboardPanelTabsCustomField' => 'PhabricatorStandardCustomField', 'PhabricatorDashboardPanelTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorDashboardPanelTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorDashboardPanelType' => 'Phobject', 'PhabricatorDashboardPanelViewController' => 'PhabricatorDashboardController', 'PhabricatorDashboardQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorDashboardQueryPanelType' => 'PhabricatorDashboardPanelType', 'PhabricatorDashboardRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorDashboardRemovePanelController' => 'PhabricatorDashboardController', 'PhabricatorDashboardRenderingEngine' => 'Phobject', 'PhabricatorDashboardSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorDashboardSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorDashboardTabsPanelType' => 'PhabricatorDashboardPanelType', 'PhabricatorDashboardTextPanelType' => 'PhabricatorDashboardPanelType', 'PhabricatorDashboardTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorDashboardTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorDashboardTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorDashboardUninstallController' => 'PhabricatorDashboardController', 'PhabricatorDashboardViewController' => 'PhabricatorDashboardController', 'PhabricatorDataCacheSpec' => 'PhabricatorCacheSpec', 'PhabricatorDataNotAttachedException' => 'Exception', 'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorDatasourceEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDebugController' => 'PhabricatorController', 'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDestructionEngine' => 'Phobject', 'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorDeveloperPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDiffInlineCommentQuery' => 'PhabricatorApplicationTransactionCommentQuery', 'PhabricatorDiffPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDifferenceEngine' => 'Phobject', 'PhabricatorDifferentialApplication' => 'PhabricatorApplication', 'PhabricatorDifferentialConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorDifferentialRevisionTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorDiffusionApplication' => 'PhabricatorApplication', 'PhabricatorDiffusionConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorDisabledUserController' => 'PhabricatorAuthController', 'PhabricatorDisplayPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorDisqusAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorDivinerApplication' => 'PhabricatorApplication', 'PhabricatorDoorkeeperApplication' => 'PhabricatorApplication', 'PhabricatorDraft' => 'PhabricatorDraftDAO', 'PhabricatorDraftDAO' => 'PhabricatorLiskDAO', 'PhabricatorDrydockApplication' => 'PhabricatorApplication', 'PhabricatorEdgeConfig' => 'PhabricatorEdgeConstants', 'PhabricatorEdgeConstants' => 'Phobject', 'PhabricatorEdgeCycleException' => 'Exception', 'PhabricatorEdgeEditType' => 'PhabricatorEditType', 'PhabricatorEdgeEditor' => 'Phobject', 'PhabricatorEdgeGraph' => 'AbstractDirectedGraph', 'PhabricatorEdgeQuery' => 'PhabricatorQuery', 'PhabricatorEdgeTestCase' => 'PhabricatorTestCase', 'PhabricatorEdgeType' => 'Phobject', 'PhabricatorEdgeTypeTestCase' => 'PhabricatorTestCase', 'PhabricatorEditEngine' => array( 'Phobject', 'PhabricatorPolicyInterface', ), 'PhabricatorEditEngineAPIMethod' => 'ConduitAPIMethod', 'PhabricatorEditEngineConfiguration' => array( 'PhabricatorSearchDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), + 'PhabricatorEditEngineConfigurationDefaultCreateController' => 'PhabricatorEditEngineController', 'PhabricatorEditEngineConfigurationDefaultsController' => 'PhabricatorEditEngineController', + 'PhabricatorEditEngineConfigurationDisableController' => 'PhabricatorEditEngineController', 'PhabricatorEditEngineConfigurationEditController' => 'PhabricatorEditEngineController', 'PhabricatorEditEngineConfigurationEditEngine' => 'PhabricatorEditEngine', 'PhabricatorEditEngineConfigurationEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorEditEngineConfigurationListController' => 'PhabricatorEditEngineController', 'PhabricatorEditEngineConfigurationLockController' => 'PhabricatorEditEngineController', 'PhabricatorEditEngineConfigurationPHIDType' => 'PhabricatorPHIDType', 'PhabricatorEditEngineConfigurationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorEditEngineConfigurationReorderController' => 'PhabricatorEditEngineController', 'PhabricatorEditEngineConfigurationSaveController' => 'PhabricatorEditEngineController', 'PhabricatorEditEngineConfigurationSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorEditEngineConfigurationTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorEditEngineConfigurationTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorEditEngineConfigurationViewController' => 'PhabricatorEditEngineController', 'PhabricatorEditEngineController' => 'PhabricatorApplicationTransactionController', + 'PhabricatorEditEngineExtension' => 'Phobject', + 'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule', 'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController', 'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorEditEngineSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorEditField' => 'Phobject', 'PhabricatorEditType' => 'Phobject', 'PhabricatorEditor' => 'Phobject', 'PhabricatorElasticSearchEngine' => 'PhabricatorSearchEngine', 'PhabricatorElasticSearchSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorEmailAddressesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorEmailFormatSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorEmailLoginController' => 'PhabricatorAuthController', 'PhabricatorEmailPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorEmailVerificationController' => 'PhabricatorAuthController', 'PhabricatorEmbedFileRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorEmojiRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorEmptyQueryException' => 'Exception', 'PhabricatorEnv' => 'Phobject', 'PhabricatorEnvTestCase' => 'PhabricatorTestCase', 'PhabricatorEvent' => 'PhutilEvent', 'PhabricatorEventEngine' => 'Phobject', 'PhabricatorEventListener' => 'PhutilEventListener', 'PhabricatorEventType' => 'PhutilEventType', 'PhabricatorExampleEventListener' => 'PhabricatorEventListener', 'PhabricatorExtendingPhabricatorConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorExtensionsSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorExternalAccount' => array( 'PhabricatorUserDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorExternalAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorExternalAccountsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorExtraConfigSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorFacebookAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorFactAggregate' => 'PhabricatorFactDAO', 'PhabricatorFactApplication' => 'PhabricatorApplication', 'PhabricatorFactChartController' => 'PhabricatorFactController', 'PhabricatorFactController' => 'PhabricatorController', 'PhabricatorFactCountEngine' => 'PhabricatorFactEngine', 'PhabricatorFactCursor' => 'PhabricatorFactDAO', 'PhabricatorFactDAO' => 'PhabricatorLiskDAO', 'PhabricatorFactDaemon' => 'PhabricatorDaemon', 'PhabricatorFactEngine' => 'Phobject', 'PhabricatorFactEngineTestCase' => 'PhabricatorTestCase', 'PhabricatorFactHomeController' => 'PhabricatorFactController', 'PhabricatorFactLastUpdatedEngine' => 'PhabricatorFactEngine', 'PhabricatorFactManagementAnalyzeWorkflow' => 'PhabricatorFactManagementWorkflow', 'PhabricatorFactManagementCursorsWorkflow' => 'PhabricatorFactManagementWorkflow', 'PhabricatorFactManagementDestroyWorkflow' => 'PhabricatorFactManagementWorkflow', 'PhabricatorFactManagementListWorkflow' => 'PhabricatorFactManagementWorkflow', 'PhabricatorFactManagementStatusWorkflow' => 'PhabricatorFactManagementWorkflow', 'PhabricatorFactManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorFactRaw' => 'PhabricatorFactDAO', 'PhabricatorFactSimpleSpec' => 'PhabricatorFactSpec', 'PhabricatorFactSpec' => 'Phobject', 'PhabricatorFactUpdateIterator' => 'PhutilBufferedIterator', 'PhabricatorFeedApplication' => 'PhabricatorApplication', 'PhabricatorFeedBuilder' => 'Phobject', 'PhabricatorFeedConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorFeedController' => 'PhabricatorController', 'PhabricatorFeedDAO' => 'PhabricatorLiskDAO', 'PhabricatorFeedDetailController' => 'PhabricatorFeedController', 'PhabricatorFeedListController' => 'PhabricatorFeedController', 'PhabricatorFeedManagementRepublishWorkflow' => 'PhabricatorFeedManagementWorkflow', 'PhabricatorFeedManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorFeedQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorFeedSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorFeedStory' => array( 'Phobject', 'PhabricatorPolicyInterface', 'PhabricatorMarkupInterface', ), 'PhabricatorFeedStoryData' => 'PhabricatorFeedDAO', 'PhabricatorFeedStoryNotification' => 'PhabricatorFeedDAO', 'PhabricatorFeedStoryPublisher' => 'Phobject', 'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO', 'PhabricatorFile' => array( 'PhabricatorFileDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorSubscribableInterface', 'PhabricatorFlaggableInterface', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorFileBundleLoader' => 'Phobject', 'PhabricatorFileChunk' => array( 'PhabricatorFileDAO', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorFileChunkIterator' => array( 'Phobject', 'Iterator', ), 'PhabricatorFileChunkQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorFileCommentController' => 'PhabricatorFileController', 'PhabricatorFileComposeController' => 'PhabricatorFileController', 'PhabricatorFileController' => 'PhabricatorController', 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', 'PhabricatorFileDataController' => 'PhabricatorFileController', 'PhabricatorFileDeleteController' => 'PhabricatorFileController', 'PhabricatorFileDropUploadController' => 'PhabricatorFileController', 'PhabricatorFileEditController' => 'PhabricatorFileController', 'PhabricatorFileEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorFileFilePHIDType' => 'PhabricatorPHIDType', 'PhabricatorFileHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorFileImageMacro' => array( 'PhabricatorFileDAO', 'PhabricatorSubscribableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFlaggableInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorPolicyInterface', ), 'PhabricatorFileImageTransform' => 'PhabricatorFileTransform', 'PhabricatorFileInfoController' => 'PhabricatorFileController', 'PhabricatorFileLinkListView' => 'AphrontView', 'PhabricatorFileLinkView' => 'AphrontView', 'PhabricatorFileListController' => 'PhabricatorFileController', 'PhabricatorFileQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorFileSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorFileSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO', 'PhabricatorFileStorageConfigurationException' => 'Exception', 'PhabricatorFileStorageEngine' => 'Phobject', 'PhabricatorFileStorageEngineTestCase' => 'PhabricatorTestCase', 'PhabricatorFileTemporaryGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorFileTestCase' => 'PhabricatorTestCase', 'PhabricatorFileTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorFileThumbnailTransform' => 'PhabricatorFileImageTransform', 'PhabricatorFileTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorFileTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorFileTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorFileTransform' => 'Phobject', 'PhabricatorFileTransformController' => 'PhabricatorFileController', 'PhabricatorFileTransformListController' => 'PhabricatorFileController', 'PhabricatorFileTransformTestCase' => 'PhabricatorTestCase', 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileUploadDialogController' => 'PhabricatorFileController', 'PhabricatorFileUploadException' => 'Exception', 'PhabricatorFileinfoSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorFilesApplication' => 'PhabricatorApplication', 'PhabricatorFilesApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel', 'PhabricatorFilesConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorFilesManagementCatWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementCompactWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementEnginesWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementPurgeWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementRebuildWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorFilesOutboundRequestAction' => 'PhabricatorSystemAction', 'PhabricatorFlag' => array( 'PhabricatorFlagDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorFlagAddFlagHeraldAction' => 'HeraldAction', 'PhabricatorFlagColor' => 'PhabricatorFlagConstants', 'PhabricatorFlagConstants' => 'Phobject', 'PhabricatorFlagController' => 'PhabricatorController', 'PhabricatorFlagDAO' => 'PhabricatorLiskDAO', 'PhabricatorFlagDeleteController' => 'PhabricatorFlagController', 'PhabricatorFlagEditController' => 'PhabricatorFlagController', 'PhabricatorFlagListController' => 'PhabricatorFlagController', 'PhabricatorFlagQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorFlagSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorFlagSelectControl' => 'AphrontFormControl', 'PhabricatorFlaggableInterface' => 'PhabricatorPHIDInterface', 'PhabricatorFlagsApplication' => 'PhabricatorApplication', 'PhabricatorFlagsUIEventListener' => 'PhabricatorEventListener', 'PhabricatorFundApplication' => 'PhabricatorApplication', 'PhabricatorGDSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorGarbageCollector' => 'Phobject', 'PhabricatorGarbageCollectorManagementCollectWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow', 'PhabricatorGarbageCollectorManagementSetPolicyWorkflow' => 'PhabricatorGarbageCollectorManagementWorkflow', 'PhabricatorGarbageCollectorManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorGestureUIExample' => 'PhabricatorUIExample', 'PhabricatorGitGraphStream' => 'PhabricatorRepositoryGraphStream', 'PhabricatorGitHubAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorGlobalLock' => 'PhutilLock', 'PhabricatorGlobalUploadTargetView' => 'AphrontView', 'PhabricatorGoogleAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorHTTPParameterTypeTableView' => 'AphrontView', 'PhabricatorHandleList' => array( 'Phobject', 'Iterator', 'ArrayAccess', 'Countable', ), 'PhabricatorHandleObjectSelectorDataView' => 'Phobject', 'PhabricatorHandlePool' => 'Phobject', 'PhabricatorHandlePoolTestCase' => 'PhabricatorTestCase', 'PhabricatorHandleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorHarbormasterApplication' => 'PhabricatorApplication', 'PhabricatorHarbormasterConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorHash' => 'Phobject', 'PhabricatorHashTestCase' => 'PhabricatorTestCase', 'PhabricatorHelpApplication' => 'PhabricatorApplication', 'PhabricatorHelpController' => 'PhabricatorController', 'PhabricatorHelpDocumentationController' => 'PhabricatorHelpController', 'PhabricatorHelpEditorProtocolController' => 'PhabricatorHelpController', 'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController', 'PhabricatorHeraldApplication' => 'PhabricatorApplication', 'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorHomeApplication' => 'PhabricatorApplication', 'PhabricatorHomeController' => 'PhabricatorController', 'PhabricatorHomeMainController' => 'PhabricatorHomeController', 'PhabricatorHomePreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorHomeQuickCreateController' => 'PhabricatorHomeController', 'PhabricatorHovercardUIExample' => 'PhabricatorUIExample', 'PhabricatorHovercardView' => 'AphrontView', 'PhabricatorHunksManagementMigrateWorkflow' => 'PhabricatorHunksManagementWorkflow', 'PhabricatorHunksManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorIRCProtocolAdapter' => 'PhabricatorProtocolAdapter', 'PhabricatorIconRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorImageMacroRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorImageTransformer' => 'Phobject', 'PhabricatorImagemagickSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorInfrastructureTestCase' => 'PhabricatorTestCase', 'PhabricatorInlineCommentController' => 'PhabricatorController', 'PhabricatorInlineCommentInterface' => 'PhabricatorMarkupInterface', 'PhabricatorInlineCommentPreviewController' => 'PhabricatorController', 'PhabricatorInlineSummaryView' => 'AphrontView', 'PhabricatorInstructionsEditField' => 'PhabricatorEditField', 'PhabricatorInternationalizationManagementExtractWorkflow' => 'PhabricatorInternationalizationManagementWorkflow', 'PhabricatorInternationalizationManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorInvalidConfigSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorIteratedMD5PasswordHasher' => 'PhabricatorPasswordHasher', 'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase', 'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider', 'PhabricatorJavelinLinter' => 'ArcanistLinter', 'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorJumpNavHandler' => 'Phobject', 'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache', 'PhabricatorLDAPAuthProvider' => 'PhabricatorAuthProvider', 'PhabricatorLegalpadApplication' => 'PhabricatorApplication', 'PhabricatorLegalpadConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorLegalpadDocumentPHIDType' => 'PhabricatorPHIDType', 'PhabricatorLegalpadSignaturePolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorLibraryTestCase' => 'PhutilLibraryTestCase', 'PhabricatorLipsumArtist' => 'Phobject', 'PhabricatorLipsumGenerateWorkflow' => 'PhabricatorLipsumManagementWorkflow', 'PhabricatorLipsumManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorLipsumMondrianArtist' => 'PhabricatorLipsumArtist', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLiskSerializer' => 'Phobject', 'PhabricatorListFilterUIExample' => 'PhabricatorUIExample', 'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorLocalTimeTestCase' => 'PhabricatorTestCase', 'PhabricatorLocaleScopeGuard' => 'Phobject', 'PhabricatorLocaleScopeGuardTestCase' => 'PhabricatorTestCase', 'PhabricatorLogTriggerAction' => 'PhabricatorTriggerAction', 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorLunarPhasePolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorMacroApplication' => 'PhabricatorApplication', 'PhabricatorMacroAudioController' => 'PhabricatorMacroController', 'PhabricatorMacroCommentController' => 'PhabricatorMacroController', 'PhabricatorMacroConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorMacroController' => 'PhabricatorController', 'PhabricatorMacroDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorMacroDisableController' => 'PhabricatorMacroController', 'PhabricatorMacroEditController' => 'PhabricatorMacroController', 'PhabricatorMacroEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorMacroListController' => 'PhabricatorMacroController', 'PhabricatorMacroMacroPHIDType' => 'PhabricatorPHIDType', 'PhabricatorMacroMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorMacroManageCapability' => 'PhabricatorPolicyCapability', 'PhabricatorMacroMemeController' => 'PhabricatorMacroController', 'PhabricatorMacroMemeDialogController' => 'PhabricatorMacroController', 'PhabricatorMacroQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorMacroReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorMacroSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorMacroTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorMacroTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorMacroViewController' => 'PhabricatorMacroController', 'PhabricatorMailEmailHeraldField' => 'HeraldField', 'PhabricatorMailEmailHeraldFieldGroup' => 'HeraldFieldGroup', 'PhabricatorMailEmailSubjectHeraldField' => 'PhabricatorMailEmailHeraldField', 'PhabricatorMailImplementationAdapter' => 'Phobject', 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', 'PhabricatorMailImplementationMailgunAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationPHPMailerAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailManagementListInboundWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementListOutboundWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementReceiveTestWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementResendWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementSendTestWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementShowInboundWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementShowOutboundWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementVolumeWorkflow' => 'PhabricatorMailManagementWorkflow', 'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorMailOutboundMailHeraldAdapter' => 'HeraldAdapter', 'PhabricatorMailOutboundRoutingHeraldAction' => 'HeraldAction', 'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction', 'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction', 'PhabricatorMailOutboundStatus' => 'Phobject', 'PhabricatorMailReceiver' => 'Phobject', 'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase', 'PhabricatorMailReplyHandler' => 'Phobject', 'PhabricatorMailRoutingRule' => 'Phobject', 'PhabricatorMailSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorMailTarget' => 'Phobject', 'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorMainMenuSearchView' => 'AphrontView', 'PhabricatorMainMenuView' => 'AphrontView', 'PhabricatorManagementWorkflow' => 'PhutilArgumentWorkflow', 'PhabricatorManiphestApplication' => 'PhabricatorApplication', 'PhabricatorManiphestConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorManiphestTaskTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorMarkupCache' => 'PhabricatorCacheDAO', 'PhabricatorMarkupEngine' => 'Phobject', 'PhabricatorMarkupOneOff' => array( 'Phobject', 'PhabricatorMarkupInterface', ), 'PhabricatorMarkupPreviewController' => 'PhabricatorController', 'PhabricatorMemeRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorMentionRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorMercurialGraphStream' => 'PhabricatorRepositoryGraphStream', 'PhabricatorMetaMTAActor' => 'Phobject', 'PhabricatorMetaMTAActorQuery' => 'PhabricatorQuery', 'PhabricatorMetaMTAApplication' => 'PhabricatorApplication', 'PhabricatorMetaMTAApplicationEmail' => array( 'PhabricatorMetaMTADAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', ), 'PhabricatorMetaMTAApplicationEmailDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorMetaMTAApplicationEmailEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorMetaMTAApplicationEmailHeraldField' => 'HeraldField', 'PhabricatorMetaMTAApplicationEmailPHIDType' => 'PhabricatorPHIDType', 'PhabricatorMetaMTAApplicationEmailPanel' => 'PhabricatorApplicationConfigurationPanel', 'PhabricatorMetaMTAApplicationEmailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorMetaMTAApplicationEmailTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorMetaMTAApplicationEmailTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorMetaMTAAttachment' => 'Phobject', 'PhabricatorMetaMTAConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorMetaMTAController' => 'PhabricatorController', 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO', 'PhabricatorMetaMTAEmailBodyParser' => 'Phobject', 'PhabricatorMetaMTAEmailBodyParserTestCase' => 'PhabricatorTestCase', 'PhabricatorMetaMTAEmailHeraldAction' => 'HeraldAction', 'PhabricatorMetaMTAEmailOthersHeraldAction' => 'PhabricatorMetaMTAEmailHeraldAction', 'PhabricatorMetaMTAEmailSelfHeraldAction' => 'PhabricatorMetaMTAEmailHeraldAction', 'PhabricatorMetaMTAErrorMailAction' => 'PhabricatorSystemAction', 'PhabricatorMetaMTAMail' => array( 'PhabricatorMetaMTADAO', 'PhabricatorPolicyInterface', ), 'PhabricatorMetaMTAMailBody' => 'Phobject', 'PhabricatorMetaMTAMailBodyTestCase' => 'PhabricatorTestCase', 'PhabricatorMetaMTAMailHasRecipientEdgeType' => 'PhabricatorEdgeType', 'PhabricatorMetaMTAMailListController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMailPHIDType' => 'PhabricatorPHIDType', 'PhabricatorMetaMTAMailQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorMetaMTAMailSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorMetaMTAMailSection' => 'Phobject', 'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase', 'PhabricatorMetaMTAMailViewController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMailableDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorMetaMTAMailableFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMemberQuery' => 'PhabricatorQuery', 'PhabricatorMetaMTAPermanentFailureException' => 'Exception', 'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAReceivedMailProcessingException' => 'Exception', 'PhabricatorMetaMTAReceivedMailTestCase' => 'PhabricatorTestCase', 'PhabricatorMetaMTASchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorMetaMTASendGridReceiveController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAWorker' => 'PhabricatorWorker', 'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorMultiColumnUIExample' => 'PhabricatorUIExample', 'PhabricatorMultiFactorSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorMultimeterApplication' => 'PhabricatorApplication', 'PhabricatorMustVerifyEmailController' => 'PhabricatorAuthController', 'PhabricatorMySQLConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorMySQLSearchEngine' => 'PhabricatorSearchEngine', 'PhabricatorMySQLSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorNamedQuery' => array( 'PhabricatorSearchDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorNotificationBuilder' => 'Phobject', 'PhabricatorNotificationClearController' => 'PhabricatorNotificationController', 'PhabricatorNotificationClient' => 'Phobject', 'PhabricatorNotificationConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorNotificationController' => 'PhabricatorController', 'PhabricatorNotificationIndividualController' => 'PhabricatorNotificationController', 'PhabricatorNotificationListController' => 'PhabricatorNotificationController', 'PhabricatorNotificationPanelController' => 'PhabricatorNotificationController', 'PhabricatorNotificationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorNotificationSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorNotificationStatusController' => 'PhabricatorNotificationController', 'PhabricatorNotificationStatusView' => 'AphrontTagView', 'PhabricatorNotificationTestController' => 'PhabricatorNotificationController', 'PhabricatorNotificationTestFeedStory' => 'PhabricatorFeedStory', 'PhabricatorNotificationUIExample' => 'PhabricatorUIExample', 'PhabricatorNotificationsApplication' => 'PhabricatorApplication', 'PhabricatorNuanceApplication' => 'PhabricatorApplication', 'PhabricatorOAuth1AuthProvider' => 'PhabricatorOAuthAuthProvider', 'PhabricatorOAuth2AuthProvider' => 'PhabricatorOAuthAuthProvider', 'PhabricatorOAuthAuthProvider' => 'PhabricatorAuthProvider', 'PhabricatorOAuthClientAuthorization' => array( 'PhabricatorOAuthServerDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorOAuthClientAuthorizationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorOAuthClientController' => 'PhabricatorOAuthServerController', 'PhabricatorOAuthClientDeleteController' => 'PhabricatorOAuthClientController', 'PhabricatorOAuthClientEditController' => 'PhabricatorOAuthClientController', 'PhabricatorOAuthClientListController' => 'PhabricatorOAuthClientController', 'PhabricatorOAuthClientSecretController' => 'PhabricatorOAuthClientController', 'PhabricatorOAuthClientViewController' => 'PhabricatorOAuthClientController', 'PhabricatorOAuthResponse' => 'AphrontResponse', 'PhabricatorOAuthServer' => 'Phobject', 'PhabricatorOAuthServerAccessToken' => 'PhabricatorOAuthServerDAO', 'PhabricatorOAuthServerApplication' => 'PhabricatorApplication', 'PhabricatorOAuthServerAuthController' => 'PhabricatorOAuthServerController', 'PhabricatorOAuthServerAuthorizationCode' => 'PhabricatorOAuthServerDAO', 'PhabricatorOAuthServerAuthorizationsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorOAuthServerClient' => array( 'PhabricatorOAuthServerDAO', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorOAuthServerClientAuthorizationPHIDType' => 'PhabricatorPHIDType', 'PhabricatorOAuthServerClientPHIDType' => 'PhabricatorPHIDType', 'PhabricatorOAuthServerClientQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorOAuthServerClientSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorOAuthServerController' => 'PhabricatorController', 'PhabricatorOAuthServerCreateClientsCapability' => 'PhabricatorPolicyCapability', 'PhabricatorOAuthServerDAO' => 'PhabricatorLiskDAO', 'PhabricatorOAuthServerScope' => 'Phobject', 'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase', 'PhabricatorOAuthServerTestController' => 'PhabricatorOAuthServerController', 'PhabricatorOAuthServerTokenController' => 'PhabricatorOAuthServerController', 'PhabricatorObjectHandle' => array( 'Phobject', 'PhabricatorPolicyInterface', ), 'PhabricatorObjectHasAsanaSubtaskEdgeType' => 'PhabricatorEdgeType', 'PhabricatorObjectHasAsanaTaskEdgeType' => 'PhabricatorEdgeType', 'PhabricatorObjectHasContributorEdgeType' => 'PhabricatorEdgeType', 'PhabricatorObjectHasFileEdgeType' => 'PhabricatorEdgeType', 'PhabricatorObjectHasJiraIssueEdgeType' => 'PhabricatorEdgeType', 'PhabricatorObjectHasSubscriberEdgeType' => 'PhabricatorEdgeType', 'PhabricatorObjectHasUnsubscriberEdgeType' => 'PhabricatorEdgeType', 'PhabricatorObjectHasWatcherEdgeType' => 'PhabricatorEdgeType', 'PhabricatorObjectListQuery' => 'Phobject', 'PhabricatorObjectListQueryTestCase' => 'PhabricatorTestCase', 'PhabricatorObjectMailReceiver' => 'PhabricatorMailReceiver', 'PhabricatorObjectMailReceiverTestCase' => 'PhabricatorTestCase', 'PhabricatorObjectMentionedByObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorObjectMentionsObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorObjectSelectorDialog' => 'Phobject', 'PhabricatorObjectUsesCredentialsEdgeType' => 'PhabricatorEdgeType', 'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery', 'PhabricatorOneTimeTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorOpcodeCacheSpec' => 'PhabricatorCacheSpec', 'PhabricatorOwnerPathQuery' => 'Phobject', 'PhabricatorOwnersApplication' => 'PhabricatorApplication', 'PhabricatorOwnersConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorOwnersConfiguredCustomField' => array( 'PhabricatorOwnersCustomField', 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorOwnersController' => 'PhabricatorController', 'PhabricatorOwnersCustomField' => 'PhabricatorCustomField', 'PhabricatorOwnersCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 'PhabricatorOwnersCustomFieldStorage' => 'PhabricatorCustomFieldStorage', 'PhabricatorOwnersCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO', 'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController', 'PhabricatorOwnersEditController' => 'PhabricatorOwnersController', 'PhabricatorOwnersListController' => 'PhabricatorOwnersController', 'PhabricatorOwnersOwner' => 'PhabricatorOwnersDAO', 'PhabricatorOwnersPackage' => array( 'PhabricatorOwnersDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorCustomFieldInterface', ), 'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorOwnersPackageEditEngine' => 'PhabricatorEditEngine', 'PhabricatorOwnersPackageFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorOwnersPackageOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType', 'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase', 'PhabricatorOwnersPackageTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorOwnersPackageTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorOwnersPackageTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', 'PhabricatorOwnersPathsController' => 'PhabricatorOwnersController', 'PhabricatorOwnersSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorOwnersSearchField' => 'PhabricatorSearchTokenizerField', 'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPHID' => 'Phobject', 'PhabricatorPHIDConstants' => 'Phobject', + 'PhabricatorPHIDListEditField' => 'PhabricatorEditField', 'PhabricatorPHIDResolver' => 'Phobject', 'PhabricatorPHIDType' => 'Phobject', 'PhabricatorPHIDTypeTestCase' => 'PhutilTestCase', 'PhabricatorPHPASTApplication' => 'PhabricatorApplication', 'PhabricatorPHPConfigSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorPHPMailerConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPagedFormUIExample' => 'PhabricatorUIExample', 'PhabricatorPagerUIExample' => 'PhabricatorUIExample', 'PhabricatorPassphraseApplication' => 'PhabricatorApplication', 'PhabricatorPasswordAuthProvider' => 'PhabricatorAuthProvider', 'PhabricatorPasswordHasher' => 'Phobject', 'PhabricatorPasswordHasherTestCase' => 'PhabricatorTestCase', 'PhabricatorPasswordHasherUnavailableException' => 'Exception', 'PhabricatorPasswordSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorPaste' => array( 'PhabricatorPasteDAO', 'PhabricatorSubscribableInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorFlaggableInterface', 'PhabricatorMentionableInterface', 'PhabricatorPolicyInterface', 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorSpacesInterface', ), 'PhabricatorPasteApplication' => 'PhabricatorApplication', - 'PhabricatorPasteCommentController' => 'PhabricatorPasteController', 'PhabricatorPasteConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPasteController' => 'PhabricatorController', 'PhabricatorPasteDAO' => 'PhabricatorLiskDAO', 'PhabricatorPasteEditController' => 'PhabricatorPasteController', 'PhabricatorPasteEditEngine' => 'PhabricatorEditEngine', 'PhabricatorPasteEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorPasteListController' => 'PhabricatorPasteController', 'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType', 'PhabricatorPasteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPasteRawController' => 'PhabricatorPasteController', 'PhabricatorPasteRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorPasteSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorPasteSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPasteSnippet' => 'Phobject', 'PhabricatorPasteTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorPasteTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorPasteTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorPasteTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorPasteViewController' => 'PhabricatorPasteController', 'PhabricatorPathSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorPeopleApplication' => 'PhabricatorApplication', 'PhabricatorPeopleApproveController' => 'PhabricatorPeopleController', 'PhabricatorPeopleCalendarController' => 'PhabricatorPeopleController', 'PhabricatorPeopleController' => 'PhabricatorController', 'PhabricatorPeopleCreateController' => 'PhabricatorPeopleController', 'PhabricatorPeopleDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorPeopleDeleteController' => 'PhabricatorPeopleController', 'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController', 'PhabricatorPeopleEmpowerController' => 'PhabricatorPeopleController', 'PhabricatorPeopleExternalPHIDType' => 'PhabricatorPHIDType', 'PhabricatorPeopleHovercardEventListener' => 'PhabricatorEventListener', 'PhabricatorPeopleInviteController' => 'PhabricatorPeopleController', 'PhabricatorPeopleInviteListController' => 'PhabricatorPeopleInviteController', 'PhabricatorPeopleInviteSendController' => 'PhabricatorPeopleInviteController', 'PhabricatorPeopleLdapController' => 'PhabricatorPeopleController', 'PhabricatorPeopleListController' => 'PhabricatorPeopleController', 'PhabricatorPeopleLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPeopleLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController', 'PhabricatorPeopleNewController' => 'PhabricatorPeopleController', 'PhabricatorPeopleNoOwnerDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorPeopleOwnerDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileEditController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfilePictureController' => 'PhabricatorPeopleController', 'PhabricatorPeopleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController', 'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPeopleTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorPeopleTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorPeopleUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorPeopleUserPHIDType' => 'PhabricatorPHIDType', 'PhabricatorPeopleWelcomeController' => 'PhabricatorPeopleController', 'PhabricatorPersonaAuthProvider' => 'PhabricatorAuthProvider', 'PhabricatorPhabricatorAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorPhameApplication' => 'PhabricatorApplication', 'PhabricatorPhameBlogPHIDType' => 'PhabricatorPHIDType', 'PhabricatorPhameConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPhamePostPHIDType' => 'PhabricatorPHIDType', 'PhabricatorPhluxApplication' => 'PhabricatorApplication', 'PhabricatorPholioApplication' => 'PhabricatorApplication', 'PhabricatorPholioConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPholioMockTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorPhortuneApplication' => 'PhabricatorApplication', 'PhabricatorPhortuneManagementInvoiceWorkflow' => 'PhabricatorPhortuneManagementWorkflow', 'PhabricatorPhortuneManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorPhragmentApplication' => 'PhabricatorApplication', 'PhabricatorPhrequentApplication' => 'PhabricatorApplication', 'PhabricatorPhrequentConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPhrictionApplication' => 'PhabricatorApplication', 'PhabricatorPhrictionConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPhurlApplication' => 'PhabricatorApplication', 'PhabricatorPhurlConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPhurlController' => 'PhabricatorController', 'PhabricatorPhurlDAO' => 'PhabricatorLiskDAO', 'PhabricatorPhurlLinkRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorPhurlRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorPhurlSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorPhurlShortURLController' => 'PhabricatorPhurlController', 'PhabricatorPhurlShortURLDefaultController' => 'PhabricatorPhurlController', 'PhabricatorPhurlURL' => array( 'PhabricatorPhurlDAO', 'PhabricatorPolicyInterface', 'PhabricatorProjectInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorSubscribableInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorDestructibleInterface', 'PhabricatorMentionableInterface', 'PhabricatorFlaggableInterface', 'PhabricatorSpacesInterface', ), 'PhabricatorPhurlURLAccessController' => 'PhabricatorPhurlController', 'PhabricatorPhurlURLCommentController' => 'PhabricatorPhurlController', 'PhabricatorPhurlURLCreateCapability' => 'PhabricatorPolicyCapability', 'PhabricatorPhurlURLEditController' => 'PhabricatorPhurlController', 'PhabricatorPhurlURLEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorPhurlURLListController' => 'PhabricatorPhurlController', 'PhabricatorPhurlURLPHIDType' => 'PhabricatorPHIDType', 'PhabricatorPhurlURLQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPhurlURLSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPhurlURLTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorPhurlURLTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorPhurlURLTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorPhurlURLViewController' => 'PhabricatorPhurlController', 'PhabricatorPlatformSite' => 'PhabricatorSite', 'PhabricatorPolicies' => 'PhabricatorPolicyConstants', 'PhabricatorPolicy' => array( 'PhabricatorPolicyDAO', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorPolicyApplication' => 'PhabricatorApplication', 'PhabricatorPolicyAwareQuery' => 'PhabricatorOffsetPagedQuery', 'PhabricatorPolicyAwareTestQuery' => 'PhabricatorPolicyAwareQuery', 'PhabricatorPolicyCanEditCapability' => 'PhabricatorPolicyCapability', 'PhabricatorPolicyCanJoinCapability' => 'PhabricatorPolicyCapability', 'PhabricatorPolicyCanViewCapability' => 'PhabricatorPolicyCapability', 'PhabricatorPolicyCapability' => 'Phobject', 'PhabricatorPolicyCapabilityTestCase' => 'PhabricatorTestCase', 'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPolicyConstants' => 'Phobject', 'PhabricatorPolicyController' => 'PhabricatorController', 'PhabricatorPolicyDAO' => 'PhabricatorLiskDAO', 'PhabricatorPolicyDataTestCase' => 'PhabricatorTestCase', 'PhabricatorPolicyEditController' => 'PhabricatorPolicyController', + 'PhabricatorPolicyEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorPolicyEditField' => 'PhabricatorEditField', 'PhabricatorPolicyException' => 'Exception', 'PhabricatorPolicyExplainController' => 'PhabricatorPolicyController', 'PhabricatorPolicyFilter' => 'Phobject', 'PhabricatorPolicyInterface' => 'PhabricatorPHIDInterface', 'PhabricatorPolicyManagementShowWorkflow' => 'PhabricatorPolicyManagementWorkflow', 'PhabricatorPolicyManagementUnlockWorkflow' => 'PhabricatorPolicyManagementWorkflow', 'PhabricatorPolicyManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType', 'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorPolicyRule' => 'Phobject', 'PhabricatorPolicyTestCase' => 'PhabricatorTestCase', 'PhabricatorPolicyTestObject' => array( 'Phobject', 'PhabricatorPolicyInterface', 'PhabricatorExtendedPolicyInterface', ), 'PhabricatorPolicyType' => 'PhabricatorPolicyConstants', 'PhabricatorPonderApplication' => 'PhabricatorApplication', 'PhabricatorProject' => array( 'PhabricatorProjectDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFlaggableInterface', 'PhabricatorPolicyInterface', 'PhabricatorSubscribableInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction', 'PhabricatorProjectApplication' => 'PhabricatorApplication', 'PhabricatorProjectArchiveController' => 'PhabricatorProjectController', 'PhabricatorProjectBoardController' => 'PhabricatorProjectController', 'PhabricatorProjectBoardImportController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectColumn' => array( 'PhabricatorProjectDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorProjectColumnDetailController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectColumnEditController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectColumnHideController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectColumnPHIDType' => 'PhabricatorPHIDType', 'PhabricatorProjectColumnPosition' => array( 'PhabricatorProjectDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorProjectColumnPositionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorProjectColumnQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorProjectColumnTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorProjectColumnTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProjectColumnTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorProjectConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorProjectConfiguredCustomField' => array( 'PhabricatorProjectStandardCustomField', 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorProjectController' => 'PhabricatorController', 'PhabricatorProjectCustomField' => 'PhabricatorCustomField', 'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage', 'PhabricatorProjectCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 'PhabricatorProjectDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorProjectDescriptionField' => 'PhabricatorProjectStandardCustomField', 'PhabricatorProjectEditDetailsController' => 'PhabricatorProjectController', 'PhabricatorProjectEditIconController' => 'PhabricatorProjectController', 'PhabricatorProjectEditPictureController' => 'PhabricatorProjectController', 'PhabricatorProjectEditorTestCase' => 'PhabricatorTestCase', 'PhabricatorProjectFeedController' => 'PhabricatorProjectController', 'PhabricatorProjectHeraldAction' => 'HeraldAction', 'PhabricatorProjectIcon' => 'Phobject', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectLogicalAndDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalOrNotDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectLogicalViewerDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorProjectMemberOfProjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectMembersDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectMembersEditController' => 'PhabricatorProjectController', 'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController', 'PhabricatorProjectMoveController' => 'PhabricatorProjectController', 'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorProjectObjectHasProjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectOrUserDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectOrUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectPHIDResolver' => 'PhabricatorPHIDResolver', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorProjectProjectHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectProjectHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorProjectProjectPHIDType' => 'PhabricatorPHIDType', 'PhabricatorProjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorProjectRemoveHeraldAction' => 'PhabricatorProjectHeraldAction', 'PhabricatorProjectSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorProjectSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorProjectSearchField' => 'PhabricatorSearchTokenizerField', 'PhabricatorProjectSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorProjectSlug' => 'PhabricatorProjectDAO', 'PhabricatorProjectStandardCustomField' => array( 'PhabricatorProjectCustomField', 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorProjectStatus' => 'Phobject', 'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorProjectTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorProjectUIEventListener' => 'PhabricatorEventListener', 'PhabricatorProjectUpdateController' => 'PhabricatorProjectController', 'PhabricatorProjectUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorProjectViewController' => 'PhabricatorProjectController', 'PhabricatorProjectWatchController' => 'PhabricatorProjectController', + 'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorProtocolAdapter' => 'Phobject', 'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorQuery' => 'Phobject', 'PhabricatorQueryConstraint' => 'Phobject', 'PhabricatorQueryOrderItem' => 'Phobject', 'PhabricatorQueryOrderTestCase' => 'PhabricatorTestCase', 'PhabricatorQueryOrderVector' => array( 'Phobject', 'Iterator', ), 'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRecipientHasBadgeEdgeType' => 'PhabricatorEdgeType', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', 'PhabricatorRegistrationProfile' => 'Phobject', 'PhabricatorReleephApplication' => 'PhabricatorApplication', 'PhabricatorReleephApplicationConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRemarkupControl' => 'AphrontFormTextAreaControl', 'PhabricatorRemarkupCowsayBlockInterpreter' => 'PhutilRemarkupBlockInterpreter', 'PhabricatorRemarkupCustomBlockRule' => 'PhutilRemarkupBlockRule', 'PhabricatorRemarkupCustomInlineRule' => 'PhutilRemarkupRule', 'PhabricatorRemarkupEditField' => 'PhabricatorEditField', 'PhabricatorRemarkupFigletBlockInterpreter' => 'PhutilRemarkupBlockInterpreter', 'PhabricatorRemarkupUIExample' => 'PhabricatorUIExample', 'PhabricatorRepositoriesSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorRepository' => array( 'PhabricatorRepositoryDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorFlaggableInterface', 'PhabricatorMarkupInterface', 'PhabricatorDestructibleInterface', 'PhabricatorProjectInterface', 'PhabricatorSpacesInterface', ), 'PhabricatorRepositoryAuditRequest' => array( 'PhabricatorRepositoryDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorRepositoryBranch' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryCommit' => array( 'PhabricatorRepositoryDAO', 'PhabricatorPolicyInterface', 'PhabricatorFlaggableInterface', 'PhabricatorProjectInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorSubscribableInterface', 'PhabricatorMentionableInterface', 'HarbormasterBuildableInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorApplicationTransactionInterface', ), 'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryCommitHeraldWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitMessageParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitOwnersWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositoryCommitParserWorker' => 'PhabricatorWorker', 'PhabricatorRepositoryCommitRef' => 'Phobject', 'PhabricatorRepositoryCommitSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorRepositoryConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorRepositoryDiscoveryEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorRepositoryEngine' => 'Phobject', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositoryGraphCache' => 'Phobject', 'PhabricatorRepositoryGraphStream' => 'Phobject', 'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementDiscoverWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementEditWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMirrorWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMovePathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementParentsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementPullWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementRefsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementReparseWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementUpdateWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositoryMirror' => array( 'PhabricatorRepositoryDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorRepositoryMirrorEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryMirrorPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositoryMirrorQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryParsedChange' => 'Phobject', 'PhabricatorRepositoryPullEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorDaemon', 'PhabricatorRepositoryPushEvent' => array( 'PhabricatorRepositoryDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorRepositoryPushEventPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositoryPushEventQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryPushLog' => array( 'PhabricatorRepositoryDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorRepositoryPushLogPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositoryPushLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryPushLogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorRepositoryPushMailWorker' => 'PhabricatorWorker', 'PhabricatorRepositoryPushReplyHandler' => 'PhabricatorMailReplyHandler', 'PhabricatorRepositoryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryRefCursor' => array( 'PhabricatorRepositoryDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorRepositoryRefCursorQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryRefEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryRepositoryPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositorySchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorRepositorySearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorRepositoryStatusMessage' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositorySvnCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositorySymbol' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryTestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorRepositoryType' => 'Phobject', 'PhabricatorRepositoryURINormalizer' => 'Phobject', 'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryVersion' => 'Phobject', 'PhabricatorRequestExceptionHandler' => 'AphrontRequestExceptionHandler', 'PhabricatorResourceSite' => 'PhabricatorSite', 'PhabricatorRobotsController' => 'PhabricatorController', 'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorSMS' => 'PhabricatorSMSDAO', 'PhabricatorSMSConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSMSDAO' => 'PhabricatorLiskDAO', 'PhabricatorSMSDemultiplexWorker' => 'PhabricatorSMSWorker', 'PhabricatorSMSImplementationAdapter' => 'Phobject', 'PhabricatorSMSImplementationTestBlackholeAdapter' => 'PhabricatorSMSImplementationAdapter', 'PhabricatorSMSImplementationTwilioAdapter' => 'PhabricatorSMSImplementationAdapter', 'PhabricatorSMSManagementListOutboundWorkflow' => 'PhabricatorSMSManagementWorkflow', 'PhabricatorSMSManagementSendTestWorkflow' => 'PhabricatorSMSManagementWorkflow', 'PhabricatorSMSManagementShowOutboundWorkflow' => 'PhabricatorSMSManagementWorkflow', 'PhabricatorSMSManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorSMSSendWorker' => 'PhabricatorSMSWorker', 'PhabricatorSMSWorker' => 'PhabricatorWorker', 'PhabricatorSQLPatchList' => 'Phobject', 'PhabricatorSSHKeyGenerator' => 'Phobject', 'PhabricatorSSHKeysSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorSSHLog' => 'Phobject', 'PhabricatorSSHPassthruCommand' => 'Phobject', 'PhabricatorSSHWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorSavedQuery' => array( 'PhabricatorSearchDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorSavedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorScheduleTaskTriggerAction' => 'PhabricatorTriggerAction', 'PhabricatorScopedEnv' => 'Phobject', 'PhabricatorSearchAbstractDocument' => 'Phobject', 'PhabricatorSearchApplication' => 'PhabricatorApplication', 'PhabricatorSearchApplicationSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorSearchApplicationStorageEnginePanel' => 'PhabricatorApplicationConfigurationPanel', 'PhabricatorSearchAttachController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchBaseController' => 'PhabricatorController', 'PhabricatorSearchCheckboxesField' => 'PhabricatorSearchField', 'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSearchController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchCustomFieldProxyField' => 'PhabricatorSearchField', 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 'PhabricatorSearchDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorSearchDatasourceField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSearchDateControlField' => 'PhabricatorSearchField', 'PhabricatorSearchDateField' => 'PhabricatorSearchField', 'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentFieldType' => 'Phobject', 'PhabricatorSearchDocumentIndexer' => 'Phobject', 'PhabricatorSearchDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentTypeDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorSearchEditController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchEngine' => 'Phobject', 'PhabricatorSearchEngineTestCase' => 'PhabricatorTestCase', 'PhabricatorSearchField' => 'Phobject', 'PhabricatorSearchHovercardController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchIndexer' => 'Phobject', 'PhabricatorSearchManagementIndexWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementInitWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorSearchOrderController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchOrderField' => 'PhabricatorSearchField', 'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorSearchRelationship' => 'Phobject', 'PhabricatorSearchResultView' => 'AphrontView', 'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchSelectField' => 'PhabricatorSearchField', 'PhabricatorSearchStringListField' => 'PhabricatorSearchField', 'PhabricatorSearchSubscribersField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSearchTextField' => 'PhabricatorSearchField', 'PhabricatorSearchThreeStateField' => 'PhabricatorSearchField', 'PhabricatorSearchTokenizerField' => 'PhabricatorSearchField', 'PhabricatorSearchWorker' => 'PhabricatorWorker', 'PhabricatorSecurityConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSecuritySetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorSelectEditField' => 'PhabricatorEditField', 'PhabricatorSendGridConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSessionsSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsAddEmailAction' => 'PhabricatorSystemAction', 'PhabricatorSettingsAdjustController' => 'PhabricatorController', 'PhabricatorSettingsApplication' => 'PhabricatorApplication', 'PhabricatorSettingsMainController' => 'PhabricatorController', 'PhabricatorSettingsPanel' => 'Phobject', 'PhabricatorSetupCheck' => 'Phobject', 'PhabricatorSetupCheckTestCase' => 'PhabricatorTestCase', 'PhabricatorSetupIssue' => 'Phobject', 'PhabricatorSetupIssueUIExample' => 'PhabricatorUIExample', 'PhabricatorSetupIssueView' => 'AphrontView', 'PhabricatorShortSite' => 'PhabricatorSite', 'PhabricatorSimpleEditType' => 'PhabricatorEditType', 'PhabricatorSite' => 'AphrontSite', 'PhabricatorSlowvoteApplication' => 'PhabricatorApplication', 'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvoteCloseController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteCommentController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteController' => 'PhabricatorController', 'PhabricatorSlowvoteDAO' => 'PhabricatorLiskDAO', 'PhabricatorSlowvoteDefaultViewCapability' => 'PhabricatorPolicyCapability', 'PhabricatorSlowvoteEditController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorSlowvoteListController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorSlowvoteOption' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvotePoll' => array( 'PhabricatorSlowvoteDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorSubscribableInterface', 'PhabricatorFlaggableInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', ), 'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvotePollPHIDType' => 'PhabricatorPHIDType', 'PhabricatorSlowvoteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorSlowvoteReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorSlowvoteSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorSlowvoteSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorSlowvoteTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorSlowvoteTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorSlowvoteTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorSlowvoteVoteController' => 'PhabricatorSlowvoteController', 'PhabricatorSlug' => 'Phobject', 'PhabricatorSlugTestCase' => 'PhabricatorTestCase', 'PhabricatorSortTableUIExample' => 'PhabricatorUIExample', 'PhabricatorSourceCodeView' => 'AphrontView', 'PhabricatorSpaceEditField' => 'PhabricatorEditField', 'PhabricatorSpacesApplication' => 'PhabricatorApplication', 'PhabricatorSpacesArchiveController' => 'PhabricatorSpacesController', 'PhabricatorSpacesCapabilityCreateSpaces' => 'PhabricatorPolicyCapability', 'PhabricatorSpacesCapabilityDefaultEdit' => 'PhabricatorPolicyCapability', 'PhabricatorSpacesCapabilityDefaultView' => 'PhabricatorPolicyCapability', 'PhabricatorSpacesController' => 'PhabricatorController', 'PhabricatorSpacesDAO' => 'PhabricatorLiskDAO', 'PhabricatorSpacesEditController' => 'PhabricatorSpacesController', 'PhabricatorSpacesInterface' => 'PhabricatorPHIDInterface', 'PhabricatorSpacesListController' => 'PhabricatorSpacesController', 'PhabricatorSpacesNamespace' => array( 'PhabricatorSpacesDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorSpacesNamespaceDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorSpacesNamespaceEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorSpacesNamespacePHIDType' => 'PhabricatorPHIDType', 'PhabricatorSpacesNamespaceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorSpacesNamespaceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorSpacesNamespaceTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorSpacesNamespaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorSpacesNoAccessController' => 'PhabricatorSpacesController', 'PhabricatorSpacesRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorSpacesSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorSpacesSearchField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSpacesTestCase' => 'PhabricatorTestCase', 'PhabricatorSpacesViewController' => 'PhabricatorSpacesController', 'PhabricatorStandardCustomField' => 'PhabricatorCustomField', 'PhabricatorStandardCustomFieldBlueprints' => 'PhabricatorStandardCustomFieldTokenizer', 'PhabricatorStandardCustomFieldBool' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldCredential' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldDatasource' => 'PhabricatorStandardCustomFieldTokenizer', 'PhabricatorStandardCustomFieldDate' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldHeader' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldInt' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldLink' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldPHIDs' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldRemarkup' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldSelect' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldText' => 'PhabricatorStandardCustomField', 'PhabricatorStandardCustomFieldTokenizer' => 'PhabricatorStandardCustomFieldPHIDs', 'PhabricatorStandardCustomFieldUsers' => 'PhabricatorStandardCustomFieldTokenizer', 'PhabricatorStandardPageView' => array( 'PhabricatorBarePageView', 'AphrontResponseProducerInterface', ), 'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorStatusController' => 'PhabricatorController', 'PhabricatorStatusUIExample' => 'PhabricatorUIExample', 'PhabricatorStorageFixtureScopeGuard' => 'Phobject', 'PhabricatorStorageManagementAPI' => 'Phobject', 'PhabricatorStorageManagementAdjustWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDatabasesWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDestroyWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementDumpWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementProbeWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementQuickstartWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementRenamespaceWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementShellWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementStatusWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementUpgradeWorkflow' => 'PhabricatorStorageManagementWorkflow', 'PhabricatorStorageManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorStoragePatch' => 'Phobject', 'PhabricatorStorageSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorStorageSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorStreamingProtocolAdapter' => 'PhabricatorProtocolAdapter', 'PhabricatorSubscribedToObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorSubscribersEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorSubscribersQuery' => 'PhabricatorQuery', 'PhabricatorSubscriptionTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorSubscriptionsAddSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 'PhabricatorSubscriptionsApplication' => 'PhabricatorApplication', 'PhabricatorSubscriptionsEditController' => 'PhabricatorController', + 'PhabricatorSubscriptionsEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor', 'PhabricatorSubscriptionsHeraldAction' => 'HeraldAction', 'PhabricatorSubscriptionsListController' => 'PhabricatorController', 'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 'PhabricatorSubscriptionsSubscribeEmailCommand' => 'MetaMTAEmailTransactionCommand', 'PhabricatorSubscriptionsSubscribersPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorSubscriptionsTransactionController' => 'PhabricatorController', 'PhabricatorSubscriptionsUIEventListener' => 'PhabricatorEventListener', 'PhabricatorSubscriptionsUnsubscribeEmailCommand' => 'MetaMTAEmailTransactionCommand', 'PhabricatorSupportApplication' => 'PhabricatorApplication', 'PhabricatorSyntaxHighlighter' => 'Phobject', 'PhabricatorSyntaxHighlightingConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSystemAction' => 'Phobject', 'PhabricatorSystemActionEngine' => 'Phobject', 'PhabricatorSystemActionGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorSystemActionLog' => 'PhabricatorSystemDAO', 'PhabricatorSystemActionRateLimitException' => 'Exception', 'PhabricatorSystemApplication' => 'PhabricatorApplication', 'PhabricatorSystemDAO' => 'PhabricatorLiskDAO', 'PhabricatorSystemDestructionGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorSystemDestructionLog' => 'PhabricatorSystemDAO', 'PhabricatorSystemRemoveDestroyWorkflow' => 'PhabricatorSystemRemoveWorkflow', 'PhabricatorSystemRemoveLogWorkflow' => 'PhabricatorSystemRemoveWorkflow', 'PhabricatorSystemRemoveWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorSystemSelectEncodingController' => 'PhabricatorController', 'PhabricatorSystemSelectHighlightController' => 'PhabricatorController', 'PhabricatorTOTPAuthFactor' => 'PhabricatorAuthFactor', 'PhabricatorTOTPAuthFactorTestCase' => 'PhabricatorTestCase', 'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon', 'PhabricatorTestApplication' => 'PhabricatorApplication', 'PhabricatorTestCase' => 'PhutilTestCase', 'PhabricatorTestController' => 'PhabricatorController', 'PhabricatorTestDataGenerator' => 'Phobject', 'PhabricatorTestNoCycleEdgeType' => 'PhabricatorEdgeType', 'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorTestWorker' => 'PhabricatorWorker', 'PhabricatorTextAreaEditField' => 'PhabricatorEditField', 'PhabricatorTextEditField' => 'PhabricatorEditField', 'PhabricatorTime' => 'Phobject', 'PhabricatorTimeGuard' => 'Phobject', 'PhabricatorTimeTestCase' => 'PhabricatorTestCase', 'PhabricatorTimezoneSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorToken' => array( 'PhabricatorTokenDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorTokenController' => 'PhabricatorController', 'PhabricatorTokenCount' => 'PhabricatorTokenDAO', 'PhabricatorTokenCountQuery' => 'PhabricatorOffsetPagedQuery', 'PhabricatorTokenDAO' => 'PhabricatorLiskDAO', 'PhabricatorTokenGiveController' => 'PhabricatorTokenController', 'PhabricatorTokenGiven' => array( 'PhabricatorTokenDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorTokenGivenController' => 'PhabricatorTokenController', 'PhabricatorTokenGivenEditor' => 'PhabricatorEditor', 'PhabricatorTokenGivenFeedStory' => 'PhabricatorFeedStory', 'PhabricatorTokenGivenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorTokenLeaderController' => 'PhabricatorTokenController', 'PhabricatorTokenQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorTokenReceiverQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorTokenTokenPHIDType' => 'PhabricatorPHIDType', 'PhabricatorTokenUIEventListener' => 'PhabricatorEventListener', - 'PhabricatorTokenizerEditField' => 'PhabricatorEditField', + 'PhabricatorTokenizerEditField' => 'PhabricatorPHIDListEditField', 'PhabricatorTokensApplication' => 'PhabricatorApplication', 'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorTooltipUIExample' => 'PhabricatorUIExample', 'PhabricatorTransactions' => 'Phobject', 'PhabricatorTransactionsApplication' => 'PhabricatorApplication', 'PhabricatorTransformedFile' => 'PhabricatorFileDAO', 'PhabricatorTranslationsConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorTriggerAction' => 'Phobject', 'PhabricatorTriggerClock' => 'Phobject', 'PhabricatorTriggerClockTestCase' => 'PhabricatorTestCase', 'PhabricatorTriggerDaemon' => 'PhabricatorDaemon', 'PhabricatorTrivialTestCase' => 'PhabricatorTestCase', 'PhabricatorTwitchAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorTwitterAuthProvider' => 'PhabricatorOAuth1AuthProvider', 'PhabricatorTypeaheadApplication' => 'PhabricatorApplication', 'PhabricatorTypeaheadCompositeDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorTypeaheadDatasource' => 'Phobject', 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', 'PhabricatorTypeaheadFunctionHelpController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadInvalidTokenException' => 'Exception', 'PhabricatorTypeaheadModularDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorTypeaheadResult' => 'Phobject', 'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorTypeaheadTokenView' => 'AphrontTagView', 'PhabricatorUIConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorUIExample' => 'Phobject', 'PhabricatorUIExampleRenderController' => 'PhabricatorController', 'PhabricatorUIExamplesApplication' => 'PhabricatorApplication', 'PhabricatorUSEnglishTranslation' => 'PhutilTranslation', 'PhabricatorUnitsTestCase' => 'PhabricatorTestCase', 'PhabricatorUnsubscribedFromObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorUser' => array( 'PhabricatorUserDAO', 'PhutilPerson', 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorDestructibleInterface', 'PhabricatorSSHPublicKeyInterface', 'PhabricatorFlaggableInterface', 'PhabricatorApplicationTransactionInterface', ), 'PhabricatorUserBlurbField' => 'PhabricatorUserCustomField', 'PhabricatorUserConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorUserConfiguredCustomField' => array( 'PhabricatorUserCustomField', 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorUserConfiguredCustomFieldStorage' => 'PhabricatorCustomFieldStorage', 'PhabricatorUserCustomField' => 'PhabricatorCustomField', 'PhabricatorUserCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', 'PhabricatorUserCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', 'PhabricatorUserEditor' => 'PhabricatorEditor', 'PhabricatorUserEditorTestCase' => 'PhabricatorTestCase', 'PhabricatorUserEmail' => 'PhabricatorUserDAO', 'PhabricatorUserEmailTestCase' => 'PhabricatorTestCase', 'PhabricatorUserLog' => array( 'PhabricatorUserDAO', 'PhabricatorPolicyInterface', ), 'PhabricatorUserLogView' => 'AphrontView', 'PhabricatorUserPHIDResolver' => 'PhabricatorPHIDResolver', 'PhabricatorUserPreferences' => 'PhabricatorUserDAO', 'PhabricatorUserProfile' => 'PhabricatorUserDAO', 'PhabricatorUserProfileEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorUserRealNameField' => 'PhabricatorUserCustomField', 'PhabricatorUserRolesField' => 'PhabricatorUserCustomField', 'PhabricatorUserSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorUserSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorUserSinceField' => 'PhabricatorUserCustomField', 'PhabricatorUserStatusField' => 'PhabricatorUserCustomField', 'PhabricatorUserTestCase' => 'PhabricatorTestCase', 'PhabricatorUserTitleField' => 'PhabricatorUserCustomField', 'PhabricatorUserTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorUsersEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorUsersPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorUsersSearchField' => 'PhabricatorSearchTokenizerField', 'PhabricatorVCSResponse' => 'AphrontResponse', + 'PhabricatorVersionedDraft' => 'PhabricatorDraftDAO', 'PhabricatorVeryWowEnglishTranslation' => 'PhutilTranslation', 'PhabricatorViewerDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorWatcherHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorWordPressAuthProvider' => 'PhabricatorOAuth2AuthProvider', 'PhabricatorWorker' => 'Phobject', 'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask', 'PhabricatorWorkerArchiveTask' => 'PhabricatorWorkerTask', 'PhabricatorWorkerArchiveTaskQuery' => 'PhabricatorQuery', 'PhabricatorWorkerBulkJob' => array( 'PhabricatorWorkerDAO', 'PhabricatorPolicyInterface', 'PhabricatorSubscribableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorWorkerBulkJobCreateWorker' => 'PhabricatorWorkerBulkJobWorker', 'PhabricatorWorkerBulkJobEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorWorkerBulkJobPHIDType' => 'PhabricatorPHIDType', 'PhabricatorWorkerBulkJobQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorWorkerBulkJobSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorWorkerBulkJobTaskWorker' => 'PhabricatorWorkerBulkJobWorker', 'PhabricatorWorkerBulkJobTestCase' => 'PhabricatorTestCase', 'PhabricatorWorkerBulkJobTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorWorkerBulkJobTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorWorkerBulkJobType' => 'Phobject', 'PhabricatorWorkerBulkJobWorker' => 'PhabricatorWorker', 'PhabricatorWorkerBulkTask' => 'PhabricatorWorkerDAO', 'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO', 'PhabricatorWorkerLeaseQuery' => 'PhabricatorQuery', 'PhabricatorWorkerManagementCancelWorkflow' => 'PhabricatorWorkerManagementWorkflow', 'PhabricatorWorkerManagementExecuteWorkflow' => 'PhabricatorWorkerManagementWorkflow', 'PhabricatorWorkerManagementFloodWorkflow' => 'PhabricatorWorkerManagementWorkflow', 'PhabricatorWorkerManagementFreeWorkflow' => 'PhabricatorWorkerManagementWorkflow', 'PhabricatorWorkerManagementRetryWorkflow' => 'PhabricatorWorkerManagementWorkflow', 'PhabricatorWorkerManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorWorkerPermanentFailureException' => 'Exception', 'PhabricatorWorkerSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO', 'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO', 'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController', 'PhabricatorWorkerTestCase' => 'PhabricatorTestCase', 'PhabricatorWorkerTrigger' => array( 'PhabricatorWorkerDAO', 'PhabricatorDestructibleInterface', 'PhabricatorPolicyInterface', ), 'PhabricatorWorkerTriggerEvent' => 'PhabricatorWorkerDAO', 'PhabricatorWorkerTriggerManagementFireWorkflow' => 'PhabricatorWorkerTriggerManagementWorkflow', 'PhabricatorWorkerTriggerManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorWorkerTriggerPHIDType' => 'PhabricatorPHIDType', 'PhabricatorWorkerTriggerQuery' => 'PhabricatorPolicyAwareQuery', 'PhabricatorWorkerYieldException' => 'Exception', 'PhabricatorWorkingCopyDiscoveryTestCase' => 'PhabricatorWorkingCopyTestCase', 'PhabricatorWorkingCopyPullTestCase' => 'PhabricatorWorkingCopyTestCase', 'PhabricatorWorkingCopyTestCase' => 'PhabricatorTestCase', 'PhabricatorXHPASTDAO' => 'PhabricatorLiskDAO', 'PhabricatorXHPASTParseTree' => 'PhabricatorXHPASTDAO', 'PhabricatorXHPASTViewController' => 'PhabricatorController', 'PhabricatorXHPASTViewFrameController' => 'PhabricatorXHPASTViewController', 'PhabricatorXHPASTViewFramesetController' => 'PhabricatorXHPASTViewController', 'PhabricatorXHPASTViewInputController' => 'PhabricatorXHPASTViewPanelController', 'PhabricatorXHPASTViewPanelController' => 'PhabricatorXHPASTViewController', 'PhabricatorXHPASTViewRunController' => 'PhabricatorXHPASTViewController', 'PhabricatorXHPASTViewStreamController' => 'PhabricatorXHPASTViewPanelController', 'PhabricatorXHPASTViewTreeController' => 'PhabricatorXHPASTViewPanelController', 'PhabricatorXHProfApplication' => 'PhabricatorApplication', 'PhabricatorXHProfController' => 'PhabricatorController', 'PhabricatorXHProfDAO' => 'PhabricatorLiskDAO', 'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController', 'PhabricatorXHProfProfileSymbolView' => 'PhabricatorXHProfProfileView', 'PhabricatorXHProfProfileTopLevelView' => 'PhabricatorXHProfProfileView', 'PhabricatorXHProfProfileView' => 'AphrontView', 'PhabricatorXHProfSample' => 'PhabricatorXHProfDAO', 'PhabricatorXHProfSampleListController' => 'PhabricatorXHProfController', 'PhabricatorYoutubeRemarkupRule' => 'PhutilRemarkupRule', 'PhameBasicBlogSkin' => 'PhameBlogSkin', 'PhameBasicTemplateBlogSkin' => 'PhameBasicBlogSkin', 'PhameBlog' => array( 'PhameDAO', 'PhabricatorPolicyInterface', 'PhabricatorMarkupInterface', 'PhabricatorSubscribableInterface', 'PhabricatorFlaggableInterface', 'PhabricatorProjectInterface', + 'PhabricatorDestructibleInterface', 'PhabricatorApplicationTransactionInterface', ), 'PhameBlogArchiveController' => 'PhameBlogController', 'PhameBlogController' => 'PhameController', 'PhameBlogCreateCapability' => 'PhabricatorPolicyCapability', 'PhameBlogEditController' => 'PhameBlogController', 'PhameBlogEditor' => 'PhabricatorApplicationTransactionEditor', 'PhameBlogFeedController' => 'PhameBlogController', 'PhameBlogListController' => 'PhameBlogController', 'PhameBlogLiveController' => 'PhameBlogController', + 'PhameBlogManageController' => 'PhameBlogController', + 'PhameBlogProfilePictureController' => 'PhameBlogController', 'PhameBlogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhameBlogReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhameBlogSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhameBlogSite' => 'PhameSite', 'PhameBlogSkin' => 'PhabricatorController', 'PhameBlogTransaction' => 'PhabricatorApplicationTransaction', 'PhameBlogTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhameBlogViewController' => 'PhameBlogController', 'PhameCelerityResources' => 'CelerityResources', 'PhameConduitAPIMethod' => 'ConduitAPIMethod', 'PhameConstants' => 'Phobject', 'PhameController' => 'PhabricatorController', 'PhameCreatePostConduitAPIMethod' => 'PhameConduitAPIMethod', 'PhameDAO' => 'PhabricatorLiskDAO', + 'PhameDescriptionView' => 'AphrontTagView', + 'PhameHomeController' => 'PhamePostController', 'PhamePost' => array( 'PhameDAO', 'PhabricatorPolicyInterface', 'PhabricatorMarkupInterface', 'PhabricatorFlaggableInterface', 'PhabricatorProjectInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorSubscribableInterface', + 'PhabricatorDestructibleInterface', 'PhabricatorTokenReceiverInterface', ), 'PhamePostCommentController' => 'PhamePostController', 'PhamePostController' => 'PhameController', 'PhamePostEditController' => 'PhamePostController', 'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor', 'PhamePostFramedController' => 'PhamePostController', + 'PhamePostHistoryController' => 'PhamePostController', 'PhamePostListController' => 'PhamePostController', + 'PhamePostListView' => 'AphrontTagView', 'PhamePostMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhamePostNewController' => 'PhamePostController', 'PhamePostNotLiveController' => 'PhamePostController', + 'PhamePostPreviewController' => 'PhamePostController', 'PhamePostPublishController' => 'PhamePostController', 'PhamePostQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhamePostReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhamePostSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhamePostTransaction' => 'PhabricatorApplicationTransaction', 'PhamePostTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhamePostTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhamePostUnpublishController' => 'PhamePostController', 'PhamePostView' => 'AphrontView', 'PhamePostViewController' => 'PhamePostController', 'PhameQueryConduitAPIMethod' => 'PhameConduitAPIMethod', 'PhameQueryPostsConduitAPIMethod' => 'PhameConduitAPIMethod', 'PhameResourceController' => 'CelerityResourceController', 'PhameSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhameSite' => 'PhabricatorSite', 'PhameSkinSpecification' => 'Phobject', 'PhluxController' => 'PhabricatorController', 'PhluxDAO' => 'PhabricatorLiskDAO', 'PhluxEditController' => 'PhluxController', 'PhluxListController' => 'PhluxController', 'PhluxTransaction' => 'PhabricatorApplicationTransaction', 'PhluxTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhluxVariable' => array( 'PhluxDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFlaggableInterface', 'PhabricatorPolicyInterface', ), 'PhluxVariableEditor' => 'PhabricatorApplicationTransactionEditor', 'PhluxVariablePHIDType' => 'PhabricatorPHIDType', 'PhluxVariableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhluxViewController' => 'PhluxController', 'PholioActionMenuEventListener' => 'PhabricatorEventListener', 'PholioController' => 'PhabricatorController', 'PholioDAO' => 'PhabricatorLiskDAO', 'PholioDefaultEditCapability' => 'PhabricatorPolicyCapability', 'PholioDefaultViewCapability' => 'PhabricatorPolicyCapability', 'PholioImage' => array( 'PholioDAO', 'PhabricatorMarkupInterface', 'PhabricatorPolicyInterface', ), 'PholioImagePHIDType' => 'PhabricatorPHIDType', 'PholioImageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PholioImageUploadController' => 'PholioController', 'PholioInlineController' => 'PholioController', 'PholioInlineListController' => 'PholioController', 'PholioMock' => array( 'PholioDAO', 'PhabricatorMarkupInterface', 'PhabricatorPolicyInterface', 'PhabricatorSubscribableInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorFlaggableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', 'PhabricatorMentionableInterface', ), 'PholioMockAuthorHeraldField' => 'PholioMockHeraldField', 'PholioMockCommentController' => 'PholioController', 'PholioMockDescriptionHeraldField' => 'PholioMockHeraldField', 'PholioMockEditController' => 'PholioController', 'PholioMockEditor' => 'PhabricatorApplicationTransactionEditor', 'PholioMockEmbedView' => 'AphrontView', 'PholioMockHasTaskEdgeType' => 'PhabricatorEdgeType', 'PholioMockHeraldField' => 'HeraldField', 'PholioMockHeraldFieldGroup' => 'HeraldFieldGroup', 'PholioMockImagesView' => 'AphrontView', 'PholioMockListController' => 'PholioController', 'PholioMockMailReceiver' => 'PhabricatorObjectMailReceiver', 'PholioMockNameHeraldField' => 'PholioMockHeraldField', 'PholioMockPHIDType' => 'PhabricatorPHIDType', 'PholioMockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PholioMockSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PholioMockThumbGridView' => 'AphrontView', 'PholioMockViewController' => 'PholioController', 'PholioRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PholioReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PholioSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PholioSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PholioTransaction' => 'PhabricatorApplicationTransaction', 'PholioTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PholioTransactionView' => 'PhabricatorApplicationTransactionView', 'PholioUploadedImageView' => 'AphrontView', 'PhortuneAccount' => array( 'PhortuneDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), 'PhortuneAccountEditController' => 'PhortuneController', 'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneAccountHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhortuneAccountListController' => 'PhortuneController', 'PhortuneAccountPHIDType' => 'PhabricatorPHIDType', 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction', 'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneAccountViewController' => 'PhortuneController', 'PhortuneAdHocCart' => 'PhortuneCartImplementation', 'PhortuneAdHocProduct' => 'PhortuneProductImplementation', 'PhortuneCart' => array( 'PhortuneDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), 'PhortuneCartAcceptController' => 'PhortuneCartController', 'PhortuneCartCancelController' => 'PhortuneCartController', 'PhortuneCartCheckoutController' => 'PhortuneCartController', 'PhortuneCartController' => 'PhortuneController', 'PhortuneCartEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneCartImplementation' => 'Phobject', 'PhortuneCartListController' => 'PhortuneController', 'PhortuneCartPHIDType' => 'PhabricatorPHIDType', 'PhortuneCartQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneCartReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhortuneCartSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhortuneCartTransaction' => 'PhabricatorApplicationTransaction', 'PhortuneCartTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneCartUpdateController' => 'PhortuneCartController', 'PhortuneCartViewController' => 'PhortuneCartController', 'PhortuneCharge' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', ), 'PhortuneChargeListController' => 'PhortuneController', 'PhortuneChargePHIDType' => 'PhabricatorPHIDType', 'PhortuneChargeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneChargeSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhortuneChargeTableView' => 'AphrontView', 'PhortuneConstants' => 'Phobject', 'PhortuneController' => 'PhabricatorController', 'PhortuneCreditCardForm' => 'Phobject', 'PhortuneCurrency' => 'Phobject', 'PhortuneCurrencySerializer' => 'PhabricatorLiskSerializer', 'PhortuneCurrencyTestCase' => 'PhabricatorTestCase', 'PhortuneDAO' => 'PhabricatorLiskDAO', 'PhortuneErrCode' => 'PhortuneConstants', 'PhortuneLandingController' => 'PhortuneController', 'PhortuneMemberHasAccountEdgeType' => 'PhabricatorEdgeType', 'PhortuneMemberHasMerchantEdgeType' => 'PhabricatorEdgeType', 'PhortuneMerchant' => array( 'PhortuneDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), 'PhortuneMerchantCapability' => 'PhabricatorPolicyCapability', 'PhortuneMerchantController' => 'PhortuneController', 'PhortuneMerchantEditController' => 'PhortuneMerchantController', 'PhortuneMerchantEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortuneMerchantHasMemberEdgeType' => 'PhabricatorEdgeType', 'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantController', 'PhortuneMerchantListController' => 'PhortuneMerchantController', 'PhortuneMerchantPHIDType' => 'PhabricatorPHIDType', 'PhortuneMerchantQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneMerchantSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhortuneMerchantTransaction' => 'PhabricatorApplicationTransaction', 'PhortuneMerchantTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneMerchantViewController' => 'PhortuneMerchantController', 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', 'PhortuneOrderTableView' => 'AphrontView', 'PhortunePayPalPaymentProvider' => 'PhortunePaymentProvider', 'PhortunePaymentMethod' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', ), 'PhortunePaymentMethodCreateController' => 'PhortuneController', 'PhortunePaymentMethodDisableController' => 'PhortuneController', 'PhortunePaymentMethodEditController' => 'PhortuneController', 'PhortunePaymentMethodPHIDType' => 'PhabricatorPHIDType', 'PhortunePaymentMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortunePaymentProvider' => 'Phobject', 'PhortunePaymentProviderConfig' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', ), 'PhortunePaymentProviderConfigEditor' => 'PhabricatorApplicationTransactionEditor', 'PhortunePaymentProviderConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortunePaymentProviderConfigTransaction' => 'PhabricatorApplicationTransaction', 'PhortunePaymentProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortunePaymentProviderPHIDType' => 'PhabricatorPHIDType', 'PhortunePaymentProviderTestCase' => 'PhabricatorTestCase', 'PhortuneProduct' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', ), 'PhortuneProductImplementation' => 'Phobject', 'PhortuneProductListController' => 'PhabricatorController', 'PhortuneProductPHIDType' => 'PhabricatorPHIDType', 'PhortuneProductQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneProductViewController' => 'PhortuneController', 'PhortuneProviderActionController' => 'PhortuneController', 'PhortuneProviderDisableController' => 'PhortuneMerchantController', 'PhortuneProviderEditController' => 'PhortuneMerchantController', 'PhortunePurchase' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', ), 'PhortunePurchasePHIDType' => 'PhabricatorPHIDType', 'PhortunePurchaseQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhortuneStripePaymentProvider' => 'PhortunePaymentProvider', 'PhortuneSubscription' => array( 'PhortuneDAO', 'PhabricatorPolicyInterface', ), 'PhortuneSubscriptionCart' => 'PhortuneCartImplementation', 'PhortuneSubscriptionEditController' => 'PhortuneController', 'PhortuneSubscriptionImplementation' => 'Phobject', 'PhortuneSubscriptionListController' => 'PhortuneController', 'PhortuneSubscriptionPHIDType' => 'PhabricatorPHIDType', 'PhortuneSubscriptionProduct' => 'PhortuneProductImplementation', 'PhortuneSubscriptionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneSubscriptionSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhortuneSubscriptionTableView' => 'AphrontView', 'PhortuneSubscriptionViewController' => 'PhortuneController', 'PhortuneSubscriptionWorker' => 'PhabricatorWorker', 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', 'PhortuneWePayPaymentProvider' => 'PhortunePaymentProvider', 'PhragmentBrowseController' => 'PhragmentController', 'PhragmentCanCreateCapability' => 'PhabricatorPolicyCapability', 'PhragmentConduitAPIMethod' => 'ConduitAPIMethod', 'PhragmentController' => 'PhabricatorController', 'PhragmentCreateController' => 'PhragmentController', 'PhragmentDAO' => 'PhabricatorLiskDAO', 'PhragmentFragment' => array( 'PhragmentDAO', 'PhabricatorPolicyInterface', ), 'PhragmentFragmentPHIDType' => 'PhabricatorPHIDType', 'PhragmentFragmentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhragmentFragmentVersion' => array( 'PhragmentDAO', 'PhabricatorPolicyInterface', ), 'PhragmentFragmentVersionPHIDType' => 'PhabricatorPHIDType', 'PhragmentFragmentVersionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhragmentGetPatchConduitAPIMethod' => 'PhragmentConduitAPIMethod', 'PhragmentHistoryController' => 'PhragmentController', 'PhragmentPatchController' => 'PhragmentController', 'PhragmentPatchUtil' => 'Phobject', 'PhragmentPolicyController' => 'PhragmentController', 'PhragmentQueryFragmentsConduitAPIMethod' => 'PhragmentConduitAPIMethod', 'PhragmentRevertController' => 'PhragmentController', 'PhragmentSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhragmentSnapshot' => array( 'PhragmentDAO', 'PhabricatorPolicyInterface', ), 'PhragmentSnapshotChild' => array( 'PhragmentDAO', 'PhabricatorPolicyInterface', ), 'PhragmentSnapshotChildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhragmentSnapshotCreateController' => 'PhragmentController', 'PhragmentSnapshotDeleteController' => 'PhragmentController', 'PhragmentSnapshotPHIDType' => 'PhabricatorPHIDType', 'PhragmentSnapshotPromoteController' => 'PhragmentController', 'PhragmentSnapshotQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhragmentSnapshotViewController' => 'PhragmentController', 'PhragmentUpdateController' => 'PhragmentController', 'PhragmentVersionController' => 'PhragmentController', 'PhragmentZIPController' => 'PhragmentController', 'PhrequentConduitAPIMethod' => 'ConduitAPIMethod', 'PhrequentController' => 'PhabricatorController', 'PhrequentDAO' => 'PhabricatorLiskDAO', 'PhrequentListController' => 'PhrequentController', 'PhrequentPopConduitAPIMethod' => 'PhrequentConduitAPIMethod', 'PhrequentPushConduitAPIMethod' => 'PhrequentConduitAPIMethod', 'PhrequentSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhrequentTimeBlock' => 'Phobject', 'PhrequentTimeBlockTestCase' => 'PhabricatorTestCase', 'PhrequentTimeSlices' => 'Phobject', 'PhrequentTrackController' => 'PhrequentController', 'PhrequentTrackingConduitAPIMethod' => 'PhrequentConduitAPIMethod', 'PhrequentTrackingEditor' => 'PhabricatorEditor', 'PhrequentUIEventListener' => 'PhabricatorEventListener', 'PhrequentUserTime' => array( 'PhrequentDAO', 'PhabricatorPolicyInterface', ), 'PhrequentUserTimeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhrictionChangeType' => 'PhrictionConstants', 'PhrictionConduitAPIMethod' => 'ConduitAPIMethod', 'PhrictionConstants' => 'Phobject', 'PhrictionContent' => array( 'PhrictionDAO', 'PhabricatorMarkupInterface', ), 'PhrictionController' => 'PhabricatorController', 'PhrictionCreateConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionDAO' => 'PhabricatorLiskDAO', 'PhrictionDeleteController' => 'PhrictionController', 'PhrictionDiffController' => 'PhrictionController', 'PhrictionDocument' => array( 'PhrictionDAO', 'PhabricatorPolicyInterface', 'PhabricatorSubscribableInterface', 'PhabricatorFlaggableInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorDestructibleInterface', 'PhabricatorApplicationTransactionInterface', ), 'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentController' => 'PhrictionController', 'PhrictionDocumentHeraldAdapter' => 'HeraldAdapter', 'PhrictionDocumentHeraldField' => 'HeraldField', 'PhrictionDocumentHeraldFieldGroup' => 'HeraldFieldGroup', 'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType', 'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhrictionDocumentStatus' => 'PhrictionConstants', 'PhrictionDocumentTitleHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionEditConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionEditController' => 'PhrictionController', 'PhrictionHistoryConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionHistoryController' => 'PhrictionController', 'PhrictionInfoConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionListController' => 'PhrictionController', 'PhrictionMoveController' => 'PhrictionController', 'PhrictionNewController' => 'PhrictionController', 'PhrictionRemarkupRule' => 'PhutilRemarkupRule', 'PhrictionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhrictionSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhrictionSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhrictionTransaction' => 'PhabricatorApplicationTransaction', 'PhrictionTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhrictionTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'PhrictionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PolicyLockOptionType' => 'PhabricatorConfigJSONOptionType', 'PonderAddAnswerView' => 'AphrontView', 'PonderAnswer' => array( 'PonderDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorMarkupInterface', 'PonderVotableInterface', 'PhabricatorPolicyInterface', 'PhabricatorFlaggableInterface', 'PhabricatorSubscribableInterface', 'PhabricatorDestructibleInterface', ), 'PonderAnswerCommentController' => 'PonderController', 'PonderAnswerEditController' => 'PonderController', 'PonderAnswerEditor' => 'PonderEditor', 'PonderAnswerHasVotingUserEdgeType' => 'PhabricatorEdgeType', 'PonderAnswerHistoryController' => 'PonderController', 'PonderAnswerMailReceiver' => 'PhabricatorObjectMailReceiver', 'PonderAnswerPHIDType' => 'PhabricatorPHIDType', 'PonderAnswerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PonderAnswerReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PonderAnswerSaveController' => 'PonderController', 'PonderAnswerStatus' => 'PonderConstants', 'PonderAnswerTransaction' => 'PhabricatorApplicationTransaction', 'PonderAnswerTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PonderAnswerTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PonderAnswerView' => 'AphrontTagView', 'PonderConstants' => 'Phobject', 'PonderController' => 'PhabricatorController', 'PonderDAO' => 'PhabricatorLiskDAO', 'PonderDefaultViewCapability' => 'PhabricatorPolicyCapability', 'PonderEditor' => 'PhabricatorApplicationTransactionEditor', 'PonderFooterView' => 'AphrontTagView', 'PonderHelpfulSaveController' => 'PonderController', 'PonderModerateCapability' => 'PhabricatorPolicyCapability', 'PonderQuestion' => array( 'PonderDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorMarkupInterface', 'PhabricatorSubscribableInterface', 'PhabricatorFlaggableInterface', 'PhabricatorPolicyInterface', 'PhabricatorTokenReceiverInterface', 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', 'PhabricatorSpacesInterface', ), 'PonderQuestionCommentController' => 'PonderController', 'PonderQuestionEditController' => 'PonderController', 'PonderQuestionEditor' => 'PonderEditor', 'PonderQuestionHistoryController' => 'PonderController', 'PonderQuestionListController' => 'PonderController', 'PonderQuestionMailReceiver' => 'PhabricatorObjectMailReceiver', 'PonderQuestionPHIDType' => 'PhabricatorPHIDType', 'PonderQuestionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PonderQuestionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PonderQuestionSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PonderQuestionStatus' => 'PonderConstants', 'PonderQuestionStatusController' => 'PonderController', 'PonderQuestionTransaction' => 'PhabricatorApplicationTransaction', 'PonderQuestionTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PonderQuestionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PonderQuestionViewController' => 'PonderController', 'PonderRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PonderSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PonderSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 'PonderVote' => 'PonderConstants', 'PonderVoteEditor' => 'PhabricatorEditor', 'PonderVotingUserHasAnswerEdgeType' => 'PhabricatorEdgeType', 'ProjectAddProjectsEmailCommand' => 'MetaMTAEmailTransactionCommand', 'ProjectBoardTaskCard' => 'Phobject', 'ProjectCanLockProjectsCapability' => 'PhabricatorPolicyCapability', 'ProjectConduitAPIMethod' => 'ConduitAPIMethod', 'ProjectCreateConduitAPIMethod' => 'ProjectConduitAPIMethod', 'ProjectCreateProjectsCapability' => 'PhabricatorPolicyCapability', 'ProjectDefaultEditCapability' => 'PhabricatorPolicyCapability', 'ProjectDefaultJoinCapability' => 'PhabricatorPolicyCapability', 'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability', 'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod', 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase', 'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'QueryFormattingTestCase' => 'PhabricatorTestCase', 'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification', 'ReleephBranch' => array( 'ReleephDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), 'ReleephBranchAccessController' => 'ReleephBranchController', 'ReleephBranchCommitFieldSpecification' => 'ReleephFieldSpecification', 'ReleephBranchController' => 'ReleephController', 'ReleephBranchCreateController' => 'ReleephProductController', 'ReleephBranchEditController' => 'ReleephBranchController', 'ReleephBranchEditor' => 'PhabricatorEditor', 'ReleephBranchHistoryController' => 'ReleephBranchController', 'ReleephBranchNamePreviewController' => 'ReleephController', 'ReleephBranchPHIDType' => 'PhabricatorPHIDType', 'ReleephBranchPreviewView' => 'AphrontFormControl', 'ReleephBranchQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ReleephBranchSearchEngine' => 'PhabricatorApplicationSearchEngine', 'ReleephBranchTemplate' => 'Phobject', 'ReleephBranchTransaction' => 'PhabricatorApplicationTransaction', 'ReleephBranchTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'ReleephBranchViewController' => 'ReleephBranchController', 'ReleephCommitFinder' => 'Phobject', 'ReleephCommitFinderException' => 'Exception', 'ReleephCommitMessageFieldSpecification' => 'ReleephFieldSpecification', 'ReleephConduitAPIMethod' => 'ConduitAPIMethod', 'ReleephController' => 'PhabricatorController', 'ReleephDAO' => 'PhabricatorLiskDAO', 'ReleephDefaultFieldSelector' => 'ReleephFieldSelector', 'ReleephDependsOnFieldSpecification' => 'ReleephFieldSpecification', 'ReleephDiffChurnFieldSpecification' => 'ReleephFieldSpecification', 'ReleephDiffMessageFieldSpecification' => 'ReleephFieldSpecification', 'ReleephDiffSizeFieldSpecification' => 'ReleephFieldSpecification', 'ReleephFieldParseException' => 'Exception', 'ReleephFieldSelector' => 'Phobject', 'ReleephFieldSpecification' => array( 'PhabricatorCustomField', 'PhabricatorMarkupInterface', ), 'ReleephGetBranchesConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephIntentFieldSpecification' => 'ReleephFieldSpecification', 'ReleephLevelFieldSpecification' => 'ReleephFieldSpecification', 'ReleephOriginalCommitFieldSpecification' => 'ReleephFieldSpecification', 'ReleephProductActionController' => 'ReleephProductController', 'ReleephProductController' => 'ReleephController', 'ReleephProductCreateController' => 'ReleephProductController', 'ReleephProductEditController' => 'ReleephProductController', 'ReleephProductEditor' => 'PhabricatorApplicationTransactionEditor', 'ReleephProductHistoryController' => 'ReleephProductController', 'ReleephProductListController' => 'ReleephController', 'ReleephProductPHIDType' => 'PhabricatorPHIDType', 'ReleephProductQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ReleephProductSearchEngine' => 'PhabricatorApplicationSearchEngine', 'ReleephProductTransaction' => 'PhabricatorApplicationTransaction', 'ReleephProductTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'ReleephProductViewController' => 'ReleephProductController', 'ReleephProject' => array( 'ReleephDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), 'ReleephQueryBranchesConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephQueryProductsConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephQueryRequestsConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephReasonFieldSpecification' => 'ReleephFieldSpecification', 'ReleephRequest' => array( 'ReleephDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', ), 'ReleephRequestActionController' => 'ReleephRequestController', 'ReleephRequestCommentController' => 'ReleephRequestController', 'ReleephRequestConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephRequestController' => 'ReleephController', 'ReleephRequestDifferentialCreateController' => 'ReleephController', 'ReleephRequestEditController' => 'ReleephBranchController', 'ReleephRequestMailReceiver' => 'PhabricatorObjectMailReceiver', 'ReleephRequestPHIDType' => 'PhabricatorPHIDType', 'ReleephRequestQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'ReleephRequestReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'ReleephRequestSearchEngine' => 'PhabricatorApplicationSearchEngine', 'ReleephRequestStatus' => 'Phobject', 'ReleephRequestTransaction' => 'PhabricatorApplicationTransaction', 'ReleephRequestTransactionComment' => 'PhabricatorApplicationTransactionComment', 'ReleephRequestTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'ReleephRequestTransactionalEditor' => 'PhabricatorApplicationTransactionEditor', 'ReleephRequestTypeaheadControl' => 'AphrontFormControl', 'ReleephRequestTypeaheadController' => 'PhabricatorTypeaheadDatasourceController', 'ReleephRequestView' => 'AphrontView', 'ReleephRequestViewController' => 'ReleephBranchController', 'ReleephRequestorFieldSpecification' => 'ReleephFieldSpecification', 'ReleephRevisionFieldSpecification' => 'ReleephFieldSpecification', 'ReleephSeverityFieldSpecification' => 'ReleephLevelFieldSpecification', 'ReleephSummaryFieldSpecification' => 'ReleephFieldSpecification', 'ReleephWorkCanPushConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephWorkGetAuthorInfoConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephWorkGetBranchCommitMessageConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephWorkGetBranchConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephWorkGetCommitMessageConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephWorkNextRequestConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephWorkRecordConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephWorkRecordPickStatusConduitAPIMethod' => 'ReleephConduitAPIMethod', 'RemarkupProcessConduitAPIMethod' => 'ConduitAPIMethod', 'RepositoryConduitAPIMethod' => 'ConduitAPIMethod', 'RepositoryCreateConduitAPIMethod' => 'RepositoryConduitAPIMethod', 'RepositoryQueryConduitAPIMethod' => 'RepositoryConduitAPIMethod', 'ShellLogView' => 'AphrontView', 'SlowvoteConduitAPIMethod' => 'ConduitAPIMethod', 'SlowvoteEmbedView' => 'AphrontView', 'SlowvoteInfoConduitAPIMethod' => 'SlowvoteConduitAPIMethod', 'SlowvoteRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'SubscriptionListDialogBuilder' => 'Phobject', 'SubscriptionListStringBuilder' => 'Phobject', 'TokenConduitAPIMethod' => 'ConduitAPIMethod', 'TokenGiveConduitAPIMethod' => 'TokenConduitAPIMethod', 'TokenGivenConduitAPIMethod' => 'TokenConduitAPIMethod', 'TokenQueryConduitAPIMethod' => 'TokenConduitAPIMethod', 'UserConduitAPIMethod' => 'ConduitAPIMethod', 'UserDisableConduitAPIMethod' => 'UserConduitAPIMethod', 'UserEnableConduitAPIMethod' => 'UserConduitAPIMethod', 'UserFindConduitAPIMethod' => 'UserConduitAPIMethod', 'UserQueryConduitAPIMethod' => 'UserConduitAPIMethod', 'UserWhoAmIConduitAPIMethod' => 'UserConduitAPIMethod', ), )); diff --git a/src/aphront/httpparametertype/AphrontBoolHTTPParameterType.php b/src/aphront/httpparametertype/AphrontBoolHTTPParameterType.php new file mode 100644 index 0000000000..4fc758ddda --- /dev/null +++ b/src/aphront/httpparametertype/AphrontBoolHTTPParameterType.php @@ -0,0 +1,29 @@ +getBool($key); + } + + protected function getParameterTypeName() { + return 'bool'; + } + + protected function getParameterFormatDescriptions() { + return array( + pht('A boolean value (true or false).'), + ); + } + + protected function getParameterExamples() { + return array( + 'v=true', + 'v=false', + 'v=1', + 'v=0', + ); + } + +} diff --git a/src/aphront/httpparametertype/AphrontIntHTTPParameterType.php b/src/aphront/httpparametertype/AphrontIntHTTPParameterType.php new file mode 100644 index 0000000000..e32614da02 --- /dev/null +++ b/src/aphront/httpparametertype/AphrontIntHTTPParameterType.php @@ -0,0 +1,26 @@ +getInt($key); + } + + protected function getParameterTypeName() { + return 'int'; + } + + protected function getParameterFormatDescriptions() { + return array( + pht('An integer.'), + ); + } + + protected function getParameterExamples() { + return array( + 'v=123', + ); + } + +} diff --git a/src/aphront/httpparametertype/AphrontListHTTPParameterType.php b/src/aphront/httpparametertype/AphrontListHTTPParameterType.php new file mode 100644 index 0000000000..af972d0f29 --- /dev/null +++ b/src/aphront/httpparametertype/AphrontListHTTPParameterType.php @@ -0,0 +1,10 @@ +getValueWithType($type, $request, $key); } protected function getParameterTypeName() { return 'list'; } protected function getParameterFormatDescriptions() { return array( pht('Comma-separated list of PHIDs.'), pht('List of PHIDs, as array.'), ); } protected function getParameterExamples() { return array( 'v=PHID-XXXX-1111', 'v=PHID-XXXX-1111,PHID-XXXX-2222', 'v[]=PHID-XXXX-1111&v[]=PHID-XXXX-2222', ); } } diff --git a/src/aphront/httpparametertype/AphrontProjectListHTTPParameterType.php b/src/aphront/httpparametertype/AphrontProjectListHTTPParameterType.php index fd7e0db5cd..e4633796fe 100644 --- a/src/aphront/httpparametertype/AphrontProjectListHTTPParameterType.php +++ b/src/aphront/httpparametertype/AphrontProjectListHTTPParameterType.php @@ -1,42 +1,42 @@ getValueWithType($type, $request, $key); return id(new PhabricatorProjectPHIDResolver()) ->setViewer($this->getViewer()) ->resolvePHIDs($list); } protected function getParameterTypeName() { return 'list'; } protected function getParameterFormatDescriptions() { return array( pht('Comma-separated list of project PHIDs.'), pht('List of project PHIDs, as array.'), pht('Comma-separated list of project hashtags.'), pht('List of project hashtags, as array.'), pht('Mixture of hashtags and PHIDs.'), ); } protected function getParameterExamples() { return array( 'v=PHID-PROJ-1111', 'v=PHID-PROJ-1111,PHID-PROJ-2222', 'v=hashtag', 'v=frontend,backend', 'v[]=PHID-PROJ-1111&v[]=PHID-PROJ-2222', 'v[]=frontend&v[]=backend', 'v=PHID-PROJ-1111,frontend', 'v[]=PHID-PROJ-1111&v[]=backend', ); } } diff --git a/src/aphront/httpparametertype/AphrontStringListHTTPParameterType.php b/src/aphront/httpparametertype/AphrontStringListHTTPParameterType.php index 5c34dbc248..7212b124d8 100644 --- a/src/aphront/httpparametertype/AphrontStringListHTTPParameterType.php +++ b/src/aphront/httpparametertype/AphrontStringListHTTPParameterType.php @@ -1,38 +1,38 @@ getArr($key, null); if ($list === null) { $list = $request->getStrList($key); } return $list; } protected function getParameterDefault() { return array(); } protected function getParameterTypeName() { return 'list'; } protected function getParameterFormatDescriptions() { return array( pht('Comma-separated list of strings.'), pht('List of strings, as array.'), ); } protected function getParameterExamples() { return array( 'v=cat,dog,pig', 'v[]=cat&v[]=dog', ); } } diff --git a/src/aphront/httpparametertype/AphrontUserListHTTPParameterType.php b/src/aphront/httpparametertype/AphrontUserListHTTPParameterType.php index 3254542115..9599ea49e1 100644 --- a/src/aphront/httpparametertype/AphrontUserListHTTPParameterType.php +++ b/src/aphront/httpparametertype/AphrontUserListHTTPParameterType.php @@ -1,42 +1,42 @@ getValueWithType($type, $request, $key); return id(new PhabricatorUserPHIDResolver()) ->setViewer($this->getViewer()) ->resolvePHIDs($list); } protected function getParameterTypeName() { return 'list'; } protected function getParameterFormatDescriptions() { return array( pht('Comma-separated list of user PHIDs.'), pht('List of user PHIDs, as array.'), pht('Comma-separated list of usernames.'), pht('List of usernames, as array.'), pht('Mixture of usernames and PHIDs.'), ); } protected function getParameterExamples() { return array( 'v=PHID-USER-1111', 'v=PHID-USER-1111,PHID-USER-2222', 'v=username', 'v=alincoln,htaft', 'v[]=PHID-USER-1111&v[]=PHID-USER-2222', 'v[]=htaft&v[]=alincoln', 'v=PHID-USER-1111,alincoln', 'v[]=PHID-USER-1111&v[]=htaft', ); } } diff --git a/src/applications/almanac/controller/AlmanacBindingEditController.php b/src/applications/almanac/controller/AlmanacBindingEditController.php index 7fc5abc395..f20011ded9 100644 --- a/src/applications/almanac/controller/AlmanacBindingEditController.php +++ b/src/applications/almanac/controller/AlmanacBindingEditController.php @@ -1,123 +1,123 @@ getViewer(); $id = $request->getURIData('id'); if ($id) { $binding = id(new AlmanacBindingQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$binding) { return new Aphront404Response(); } $service = $binding->getService(); $is_new = false; $service_uri = $service->getURI(); $cancel_uri = $binding->getURI(); $title = pht('Edit Binding'); $save_button = pht('Save Changes'); } else { $service = id(new AlmanacServiceQuery()) ->setViewer($viewer) ->withIDs(array($request->getStr('serviceID'))) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); $binding = AlmanacBinding::initializeNewBinding($service); $is_new = true; $service_uri = $service->getURI(); $cancel_uri = $service_uri; $title = pht('Create Binding'); $save_button = pht('Create Binding'); } $v_interface = array(); if ($binding->getInterfacePHID()) { $v_interface = array($binding->getInterfacePHID()); } $e_interface = true; $validation_exception = null; if ($request->isFormPost()) { $v_interface = $request->getArr('interfacePHIDs'); $type_interface = AlmanacBindingTransaction::TYPE_INTERFACE; $xactions = array(); $xactions[] = id(new AlmanacBindingTransaction()) ->setTransactionType($type_interface) ->setNewValue(head($v_interface)); $editor = id(new AlmanacBindingEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($binding, $xactions); $binding_uri = $binding->getURI(); return id(new AphrontRedirectResponse())->setURI($binding_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_interface = $ex->getShortMessage($type_interface); } } $form = id(new AphrontFormView()) ->setUser($viewer) ->appendControl( id(new AphrontFormTokenizerControl()) ->setName('interfacePHIDs') ->setLabel(pht('Interface')) ->setLimit(1) ->setDatasource(new AlmanacInterfaceDatasource()) ->setValue($v_interface) ->setError($e_interface)) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($save_button)); $box = id(new PHUIObjectBoxView()) ->setValidationException($validation_exception) ->setHeaderText($title) ->appendChild($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($service->getName(), $service_uri); if ($is_new) { $crumbs->addTextCrumb(pht('Create Binding')); } else { $crumbs->addTextCrumb(pht('Edit Binding')); } - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, )); + } } diff --git a/src/applications/almanac/controller/AlmanacBindingViewController.php b/src/applications/almanac/controller/AlmanacBindingViewController.php index f68c335fbe..9e26ba2012 100644 --- a/src/applications/almanac/controller/AlmanacBindingViewController.php +++ b/src/applications/almanac/controller/AlmanacBindingViewController.php @@ -1,118 +1,117 @@ getViewer(); $id = $request->getURIData('id'); $binding = id(new AlmanacBindingQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$binding) { return new Aphront404Response(); } $service = $binding->getService(); $service_uri = $service->getURI(); $title = pht('Binding %s', $binding->getID()); $property_list = $this->buildPropertyList($binding); $action_list = $this->buildActionList($binding); $property_list->setActionList($action_list); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($title) ->setPolicyObject($binding); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($property_list); if ($binding->getService()->getIsLocked()) { $this->addLockMessage( $box, pht( 'This service for this binding is locked, so the binding can '. 'not be edited.')); } $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($service->getName(), $service_uri); $crumbs->addTextCrumb($title); $timeline = $this->buildTransactionTimeline( $binding, new AlmanacBindingTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $this->buildAlmanacPropertiesTable($binding), - $timeline, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, + $this->buildAlmanacPropertiesTable($binding), + $timeline, )); } private function buildPropertyList(AlmanacBinding $binding) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer); $properties->addProperty( pht('Service'), $viewer->renderHandle($binding->getServicePHID())); $properties->addProperty( pht('Device'), $viewer->renderHandle($binding->getDevicePHID())); $properties->addProperty( pht('Network'), $viewer->renderHandle($binding->getInterface()->getNetworkPHID())); $properties->addProperty( pht('Interface'), $binding->getInterface()->renderDisplayAddress()); return $properties; } private function buildActionList(AlmanacBinding $binding) { $viewer = $this->getViewer(); $id = $binding->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $binding, PhabricatorPolicyCapability::CAN_EDIT); $actions = id(new PhabricatorActionListView()) ->setUser($viewer); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Binding')) ->setHref($this->getApplicationURI("binding/edit/{$id}/")) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); return $actions; } } diff --git a/src/applications/almanac/controller/AlmanacConsoleController.php b/src/applications/almanac/controller/AlmanacConsoleController.php index 020027f23b..8abd8c6113 100644 --- a/src/applications/almanac/controller/AlmanacConsoleController.php +++ b/src/applications/almanac/controller/AlmanacConsoleController.php @@ -1,53 +1,53 @@ getViewer(); $menu = id(new PHUIObjectItemListView()) ->setUser($viewer); $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Services')) ->setHref($this->getApplicationURI('service/')) ->setFontIcon('fa-plug') ->addAttribute(pht('Manage Almanac services.'))); $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Devices')) ->setHref($this->getApplicationURI('device/')) ->setFontIcon('fa-server') ->addAttribute(pht('Manage Almanac devices.'))); $menu->addItem( id(new PHUIObjectItemView()) ->setHeader(pht('Networks')) ->setHref($this->getApplicationURI('network/')) ->setFontIcon('fa-globe') ->addAttribute(pht('Manage Almanac networks.'))); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Console')); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Console')) ->setObjectList($menu); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => pht('Almanac Console'), + return $this->newPage() + ->setTitle(pht('Almanac Console')) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, )); + } } diff --git a/src/applications/almanac/controller/AlmanacDeviceEditController.php b/src/applications/almanac/controller/AlmanacDeviceEditController.php index dede68a971..3bbc388838 100644 --- a/src/applications/almanac/controller/AlmanacDeviceEditController.php +++ b/src/applications/almanac/controller/AlmanacDeviceEditController.php @@ -1,164 +1,163 @@ getViewer(); $list_uri = $this->getApplicationURI('device/'); $id = $request->getURIData('id'); if ($id) { $device = id(new AlmanacDeviceQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$device) { return new Aphront404Response(); } $is_new = false; $device_uri = $device->getURI(); $cancel_uri = $device_uri; $title = pht('Edit Device'); $save_button = pht('Save Changes'); } else { $this->requireApplicationCapability( AlmanacCreateDevicesCapability::CAPABILITY); $device = AlmanacDevice::initializeNewDevice(); $is_new = true; $cancel_uri = $list_uri; $title = pht('Create Device'); $save_button = pht('Create Device'); } $v_name = $device->getName(); $e_name = true; $validation_exception = null; if ($is_new) { $v_projects = array(); } else { $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( $device->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $v_projects = array_reverse($v_projects); } if ($request->isFormPost()) { $v_name = $request->getStr('name'); $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); $v_projects = $request->getArr('projects'); $type_name = AlmanacDeviceTransaction::TYPE_NAME; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; $xactions = array(); $xactions[] = id(new AlmanacDeviceTransaction()) ->setTransactionType($type_name) ->setNewValue($v_name); $xactions[] = id(new AlmanacDeviceTransaction()) ->setTransactionType($type_view) ->setNewValue($v_view); $xactions[] = id(new AlmanacDeviceTransaction()) ->setTransactionType($type_edit) ->setNewValue($v_edit); $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new AlmanacDeviceTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $proj_edge_type) ->setNewValue(array('=' => array_fuse($v_projects))); $editor = id(new AlmanacDeviceEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($device, $xactions); $device_uri = $device->getURI(); return id(new AphrontRedirectResponse())->setURI($device_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_name = $ex->getShortMessage($type_name); $device->setViewPolicy($v_view); $device->setEditPolicy($v_edit); } } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($device) ->execute(); $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') ->setValue($v_name) ->setError($e_name)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('viewPolicy') ->setPolicyObject($device) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicies($policies)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('editPolicy') ->setPolicyObject($device) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicies($policies)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setValue($v_projects) ->setDatasource(new PhabricatorProjectDatasource())) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($save_button)); $box = id(new PHUIObjectBoxView()) ->setValidationException($validation_exception) ->setHeaderText($title) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); if ($is_new) { $crumbs->addTextCrumb(pht('Create Device')); } else { $crumbs->addTextCrumb($device->getName(), $device_uri); $crumbs->addTextCrumb(pht('Edit')); } - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, )); } } diff --git a/src/applications/almanac/controller/AlmanacDeviceViewController.php b/src/applications/almanac/controller/AlmanacDeviceViewController.php index 61dacdee56..e611cceb7a 100644 --- a/src/applications/almanac/controller/AlmanacDeviceViewController.php +++ b/src/applications/almanac/controller/AlmanacDeviceViewController.php @@ -1,256 +1,255 @@ getViewer(); $name = $request->getURIData('name'); $device = id(new AlmanacDeviceQuery()) ->setViewer($viewer) ->withNames(array($name)) ->executeOne(); if (!$device) { return new Aphront404Response(); } // We rebuild locks on a device when viewing the detail page, so they // automatically get corrected if they fall out of sync. $device->rebuildDeviceLocks(); $title = pht('Device %s', $device->getName()); $property_list = $this->buildPropertyList($device); $action_list = $this->buildActionList($device); $property_list->setActionList($action_list); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($device->getName()) ->setPolicyObject($device); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($property_list); if ($device->getIsLocked()) { $this->addLockMessage( $box, pht( 'This device is bound to a locked service, so it can not be '. 'edited.')); } $interfaces = $this->buildInterfaceList($device); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($device->getName()); $timeline = $this->buildTransactionTimeline( $device, new AlmanacDeviceTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $interfaces, - $this->buildAlmanacPropertiesTable($device), - $this->buildSSHKeysTable($device), - $this->buildServicesTable($device), - $timeline, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, + $interfaces, + $this->buildAlmanacPropertiesTable($device), + $this->buildSSHKeysTable($device), + $this->buildServicesTable($device), + $timeline, )); } private function buildPropertyList(AlmanacDevice $device) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($device); return $properties; } private function buildActionList(AlmanacDevice $device) { $viewer = $this->getViewer(); $id = $device->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $device, PhabricatorPolicyCapability::CAN_EDIT); $actions = id(new PhabricatorActionListView()) ->setUser($viewer); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Device')) ->setHref($this->getApplicationURI("device/edit/{$id}/")) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); return $actions; } private function buildInterfaceList(AlmanacDevice $device) { $viewer = $this->getViewer(); $id = $device->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $device, PhabricatorPolicyCapability::CAN_EDIT); $interfaces = id(new AlmanacInterfaceQuery()) ->setViewer($viewer) ->withDevicePHIDs(array($device->getPHID())) ->execute(); $table = id(new AlmanacInterfaceTableView()) ->setUser($viewer) ->setInterfaces($interfaces) ->setCanEdit($can_edit); $header = id(new PHUIHeaderView()) ->setHeader(pht('Device Interfaces')) ->addActionLink( id(new PHUIButtonView()) ->setTag('a') ->setHref($this->getApplicationURI("interface/edit/?deviceID={$id}")) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit) ->setText(pht('Add Interface')) ->setIcon( id(new PHUIIconView()) ->setIconFont('fa-plus'))); return id(new PHUIObjectBoxView()) ->setHeader($header) ->setTable($table); } private function buildSSHKeysTable(AlmanacDevice $device) { $viewer = $this->getViewer(); $id = $device->getID(); $device_phid = $device->getPHID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $device, PhabricatorPolicyCapability::CAN_EDIT); $keys = id(new PhabricatorAuthSSHKeyQuery()) ->setViewer($viewer) ->withObjectPHIDs(array($device_phid)) ->execute(); $table = id(new PhabricatorAuthSSHKeyTableView()) ->setUser($viewer) ->setKeys($keys) ->setCanEdit($can_edit) ->setShowID(true) ->setShowTrusted(true) ->setNoDataString(pht('This device has no associated SSH public keys.')); try { PhabricatorSSHKeyGenerator::assertCanGenerateKeypair(); $can_generate = true; } catch (Exception $ex) { $can_generate = false; } $generate_uri = '/auth/sshkey/generate/?objectPHID='.$device_phid; $upload_uri = '/auth/sshkey/upload/?objectPHID='.$device_phid; $header = id(new PHUIHeaderView()) ->setHeader(pht('SSH Public Keys')) ->addActionLink( id(new PHUIButtonView()) ->setTag('a') ->setHref($generate_uri) ->setWorkflow(true) ->setDisabled(!$can_edit || !$can_generate) ->setText(pht('Generate Keypair')) ->setIcon( id(new PHUIIconView()) ->setIconFont('fa-lock'))) ->addActionLink( id(new PHUIButtonView()) ->setTag('a') ->setHref($upload_uri) ->setWorkflow(true) ->setDisabled(!$can_edit) ->setText(pht('Upload Public Key')) ->setIcon( id(new PHUIIconView()) ->setIconFont('fa-upload'))); return id(new PHUIObjectBoxView()) ->setHeader($header) ->setTable($table); } private function buildServicesTable(AlmanacDevice $device) { $viewer = $this->getViewer(); // NOTE: We're loading all services so we can show hidden, locked services. // In general, we let you know about all the things the device is bound to, // even if you don't have permission to see their details. This is similar // to exposing the existence of edges in other applications, with the // addition of always letting you see that locks exist. $services = id(new AlmanacServiceQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withDevicePHIDs(array($device->getPHID())) ->execute(); $handles = $viewer->loadHandles(mpull($services, 'getPHID')); $icon_lock = id(new PHUIIconView()) ->setIconFont('fa-lock'); $rows = array(); foreach ($services as $service) { $rows[] = array( ($service->getIsLocked() ? $icon_lock : null), $handles->renderHandle($service->getPHID()), ); } $table = id(new AphrontTableView($rows)) ->setNoDataString(pht('No services are bound to this device.')) ->setHeaders( array( null, pht('Service'), )) ->setColumnClasses( array( null, 'wide pri', )); return id(new PHUIObjectBoxView()) ->setHeaderText(pht('Bound Services')) ->setTable($table); } } diff --git a/src/applications/almanac/controller/AlmanacInterfaceEditController.php b/src/applications/almanac/controller/AlmanacInterfaceEditController.php index 9bb45fd2ce..6d750bfbf5 100644 --- a/src/applications/almanac/controller/AlmanacInterfaceEditController.php +++ b/src/applications/almanac/controller/AlmanacInterfaceEditController.php @@ -1,155 +1,154 @@ getViewer(); $id = $request->getURIData('id'); if ($id) { $interface = id(new AlmanacInterfaceQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$interface) { return new Aphront404Response(); } $device = $interface->getDevice(); $is_new = false; $title = pht('Edit Interface'); $save_button = pht('Save Changes'); } else { $device = id(new AlmanacDeviceQuery()) ->setViewer($viewer) ->withIDs(array($request->getStr('deviceID'))) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$device) { return new Aphront404Response(); } $interface = AlmanacInterface::initializeNewInterface(); $is_new = true; $title = pht('Create Interface'); $save_button = pht('Create Interface'); } $device_uri = $device->getURI(); $cancel_uri = $device_uri; $v_network = $interface->getNetworkPHID(); $v_address = $interface->getAddress(); $e_address = true; $v_port = $interface->getPort(); $validation_exception = null; if ($request->isFormPost()) { $v_network = $request->getStr('networkPHID'); $v_address = $request->getStr('address'); $v_port = $request->getStr('port'); $type_interface = AlmanacDeviceTransaction::TYPE_INTERFACE; $address = AlmanacAddress::newFromParts($v_network, $v_address, $v_port); $xaction = id(new AlmanacDeviceTransaction()) ->setTransactionType($type_interface) ->setNewValue($address->toDictionary()); if ($interface->getID()) { $xaction->setOldValue(array( 'id' => $interface->getID(), ) + $interface->toAddress()->toDictionary()); } else { $xaction->setOldValue(array()); } $xactions = array(); $xactions[] = $xaction; $editor = id(new AlmanacDeviceEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true); try { $editor->applyTransactions($device, $xactions); $device_uri = $device->getURI(); return id(new AphrontRedirectResponse())->setURI($device_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_address = $ex->getShortMessage($type_interface); } } $networks = id(new AlmanacNetworkQuery()) ->setViewer($viewer) ->execute(); $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Network')) ->setName('networkPHID') ->setValue($v_network) ->setOptions(mpull($networks, 'getName', 'getPHID'))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Address')) ->setName('address') ->setValue($v_address) ->setError($e_address)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Port')) ->setName('port') ->setValue($v_port) ->setError($e_address)) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($save_button)); $box = id(new PHUIObjectBoxView()) ->setValidationException($validation_exception) ->setHeaderText($title) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($device->getName(), $device_uri); if ($is_new) { $crumbs->addTextCrumb(pht('Create Interface')); } else { $crumbs->addTextCrumb(pht('Edit Interface')); } - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, )); } } diff --git a/src/applications/almanac/controller/AlmanacNetworkEditController.php b/src/applications/almanac/controller/AlmanacNetworkEditController.php index 6c756969f8..b4ab77b95b 100644 --- a/src/applications/almanac/controller/AlmanacNetworkEditController.php +++ b/src/applications/almanac/controller/AlmanacNetworkEditController.php @@ -1,143 +1,142 @@ getViewer(); $list_uri = $this->getApplicationURI('network/'); $id = $request->getURIData('id'); if ($id) { $network = id(new AlmanacNetworkQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$network) { return new Aphront404Response(); } $is_new = false; $network_uri = $this->getApplicationURI('network/'.$network->getID().'/'); $cancel_uri = $network_uri; $title = pht('Edit Network'); $save_button = pht('Save Changes'); } else { $this->requireApplicationCapability( AlmanacCreateNetworksCapability::CAPABILITY); $network = AlmanacNetwork::initializeNewNetwork(); $is_new = true; $cancel_uri = $list_uri; $title = pht('Create Network'); $save_button = pht('Create Network'); } $v_name = $network->getName(); $e_name = true; $validation_exception = null; if ($request->isFormPost()) { $v_name = $request->getStr('name'); $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); $type_name = AlmanacNetworkTransaction::TYPE_NAME; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; $xactions = array(); $xactions[] = id(new AlmanacNetworkTransaction()) ->setTransactionType($type_name) ->setNewValue($v_name); $xactions[] = id(new AlmanacNetworkTransaction()) ->setTransactionType($type_view) ->setNewValue($v_view); $xactions[] = id(new AlmanacNetworkTransaction()) ->setTransactionType($type_edit) ->setNewValue($v_edit); $editor = id(new AlmanacNetworkEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($network, $xactions); $id = $network->getID(); $network_uri = $this->getApplicationURI("network/{$id}/"); return id(new AphrontRedirectResponse())->setURI($network_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_name = $ex->getShortMessage($type_name); $network->setViewPolicy($v_view); $network->setEditPolicy($v_edit); } } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($network) ->execute(); $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') ->setValue($v_name) ->setError($e_name)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('viewPolicy') ->setPolicyObject($network) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicies($policies)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('editPolicy') ->setPolicyObject($network) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicies($policies)) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($save_button)); $box = id(new PHUIObjectBoxView()) ->setValidationException($validation_exception) ->setHeaderText($title) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); if ($is_new) { $crumbs->addTextCrumb(pht('Create Network')); } else { $crumbs->addTextCrumb($network->getName(), $network_uri); $crumbs->addTextCrumb(pht('Edit')); } - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, )); } } diff --git a/src/applications/almanac/controller/AlmanacNetworkViewController.php b/src/applications/almanac/controller/AlmanacNetworkViewController.php index 294039dae7..70bd0cb87e 100644 --- a/src/applications/almanac/controller/AlmanacNetworkViewController.php +++ b/src/applications/almanac/controller/AlmanacNetworkViewController.php @@ -1,88 +1,87 @@ getViewer(); $id = $request->getURIData('id'); $network = id(new AlmanacNetworkQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$network) { return new Aphront404Response(); } $title = pht('Network %s', $network->getName()); $property_list = $this->buildPropertyList($network); $action_list = $this->buildActionList($network); $property_list->setActionList($action_list); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($network->getName()) ->setPolicyObject($network); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($property_list); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($network->getName()); $timeline = $this->buildTransactionTimeline( $network, new AlmanacNetworkTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $timeline, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, + $timeline, )); } private function buildPropertyList(AlmanacNetwork $network) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer); return $properties; } private function buildActionList(AlmanacNetwork $network) { $viewer = $this->getViewer(); $id = $network->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $network, PhabricatorPolicyCapability::CAN_EDIT); $actions = id(new PhabricatorActionListView()) ->setUser($viewer); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Network')) ->setHref($this->getApplicationURI("network/edit/{$id}/")) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); return $actions; } } diff --git a/src/applications/almanac/controller/AlmanacServiceEditController.php b/src/applications/almanac/controller/AlmanacServiceEditController.php index 699e1bb3da..20a7528704 100644 --- a/src/applications/almanac/controller/AlmanacServiceEditController.php +++ b/src/applications/almanac/controller/AlmanacServiceEditController.php @@ -1,253 +1,252 @@ getViewer(); $list_uri = $this->getApplicationURI('service/'); $id = $request->getURIData('id'); if ($id) { $service = id(new AlmanacServiceQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$service) { return new Aphront404Response(); } $is_new = false; $service_uri = $service->getURI(); $cancel_uri = $service_uri; $title = pht('Edit Service'); $save_button = pht('Save Changes'); } else { $cancel_uri = $list_uri; $this->requireApplicationCapability( AlmanacCreateServicesCapability::CAPABILITY); $service_class = $request->getStr('serviceClass'); $service_types = AlmanacServiceType::getAllServiceTypes(); if (empty($service_types[$service_class])) { return $this->buildServiceTypeResponse($service_types, $cancel_uri); } $service_type = $service_types[$service_class]; if ($service_type->isClusterServiceType()) { $this->requireApplicationCapability( AlmanacCreateClusterServicesCapability::CAPABILITY); } $service = AlmanacService::initializeNewService(); $service->setServiceClass($service_class); $service->attachServiceType($service_type); $is_new = true; $title = pht('Create Service'); $save_button = pht('Create Service'); } $v_name = $service->getName(); $e_name = true; $validation_exception = null; if ($is_new) { $v_projects = array(); } else { $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( $service->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $v_projects = array_reverse($v_projects); } if ($request->isFormPost() && $request->getStr('edit')) { $v_name = $request->getStr('name'); $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); $v_projects = $request->getArr('projects'); $type_name = AlmanacServiceTransaction::TYPE_NAME; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; $xactions = array(); $xactions[] = id(new AlmanacServiceTransaction()) ->setTransactionType($type_name) ->setNewValue($v_name); $xactions[] = id(new AlmanacServiceTransaction()) ->setTransactionType($type_view) ->setNewValue($v_view); $xactions[] = id(new AlmanacServiceTransaction()) ->setTransactionType($type_edit) ->setNewValue($v_edit); $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new AlmanacServiceTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $proj_edge_type) ->setNewValue(array('=' => array_fuse($v_projects))); $editor = id(new AlmanacServiceEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($service, $xactions); $service_uri = $service->getURI(); return id(new AphrontRedirectResponse())->setURI($service_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_name = $ex->getShortMessage($type_name); $service->setViewPolicy($v_view); $service->setEditPolicy($v_edit); } } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($service) ->execute(); $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('edit', true) ->addHiddenInput('serviceClass', $service->getServiceClass()) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') ->setValue($v_name) ->setError($e_name)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('viewPolicy') ->setPolicyObject($service) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicies($policies)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('editPolicy') ->setPolicyObject($service) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicies($policies)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setValue($v_projects) ->setDatasource(new PhabricatorProjectDatasource())) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($save_button)); $box = id(new PHUIObjectBoxView()) ->setValidationException($validation_exception) ->setHeaderText($title) ->appendChild($form); $crumbs = $this->buildApplicationCrumbs(); if ($is_new) { $crumbs->addTextCrumb(pht('Create Service')); } else { $crumbs->addTextCrumb($service->getName(), $service_uri); $crumbs->addTextCrumb(pht('Edit')); } - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, )); } private function buildServiceTypeResponse(array $service_types, $cancel_uri) { $request = $this->getRequest(); $viewer = $this->getViewer(); $e_service = null; $errors = array(); if ($request->isFormPost()) { $e_service = pht('Required'); $errors[] = pht( 'To create a new service, you must select a service type.'); } list($can_cluster, $cluster_link) = $this->explainApplicationCapability( AlmanacCreateClusterServicesCapability::CAPABILITY, pht('You have permission to create cluster services.'), pht('You do not have permission to create new cluster services.')); $type_control = id(new AphrontFormRadioButtonControl()) ->setLabel(pht('Service Type')) ->setName('serviceClass') ->setError($e_service); foreach ($service_types as $service_type) { $is_cluster = $service_type->isClusterServiceType(); $is_disabled = ($is_cluster && !$can_cluster); if ($is_cluster) { $extra = $cluster_link; } else { $extra = null; } $type_control->addButton( get_class($service_type), $service_type->getServiceTypeName(), array( $service_type->getServiceTypeDescription(), $extra, ), $is_disabled ? 'disabled' : null, $is_disabled); } $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Create Service')); $title = pht('Choose Service Type'); $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild($type_control) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Continue')) ->addCancelButton($cancel_uri)); $box = id(new PHUIObjectBoxView()) ->setFormErrors($errors) ->setHeaderText($title) ->setForm($form); return $this->buildApplicationPage( array( $crumbs, $box, ), array( 'title' => $title, )); } } diff --git a/src/applications/almanac/controller/AlmanacServiceViewController.php b/src/applications/almanac/controller/AlmanacServiceViewController.php index 2fef847fec..bfeed96e2f 100644 --- a/src/applications/almanac/controller/AlmanacServiceViewController.php +++ b/src/applications/almanac/controller/AlmanacServiceViewController.php @@ -1,147 +1,146 @@ getViewer(); $name = $request->getURIData('name'); $service = id(new AlmanacServiceQuery()) ->setViewer($viewer) ->withNames(array($name)) ->executeOne(); if (!$service) { return new Aphront404Response(); } $title = pht('Service %s', $service->getName()); $property_list = $this->buildPropertyList($service); $action_list = $this->buildActionList($service); $property_list->setActionList($action_list); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($service->getName()) ->setPolicyObject($service); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($property_list); $messages = $service->getServiceType()->getStatusMessages($service); if ($messages) { $box->setFormErrors($messages); } if ($service->getIsLocked()) { $this->addLockMessage( $box, pht('This service is locked, and can not be edited.')); } $bindings = $this->buildBindingList($service); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($service->getName()); $timeline = $this->buildTransactionTimeline( $service, new AlmanacServiceTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $bindings, - $this->buildAlmanacPropertiesTable($service), - $timeline, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, + $bindings, + $this->buildAlmanacPropertiesTable($service), + $timeline, + )); } private function buildPropertyList(AlmanacService $service) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($service); $properties->addProperty( pht('Service Type'), $service->getServiceType()->getServiceTypeShortName()); return $properties; } private function buildActionList(AlmanacService $service) { $viewer = $this->getViewer(); $id = $service->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $service, PhabricatorPolicyCapability::CAN_EDIT); $actions = id(new PhabricatorActionListView()) ->setUser($viewer); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Service')) ->setHref($this->getApplicationURI("service/edit/{$id}/")) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); return $actions; } private function buildBindingList(AlmanacService $service) { $viewer = $this->getViewer(); $id = $service->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $service, PhabricatorPolicyCapability::CAN_EDIT); $bindings = id(new AlmanacBindingQuery()) ->setViewer($viewer) ->withServicePHIDs(array($service->getPHID())) ->execute(); $table = id(new AlmanacBindingTableView()) ->setNoDataString( pht('This service has not been bound to any device interfaces yet.')) ->setUser($viewer) ->setBindings($bindings); $header = id(new PHUIHeaderView()) ->setHeader(pht('Service Bindings')) ->addActionLink( id(new PHUIButtonView()) ->setTag('a') ->setHref($this->getApplicationURI("binding/edit/?serviceID={$id}")) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit) ->setText(pht('Add Binding')) ->setIcon( id(new PHUIIconView()) ->setIconFont('fa-plus'))); return id(new PHUIObjectBoxView()) ->setHeader($header) ->setTable($table); } } diff --git a/src/applications/audit/application/PhabricatorAuditApplication.php b/src/applications/audit/application/PhabricatorAuditApplication.php index 858993e5aa..4f4c5a56d1 100644 --- a/src/applications/audit/application/PhabricatorAuditApplication.php +++ b/src/applications/audit/application/PhabricatorAuditApplication.php @@ -1,92 +1,97 @@ pht('Audit User Guide'), 'href' => PhabricatorEnv::getDoclink('Audit User Guide'), ), ); } public function getRoutes() { return array( '/audit/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorAuditListController', 'addcomment/' => 'PhabricatorAuditAddCommentController', 'preview/(?P[1-9]\d*)/' => 'PhabricatorAuditPreviewController', ), ); } public function getApplicationOrder() { return 0.130; } public function loadStatus(PhabricatorUser $user) { $status = array(); + $limit = self::MAX_STATUS_ITEMS; $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user); $query = id(new DiffusionCommitQuery()) ->setViewer($user) ->withAuthorPHIDs(array($user->getPHID())) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_CONCERN) - ->setLimit(self::MAX_STATUS_ITEMS); + ->setLimit($limit); $commits = $query->execute(); $count = count($commits); - $count_str = self::formatStatusCount( - $count, - '%s Problem Commits', - '%d Problem Commit(s)'); + if ($count >= $limit) { + $count_str = pht('%s+ Problem Commit(s)', new PhutilNumber($limit - 1)); + } else { + $count_str = pht('%s Problem Commit(s)', new PhutilNumber($count)); + } + $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($count_str) ->setCount($count); $query = id(new DiffusionCommitQuery()) ->setViewer($user) ->withNeedsAuditByPHIDs($phids) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN) - ->setLimit(self::MAX_STATUS_ITEMS); + ->setLimit($limit); $commits = $query->execute(); $count = count($commits); - $count_str = self::formatStatusCount( - $count, - '%s Commits Awaiting Audit', - '%d Commit(s) Awaiting Audit'); + if ($count >= $limit) { + $count_str = pht('%s+ Problem Commit(s)', new PhutilNumber($limit - 1)); + } else { + $count_str = pht('%s Problem Commit(s)', new PhutilNumber($count)); + } + $type = PhabricatorApplicationStatusView::TYPE_WARNING; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($count_str) ->setCount($count); return $status; } } diff --git a/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php b/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php index 8bbbedc3c6..4b9d53db85 100644 --- a/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php +++ b/src/applications/auth/provider/PhabricatorJIRAAuthProvider.php @@ -1,335 +1,335 @@ getProviderConfig()->getProperty(self::PROPERTY_JIRA_URI); } public function getProviderName() { return pht('JIRA'); } public function getDescriptionForCreate() { return pht('Configure JIRA OAuth. NOTE: Only supports JIRA 6.'); } public function getConfigurationHelp() { return $this->getProviderConfigurationHelp(); } protected function getProviderConfigurationHelp() { if ($this->isSetup()) { return pht( "**Step 1 of 2**: Provide the name and URI for your JIRA install.\n\n". "In the next step, you will configure JIRA."); } else { $login_uri = PhabricatorEnv::getURI($this->getLoginURI()); return pht( "**Step 2 of 2**: In this step, you will configure JIRA.\n\n". "**Create a JIRA Application**: Log into JIRA and go to ". "**Administration**, then **Add-ons**, then **Application Links**. ". "Click the button labeled **Add Application Link**, and use these ". "settings to create an application:\n\n". " - **Server URL**: `%s`\n". " - Then, click **Next**. On the second page:\n". " - **Application Name**: `Phabricator`\n". " - **Application Type**: `Generic Application`\n". " - Then, click **Create**.\n\n". "**Configure Your Application**: Find the application you just ". "created in the table, and click the **Configure** link under ". "**Actions**. Select **Incoming Authentication** and click the ". "**OAuth** tab (it may be selected by default). Then, use these ". "settings:\n\n". " - **Consumer Key**: Set this to the \"Consumer Key\" value in the ". "form above.\n". " - **Consumer Name**: `Phabricator`\n". " - **Public Key**: Set this to the \"Public Key\" value in the ". "form above.\n". " - **Consumer Callback URL**: `%s`\n". "Click **Save** in JIRA. Authentication should now be configured, ". "and this provider should work correctly.", PhabricatorEnv::getProductionURI('/'), $login_uri); } } protected function newOAuthAdapter() { $config = $this->getProviderConfig(); return id(new PhutilJIRAAuthAdapter()) ->setAdapterDomain($config->getProviderDomain()) ->setJIRABaseURI($config->getProperty(self::PROPERTY_JIRA_URI)) ->setPrivateKey( new PhutilOpaqueEnvelope( $config->getProperty(self::PROPERTY_PRIVATE_KEY))); } protected function getLoginIcon() { return 'Jira'; } private function isSetup() { return !$this->getProviderConfig()->getID(); } const PROPERTY_JIRA_NAME = 'oauth1:jira:name'; const PROPERTY_JIRA_URI = 'oauth1:jira:uri'; const PROPERTY_PUBLIC_KEY = 'oauth1:jira:key:public'; const PROPERTY_PRIVATE_KEY = 'oauth1:jira:key:private'; const PROPERTY_REPORT_LINK = 'oauth1:jira:report:link'; const PROPERTY_REPORT_COMMENT = 'oauth1:jira:report:comment'; public function readFormValuesFromProvider() { $config = $this->getProviderConfig(); $uri = $config->getProperty(self::PROPERTY_JIRA_URI); return array( self::PROPERTY_JIRA_NAME => $this->getProviderDomain(), self::PROPERTY_JIRA_URI => $uri, ); } public function readFormValuesFromRequest(AphrontRequest $request) { $is_setup = $this->isSetup(); if ($is_setup) { $name = $request->getStr(self::PROPERTY_JIRA_NAME); } else { $name = $this->getProviderDomain(); } return array( self::PROPERTY_JIRA_NAME => $name, self::PROPERTY_JIRA_URI => $request->getStr(self::PROPERTY_JIRA_URI), self::PROPERTY_REPORT_LINK => $request->getInt(self::PROPERTY_REPORT_LINK, 0), self::PROPERTY_REPORT_COMMENT => $request->getInt(self::PROPERTY_REPORT_COMMENT, 0), ); } public function processEditForm( AphrontRequest $request, array $values) { $errors = array(); $issues = array(); $is_setup = $this->isSetup(); $key_name = self::PROPERTY_JIRA_NAME; $key_uri = self::PROPERTY_JIRA_URI; if (!strlen($values[$key_name])) { $errors[] = pht('JIRA instance name is required.'); $issues[$key_name] = pht('Required'); } else if (!preg_match('/^[a-z0-9.]+\z/', $values[$key_name])) { $errors[] = pht( 'JIRA instance name must contain only lowercase letters, digits, and '. 'period.'); $issues[$key_name] = pht('Invalid'); } if (!strlen($values[$key_uri])) { $errors[] = pht('JIRA base URI is required.'); $issues[$key_uri] = pht('Required'); } else { $uri = new PhutilURI($values[$key_uri]); if (!$uri->getProtocol()) { $errors[] = pht( 'JIRA base URI should include protocol (like "https://").'); $issues[$key_uri] = pht('Invalid'); } } if (!$errors && $is_setup) { $config = $this->getProviderConfig(); $config->setProviderDomain($values[$key_name]); $consumer_key = 'phjira.'.Filesystem::readRandomCharacters(16); list($public, $private) = PhutilJIRAAuthAdapter::newJIRAKeypair(); $config->setProperty(self::PROPERTY_PUBLIC_KEY, $public); $config->setProperty(self::PROPERTY_PRIVATE_KEY, $private); $config->setProperty(self::PROPERTY_CONSUMER_KEY, $consumer_key); } return array($errors, $issues, $values); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { if (!function_exists('openssl_pkey_new')) { // TODO: This could be a bit prettier. throw new Exception( pht( "The PHP 'openssl' extension is not installed. You must install ". "this extension in order to add a JIRA authentication provider, ". "because JIRA OAuth requests use the RSA-SHA1 signing algorithm. ". - "Install the 'openssl' extension, restart your webserver, and try ". + "Install the 'openssl' extension, restart Phabricator, and try ". "again.")); } $form->appendRemarkupInstructions( pht( 'NOTE: This provider **only supports JIRA 6**. It will not work with '. 'JIRA 5 or earlier.')); $is_setup = $this->isSetup(); $viewer = $request->getViewer(); $e_required = $request->isFormPost() ? null : true; $v_name = $values[self::PROPERTY_JIRA_NAME]; if ($is_setup) { $e_name = idx($issues, self::PROPERTY_JIRA_NAME, $e_required); } else { $e_name = null; } $v_uri = $values[self::PROPERTY_JIRA_URI]; $e_uri = idx($issues, self::PROPERTY_JIRA_URI, $e_required); if ($is_setup) { $form ->appendRemarkupInstructions( pht( "**JIRA Instance Name**\n\n". "Choose a permanent name for this instance of JIRA. Phabricator ". "uses this name internally to keep track of this instance of ". "JIRA, in case the URL changes later.\n\n". "Use lowercase letters, digits, and period. For example, ". "`jira`, `jira.mycompany` or `jira.engineering` are reasonable ". "names.")) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('JIRA Instance Name')) ->setValue($v_name) ->setName(self::PROPERTY_JIRA_NAME) ->setError($e_name)); } else { $form ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('JIRA Instance Name')) ->setValue($v_name)); } $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('JIRA Base URI')) ->setValue($v_uri) ->setName(self::PROPERTY_JIRA_URI) ->setCaption( pht( 'The URI where JIRA is installed. For example: %s', phutil_tag('tt', array(), 'https://jira.mycompany.com/'))) ->setError($e_uri)); if (!$is_setup) { $config = $this->getProviderConfig(); $ckey = $config->getProperty(self::PROPERTY_CONSUMER_KEY); $ckey = phutil_tag('tt', array(), $ckey); $pkey = $config->getProperty(self::PROPERTY_PUBLIC_KEY); $pkey = phutil_escape_html_newlines($pkey); $pkey = phutil_tag('tt', array(), $pkey); $form ->appendRemarkupInstructions( pht( 'NOTE: **To complete setup**, copy and paste these keys into JIRA '. 'according to the instructions below.')) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Consumer Key')) ->setValue($ckey)) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Public Key')) ->setValue($pkey)); $form ->appendRemarkupInstructions( pht( '= Integration Options = '."\n". 'Configure how to record Revisions on JIRA tasks.'."\n\n". 'Note you\'ll have to restart the daemons for this to take '. 'effect.')) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( self::PROPERTY_REPORT_LINK, 1, new PHUIRemarkupView( $viewer, pht( 'Create **Issue Link** to the Revision, as an "implemented '. 'in" relationship.')), $this->shouldCreateJIRALink())) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( self::PROPERTY_REPORT_COMMENT, 1, new PHUIRemarkupView( $viewer, pht( '**Post a comment** in the JIRA task, similar to the '. 'emails Phabricator sends.')), $this->shouldCreateJIRAComment())); } } /** * JIRA uses a setup step to generate public/private keys. */ public function hasSetupStep() { return true; } public static function getJIRAProvider() { $providers = self::getAllEnabledProviders(); foreach ($providers as $provider) { if ($provider instanceof PhabricatorJIRAAuthProvider) { return $provider; } } return null; } public function newJIRAFuture( PhabricatorExternalAccount $account, $path, $method, $params = array()) { $adapter = clone $this->getAdapter(); $adapter->setToken($account->getProperty('oauth1.token')); $adapter->setTokenSecret($account->getProperty('oauth1.token.secret')); return $adapter->newJIRAFuture($path, $method, $params); } public function shouldCreateJIRALink() { $config = $this->getProviderConfig(); return $config->getProperty(self::PROPERTY_REPORT_LINK, true); } public function shouldCreateJIRAComment() { $config = $this->getProviderConfig(); return $config->getProperty(self::PROPERTY_REPORT_COMMENT, true); } } diff --git a/src/applications/badges/controller/PhabricatorBadgesController.php b/src/applications/badges/controller/PhabricatorBadgesController.php index 4213da6844..ecfee02900 100644 --- a/src/applications/badges/controller/PhabricatorBadgesController.php +++ b/src/applications/badges/controller/PhabricatorBadgesController.php @@ -1,3 +1,10 @@ newApplicationMenu() + ->setSearchEngine(new PhabricatorBadgesSearchEngine()); + } + +} diff --git a/src/applications/badges/controller/PhabricatorBadgesEditController.php b/src/applications/badges/controller/PhabricatorBadgesEditController.php index 77ced98533..5085a6ed21 100644 --- a/src/applications/badges/controller/PhabricatorBadgesEditController.php +++ b/src/applications/badges/controller/PhabricatorBadgesEditController.php @@ -1,212 +1,211 @@ getViewer(); $id = $request->getURIData('id'); if ($id) { $badge = id(new PhabricatorBadgesQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$badge) { return new Aphront404Response(); } $is_new = false; } else { $this->requireApplicationCapability( PhabricatorBadgesCreateCapability::CAPABILITY); $badge = PhabricatorBadgesBadge::initializeNewBadge($viewer); $is_new = true; } if ($is_new) { $title = pht('Create Badge'); $button_text = pht('Create Badge'); $cancel_uri = $this->getApplicationURI(); } else { $title = pht( 'Edit %s', $badge->getName()); $button_text = pht('Save Changes'); $cancel_uri = $this->getApplicationURI('view/'.$id.'/'); } $e_name = true; $v_name = $badge->getName(); $v_icon = $badge->getIcon(); $v_flav = $badge->getFlavor(); $v_desc = $badge->getDescription(); $v_qual = $badge->getQuality(); $v_stat = $badge->getStatus(); $v_edit = $badge->getEditPolicy(); $validation_exception = null; if ($request->isFormPost()) { $v_name = $request->getStr('name'); $v_flav = $request->getStr('flavor'); $v_desc = $request->getStr('description'); $v_icon = $request->getStr('icon'); $v_stat = $request->getStr('status'); $v_qual = $request->getStr('quality'); $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); $type_name = PhabricatorBadgesTransaction::TYPE_NAME; $type_flav = PhabricatorBadgesTransaction::TYPE_FLAVOR; $type_desc = PhabricatorBadgesTransaction::TYPE_DESCRIPTION; $type_icon = PhabricatorBadgesTransaction::TYPE_ICON; $type_qual = PhabricatorBadgesTransaction::TYPE_QUALITY; $type_stat = PhabricatorBadgesTransaction::TYPE_STATUS; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; $xactions = array(); $xactions[] = id(new PhabricatorBadgesTransaction()) ->setTransactionType($type_name) ->setNewValue($v_name); $xactions[] = id(new PhabricatorBadgesTransaction()) ->setTransactionType($type_flav) ->setNewValue($v_flav); $xactions[] = id(new PhabricatorBadgesTransaction()) ->setTransactionType($type_desc) ->setNewValue($v_desc); $xactions[] = id(new PhabricatorBadgesTransaction()) ->setTransactionType($type_icon) ->setNewValue($v_icon); $xactions[] = id(new PhabricatorBadgesTransaction()) ->setTransactionType($type_qual) ->setNewValue($v_qual); $xactions[] = id(new PhabricatorBadgesTransaction()) ->setTransactionType($type_stat) ->setNewValue($v_stat); $xactions[] = id(new PhabricatorBadgesTransaction()) ->setTransactionType($type_edit) ->setNewValue($v_edit); $editor = id(new PhabricatorBadgesEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($badge, $xactions); $return_uri = $this->getApplicationURI('view/'.$badge->getID().'/'); return id(new AphrontRedirectResponse())->setURI($return_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_name = $ex->getShortMessage($type_name); } } if ($is_new) { $icon_uri = $this->getApplicationURI('icon/'); } else { $icon_uri = $this->getApplicationURI('icon/'.$badge->getID().'/'); } $icon_display = PhabricatorBadgesIcon::renderIconForChooser($v_icon); $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($badge) ->execute(); $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setName('name') ->setLabel(pht('Name')) ->setValue($v_name) ->setError($e_name)) ->appendChild( id(new AphrontFormTextControl()) ->setName('flavor') ->setLabel(pht('Flavor Text')) ->setValue($v_flav)) ->appendChild( id(new AphrontFormChooseButtonControl()) ->setLabel(pht('Icon')) ->setName('icon') ->setDisplayValue($icon_display) ->setButtonText(pht('Choose Icon...')) ->setChooseURI($icon_uri) ->setValue($v_icon)) ->appendChild( id(new AphrontFormSelectControl()) ->setName('quality') ->setLabel(pht('Quality')) ->setValue($v_qual) ->setOptions($badge->getQualityNameMap())) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Status')) ->setName('status') ->setValue($v_stat) ->setOptions($badge->getStatusNameMap())) ->appendChild( id(new PhabricatorRemarkupControl()) ->setUser($viewer) ->setName('description') ->setLabel(pht('Description')) ->setValue($v_desc)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('editPolicy') ->setPolicyObject($badge) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setValue($v_edit) ->setPolicies($policies)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue($button_text) ->addCancelButton($cancel_uri)); $crumbs = $this->buildApplicationCrumbs(); if ($is_new) { $crumbs->addTextCrumb(pht('Create Badge')); } else { $crumbs->addTextCrumb( $badge->getName(), '/badges/view/'.$badge->getID().'/'); $crumbs->addTextCrumb(pht('Edit')); } $box = id(new PHUIObjectBoxView()) ->setValidationException($validation_exception) ->setHeaderText($title) ->appendChild($form); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, )); } } diff --git a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php index d54b4fafeb..872b715e5b 100644 --- a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php +++ b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php @@ -1,120 +1,119 @@ getViewer(); $id = $request->getURIData('id'); $badge = id(new PhabricatorBadgesQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needRecipients(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_EDIT, PhabricatorPolicyCapability::CAN_VIEW, )) ->executeOne(); if (!$badge) { return new Aphront404Response(); } $recipient_phids = $badge->getRecipientPHIDs(); if ($request->isFormPost()) { $recipient_spec = array(); $remove = $request->getStr('remove'); if ($remove) { $recipient_spec['-'] = array_fuse(array($remove)); } $add_recipients = $request->getArr('phids'); if ($add_recipients) { $recipient_spec['+'] = array_fuse($add_recipients); } $type_recipient = PhabricatorBadgeHasRecipientEdgeType::EDGECONST; $xactions = array(); $xactions[] = id(new PhabricatorBadgesTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $type_recipient) ->setNewValue($recipient_spec); $editor = id(new PhabricatorBadgesEditor($badge)) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->applyTransactions($badge, $xactions); return id(new AphrontRedirectResponse()) ->setURI($request->getRequestURI()); } $recipient_phids = array_reverse($recipient_phids); $handles = $this->loadViewerHandles($recipient_phids); $state = array(); foreach ($handles as $handle) { $state[] = array( 'phid' => $handle->getPHID(), 'name' => $handle->getFullName(), ); } $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $badge, PhabricatorPolicyCapability::CAN_EDIT); $form_box = null; $title = pht('Add Recipient'); if ($can_edit) { $header_name = pht('Edit Recipients'); $view_uri = $this->getApplicationURI('view/'.$badge->getID().'/'); $form = new AphrontFormView(); $form ->setUser($viewer) ->appendControl( id(new AphrontFormTokenizerControl()) ->setName('phids') ->setLabel(pht('Add Recipients')) ->setDatasource(new PhabricatorPeopleDatasource())) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($view_uri) ->setValue(pht('Add Recipients'))); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setForm($form); } $recipient_list = id(new PhabricatorBadgesRecipientsListView()) ->setBadge($badge) ->setHandles($handles) ->setUser($viewer); $badge_url = $this->getApplicationURI('view/'.$id.'/'); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($badge->getName(), $badge_url); $crumbs->addTextCrumb(pht('Recipients')); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - $recipient_list, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $form_box, + $recipient_list, )); } } diff --git a/src/applications/badges/controller/PhabricatorBadgesListController.php b/src/applications/badges/controller/PhabricatorBadgesListController.php index 828fb3fd3a..c47f38fc5d 100644 --- a/src/applications/badges/controller/PhabricatorBadgesListController.php +++ b/src/applications/badges/controller/PhabricatorBadgesListController.php @@ -1,52 +1,33 @@ getURIData('queryKey'); - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($query_key) - ->setSearchEngine(new PhabricatorBadgesSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView() { - $user = $this->getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new PhabricatorBadgesSearchEngine()) - ->setViewer($user) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + return id(new PhabricatorBadgesSearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $can_create = $this->hasApplicationCapability( PhabricatorBadgesCreateCapability::CAPABILITY); $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Badge')) ->setHref($this->getApplicationURI('create/')) ->setIcon('fa-plus-square') ->setDisabled(!$can_create) ->setWorkflow(!$can_create)); return $crumbs; } } diff --git a/src/applications/badges/controller/PhabricatorBadgesViewController.php b/src/applications/badges/controller/PhabricatorBadgesViewController.php index 411970f0bd..a2e61fee8e 100644 --- a/src/applications/badges/controller/PhabricatorBadgesViewController.php +++ b/src/applications/badges/controller/PhabricatorBadgesViewController.php @@ -1,175 +1,174 @@ getViewer(); $id = $request->getURIData('id'); $badge = id(new PhabricatorBadgesQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needRecipients(true) ->executeOne(); if (!$badge) { return new Aphront404Response(); } $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($badge->getName()); $title = $badge->getName(); if ($badge->isClosed()) { $status_icon = 'fa-ban'; $status_color = 'dark'; } else { $status_icon = 'fa-check'; $status_color = 'bluegrey'; } $status_name = idx( PhabricatorBadgesBadge::getStatusNameMap(), $badge->getStatus()); $header = id(new PHUIHeaderView()) ->setHeader($badge->getName()) ->setUser($viewer) ->setPolicyObject($badge) ->setStatus($status_icon, $status_color, $status_name); $properties = $this->buildPropertyListView($badge); $actions = $this->buildActionListView($badge); $properties->setActionList($actions); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); $timeline = $this->buildTransactionTimeline( $badge, new PhabricatorBadgesTransactionQuery()); $recipient_phids = $badge->getRecipientPHIDs(); $recipient_phids = array_reverse($recipient_phids); $handles = $this->loadViewerHandles($recipient_phids); $recipient_list = id(new PhabricatorBadgesRecipientsListView()) ->setBadge($badge) ->setHandles($handles) ->setUser($viewer); $add_comment = $this->buildCommentForm($badge); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $recipient_list, - $timeline, - $add_comment, - ), - array( - 'title' => $title, - 'pageObjects' => array($badge->getPHID()), + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($badge->getPHID())) + ->appendChild( + array( + $box, + $recipient_list, + $timeline, + $add_comment, )); } private function buildPropertyListView(PhabricatorBadgesBadge $badge) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($badge); $quality = idx($badge->getQualityNameMap(), $badge->getQuality()); $icon = idx($badge->getIconNameMap(), $badge->getIcon()); $view->addProperty( pht('Quality'), $quality); $view->addProperty( pht('Icon'), $icon); $view->addProperty( pht('Flavor'), $badge->getFlavor()); $view->invokeWillRenderEvent(); $description = $badge->getDescription(); if (strlen($description)) { $view->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent( new PHUIRemarkupView($viewer, $description)); } $badge = id(new PHUIBadgeView()) ->setIcon($badge->getIcon()) ->setHeader($badge->getName()) ->setSubhead($badge->getFlavor()) ->setQuality($badge->getQuality()); $view->addTextContent($badge); return $view; } private function buildActionListView(PhabricatorBadgesBadge $badge) { $viewer = $this->getViewer(); $id = $badge->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $badge, PhabricatorPolicyCapability::CAN_EDIT); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($badge); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Badge')) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setHref($this->getApplicationURI("/edit/{$id}/"))); $view->addAction( id(new PhabricatorActionView()) ->setName('Manage Recipients') ->setIcon('fa-users') ->setDisabled(!$can_edit) ->setHref($this->getApplicationURI("/recipients/{$id}/"))); return $view; } private function buildCommentForm(PhabricatorBadgesBadge $badge) { $viewer = $this->getViewer(); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $add_comment_header = $is_serious ? pht('Add Comment') : pht('Render Honors'); $draft = PhabricatorDraft::newFromUserAndKey($viewer, $badge->getPHID()); return id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setObjectPHID($badge->getPHID()) ->setDraft($draft) ->setHeaderText($add_comment_header) ->setAction($this->getApplicationURI('/comment/'.$badge->getID().'/')) ->setSubmitButtonName(pht('Add Comment')); } } diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index f4b43a0ed1..c1cda07710 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -1,654 +1,638 @@ pht('Core Applications'), self::GROUP_UTILITIES => pht('Utilities'), self::GROUP_ADMIN => pht('Administration'), self::GROUP_DEVELOPER => pht('Developer Tools'), ); } /* -( Application Information )-------------------------------------------- */ abstract public function getName(); public function getShortDescription() { return pht('%s Application', $this->getName()); } final public function isInstalled() { if (!$this->canUninstall()) { return true; } $prototypes = PhabricatorEnv::getEnvConfig('phabricator.show-prototypes'); if (!$prototypes && $this->isPrototype()) { return false; } $uninstalled = PhabricatorEnv::getEnvConfig( 'phabricator.uninstalled-applications'); return empty($uninstalled[get_class($this)]); } public function isPrototype() { return false; } /** * Return `true` if this application should never appear in application lists * in the UI. Primarily intended for unit test applications or other * pseudo-applications. * * Few applications should be unlisted. For most applications, use * @{method:isLaunchable} to hide them from main launch views instead. * * @return bool True to remove application from UI lists. */ public function isUnlisted() { return false; } /** * Return `true` if this application is a normal application with a base * URI and a web interface. * * Launchable applications can be pinned to the home page, and show up in the * "Launcher" view of the Applications application. Making an application * unlauncahble prevents pinning and hides it from this view. * * Usually, an application should be marked unlaunchable if: * * - it is available on every page anyway (like search); or * - it does not have a web interface (like subscriptions); or * - it is still pre-release and being intentionally buried. * * To hide applications more completely, use @{method:isUnlisted}. * * @return bool True if the application is launchable. */ public function isLaunchable() { return true; } /** * Return `true` if this application should be pinned by default. * * Users who have not yet set preferences see a default list of applications. * * @param PhabricatorUser User viewing the pinned application list. * @return bool True if this application should be pinned by default. */ public function isPinnedByDefault(PhabricatorUser $viewer) { return false; } /** * Returns true if an application is first-party (developed by Phacility) * and false otherwise. * * @return bool True if this application is developed by Phacility. */ final public function isFirstParty() { $where = id(new ReflectionClass($this))->getFileName(); $root = phutil_get_library_root('phabricator'); if (!Filesystem::isDescendant($where, $root)) { return false; } if (Filesystem::isDescendant($where, $root.'/extensions')) { return false; } return true; } public function canUninstall() { return true; } final public function getPHID() { return 'PHID-APPS-'.get_class($this); } public function getTypeaheadURI() { return $this->isLaunchable() ? $this->getBaseURI() : null; } public function getBaseURI() { return null; } final public function getApplicationURI($path = '') { return $this->getBaseURI().ltrim($path, '/'); } public function getIconURI() { return null; } public function getFontIcon() { return 'fa-puzzle-piece'; } public function getApplicationOrder() { return PHP_INT_MAX; } public function getApplicationGroup() { return self::GROUP_CORE; } public function getTitleGlyph() { return null; } final public function getHelpMenuItems(PhabricatorUser $viewer) { $items = array(); $articles = $this->getHelpDocumentationArticles($viewer); if ($articles) { $items[] = id(new PHUIListItemView()) ->setType(PHUIListItemView::TYPE_LABEL) ->setName(pht('%s Documentation', $this->getName())); foreach ($articles as $article) { $item = id(new PHUIListItemView()) ->setName($article['name']) ->setIcon('fa-book') ->setHref($article['href']); $items[] = $item; } } $command_specs = $this->getMailCommandObjects(); if ($command_specs) { $items[] = id(new PHUIListItemView()) ->setType(PHUIListItemView::TYPE_LABEL) ->setName(pht('Email Help')); foreach ($command_specs as $key => $spec) { $object = $spec['object']; $class = get_class($this); $href = '/applications/mailcommands/'.$class.'/'.$key.'/'; $item = id(new PHUIListItemView()) ->setName($spec['name']) ->setIcon('fa-envelope-o') ->setHref($href); $items[] = $item; } } return $items; } public function getHelpDocumentationArticles(PhabricatorUser $viewer) { return array(); } public function getOverview() { return null; } public function getEventListeners() { return array(); } public function getRemarkupRules() { return array(); } public function getQuicksandURIPatternBlacklist() { return array(); } public function getMailCommandObjects() { return array(); } /* -( URI Routing )-------------------------------------------------------- */ public function getRoutes() { return array(); } public function getResourceRoutes() { return array(); } /* -( Email Integration )-------------------------------------------------- */ public function supportsEmailIntegration() { return false; } final protected function getInboundEmailSupportLink() { return PhabricatorEnv::getDocLink('Configuring Inbound Email'); } public function getAppEmailBlurb() { throw new PhutilMethodNotImplementedException(); } /* -( Fact Integration )--------------------------------------------------- */ public function getFactObjectsForAnalysis() { return array(); } /* -( UI Integration )----------------------------------------------------- */ /** * Render status elements (like "3 Waiting Reviews") for application list * views. These provide a way to alert users to new or pending action items * in applications. * * @param PhabricatorUser Viewing user. * @return list Application status elements. * @task ui */ public function loadStatus(PhabricatorUser $user) { return array(); } - /** - * @return string - * @task ui - */ - final public static function formatStatusCount( - $count, - $limit_string = '%s', - $base_string = '%d') { - if ($count == self::MAX_STATUS_ITEMS) { - $count_str = pht($limit_string, ($count - 1).'+'); - } else { - $count_str = pht($base_string, $count); - } - return $count_str; - } - /** * You can provide an optional piece of flavor text for the application. This * is currently rendered in application launch views if the application has no * status elements. * * @return string|null Flavor text. * @task ui */ public function getFlavorText() { return null; } /** * Build items for the main menu. * * @param PhabricatorUser The viewing user. * @param AphrontController The current controller. May be null for special * pages like 404, exception handlers, etc. * @return list List of menu items. * @task ui */ public function buildMainMenuItems( PhabricatorUser $user, PhabricatorController $controller = null) { return array(); } /** * Build extra items for the main menu. Generally, this is used to render * static dropdowns. * * @param PhabricatorUser The viewing user. * @param AphrontController The current controller. May be null for special * pages like 404, exception handlers, etc. * @return view List of menu items. * @task ui */ public function buildMainMenuExtraNodes( PhabricatorUser $viewer, PhabricatorController $controller = null) { return array(); } /** * Build items for the "quick create" menu. * * @param PhabricatorUser The viewing user. * @return list List of menu items. */ public function getQuickCreateItems(PhabricatorUser $viewer) { return array(); } /* -( Application Management )--------------------------------------------- */ final public static function getByClass($class_name) { $selected = null; $applications = self::getAllApplications(); foreach ($applications as $application) { if (get_class($application) == $class_name) { $selected = $application; break; } } if (!$selected) { throw new Exception(pht("No application '%s'!", $class_name)); } return $selected; } final public static function getAllApplications() { static $applications; if ($applications === null) { $apps = id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) ->setSortMethod('getApplicationOrder') ->execute(); // Reorder the applications into "application order". Notably, this // ensures their event handlers register in application order. $apps = mgroup($apps, 'getApplicationGroup'); $group_order = array_keys(self::getApplicationGroups()); $apps = array_select_keys($apps, $group_order) + $apps; $apps = array_mergev($apps); $applications = $apps; } return $applications; } final public static function getAllInstalledApplications() { $all_applications = self::getAllApplications(); $apps = array(); foreach ($all_applications as $app) { if (!$app->isInstalled()) { continue; } $apps[] = $app; } return $apps; } /** * Determine if an application is installed, by application class name. * * To check if an application is installed //and// available to a particular * viewer, user @{method:isClassInstalledForViewer}. * * @param string Application class name. * @return bool True if the class is installed. * @task meta */ final public static function isClassInstalled($class) { return self::getByClass($class)->isInstalled(); } /** * Determine if an application is installed and available to a viewer, by * application class name. * * To check if an application is installed at all, use * @{method:isClassInstalled}. * * @param string Application class name. * @param PhabricatorUser Viewing user. * @return bool True if the class is installed for the viewer. * @task meta */ final public static function isClassInstalledForViewer( $class, PhabricatorUser $viewer) { if ($viewer->isOmnipotent()) { return true; } $cache = PhabricatorCaches::getRequestCache(); $viewer_phid = $viewer->getPHID(); $key = 'app.'.$class.'.installed.'.$viewer_phid; $result = $cache->getKey($key); if ($result === null) { if (!self::isClassInstalled($class)) { $result = false; } else { $result = PhabricatorPolicyFilter::hasCapability( $viewer, self::getByClass($class), PhabricatorPolicyCapability::CAN_VIEW); } $cache->setKey($key, $result); } return $result; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array_merge( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ), array_keys($this->getCustomCapabilities())); } public function getPolicy($capability) { $default = $this->getCustomPolicySetting($capability); if ($default) { return $default; } switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return PhabricatorPolicies::getMostOpenPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return PhabricatorPolicies::POLICY_ADMIN; default: $spec = $this->getCustomCapabilitySpecification($capability); return idx($spec, 'default', PhabricatorPolicies::POLICY_USER); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } public function describeAutomaticCapability($capability) { return null; } /* -( Policies )----------------------------------------------------------- */ protected function getCustomCapabilities() { return array(); } final private function getCustomPolicySetting($capability) { if (!$this->isCapabilityEditable($capability)) { return null; } $policy_locked = PhabricatorEnv::getEnvConfig('policy.locked'); if (isset($policy_locked[$capability])) { return $policy_locked[$capability]; } $config = PhabricatorEnv::getEnvConfig('phabricator.application-settings'); $app = idx($config, $this->getPHID()); if (!$app) { return null; } $policy = idx($app, 'policy'); if (!$policy) { return null; } return idx($policy, $capability); } final private function getCustomCapabilitySpecification($capability) { $custom = $this->getCustomCapabilities(); if (!isset($custom[$capability])) { throw new Exception(pht("Unknown capability '%s'!", $capability)); } return $custom[$capability]; } final public function getCapabilityLabel($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return pht('Can Use Application'); case PhabricatorPolicyCapability::CAN_EDIT: return pht('Can Configure Application'); } $capobj = PhabricatorPolicyCapability::getCapabilityByKey($capability); if ($capobj) { return $capobj->getCapabilityName(); } return null; } final public function isCapabilityEditable($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->canUninstall(); case PhabricatorPolicyCapability::CAN_EDIT: return false; default: $spec = $this->getCustomCapabilitySpecification($capability); return idx($spec, 'edit', true); } } final public function getCapabilityCaption($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: if (!$this->canUninstall()) { return pht( 'This application is required for Phabricator to operate, so all '. 'users must have access to it.'); } else { return null; } case PhabricatorPolicyCapability::CAN_EDIT: return null; default: $spec = $this->getCustomCapabilitySpecification($capability); return idx($spec, 'caption'); } } final public function getCapabilityTemplatePHIDType($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: case PhabricatorPolicyCapability::CAN_EDIT: return null; } $spec = $this->getCustomCapabilitySpecification($capability); return idx($spec, 'template'); } final public function getDefaultObjectTypePolicyMap() { $map = array(); foreach ($this->getCustomCapabilities() as $capability => $spec) { if (empty($spec['template'])) { continue; } if (empty($spec['capability'])) { continue; } $default = $this->getPolicy($capability); $map[$spec['template']][$spec['capability']] = $default; } return $map; } public function getApplicationSearchDocumentTypes() { return array(); } protected function getEditRoutePattern($base = null) { return $base.'(?:'. '(?P[0-9]\d*)/)?'. '(?:'. '(?:'. - '(?Pparameters)'. + '(?Pparameters|nodefault|comment)'. '|'. '(?:form/(?P[^/]+))'. ')'. '/)?'; } protected function getQueryRoutePattern($base = null) { return $base.'(?:query/(?P[^/]+)/)?'; } } diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index e10b4b2802..a1720bb5d5 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -1,609 +1,590 @@ shouldRequireLogin()) { return false; } if (!$this->shouldRequireEnabledUser()) { return false; } if ($this->shouldAllowPartialSessions()) { return false; } $user = $this->getRequest()->getUser(); if (!$user->getIsStandardUser()) { return false; } return PhabricatorEnv::getEnvConfig('security.require-multi-factor-auth'); } public function shouldAllowLegallyNonCompliantUsers() { return false; } public function isGlobalDragAndDropUploadEnabled() { return false; } public function willBeginExecution() { $request = $this->getRequest(); if ($request->getUser()) { // NOTE: Unit tests can set a user explicitly. Normal requests are not // permitted to do this. PhabricatorTestCase::assertExecutingUnitTests(); $user = $request->getUser(); } else { $user = new PhabricatorUser(); $session_engine = new PhabricatorAuthSessionEngine(); $phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION); if (strlen($phsid)) { $session_user = $session_engine->loadUserForSession( PhabricatorAuthSession::TYPE_WEB, $phsid); if ($session_user) { $user = $session_user; } } else { // If the client doesn't have a session token, generate an anonymous // session. This is used to provide CSRF protection to logged-out users. $phsid = $session_engine->establishSession( PhabricatorAuthSession::TYPE_WEB, null, $partial = false); // This may be a resource request, in which case we just don't set // the cookie. if ($request->canSetCookies()) { $request->setCookie(PhabricatorCookies::COOKIE_SESSION, $phsid); } } if (!$user->isLoggedIn()) { $user->attachAlternateCSRFString(PhabricatorHash::digest($phsid)); } $request->setUser($user); } PhabricatorEnv::setLocaleCode($user->getTranslation()); $preferences = $user->loadPreferences(); if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) { $dark_console = PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE; if ($preferences->getPreference($dark_console) || PhabricatorEnv::getEnvConfig('darkconsole.always-on')) { $console = new DarkConsoleCore(); $request->getApplicationConfiguration()->setConsole($console); } } // NOTE: We want to set up the user first so we can render a real page // here, but fire this before any real logic. $restricted = array( 'code', ); foreach ($restricted as $parameter) { if ($request->getExists($parameter)) { if (!$this->shouldAllowRestrictedParameter($parameter)) { throw new Exception( pht( 'Request includes restricted parameter "%s", but this '. 'controller ("%s") does not whitelist it. Refusing to '. 'serve this request because it might be part of a redirection '. 'attack.', $parameter, get_class($this))); } } } if ($this->shouldRequireEnabledUser()) { if ($user->isLoggedIn() && !$user->getIsApproved()) { $controller = new PhabricatorAuthNeedsApprovalController(); return $this->delegateToController($controller); } if ($user->getIsDisabled()) { $controller = new PhabricatorDisabledUserController(); return $this->delegateToController($controller); } } $auth_class = 'PhabricatorAuthApplication'; $auth_application = PhabricatorApplication::getByClass($auth_class); // Require partial sessions to finish login before doing anything. if (!$this->shouldAllowPartialSessions()) { if ($user->hasSession() && $user->getSession()->getIsPartial()) { $login_controller = new PhabricatorAuthFinishController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($login_controller); } } // Check if the user needs to configure MFA. $need_mfa = $this->shouldRequireMultiFactorEnrollment(); $have_mfa = $user->getIsEnrolledInMultiFactor(); if ($need_mfa && !$have_mfa) { // Check if the cache is just out of date. Otherwise, roadblock the user // and require MFA enrollment. $user->updateMultiFactorEnrollment(); if (!$user->getIsEnrolledInMultiFactor()) { $mfa_controller = new PhabricatorAuthNeedsMultiFactorController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($mfa_controller); } } if ($this->shouldRequireLogin()) { // This actually means we need either: // - a valid user, or a public controller; and // - permission to see the application; and // - permission to see at least one Space if spaces are configured. $allow_public = $this->shouldAllowPublic() && PhabricatorEnv::getEnvConfig('policy.allow-public'); // If this controller isn't public, and the user isn't logged in, require // login. if (!$allow_public && !$user->isLoggedIn()) { $login_controller = new PhabricatorAuthStartController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($login_controller); } if ($user->isLoggedIn()) { if ($this->shouldRequireEmailVerification()) { if (!$user->getIsEmailVerified()) { $controller = new PhabricatorMustVerifyEmailController(); $this->setCurrentApplication($auth_application); return $this->delegateToController($controller); } } } // If Spaces are configured, require that the user have access to at // least one. If we don't do this, they'll get confusing error messages // later on. $spaces = PhabricatorSpacesNamespaceQuery::getSpacesExist(); if ($spaces) { $viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces( $user); if (!$viewer_spaces) { $controller = new PhabricatorSpacesNoAccessController(); return $this->delegateToController($controller); } } // If the user doesn't have access to the application, don't let them use // any of its controllers. We query the application in order to generate // a policy exception if the viewer doesn't have permission. $application = $this->getCurrentApplication(); if ($application) { id(new PhabricatorApplicationQuery()) ->setViewer($user) ->withPHIDs(array($application->getPHID())) ->executeOne(); } } if (!$this->shouldAllowLegallyNonCompliantUsers()) { $legalpad_class = 'PhabricatorLegalpadApplication'; $legalpad = id(new PhabricatorApplicationQuery()) ->setViewer($user) ->withClasses(array($legalpad_class)) ->withInstalled(true) ->execute(); $legalpad = head($legalpad); $doc_query = id(new LegalpadDocumentQuery()) ->setViewer($user) ->withSignatureRequired(1) ->needViewerSignatures(true); if ($user->hasSession() && !$user->getSession()->getIsPartial() && !$user->getSession()->getSignedLegalpadDocuments() && $user->isLoggedIn() && $legalpad) { $sign_docs = $doc_query->execute(); $must_sign_docs = array(); foreach ($sign_docs as $sign_doc) { if (!$sign_doc->getUserSignature($user->getPHID())) { $must_sign_docs[] = $sign_doc; } } if ($must_sign_docs) { $controller = new LegalpadDocumentSignController(); $this->getRequest()->setURIMap(array( 'id' => head($must_sign_docs)->getID(), )); $this->setCurrentApplication($legalpad); return $this->delegateToController($controller); } else { $engine = id(new PhabricatorAuthSessionEngine()) ->signLegalpadDocuments($user, $sign_docs); } } } // NOTE: We do this last so that users get a login page instead of a 403 // if they need to login. if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) { return new Aphront403Response(); } } public function getApplicationURI($path = '') { if (!$this->getCurrentApplication()) { throw new Exception(pht('No application!')); } return $this->getCurrentApplication()->getApplicationURI($path); } public function willSendResponse(AphrontResponse $response) { $request = $this->getRequest(); if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax() && !$request->isQuicksand()) { $dialog = $response->getDialog(); $title = $dialog->getTitle(); $short = $dialog->getShortTitle(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(coalesce($short, $title)); $page_content = array( $crumbs, $response->buildResponseString(), ); $view = id(new PhabricatorStandardPageView()) ->setRequest($request) ->setController($this) ->setDeviceReady(true) ->setTitle($title) ->appendChild($page_content); $response = id(new AphrontWebpageResponse()) ->setContent($view->render()) ->setHTTPResponseCode($response->getHTTPResponseCode()); } else { $response->getDialog()->setIsStandalone(true); return id(new AphrontAjaxResponse()) ->setContent(array( 'dialog' => $response->buildResponseString(), )); } } else if ($response instanceof AphrontRedirectResponse) { if ($request->isAjax() || $request->isQuicksand()) { return id(new AphrontAjaxResponse()) ->setContent( array( 'redirect' => $response->getURI(), )); } } return $response; } /** * WARNING: Do not call this in new code. * * @deprecated See "Handles Technical Documentation". */ protected function loadViewerHandles(array $phids) { return id(new PhabricatorHandleQuery()) ->setViewer($this->getRequest()->getUser()) ->withPHIDs($phids) ->execute(); } public function buildApplicationMenu() { return null; } protected function buildApplicationCrumbs() { $crumbs = array(); $application = $this->getCurrentApplication(); if ($application) { $icon = $application->getFontIcon(); if (!$icon) { $icon = 'fa-puzzle'; } $crumbs[] = id(new PHUICrumbView()) ->setHref($this->getApplicationURI()) ->setName($application->getName()) ->setIcon($icon); } $view = new PHUICrumbsView(); foreach ($crumbs as $crumb) { $view->addCrumb($crumb); } return $view; } protected function hasApplicationCapability($capability) { return PhabricatorPolicyFilter::hasCapability( $this->getRequest()->getUser(), $this->getCurrentApplication(), $capability); } protected function requireApplicationCapability($capability) { PhabricatorPolicyFilter::requireCapability( $this->getRequest()->getUser(), $this->getCurrentApplication(), $capability); } protected function explainApplicationCapability( $capability, $positive_message, $negative_message) { $can_act = $this->hasApplicationCapability($capability); if ($can_act) { $message = $positive_message; $icon_name = 'fa-play-circle-o lightgreytext'; } else { $message = $negative_message; $icon_name = 'fa-lock'; } $icon = id(new PHUIIconView()) ->setIconFont($icon_name); require_celerity_resource('policy-css'); $phid = $this->getCurrentApplication()->getPHID(); $explain_uri = "/policy/explain/{$phid}/{$capability}/"; $message = phutil_tag( 'div', array( 'class' => 'policy-capability-explanation', ), array( $icon, javelin_tag( 'a', array( 'href' => $explain_uri, 'sigil' => 'workflow', ), $message), )); return array($can_act, $message); } public function getDefaultResourceSource() { return 'phabricator'; } /** * Create a new @{class:AphrontDialogView} with defaults filled in. * * @return AphrontDialogView New dialog. */ public function newDialog() { $submit_uri = new PhutilURI($this->getRequest()->getRequestURI()); $submit_uri = $submit_uri->getPath(); return id(new AphrontDialogView()) ->setUser($this->getRequest()->getUser()) ->setSubmitURI($submit_uri); } public function newPage() { $page = id(new PhabricatorStandardPageView()) ->setRequest($this->getRequest()) ->setController($this) ->setDeviceReady(true); $application = $this->getCurrentApplication(); if ($application) { $page->setApplicationName($application->getName()); if ($application->getTitleGlyph()) { $page->setGlyph($application->getTitleGlyph()); } } $viewer = $this->getRequest()->getUser(); if ($viewer) { $page->setUser($viewer); } - // TODO: Remove after removing callsites to addExtraQuicksandConfig(). - $page->addQuicksandConfig($this->extraQuicksandConfig); - return $page; } public function newApplicationMenu() { return id(new PHUIApplicationMenuView()) ->setViewer($this->getRequest()->getUser()); } protected function buildTransactionTimeline( PhabricatorApplicationTransactionInterface $object, PhabricatorApplicationTransactionQuery $query, PhabricatorMarkupEngine $engine = null, $render_data = array()) { $viewer = $this->getRequest()->getUser(); $xaction = $object->getApplicationTransactionTemplate(); $view = $xaction->getApplicationTransactionViewObject(); $pager = id(new AphrontCursorPagerView()) ->readFromRequest($this->getRequest()) ->setURI(new PhutilURI( '/transactions/showolder/'.$object->getPHID().'/')); $xactions = $query ->setViewer($viewer) ->withObjectPHIDs(array($object->getPHID())) ->needComments(true) ->executeWithCursorPager($pager); $xactions = array_reverse($xactions); if ($engine) { foreach ($xactions as $xaction) { if ($xaction->getComment()) { $engine->addObject( $xaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } } $engine->process(); $view->setMarkupEngine($engine); } $timeline = $view ->setUser($viewer) ->setObjectPHID($object->getPHID()) ->setTransactions($xactions) ->setPager($pager) ->setRenderData($render_data) ->setQuoteTargetID($this->getRequest()->getStr('quoteTargetID')) ->setQuoteRef($this->getRequest()->getStr('quoteRef')); $object->willRenderTimeline($timeline, $this->getRequest()); return $timeline; } public function buildApplicationCrumbsForEditEngine() { // TODO: This is kind of gross, I'm bascially just making this public so // I can use it in EditEngine. We could do this without making it public // by using controller delegation, or make it properly public. return $this->buildApplicationCrumbs(); } /* -( Deprecated )--------------------------------------------------------- */ - /** - * DEPRECATED. - */ - private $extraQuicksandConfig = array(); - - - /** - * DEPRECATED. Use @{method:newPage} and call addQuicksandConfig(). - */ - public function addExtraQuicksandConfig($config) { - // TODO: When this method is removed, - $this->extraQuicksandConfig += $config; - return $this; - } - - /** * DEPRECATED. Use @{method:newPage}. */ public function buildStandardPageView() { return $this->newPage(); } /** * DEPRECATED. Use @{method:newPage}. */ public function buildStandardPageResponse($view, array $data) { $page = $this->buildStandardPageView(); $page->appendChild($view); return $page->produceAphrontResponse(); } /** * DEPRECATED. Use @{method:newPage}. */ public function buildApplicationPage($view, array $options) { $page = $this->newPage(); $title = PhabricatorEnv::getEnvConfig('phabricator.serious-business') ? 'Phabricator' : pht('Bacon Ice Cream for Breakfast'); $page->setTitle(idx($options, 'title', $title)); if (idx($options, 'class')) { $page->addClass($options['class']); } if (!($view instanceof AphrontSideNavFilterView)) { $nav = new AphrontSideNavFilterView(); $nav->appendChild($view); $view = $nav; } $page->appendChild($view); $object_phids = idx($options, 'pageObjects', array()); if ($object_phids) { $page->setPageObjectPHIDs($object_phids); } if (!idx($options, 'device', true)) { $page->setDeviceReady(false); } $page->setShowFooter(idx($options, 'showFooter', true)); $page->setShowChrome(idx($options, 'chrome', true)); return $page->produceAphrontResponse(); } } diff --git a/src/applications/cache/spec/PhabricatorOpcodeCacheSpec.php b/src/applications/cache/spec/PhabricatorOpcodeCacheSpec.php index f9739a0328..37c02a437b 100644 --- a/src/applications/cache/spec/PhabricatorOpcodeCacheSpec.php +++ b/src/applications/cache/spec/PhabricatorOpcodeCacheSpec.php @@ -1,206 +1,206 @@ initAPCSpec(); } else if (extension_loaded('Zend OPcache')) { $spec->initOpcacheSpec(); } else { $spec->initNoneSpec(); } return $spec; } private function initAPCSpec() { $this ->setName(pht('APC')) ->setVersion(phpversion('apc')); if (ini_get('apc.enabled')) { $this ->setIsEnabled(true) ->setClearCacheCallback('apc_clear_cache'); $mem = apc_sma_info(); $this->setTotalMemory($mem['num_seg'] * $mem['seg_size']); $info = apc_cache_info(); $this->setUsedMemory($info['mem_size']); $write_lock = ini_get('apc.write_lock'); $slam_defense = ini_get('apc.slam_defense'); if (!$write_lock || $slam_defense) { $summary = pht('Adjust APC settings to quiet unnecessary errors.'); $message = pht( 'Some versions of APC may emit unnecessary errors into the '. 'error log under the current APC settings. To resolve this, '. 'enable "%s" and disable "%s" in your PHP configuration.', 'apc.write_lock', 'apc.slam_defense'); $this ->newIssue('extension.apc.write-lock') ->setShortName(pht('Noisy APC')) ->setName(pht('APC Has Noisy Configuration')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('apc.write_lock') ->addPHPConfig('apc.slam_defense'); } $is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); $is_stat_enabled = ini_get('apc.stat'); if ($is_stat_enabled && !$is_dev) { $summary = pht( '"%s" is currently enabled, but should probably be disabled.', 'apc.stat'); $message = pht( 'The "%s" setting is currently enabled in your PHP configuration. '. 'In production mode, "%s" should be disabled. '. 'This will improve performance slightly.', 'apc.stat', 'apc.stat'); $this ->newIssue('extension.apc.stat-enabled') ->setShortName(pht('"%s" Enabled', 'apc.stat')) ->setName(pht('"%s" Enabled in Production', 'apc.stat')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('apc.stat') ->addPhabricatorConfig('phabricator.developer-mode'); } else if (!$is_stat_enabled && $is_dev) { $summary = pht( '"%s" is currently disabled, but should probably be enabled.', 'apc.stat'); $message = pht( 'The "%s" setting is currently disabled in your PHP configuration, '. 'but Phabricator is running in development mode. This option should '. 'normally be enabled in development so you do not need to restart '. - 'your webserver after making changes to the code.', + 'anything after making changes to the code.', 'apc.stat'); $this ->newIssue('extension.apc.stat-disabled') ->setShortName(pht('"%s" Disabled', 'apc.stat')) ->setName(pht('"%s" Disabled in Development', 'apc.stat')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('apc.stat') ->addPhabricatorConfig('phabricator.developer-mode'); } } else { $this->setIsEnabled(false); $this->raiseEnableAPCIssue(); } } private function initOpcacheSpec() { $this ->setName(pht('Zend OPcache')) ->setVersion(phpversion('Zend OPcache')); if (ini_get('opcache.enable')) { $this ->setIsEnabled(true) ->setClearCacheCallback('opcache_reset'); $status = opcache_get_status(); $memory = $status['memory_usage']; $mem_used = $memory['used_memory']; $mem_free = $memory['free_memory']; $mem_junk = $memory['wasted_memory']; $this->setUsedMemory($mem_used + $mem_junk); $this->setTotalMemory($mem_used + $mem_junk + $mem_free); $this->setEntryCount($status['opcache_statistics']['num_cached_keys']); $is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode'); $validate = ini_get('opcache.validate_timestamps'); $freq = ini_get('opcache.revalidate_freq'); if ($is_dev && (!$validate || $freq)) { $summary = pht( 'OPcache is not configured properly for development.'); $message = pht( 'In development, OPcache should be configured to always reload '. - 'code so the webserver does not need to be restarted after making '. - 'changes. To do this, enable "%s" and set "%s" to 0.', + 'code so nothing needs to be restarted after making changes. To do '. + 'this, enable "%s" and set "%s" to 0.', 'opcache.validate_timestamps', 'opcache.revalidate_freq'); $this ->newIssue('extension.opcache.devmode') ->setShortName(pht('OPcache Config')) ->setName(pht('OPCache Not Configured for Development')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('opcache.validate_timestamps') ->addPHPConfig('opcache.revalidate_freq') ->addPhabricatorConfig('phabricator.developer-mode'); } else if (!$is_dev && $validate) { $summary = pht('OPcache is not configured ideally for production.'); $message = pht( 'In production, OPcache should be configured to never '. 'revalidate code. This will slightly improve performance. '. 'To do this, disable "%s" in your PHP configuration.', 'opcache.validate_timestamps'); $this ->newIssue('extension.opcache.production') ->setShortName(pht('OPcache Config')) ->setName(pht('OPcache Not Configured for Production')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('opcache.validate_timestamps') ->addPhabricatorConfig('phabricator.developer-mode'); } } else { $this->setIsEnabled(false); $summary = pht('Enabling OPcache will dramatically improve performance.'); $message = pht( 'The PHP "Zend OPcache" extension is installed, but not enabled in '. 'your PHP configuration. Enabling it will dramatically improve '. 'Phabricator performance. Edit the "%s" setting to '. 'enable the extension.', 'opcache.enable'); $this->newIssue('extension.opcache.enable') ->setShortName(pht('OPcache Disabled')) ->setName(pht('Zend OPcache Not Enabled')) ->setSummary($summary) ->setMessage($message) ->addPHPConfig('opcache.enable'); } } private function initNoneSpec() { if (version_compare(phpversion(), '5.5', '>=')) { $message = pht( 'Installing the "Zend OPcache" extension will dramatically improve '. 'performance.'); $this ->newIssue('extension.opcache') ->setShortName(pht('OPcache')) ->setName(pht('Zend OPcache Not Installed')) ->setMessage($message) ->addPHPExtension('Zend OPcache'); } else { $this->raiseInstallAPCIssue(); } } } diff --git a/src/applications/calendar/util/CalendarTimeUtil.php b/src/applications/calendar/util/CalendarTimeUtil.php index 0fc4f2e527..6051ade059 100644 --- a/src/applications/calendar/util/CalendarTimeUtil.php +++ b/src/applications/calendar/util/CalendarTimeUtil.php @@ -1,89 +1,90 @@ Saturday list, whilest the profile view shows a more simple * seven day rolling list of events. */ final class CalendarTimeUtil extends Phobject { public static function getCalendarEventEpochs( PhabricatorUser $user, $start_day_str = 'Sunday', $days = 9) { $objects = self::getStartDateTimeObjects($user, $start_day_str); $start_day = $objects['start_day']; $end_day = clone $start_day; $end_day->modify('+'.$days.' days'); return array( 'start_epoch' => $start_day->format('U'), 'end_epoch' => $end_day->format('U'), ); } public static function getCalendarWeekTimestamps( PhabricatorUser $user) { return self::getTimestamps($user, 'Today', 7); } public static function getCalendarWidgetTimestamps( PhabricatorUser $user) { return self::getTimestamps($user, 'Sunday', 9); } /** * Public for testing purposes only. You should probably use one of the * functions above. */ public static function getTimestamps( PhabricatorUser $user, $start_day_str, $days) { $objects = self::getStartDateTimeObjects($user, $start_day_str); $start_day = $objects['start_day']; $timestamps = array(); for ($day = 0; $day < $days; $day++) { $timestamp = clone $start_day; $timestamp->modify(sprintf('+%d days', $day)); $timestamps[] = $timestamp; } return array( 'today' => $objects['today'], 'epoch_stamps' => $timestamps, ); } private static function getStartDateTimeObjects( PhabricatorUser $user, $start_day_str) { $timezone = new DateTimeZone($user->getTimezoneIdentifier()); $today_epoch = PhabricatorTime::parseLocalTime('today', $user); $today = new DateTime('@'.$today_epoch); $today->setTimeZone($timezone); if (strtolower($start_day_str) == 'today' || $today->format('l') == $start_day_str) { $start_day = clone $today; } else { $start_epoch = PhabricatorTime::parseLocalTime( 'last '.$start_day_str, $user); $start_day = new DateTime('@'.$start_epoch); $start_day->setTimeZone($timezone); } return array( 'today' => $today, 'start_day' => $start_day, ); } } diff --git a/src/applications/config/check/PhabricatorBinariesSetupCheck.php b/src/applications/config/check/PhabricatorBinariesSetupCheck.php index 59dc3a78ab..c33dcae36f 100644 --- a/src/applications/config/check/PhabricatorBinariesSetupCheck.php +++ b/src/applications/config/check/PhabricatorBinariesSetupCheck.php @@ -1,278 +1,279 @@ raiseWarning($bin_name, $message); // We need to return here if we can't find the 'which' / 'where' binary // because the other tests won't be valid. return; } if (!Filesystem::binaryExists('diff')) { $message = pht( "Without '%s', Phabricator will not be able to generate or render ". "diffs in multiple applications.", 'diff'); $this->raiseWarning('diff', $message); } else { $tmp_a = new TempFile(); $tmp_b = new TempFile(); $tmp_c = new TempFile(); Filesystem::writeFile($tmp_a, 'A'); Filesystem::writeFile($tmp_b, 'A'); Filesystem::writeFile($tmp_c, 'B'); list($err) = exec_manual('diff %s %s', $tmp_a, $tmp_b); if ($err) { $this->newIssue('bin.diff.same') ->setName(pht("Unexpected '%s' Behavior", 'diff')) ->setMessage( pht( "The '%s' binary on this system has unexpected behavior: ". "it was expected to exit without an error code when passed ". "identical files, but exited with code %d.", 'diff', $err)); } list($err) = exec_manual('diff %s %s', $tmp_a, $tmp_c); if (!$err) { $this->newIssue('bin.diff.diff') ->setName(pht("Unexpected 'diff' Behavior")) ->setMessage( pht( "The '%s' binary on this system has unexpected behavior: ". "it was expected to exit with a nonzero error code when passed ". "differing files, but did not.", 'diff')); } } $table = new PhabricatorRepository(); $vcses = queryfx_all( $table->establishConnection('r'), 'SELECT DISTINCT versionControlSystem FROM %T', $table->getTableName()); foreach ($vcses as $vcs) { switch ($vcs['versionControlSystem']) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $binary = 'git'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $binary = 'svn'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $binary = 'hg'; break; default: $binary = null; break; } if (!$binary) { continue; } if (!Filesystem::binaryExists($binary)) { $message = pht( 'You have at least one repository configured which uses this '. 'version control system. It will not work without the VCS binary.'); $this->raiseWarning($binary, $message); + continue; } $version = null; - switch ($binary) { + switch ($vcs['versionControlSystem']) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $minimum_version = null; $bad_versions = array(); list($err, $stdout, $stderr) = exec_manual('git --version'); $version = trim(substr($stdout, strlen('git version '))); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $minimum_version = '1.5'; $bad_versions = array( '1.7.1' => pht( 'This version of Subversion has a bug where `%s` does not work '. 'for files added in rN (Subversion issue #2873), fixed in 1.7.2.', 'svn diff -c N'), ); list($err, $stdout, $stderr) = exec_manual('svn --version --quiet'); $version = trim($stdout); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $minimum_version = '1.9'; $bad_versions = array( '2.1' => pht( 'This version of Mercurial returns a bad exit code '. 'after a successful pull.'), '2.2' => pht( 'This version of Mercurial has a significant memory leak, fixed '. 'in 2.2.1. Pushing fails with this version as well; see %s.', 'T3046#54922'), ); $version = PhabricatorRepositoryVersion::getMercurialVersion(); break; } if ($version === null) { $this->raiseUnknownVersionWarning($binary); } else { if ($minimum_version && version_compare($version, $minimum_version, '<')) { $this->raiseMinimumVersionWarning( $binary, $minimum_version, $version); } foreach ($bad_versions as $bad_version => $details) { if ($bad_version === $version) { $this->raiseBadVersionWarning( $binary, $bad_version); } } } } } private function raiseWarning($bin, $message) { if (phutil_is_windows()) { $preamble = pht( "The '%s' binary could not be found. Set the webserver's %s ". "environmental variable to include the directory where it resides, or ". "add that directory to '%s' in the Phabricator configuration.", $bin, 'PATH', 'environment.append-paths'); } else { $preamble = pht( "The '%s' binary could not be found. Symlink it into '%s', or set the ". "webserver's %s environmental variable to include the directory where ". "it resides, or add that directory to '%s' in the Phabricator ". "configuration.", $bin, 'phabricator/support/bin/', 'PATH', 'environment.append-paths'); } $this->newIssue('bin.'.$bin) ->setShortName(pht("'%s' Missing", $bin)) ->setName(pht("Missing '%s' Binary", $bin)) ->setSummary( pht("The '%s' binary could not be located or executed.", $bin)) ->setMessage($preamble.' '.$message) ->addPhabricatorConfig('environment.append-paths'); } private function raiseUnknownVersionWarning($binary) { $summary = pht( 'Unable to determine the version number of "%s".', $binary); $message = pht( 'Unable to determine the version number of "%s". Usually, this means '. 'the program changed its version format string recently and Phabricator '. 'does not know how to parse the new one yet, but might indicate that '. 'you have a very old (or broken) binary.'. "\n\n". 'Because we can not determine the version number, checks against '. 'minimum and known-bad versions will be skipped, so we might fail '. 'to detect an incompatible binary.'. "\n\n". 'You may be able to resolve this issue by updating Phabricator, since '. 'a newer version of Phabricator is likely to be able to parse the '. 'newer version string.'. "\n\n". 'If updating Phabricator does not fix this, you can report the issue '. 'to the upstream so we can adjust the parser.'. "\n\n". 'If you are confident you have a recent version of "%s" installed and '. 'working correctly, it is usually safe to ignore this warning.', $binary, $binary); $this->newIssue('bin.'.$binary.'.unknown-version') ->setShortName(pht("Unknown '%s' Version", $binary)) ->setName(pht("Unknown '%s' Version", $binary)) ->setSummary($summary) ->setMessage($message) ->addLink( PhabricatorEnv::getDoclink('Contributing Bug Reports'), pht('Report this Issue to the Upstream')); } private function raiseMinimumVersionWarning( $binary, $minimum_version, $version) { switch ($binary) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $summary = pht( "The '%s' binary is version %s and Phabricator requires version ". "%s or higher.", $binary, $version, $minimum_version); $message = pht( "Please upgrade the '%s' binary to a more modern version.", $binary); $this->newIssue('bin.'.$binary) ->setShortName(pht("Unsupported '%s' Version", $binary)) ->setName(pht("Unsupported '%s' Version", $binary)) ->setSummary($summary) ->setMessage($summary.' '.$message); break; } } private function raiseBadVersionWarning($binary, $bad_version) { switch ($binary) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $summary = pht( "The '%s' binary is version %s which has bugs that break ". "Phabricator.", $binary, $bad_version); $message = pht( "Please upgrade the '%s' binary to a more modern version.", $binary); $this->newIssue('bin.'.$binary) ->setShortName(pht("Unsupported '%s' Version", $binary)) ->setName(pht("Unsupported '%s' Version", $binary)) ->setSummary($summary) ->setMessage($summary.' '.$message); break; } } } diff --git a/src/applications/config/module/PhabricatorConfigVersionsModule.php b/src/applications/config/module/PhabricatorConfigVersionsModule.php index b3240aec51..611c332eb0 100644 --- a/src/applications/config/module/PhabricatorConfigVersionsModule.php +++ b/src/applications/config/module/PhabricatorConfigVersionsModule.php @@ -1,79 +1,75 @@ getViewer(); - - $versions = $this->loadVersions(); + $versions = $this->loadVersions($viewer); $version_property_list = id(new PHUIPropertyListView()); - foreach ($versions as $version) { - list($name, $hash) = $version; - $version_property_list->addProperty($name, $hash); + foreach ($versions as $name => $version) { + $version_property_list->addProperty($name, $version); } $object_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Current Versions')) ->addPropertyList($version_property_list); $phabricator_root = dirname(phutil_get_library_root('phabricator')); $version_path = $phabricator_root.'/conf/local/VERSION'; if (Filesystem::pathExists($version_path)) { $version_from_file = Filesystem::readFile($version_path); $version_property_list->addProperty( pht('Local Version'), $version_from_file); } return $object_box; } - private function loadVersions() { + private function loadVersions(PhabricatorUser $viewer) { $specs = array( - array( - 'name' => pht('Phabricator Version'), - 'root' => 'phabricator', - ), - array( - 'name' => pht('Arcanist Version'), - 'root' => 'arcanist', - ), - array( - 'name' => pht('libphutil Version'), - 'root' => 'phutil', - ), + 'phabricator', + 'arcanist', + 'phutil', ); + $all_libraries = PhutilBootloader::getInstance()->getAllLibraries(); + $other_libraries = array_diff($all_libraries, ipull($specs, 'lib')); + $specs = $specs + $other_libraries; + + $futures = array(); - foreach ($specs as $key => $spec) { - $root = dirname(phutil_get_library_root($spec['root'])); - $futures[$key] = id(new ExecFuture('git log --format=%%H -n 1 --')) + foreach ($specs as $lib) { + $root = dirname(phutil_get_library_root($lib)); + $futures[$lib] = + id(new ExecFuture('git log --format=%s -n 1 --', '%H %ct')) ->setCWD($root); } $results = array(); foreach ($futures as $key => $future) { list($err, $stdout) = $future->resolve(); if (!$err) { - $name = trim($stdout); + list($hash, $epoch) = explode(' ', $stdout); + $version = pht('%s (%s)', $hash, phabricator_date($epoch, $viewer)); } else { - $name = pht('Unknown'); + $version = pht('Unknown'); } - $results[$key] = array($specs[$key]['name'], $name); + $results[$key] = $version; } - return array_select_keys($results, array_keys($specs)); + return $results; } } diff --git a/src/applications/config/option/PhabricatorUIConfigOptions.php b/src/applications/config/option/PhabricatorUIConfigOptions.php index d97225979d..84a7b4be6b 100644 --- a/src/applications/config/option/PhabricatorUIConfigOptions.php +++ b/src/applications/config/option/PhabricatorUIConfigOptions.php @@ -1,98 +1,98 @@ newOption('ui.header-color', 'enum', 'blindigo') ->setDescription( pht('Sets the color of the main header.')) ->setEnumOptions($options), $this->newOption('ui.footer-items', 'list', array()) ->setSummary( pht( 'Allows you to add footer links on most pages.')) ->setDescription( pht( "Allows you to add a footer with links in it to most ". "pages. You might want to use these links to point at legal ". "information or an about page.\n\n". "Specify a list of dictionaries. Each dictionary describes ". "a footer item. These keys are supported:\n\n". " - `name` The name of the item.\n". " - `href` Optionally, the link target of the item. You can ". " omit this if you just want a piece of text, like a copyright ". " notice.")) ->addExample($example, pht('Basic Example')), $this->newOption( 'ui.custom-header', 'custom:PhabricatorCustomHeaderConfigType', null) ->setSummary( pht('Customize the Phabricator logo.')) ->setDescription( pht('You can customize the Phabricator logo by specifying the '. 'phid for a viewable image you have uploaded to Phabricator '. 'via the [[ /file/ | Files application]]. This image should '. 'be:'."\n". ' - 192px X 80px; while not enforced, images with these '. 'dimensions will look best across devices.'."\n". ' - have view policy public if [[ '. '/config/edit/policy.allow-public | `policy.allow-public`]] '. 'is true and otherwise view policy user; mismatches in these '. 'policy settings will result in a broken logo for some users.'. "\n\n". - 'You should restart your webserver after updating this value '. + 'You should restart Phabricator after updating this value '. 'to see this change take effect.'. "\n\n". 'As this feature is experimental, please read [[ %s | T4214 ]] '. 'for up to date information.', $experimental_link)) ->addExample($custom_header_example, pht('Valid Config')), ); } } diff --git a/src/applications/config/view/PhabricatorSetupIssueView.php b/src/applications/config/view/PhabricatorSetupIssueView.php index cb8859bb86..ed82ffa7b7 100644 --- a/src/applications/config/view/PhabricatorSetupIssueView.php +++ b/src/applications/config/view/PhabricatorSetupIssueView.php @@ -1,550 +1,563 @@ issue = $issue; return $this; } public function getIssue() { return $this->issue; } public function render() { $issue = $this->getIssue(); $description = array(); $description[] = phutil_tag( 'div', array( 'class' => 'setup-issue-instructions', ), phutil_escape_html_newlines($issue->getMessage())); $configs = $issue->getPHPConfig(); if ($configs) { $description[] = $this->renderPHPConfig($configs, $issue); } $configs = $issue->getMySQLConfig(); if ($configs) { $description[] = $this->renderMySQLConfig($configs); } $configs = $issue->getPhabricatorConfig(); if ($configs) { $description[] = $this->renderPhabricatorConfig($configs); } $related_configs = $issue->getRelatedPhabricatorConfig(); if ($related_configs) { $description[] = $this->renderPhabricatorConfig($related_configs, $related = true); } $commands = $issue->getCommands(); if ($commands) { $run_these = pht('Run these %d command(s):', count($commands)); $description[] = phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), array( phutil_tag('p', array(), $run_these), phutil_tag('pre', array(), phutil_implode_html("\n", $commands)), )); } $extensions = $issue->getPHPExtensions(); if ($extensions) { $install_these = pht( 'Install these %d PHP extension(s):', count($extensions)); $install_info = pht( 'You can usually install a PHP extension using %s or %s. Common '. 'package names are %s or %s. Try commands like these:', phutil_tag('tt', array(), 'apt-get'), phutil_tag('tt', array(), 'yum'), hsprintf('php-%s', pht('extname')), hsprintf('php5-%s', pht('extname'))); // TODO: We should do a better job of detecting how to install extensions // on the current system. $install_commands = hsprintf( "\$ sudo apt-get install php5-extname ". "# Debian / Ubuntu\n". "\$ sudo yum install php-extname ". "# Red Hat / Derivatives"); $fallback_info = pht( "If those commands don't work, try Google. The process of installing ". "PHP extensions is not specific to Phabricator, and any instructions ". "you can find for installing them on your system should work. On Mac ". "OS X, you might want to try Homebrew."); $restart_info = pht( - 'After installing new PHP extensions, restart your webserver '. - 'for the changes to take effect.', - hsprintf('')); + 'After installing new PHP extensions, restart Phabricator '. + 'for the changes to take effect. For help with restarting '. + 'Phabricator, see %s in the documentation.', + $this->renderRestartLink()); $description[] = phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), array( phutil_tag('p', array(), $install_these), phutil_tag('pre', array(), implode("\n", $extensions)), phutil_tag('p', array(), $install_info), phutil_tag('pre', array(), $install_commands), phutil_tag('p', array(), $fallback_info), phutil_tag('p', array(), $restart_info), )); } $related_links = $issue->getLinks(); if ($related_links) { $description[] = $this->renderRelatedLinks($related_links); } $actions = array(); if (!$issue->getIsFatal()) { if ($issue->getIsIgnored()) { $actions[] = javelin_tag( 'a', array( 'href' => '/config/unignore/'.$issue->getIssueKey().'/', 'sigil' => 'workflow', 'class' => 'button grey', ), pht('Unignore Setup Issue')); } else { $actions[] = javelin_tag( 'a', array( 'href' => '/config/ignore/'.$issue->getIssueKey().'/', 'sigil' => 'workflow', 'class' => 'button grey', ), pht('Ignore Setup Issue')); } $actions[] = javelin_tag( 'a', array( 'href' => '/config/issue/'.$issue->getIssueKey().'/', 'class' => 'button grey', 'style' => 'float: right', ), pht('Reload Page')); } if ($actions) { $actions = phutil_tag( 'div', array( 'class' => 'setup-issue-actions', ), $actions); } if ($issue->getIsIgnored()) { $status = phutil_tag( 'div', array( 'class' => 'setup-issue-status', ), pht( 'This issue is currently ignored, and does not show a global '. 'warning.')); $next = null; } else { $status = null; $next = phutil_tag( 'div', array( 'class' => 'setup-issue-next', ), pht('To continue, resolve this problem and reload the page.')); } $name = phutil_tag( 'div', array( 'class' => 'setup-issue-name', ), $issue->getName()); $head = phutil_tag( 'div', array( 'class' => 'setup-issue-head', ), array($name, $status)); $tail = phutil_tag( 'div', array( 'class' => 'setup-issue-tail', ), array($actions)); $issue = phutil_tag( 'div', array( 'class' => 'setup-issue', ), array( $head, $description, $tail, )); $debug_info = phutil_tag( 'div', array( 'class' => 'setup-issue-debug', ), pht('Host: %s', php_uname('n'))); return phutil_tag( 'div', array( 'class' => 'setup-issue-shell', ), array( $issue, $next, $debug_info, )); } private function renderPhabricatorConfig(array $configs, $related = false) { $issue = $this->getIssue(); $table_info = phutil_tag( 'p', array(), pht( 'The current Phabricator configuration has these %d value(s):', count($configs))); $options = PhabricatorApplicationConfigOptions::loadAllOptions(); $hidden = array(); foreach ($options as $key => $option) { if ($option->getHidden()) { $hidden[$key] = true; } } $table = null; $dict = array(); foreach ($configs as $key) { if (isset($hidden[$key])) { $dict[$key] = null; } else { $dict[$key] = PhabricatorEnv::getUnrepairedEnvConfig($key); } } $table = $this->renderValueTable($dict, $hidden); if ($this->getIssue()->getIsFatal()) { $update_info = phutil_tag( 'p', array(), pht( 'To update these %d value(s), run these command(s) from the command '. 'line:', count($configs))); $update = array(); foreach ($configs as $key) { $update[] = hsprintf( 'phabricator/ $ ./bin/config set %s value', $key); } $update = phutil_tag('pre', array(), phutil_implode_html("\n", $update)); } else { $update = array(); foreach ($configs as $config) { if (idx($options, $config) && $options[$config]->getLocked()) { continue; } $link = phutil_tag( 'a', array( 'href' => '/config/edit/'.$config.'/?issue='.$issue->getIssueKey(), ), pht('Edit %s', $config)); $update[] = phutil_tag('li', array(), $link); } if ($update) { $update = phutil_tag('ul', array(), $update); if (!$related) { $update_info = phutil_tag( 'p', array(), pht('You can update these %d value(s) here:', count($configs))); } else { $update_info = phutil_tag( 'p', array(), pht('These %d configuration value(s) are related:', count($configs))); } } else { $update = null; $update_info = null; } } return phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), array( $table_info, $table, $update_info, $update, )); } private function renderPHPConfig(array $configs, $issue) { $table_info = phutil_tag( 'p', array(), pht( 'The current PHP configuration has these %d value(s):', count($configs))); $dict = array(); foreach ($configs as $key) { $dict[$key] = $issue->getPHPConfigOriginalValue( $key, ini_get($key)); } $table = $this->renderValueTable($dict); ob_start(); phpinfo(); $phpinfo = ob_get_clean(); $rex = '@Loaded Configuration File\s*(.*?)@i'; $matches = null; $ini_loc = null; if (preg_match($rex, $phpinfo, $matches)) { $ini_loc = trim($matches[1]); } $rex = '@Additional \.ini files parsed\s*(.*?)@i'; $more_loc = array(); if (preg_match($rex, $phpinfo, $matches)) { $more_loc = trim($matches[1]); if ($more_loc == '(none)') { $more_loc = array(); } else { $more_loc = preg_split('/\s*,\s*/', $more_loc); } } $info = array(); if (!$ini_loc) { $info[] = phutil_tag( 'p', array(), pht( 'To update these %d value(s), edit your PHP configuration file.', count($configs))); } else { $info[] = phutil_tag( 'p', array(), pht( 'To update these %d value(s), edit your PHP configuration file, '. 'located here:', count($configs))); $info[] = phutil_tag( 'pre', array(), $ini_loc); } if ($more_loc) { $info[] = phutil_tag( 'p', array(), pht( 'PHP also loaded these %s configuration file(s):', phutil_count($more_loc))); $info[] = phutil_tag( 'pre', array(), implode("\n", $more_loc)); } $info[] = phutil_tag( 'p', array(), pht( 'You can find more information about PHP configuration values in the '. '%s.', phutil_tag( 'a', array( 'href' => 'http://php.net/manual/ini.list.php', 'target' => '_blank', ), pht('PHP Documentation')))); $info[] = phutil_tag( 'p', array(), pht( - 'After editing the PHP configuration, restart your '. - 'webserver for the changes to take effect.', - hsprintf(''))); + 'After editing the PHP configuration, restart Phabricator for '. + 'the changes to take effect. For help with restarting '. + 'Phabricator, see %s in the documentation.', + $this->renderRestartLink())); return phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), array( $table_info, $table, $info, )); } private function renderMySQLConfig(array $config) { $values = array(); foreach ($config as $key) { $value = PhabricatorMySQLSetupCheck::loadRawConfigValue($key); if ($value === null) { $value = phutil_tag( 'em', array(), pht('(Not Supported)')); } $values[$key] = $value; } $table = $this->renderValueTable($values); $doc_href = PhabricatorEnv::getDoclink('User Guide: Amazon RDS'); $doc_link = phutil_tag( 'a', array( 'href' => $doc_href, 'target' => '_blank', ), pht('User Guide: Amazon RDS')); $info = array(); $info[] = phutil_tag( 'p', array(), pht( 'If you are using Amazon RDS, some of the instructions above may '. 'not apply to you. See %s for discussion of Amazon RDS.', $doc_link)); $table_info = phutil_tag( 'p', array(), pht( 'The current MySQL configuration has these %d value(s):', count($config))); return phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), array( $table_info, $table, $info, )); } private function renderValueTable(array $dict, array $hidden = array()) { $rows = array(); foreach ($dict as $key => $value) { if (isset($hidden[$key])) { $value = phutil_tag('em', array(), 'hidden'); } else { $value = $this->renderValueForDisplay($value); } $cols = array( phutil_tag('th', array(), $key), phutil_tag('td', array(), $value), ); $rows[] = phutil_tag('tr', array(), $cols); } return phutil_tag('table', array(), $rows); } private function renderValueForDisplay($value) { if ($value === null) { return phutil_tag('em', array(), 'null'); } else if ($value === false) { return phutil_tag('em', array(), 'false'); } else if ($value === true) { return phutil_tag('em', array(), 'true'); } else if ($value === '') { return phutil_tag('em', array(), 'empty string'); } else if ($value instanceof PhutilSafeHTML) { return $value; } else { return PhabricatorConfigJSON::prettyPrintJSON($value); } } private function renderRelatedLinks(array $links) { $link_info = phutil_tag( 'p', array(), pht( '%d related link(s):', count($links))); $link_list = array(); foreach ($links as $link) { $link_tag = phutil_tag( 'a', array( 'target' => '_blank', 'href' => $link['href'], ), $link['name']); $link_item = phutil_tag('li', array(), $link_tag); $link_list[] = $link_item; } $link_list = phutil_tag('ul', array(), $link_list); return phutil_tag( 'div', array( 'class' => 'setup-issue-config', ), array( $link_info, $link_list, )); } + private function renderRestartLink() { + $doc_href = PhabricatorEnv::getDoclink('Restarting Phabricator'); + return phutil_tag( + 'a', + array( + 'href' => $doc_href, + 'target' => '_blank', + ), + pht('Restarting Phabricator')); + } + } diff --git a/src/applications/countdown/controller/PhabricatorCountdownController.php b/src/applications/countdown/controller/PhabricatorCountdownController.php index 3817f2b1d8..e22ce95a47 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownController.php @@ -1,40 +1,22 @@ getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - if ($for_app) { - $nav->addFilter('create', pht('Create Countdown')); - } - - id(new PhabricatorCountdownSearchEngine()) - ->setViewer($user) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; - } - public function buildApplicationMenu() { - return $this->buildSideNavView($for_app = true)->getMenu(); + return $this->newApplicationMenu() + ->setSearchEngine(new PhabricatorCountdownSearchEngine()); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Countdown')) ->setHref($this->getApplicationURI('edit/')) ->setIcon('fa-plus-square')); return $crumbs; } } diff --git a/src/applications/countdown/controller/PhabricatorCountdownEditController.php b/src/applications/countdown/controller/PhabricatorCountdownEditController.php index 3b2d3ed831..a6cdacc047 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownEditController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownEditController.php @@ -1,197 +1,196 @@ getViewer(); $id = $request->getURIData('id'); if ($id) { $page_title = pht('Edit Countdown'); $countdown = id(new PhabricatorCountdownQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$countdown) { return new Aphront404Response(); } $date_value = AphrontFormDateControlValue::newFromEpoch( $viewer, $countdown->getEpoch()); $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( $countdown->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $v_projects = array_reverse($v_projects); } else { $page_title = pht('Create Countdown'); $countdown = PhabricatorCountdown::initializeNewCountdown($viewer); $date_value = AphrontFormDateControlValue::newFromEpoch( $viewer, PhabricatorTime::getNow()); $v_projects = array(); } $errors = array(); $e_text = true; $e_epoch = null; $v_text = $countdown->getTitle(); $v_desc = $countdown->getDescription(); $v_space = $countdown->getSpacePHID(); $v_view = $countdown->getViewPolicy(); $v_edit = $countdown->getEditPolicy(); if ($request->isFormPost()) { $v_text = $request->getStr('title'); $v_desc = $request->getStr('description'); $v_space = $request->getStr('spacePHID'); $date_value = AphrontFormDateControlValue::newFromRequest( $request, 'epoch'); $v_view = $request->getStr('viewPolicy'); $v_edit = $request->getStr('editPolicy'); $v_projects = $request->getArr('projects'); $type_title = PhabricatorCountdownTransaction::TYPE_TITLE; $type_epoch = PhabricatorCountdownTransaction::TYPE_EPOCH; $type_description = PhabricatorCountdownTransaction::TYPE_DESCRIPTION; $type_space = PhabricatorTransactions::TYPE_SPACE; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; $xactions = array(); $xactions[] = id(new PhabricatorCountdownTransaction()) ->setTransactionType($type_title) ->setNewValue($v_text); $xactions[] = id(new PhabricatorCountdownTransaction()) ->setTransactionType($type_epoch) ->setNewValue($date_value); $xactions[] = id(new PhabricatorCountdownTransaction()) ->setTransactionType($type_description) ->setNewValue($v_desc); $xactions[] = id(new PhabricatorCountdownTransaction()) ->setTransactionType($type_space) ->setNewValue($v_space); $xactions[] = id(new PhabricatorCountdownTransaction()) ->setTransactionType($type_view) ->setNewValue($v_view); $xactions[] = id(new PhabricatorCountdownTransaction()) ->setTransactionType($type_edit) ->setNewValue($v_edit); $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new PhabricatorCountdownTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $proj_edge_type) ->setNewValue(array('=' => array_fuse($v_projects))); $editor = id(new PhabricatorCountdownEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($countdown, $xactions); return id(new AphrontRedirectResponse()) ->setURI('/'.$countdown->getMonogram()); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_title = $ex->getShortMessage($type_title); $e_epoch = $ex->getShortMessage($type_epoch); } } $crumbs = $this->buildApplicationCrumbs(); $cancel_uri = '/countdown/'; if ($countdown->getID()) { $cancel_uri = '/countdown/'.$countdown->getID().'/'; $crumbs->addTextCrumb('C'.$countdown->getID(), $cancel_uri); $crumbs->addTextCrumb(pht('Edit')); $submit_label = pht('Save Changes'); } else { $crumbs->addTextCrumb(pht('Create Countdown')); $submit_label = pht('Create Countdown'); } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($countdown) ->execute(); $form = id(new AphrontFormView()) ->setUser($viewer) ->setAction($request->getRequestURI()->getPath()) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Title')) ->setValue($v_text) ->setName('title') ->setError($e_text)) ->appendControl( id(new AphrontFormDateControl()) ->setName('epoch') ->setLabel(pht('End Date')) ->setError($e_epoch) ->setValue($date_value)) ->appendControl( id(new PhabricatorRemarkupControl()) ->setName('description') ->setLabel(pht('Description')) ->setValue($v_desc)) ->appendControl( id(new AphrontFormPolicyControl()) ->setName('viewPolicy') ->setPolicyObject($countdown) ->setPolicies($policies) ->setSpacePHID($v_space) ->setValue($v_view) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW)) ->appendControl( id(new AphrontFormPolicyControl()) ->setName('editPolicy') ->setPolicyObject($countdown) ->setPolicies($policies) ->setValue($v_edit) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setValue($v_projects) ->setDatasource(new PhabricatorProjectDatasource())) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($submit_label)); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setFormErrors($errors) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $page_title, - )); + return $this->newPage() + ->setTitle($page_title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $form_box, + )); } } diff --git a/src/applications/countdown/controller/PhabricatorCountdownListController.php b/src/applications/countdown/controller/PhabricatorCountdownListController.php index 031e5430fe..382f1a0306 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownListController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownListController.php @@ -1,22 +1,16 @@ getURIData('queryKey'); - - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine(new PhabricatorCountdownSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); + return id(new PhabricatorCountdownSearchEngine()) + ->setController($this) + ->buildResponse(); } - } diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php index 282a658da9..9cd0d982ff 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php @@ -1,168 +1,170 @@ getViewer(); $id = $request->getURIData('id'); $countdown = id(new PhabricatorCountdownQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$countdown) { return new Aphront404Response(); } $countdown_view = id(new PhabricatorCountdownView()) ->setUser($viewer) ->setCountdown($countdown) ->setHeadless(true); $id = $countdown->getID(); $title = $countdown->getTitle(); $crumbs = $this ->buildApplicationCrumbs() ->addTextCrumb("C{$id}"); $epoch = $countdown->getEpoch(); if ($epoch >= PhabricatorTime::getNow()) { $icon = 'fa-clock-o'; $color = ''; $status = pht('Running'); } else { $icon = 'fa-check-square-o'; $color = 'dark'; $status = pht('Launched'); } $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) ->setPolicyObject($countdown) ->setStatus($icon, $color, $status); $actions = $this->buildActionListView($countdown); $properties = $this->buildPropertyListView($countdown, $actions); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); $timeline = $this->buildTransactionTimeline( $countdown, new PhabricatorCountdownTransactionQuery()); $add_comment = $this->buildCommentForm($countdown); - $content = array( - $crumbs, - $object_box, - $countdown_view, - $timeline, - $add_comment, - ); - - return $this->buildApplicationPage( - $content, - array( - 'title' => $title, - 'pageObjects' => array($countdown->getPHID()), - )); + + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs( + array( + $countdown->getPHID(), + )) + ->appendChild( + array( + $object_box, + $countdown_view, + $timeline, + $add_comment, + )); } private function buildActionListView(PhabricatorCountdown $countdown) { $request = $this->getRequest(); $viewer = $request->getUser(); $id = $countdown->getID(); $view = id(new PhabricatorActionListView()) ->setObject($countdown) ->setUser($viewer); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $countdown, PhabricatorPolicyCapability::CAN_EDIT); $view->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Countdown')) ->setHref($this->getApplicationURI("edit/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $view->addAction( id(new PhabricatorActionView()) ->setIcon('fa-times') ->setName(pht('Delete Countdown')) ->setHref($this->getApplicationURI("delete/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(true)); return $view; } private function buildPropertyListView( PhabricatorCountdown $countdown, PhabricatorActionListView $actions) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($countdown) ->setActionList($actions); $view->addProperty( pht('Author'), $viewer->renderHandle($countdown->getAuthorPHID())); $view->invokeWillRenderEvent(); $description = $countdown->getDescription(); if (strlen($description)) { $description = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($description), 'default', $viewer); $view->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $view->addTextContent($description); } return $view; } private function buildCommentForm(PhabricatorCountdown $countdown) { $viewer = $this->getViewer(); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $add_comment_header = $is_serious ? pht('Add Comment') : pht('Last Words'); $draft = PhabricatorDraft::newFromUserAndKey( $viewer, $countdown->getPHID()); return id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setObjectPHID($countdown->getPHID()) ->setDraft($draft) ->setHeaderText($add_comment_header) ->setAction($this->getApplicationURI('/comment/'.$countdown->getID().'/')) ->setSubmitButtonName(pht('Add Comment')); } } diff --git a/src/applications/differential/application/PhabricatorDifferentialApplication.php b/src/applications/differential/application/PhabricatorDifferentialApplication.php index 9203d0c522..e8113dc3bd 100644 --- a/src/applications/differential/application/PhabricatorDifferentialApplication.php +++ b/src/applications/differential/application/PhabricatorDifferentialApplication.php @@ -1,216 +1,218 @@ pht('Differential User Guide'), 'href' => PhabricatorEnv::getDoclink('Differential User Guide'), ), ); } public function getFactObjectsForAnalysis() { return array( new DifferentialRevision(), ); } public function getTitleGlyph() { return "\xE2\x9A\x99"; } public function getEventListeners() { return array( new DifferentialActionMenuEventListener(), new DifferentialHovercardEventListener(), new DifferentialLandingActionMenuEventListener(), ); } public function getOverview() { return pht( 'Differential is a **code review application** which allows '. 'engineers to review, discuss and approve changes to software.'); } public function getRoutes() { return array( '/D(?P[1-9]\d*)' => 'DifferentialRevisionViewController', '/differential/' => array( '(?:query/(?P[^/]+)/)?' => 'DifferentialRevisionListController', 'diff/' => array( '(?P[1-9]\d*)/' => 'DifferentialDiffViewController', 'create/' => 'DifferentialDiffCreateController', ), 'changeset/' => 'DifferentialChangesetViewController', 'revision/' => array( 'edit/(?:(?P[1-9]\d*)/)?' => 'DifferentialRevisionEditController', 'land/(?:(?P[1-9]\d*))/(?P[^/]+)/' => 'DifferentialRevisionLandController', 'closedetails/(?P[^/]+)/' => 'DifferentialRevisionCloseDetailsController', 'update/(?P[1-9]\d*)/' => 'DifferentialDiffCreateController', 'operation/(?P[1-9]\d*)/' => 'DifferentialRevisionOperationController', ), 'comment/' => array( 'preview/(?P[1-9]\d*)/' => 'DifferentialCommentPreviewController', 'save/(?P[1-9]\d*)/' => 'DifferentialCommentSaveController', 'inline/' => array( 'preview/(?P[1-9]\d*)/' => 'DifferentialInlineCommentPreviewController', 'edit/(?P[1-9]\d*)/' => 'DifferentialInlineCommentEditController', ), ), 'preview/' => 'PhabricatorMarkupPreviewController', ), ); } public function getApplicationOrder() { return 0.100; } public function getRemarkupRules() { return array( new DifferentialRemarkupRule(), ); } public function loadStatus(PhabricatorUser $user) { + $limit = self::MAX_STATUS_ITEMS; + $revisions = id(new DifferentialRevisionQuery()) ->setViewer($user) ->withResponsibleUsers(array($user->getPHID())) ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) ->needRelationships(true) - ->setLimit(self::MAX_STATUS_ITEMS) + ->setLimit($limit) ->execute(); $status = array(); - if (count($revisions) == self::MAX_STATUS_ITEMS) { + if (count($revisions) >= $limit) { $all_count = count($revisions); - $all_count_str = self::formatStatusCount( - $all_count, - '%s Active Reviews', - '%d Active Review(s)'); + $all_count_str = pht( + '%s+ Active Review(s)', + new PhutilNumber($limit - 1)); + $type = PhabricatorApplicationStatusView::TYPE_WARNING; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($all_count_str) ->setCount($all_count); } else { list($blocking, $active, $waiting) = DifferentialRevisionQuery::splitResponsible( $revisions, array($user->getPHID())); $blocking = count($blocking); - $blocking_str = self::formatStatusCount( - $blocking, - '%s Reviews Blocking Others', - '%d Review(s) Blocking Others'); + $blocking_str = pht( + '%s Review(s) Blocking Others', + new PhutilNumber($blocking)); + $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($blocking_str) ->setCount($blocking); $active = count($active); - $active_str = self::formatStatusCount( - $active, - '%s Reviews Need Attention', - '%d Review(s) Need Attention'); + $active_str = pht( + '%s Review(s) Need Attention', + new PhutilNumber($active)); + $type = PhabricatorApplicationStatusView::TYPE_WARNING; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($active_str) ->setCount($active); $waiting = count($waiting); - $waiting_str = self::formatStatusCount( - $waiting, - '%s Reviews Waiting on Others', - '%d Review(s) Waiting on Others'); + $waiting_str = pht( + '%s Review(s) Waiting on Others', + new PhutilNumber($waiting)); + $type = PhabricatorApplicationStatusView::TYPE_INFO; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($waiting_str) ->setCount($waiting); } return $status; } public function supportsEmailIntegration() { return true; } public function getAppEmailBlurb() { return pht( 'Send email to these addresses to create revisions. The body of the '. 'message and / or one or more attachments should be the output of a '. '"diff" command. %s', phutil_tag( 'a', array( 'href' => $this->getInboundEmailSupportLink(), ), pht('Learn More'))); } protected function getCustomCapabilities() { return array( DifferentialDefaultViewCapability::CAPABILITY => array( 'caption' => pht('Default view policy for newly created revisions.'), 'template' => DifferentialRevisionPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_VIEW, ), ); } public function getMailCommandObjects() { return array( 'revision' => array( 'name' => pht('Email Commands: Revisions'), 'header' => pht('Interacting with Differential Revisions'), 'object' => new DifferentialRevision(), 'summary' => pht( 'This page documents the commands you can use to interact with '. 'revisions in Differential.'), ), ); } public function getApplicationSearchDocumentTypes() { return array( DifferentialRevisionPHIDType::TYPECONST, ); } } diff --git a/src/applications/diviner/cache/DivinerAtomCache.php b/src/applications/diviner/cache/DivinerAtomCache.php index b37bd71419..8ef237324b 100644 --- a/src/applications/diviner/cache/DivinerAtomCache.php +++ b/src/applications/diviner/cache/DivinerAtomCache.php @@ -1,233 +1,233 @@ fileHashMap = null; $this->atomMap = null; $this->atoms = array(); return $this; } /* -( File Hash Map )------------------------------------------------------ */ public function getFileHashMap() { if ($this->fileHashMap === null) { $this->fileHashMap = $this->getCache()->getKey('file', array()); } return $this->fileHashMap; } public function addFileHash($file_hash, $atom_hash) { $this->getFileHashMap(); $this->fileHashMap[$file_hash] = $atom_hash; return $this; } public function fileHashExists($file_hash) { $map = $this->getFileHashMap(); return isset($map[$file_hash]); } public function deleteFileHash($file_hash) { if ($this->fileHashExists($file_hash)) { $map = $this->getFileHashMap(); $atom_hash = $map[$file_hash]; unset($this->fileHashMap[$file_hash]); $this->deleteAtomHash($atom_hash); } return $this; } /* -( Atom Map )----------------------------------------------------------- */ public function getAtomMap() { if ($this->atomMap === null) { $this->atomMap = $this->getCache()->getKey('atom', array()); } return $this->atomMap; } public function getAtom($atom_hash) { if (!array_key_exists($atom_hash, $this->atoms)) { $key = 'atom/'.$this->getHashKey($atom_hash); $this->atoms[$atom_hash] = $this->getCache()->getKey($key); } return $this->atoms[$atom_hash]; } public function addAtom(array $atom) { $hash = $atom['hash']; $this->atoms[$hash] = $atom; $this->getAtomMap(); $this->atomMap[$hash] = true; $this->writeAtoms['atom/'.$this->getHashKey($hash)] = $atom; return $this; } public function deleteAtomHash($atom_hash) { $atom = $this->getAtom($atom_hash); if ($atom) { foreach ($atom['childHashes'] as $child_hash) { $this->deleteAtomHash($child_hash); } } $this->getAtomMap(); unset($this->atomMap[$atom_hash]); unset($this->writeAtoms[$atom_hash]); $this->getCache()->deleteKey('atom/'.$this->getHashKey($atom_hash)); return $this; } public function saveAtoms() { $this->getCache()->setKeys( array( 'file' => $this->getFileHashMap(), 'atom' => $this->getAtomMap(), ) + $this->writeAtoms); $this->writeAtoms = array(); return $this; } /* -( Symbol Hash Map )---------------------------------------------------- */ public function getSymbolMap() { if ($this->symbolMap === null) { $this->symbolMap = $this->getCache()->getKey('symbol', array()); } return $this->symbolMap; } public function addSymbol($atom_hash, $symbol_hash) { $this->getSymbolMap(); $this->symbolMap[$atom_hash] = $symbol_hash; return $this; } public function deleteSymbol($atom_hash) { $this->getSymbolMap(); unset($this->symbolMap[$atom_hash]); return $this; } public function saveSymbols() { $this->getCache()->setKeys( array( 'symbol' => $this->getSymbolMap(), )); return $this; } /* -( Edge Map )----------------------------------------------------------- */ public function getEdgeMap() { if ($this->edgeDstMap === null) { $this->edgeDstMap = $this->getCache()->getKey('edge', array()); $this->edgeSrcMap = array(); foreach ($this->edgeDstMap as $dst => $srcs) { foreach ($srcs as $src => $ignored) { $this->edgeSrcMap[$src][$dst] = true; } } } return $this->edgeDstMap; } public function getEdgesWithDestination($symbol_hash) { $this->getEdgeMap(); return array_keys(idx($this->edgeDstMap, $symbol_hash, array())); } public function addEdges($node_hash, array $symbol_hash_list) { $this->getEdgeMap(); $this->edgeSrcMap[$node_hash] = array_fill_keys($symbol_hash_list, true); foreach ($symbol_hash_list as $symbol_hash) { $this->edgeDstMap[$symbol_hash][$node_hash] = true; } return $this; } public function deleteEdges($node_hash) { $this->getEdgeMap(); foreach (idx($this->edgeSrcMap, $node_hash, array()) as $dst => $ignored) { unset($this->edgeDstMap[$dst][$node_hash]); if (empty($this->edgeDstMap[$dst])) { unset($this->edgeDstMap[$dst]); } } unset($this->edgeSrcMap[$node_hash]); return $this; } public function saveEdges() { $this->getCache()->setKeys( array( 'edge' => $this->getEdgeMap(), )); return $this; } /* -( Graph Map )---------------------------------------------------------- */ public function getGraphMap() { if ($this->graphMap === null) { $this->graphMap = $this->getCache()->getKey('graph', array()); } return $this->graphMap; } public function deleteGraph($node_hash) { $this->getGraphMap(); unset($this->graphMap[$node_hash]); return $this; } public function addGraph($node_hash, $graph_hash) { $this->getGraphMap(); $this->graphMap[$node_hash] = $graph_hash; return $this; } public function saveGraph() { $this->getCache()->setKeys( array( 'graph' => $this->getGraphMap(), )); return $this; } } diff --git a/src/applications/diviner/cache/DivinerPublishCache.php b/src/applications/diviner/cache/DivinerPublishCache.php index e1bff354dd..1b3859b3e9 100644 --- a/src/applications/diviner/cache/DivinerPublishCache.php +++ b/src/applications/diviner/cache/DivinerPublishCache.php @@ -1,74 +1,74 @@ pathMap === null) { $this->pathMap = $this->getCache()->getKey('path', array()); } return $this->pathMap; } public function writePathMap() { $this->getCache()->setKey('path', $this->getPathMap()); } public function getAtomPathsFromCache($hash) { return idx($this->getPathMap(), $hash, array()); } public function removeAtomPathsFromCache($hash) { $map = $this->getPathMap(); unset($map[$hash]); $this->pathMap = $map; return $this; } public function addAtomPathsToCache($hash, array $paths) { $map = $this->getPathMap(); $map[$hash] = $paths; $this->pathMap = $map; return $this; } /* -( Index )-------------------------------------------------------------- */ public function getIndex() { if ($this->index === null) { $this->index = $this->getCache()->getKey('index', array()); } return $this->index; } public function writeIndex() { $this->getCache()->setKey('index', $this->getIndex()); } public function deleteAtomFromIndex($hash) { $index = $this->getIndex(); unset($index[$hash]); $this->index = $index; return $this; } public function addAtomToIndex($hash, array $data) { $index = $this->getIndex(); $index[$hash] = $data; $this->index = $index; return $this; } } diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php index 05bb80c16a..46506ff317 100644 --- a/src/applications/diviner/controller/DivinerAtomController.php +++ b/src/applications/diviner/controller/DivinerAtomController.php @@ -1,689 +1,688 @@ getUser(); $book_name = $request->getURIData('book'); $atom_type = $request->getURIData('type'); $atom_name = $request->getURIData('name'); $atom_context = nonempty($request->getURIData('context'), null); $atom_index = nonempty($request->getURIData('index'), null); require_celerity_resource('diviner-shared-css'); $book = id(new DivinerBookQuery()) ->setViewer($viewer) ->withNames(array($book_name)) ->executeOne(); if (!$book) { return new Aphront404Response(); } $symbol = id(new DivinerAtomQuery()) ->setViewer($viewer) ->withBookPHIDs(array($book->getPHID())) ->withTypes(array($atom_type)) ->withNames(array($atom_name)) ->withContexts(array($atom_context)) ->withIndexes(array($atom_index)) ->withIsDocumentable(true) ->needAtoms(true) ->needExtends(true) ->needChildren(true) ->executeOne(); if (!$symbol) { return new Aphront404Response(); } $atom = $symbol->getAtom(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); $crumbs->addTextCrumb( $book->getShortTitle(), '/book/'.$book->getName().'/'); $atom_short_title = $atom ? $atom->getDocblockMetaValue('short', $symbol->getTitle()) : $symbol->getTitle(); $crumbs->addTextCrumb($atom_short_title); $header = id(new PHUIHeaderView()) ->setHeader($this->renderFullSignature($symbol)); $properties = new PHUIPropertyListView(); $group = $atom ? $atom->getProperty('group') : $symbol->getGroupName(); if ($group) { $group_name = $book->getGroupName($group); } else { $group_name = null; } $prop_list = new PHUIPropertyGroupView(); $prop_list->addPropertyList($properties); $document = id(new PHUIDocumentViewPro()) ->setBook($book->getTitle(), $group_name) ->setHeader($header) - ->addClass('diviner-view') - ->setPropertyList($prop_list); + ->addClass('diviner-view'); if ($atom) { $this->buildDefined($properties, $symbol); $this->buildExtendsAndImplements($properties, $symbol); $this->buildRepository($properties, $symbol); $warnings = $atom->getWarnings(); if ($warnings) { $warnings = id(new PHUIInfoView()) ->setErrors($warnings) ->setTitle(pht('Documentation Warnings')) ->setSeverity(PHUIInfoView::SEVERITY_WARNING); } $document->appendChild($warnings); } $methods = $this->composeMethods($symbol); $field = 'default'; $engine = id(new PhabricatorMarkupEngine()) ->setViewer($viewer) ->addObject($symbol, $field); foreach ($methods as $method) { foreach ($method['atoms'] as $matom) { $engine->addObject($matom, $field); } } $engine->process(); if ($atom) { $content = $this->renderDocumentationText($symbol, $engine); $document->appendChild($content); } $toc = $engine->getEngineMetadata( $symbol, $field, PhutilRemarkupHeaderBlockRule::KEY_HEADER_TOC, array()); if (!$atom) { $document->appendChild( id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->appendChild(pht('This atom no longer exists.'))); } if ($atom) { $document->appendChild($this->buildParametersAndReturn(array($symbol))); } if ($methods) { $tasks = $this->composeTasks($symbol); if ($tasks) { $methods_by_task = igroup($methods, 'task'); // Add phantom tasks for methods which have a "@task" name that isn't // documented anywhere, or methods that have no "@task" name. foreach ($methods_by_task as $task => $ignored) { if (empty($tasks[$task])) { $tasks[$task] = array( 'name' => $task, 'title' => $task ? $task : pht('Other Methods'), 'defined' => $symbol, ); } } $section = id(new DivinerSectionView()) ->setHeader(pht('Tasks')); foreach ($tasks as $spec) { $section->addContent( id(new PHUIHeaderView()) ->setNoBackground(true) ->setHeader($spec['title'])); $task_methods = idx($methods_by_task, $spec['name'], array()); $box_content = array(); if ($task_methods) { $list_items = array(); foreach ($task_methods as $task_method) { $atom = last($task_method['atoms']); $item = $this->renderFullSignature($atom, true); if (strlen($atom->getSummary())) { $item = array( $item, " \xE2\x80\x94 ", $atom->getSummary(), ); } $list_items[] = phutil_tag('li', array(), $item); } $box_content[] = phutil_tag( 'ul', array( 'class' => 'diviner-list', ), $list_items); } else { $no_methods = pht('No methods for this task.'); $box_content = phutil_tag('em', array(), $no_methods); } $inner_box = phutil_tag_div('diviner-task-items', $box_content); $section->addContent($inner_box); } $document->appendChild($section); } $section = id(new DivinerSectionView()) ->setHeader(pht('Methods')); foreach ($methods as $spec) { $matom = last($spec['atoms']); $method_header = id(new PHUIHeaderView()) ->setNoBackground(true); $inherited = $spec['inherited']; if ($inherited) { $method_header->addTag( id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor(PHUITagView::COLOR_GREY) ->setName(pht('Inherited'))); } $method_header->setHeader($this->renderFullSignature($matom)); $section->addContent( array( $method_header, $this->renderMethodDocumentationText($symbol, $spec, $engine), $this->buildParametersAndReturn($spec['atoms']), )); } $document->appendChild($section); } if ($toc) { $side = new PHUIListView(); $side->addMenuItem( id(new PHUIListItemView()) ->setName(pht('Contents')) ->setType(PHUIListItemView::TYPE_LABEL)); foreach ($toc as $key => $entry) { $side->addMenuItem( id(new PHUIListItemView()) ->setName($entry[1]) ->setHref('#'.$key)); } $document->setToc($side); } return $this->buildApplicationPage( array( $crumbs, $document, + $prop_list, ), array( 'title' => $symbol->getTitle(), - 'class' => 'pro-white-background', )); } private function buildExtendsAndImplements( PHUIPropertyListView $view, DivinerLiveSymbol $symbol) { $lineage = $this->getExtendsLineage($symbol); if ($lineage) { $tags = array(); foreach ($lineage as $item) { $tags[] = $this->renderAtomTag($item); } $caret = phutil_tag('span', array('class' => 'caret-right msl msr')); $tags = phutil_implode_html($caret, $tags); $view->addProperty(pht('Extends'), $tags); } $implements = $this->getImplementsLineage($symbol); if ($implements) { $items = array(); foreach ($implements as $spec) { $via = $spec['via']; $iface = $spec['interface']; if ($via == $symbol) { $items[] = $this->renderAtomTag($iface); } else { $items[] = array( $this->renderAtomTag($iface), " \xE2\x97\x80 ", $this->renderAtomTag($via), ); } } $view->addProperty( pht('Implements'), phutil_implode_html(phutil_tag('br'), $items)); } } private function buildRepository( PHUIPropertyListView $view, DivinerLiveSymbol $symbol) { if (!$symbol->getRepositoryPHID()) { return; } $view->addProperty( pht('Repository'), $this->getViewer()->renderHandle($symbol->getRepositoryPHID())); } private function renderAtomTag(DivinerLiveSymbol $symbol) { return id(new PHUITagView()) ->setType(PHUITagView::TYPE_OBJECT) ->setName($symbol->getName()) ->setHref($symbol->getURI()); } private function getExtendsLineage(DivinerLiveSymbol $symbol) { foreach ($symbol->getExtends() as $extends) { if ($extends->getType() == 'class') { $lineage = $this->getExtendsLineage($extends); $lineage[] = $extends; return $lineage; } } return array(); } private function getImplementsLineage(DivinerLiveSymbol $symbol) { $implements = array(); // Do these first so we get interfaces ordered from most to least specific. foreach ($symbol->getExtends() as $extends) { if ($extends->getType() == 'interface') { $implements[$extends->getName()] = array( 'interface' => $extends, 'via' => $symbol, ); } } // Now do parent interfaces. foreach ($symbol->getExtends() as $extends) { if ($extends->getType() == 'class') { $implements += $this->getImplementsLineage($extends); } } return $implements; } private function buildDefined( PHUIPropertyListView $view, DivinerLiveSymbol $symbol) { $atom = $symbol->getAtom(); $defined = $atom->getFile().':'.$atom->getLine(); $link = $symbol->getBook()->getConfig('uri.source'); if ($link) { $link = strtr( $link, array( '%%' => '%', '%f' => phutil_escape_uri($atom->getFile()), '%l' => phutil_escape_uri($atom->getLine()), )); $defined = phutil_tag( 'a', array( 'href' => $link, 'target' => '_blank', ), $defined); } $view->addProperty(pht('Defined'), $defined); } private function composeMethods(DivinerLiveSymbol $symbol) { $methods = $this->findMethods($symbol); if (!$methods) { return $methods; } foreach ($methods as $name => $method) { // Check for "@task" on each parent, to find the most recently declared // "@task". $task = null; foreach ($method['atoms'] as $key => $method_symbol) { $atom = $method_symbol->getAtom(); if ($atom->getDocblockMetaValue('task')) { $task = $atom->getDocblockMetaValue('task'); } } $methods[$name]['task'] = $task; // Set 'inherited' if this atom has no implementation of the method. if (last($method['implementations']) !== $symbol) { $methods[$name]['inherited'] = true; } else { $methods[$name]['inherited'] = false; } } return $methods; } private function findMethods(DivinerLiveSymbol $symbol) { $child_specs = array(); foreach ($symbol->getExtends() as $extends) { if ($extends->getType() == DivinerAtom::TYPE_CLASS) { $child_specs = $this->findMethods($extends); } } foreach ($symbol->getChildren() as $child) { if ($child->getType() == DivinerAtom::TYPE_METHOD) { $name = $child->getName(); if (isset($child_specs[$name])) { $child_specs[$name]['atoms'][] = $child; $child_specs[$name]['implementations'][] = $symbol; } else { $child_specs[$name] = array( 'atoms' => array($child), 'defined' => $symbol, 'implementations' => array($symbol), ); } } } return $child_specs; } private function composeTasks(DivinerLiveSymbol $symbol) { $extends_task_specs = array(); foreach ($symbol->getExtends() as $extends) { $extends_task_specs += $this->composeTasks($extends); } $task_specs = array(); $tasks = $symbol->getAtom()->getDocblockMetaValue('task'); if (strlen($tasks)) { $tasks = phutil_split_lines($tasks, $retain_endings = false); foreach ($tasks as $task) { list($name, $title) = explode(' ', $task, 2); $name = trim($name); $title = trim($title); $task_specs[$name] = array( 'name' => $name, 'title' => $title, 'defined' => $symbol, ); } } $specs = $task_specs + $extends_task_specs; // Reorder "@tasks" in original declaration order. Basically, we want to // use the documentation of the closest subclass, but put tasks which // were declared by parents first. $keys = array_keys($extends_task_specs); $specs = array_select_keys($specs, $keys) + $specs; return $specs; } private function renderFullSignature( DivinerLiveSymbol $symbol, $is_link = false) { switch ($symbol->getType()) { case DivinerAtom::TYPE_CLASS: case DivinerAtom::TYPE_INTERFACE: case DivinerAtom::TYPE_METHOD: case DivinerAtom::TYPE_FUNCTION: break; default: return $symbol->getTitle(); } $atom = $symbol->getAtom(); $out = array(); if ($atom) { if ($atom->getProperty('final')) { $out[] = 'final'; } if ($atom->getProperty('abstract')) { $out[] = 'abstract'; } if ($atom->getProperty('access')) { $out[] = $atom->getProperty('access'); } if ($atom->getProperty('static')) { $out[] = 'static'; } } switch ($symbol->getType()) { case DivinerAtom::TYPE_CLASS: case DivinerAtom::TYPE_INTERFACE: $out[] = $symbol->getType(); break; case DivinerAtom::TYPE_FUNCTION: switch ($atom->getLanguage()) { case 'php': $out[] = $symbol->getType(); break; } break; case DivinerAtom::TYPE_METHOD: switch ($atom->getLanguage()) { case 'php': $out[] = DivinerAtom::TYPE_FUNCTION; break; } break; } $anchor = null; switch ($symbol->getType()) { case DivinerAtom::TYPE_METHOD: $anchor = $symbol->getType().'/'.$symbol->getName(); break; default: break; } $out[] = phutil_tag( $anchor ? 'a' : 'span', array( 'class' => 'diviner-atom-signature-name', 'href' => $anchor ? '#'.$anchor : null, 'name' => $is_link ? null : $anchor, ), $symbol->getName()); $out = phutil_implode_html(' ', $out); if ($atom) { $parameters = $atom->getProperty('parameters'); if ($parameters !== null) { $pout = array(); foreach ($parameters as $parameter) { $pout[] = idx($parameter, 'name', '...'); } $out = array($out, '('.implode(', ', $pout).')'); } } return phutil_tag( 'span', array( 'class' => 'diviner-atom-signature', ), $out); } private function buildParametersAndReturn(array $symbols) { assert_instances_of($symbols, 'DivinerLiveSymbol'); $symbols = array_reverse($symbols); $out = array(); $collected_parameters = null; foreach ($symbols as $symbol) { $parameters = $symbol->getAtom()->getProperty('parameters'); if ($parameters !== null) { if ($collected_parameters === null) { $collected_parameters = array(); } foreach ($parameters as $key => $parameter) { if (isset($collected_parameters[$key])) { $collected_parameters[$key] += $parameter; } else { $collected_parameters[$key] = $parameter; } } } } if (nonempty($parameters)) { $out[] = id(new DivinerParameterTableView()) ->setHeader(pht('Parameters')) ->setParameters($parameters); } $collected_return = null; foreach ($symbols as $symbol) { $return = $symbol->getAtom()->getProperty('return'); if ($return) { if ($collected_return) { $collected_return += $return; } else { $collected_return = $return; } } } if (nonempty($return)) { $out[] = id(new DivinerReturnTableView()) ->setHeader(pht('Return')) ->setReturn($collected_return); } return $out; } private function renderDocumentationText( DivinerLiveSymbol $symbol, PhabricatorMarkupEngine $engine) { $field = 'default'; $content = $engine->getOutput($symbol, $field); if (strlen(trim($symbol->getMarkupText($field)))) { $content = phutil_tag( 'div', array( 'class' => 'phabricator-remarkup diviner-remarkup-section', ), $content); } else { $atom = $symbol->getAtom(); $content = phutil_tag( 'div', array( 'class' => 'diviner-message-not-documented', ), DivinerAtom::getThisAtomIsNotDocumentedString($atom->getType())); } return $content; } private function renderMethodDocumentationText( DivinerLiveSymbol $parent, array $spec, PhabricatorMarkupEngine $engine) { $symbols = array_values($spec['atoms']); $implementations = array_values($spec['implementations']); $field = 'default'; $out = array(); foreach ($symbols as $key => $symbol) { $impl = $implementations[$key]; if ($impl !== $parent) { if (!strlen(trim($symbol->getMarkupText($field)))) { continue; } } $doc = $this->renderDocumentationText($symbol, $engine); if (($impl !== $parent) || $out) { $where = id(new PHUIBoxView()) ->addClass('diviner-method-implementation-header') ->appendChild($impl->getName()); $doc = array($where, $doc); if ($impl !== $parent) { $doc = phutil_tag( 'div', array( 'class' => 'diviner-method-implementation-inherited', ), $doc); } } $out[] = $doc; } // If we only have inherited implementations but none have documentation, // render the last one here so we get the "this thing has no documentation" // element. if (!$out) { $out[] = $this->renderDocumentationText($symbol, $engine); } return $out; } } diff --git a/src/applications/diviner/controller/DivinerBookController.php b/src/applications/diviner/controller/DivinerBookController.php index 2731823588..38e6cc3767 100644 --- a/src/applications/diviner/controller/DivinerBookController.php +++ b/src/applications/diviner/controller/DivinerBookController.php @@ -1,142 +1,141 @@ getViewer(); $book_name = $request->getURIData('book'); $book = id(new DivinerBookQuery()) ->setViewer($viewer) ->withNames(array($book_name)) ->needRepositories(true) ->executeOne(); if (!$book) { return new Aphront404Response(); } $actions = $this->buildActionView($viewer, $book); $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); $crumbs->addTextCrumb( $book->getShortTitle(), '/book/'.$book->getName().'/'); $action_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Actions')) ->setHref('#') ->setIconFont('fa-bars') ->addClass('phui-mobile-menu') ->setDropdownMenu($actions); $header = id(new PHUIHeaderView()) ->setHeader($book->getTitle()) ->setUser($viewer) ->setPolicyObject($book) ->setEpoch($book->getDateModified()) ->addActionLink($action_button); // TODO: This could probably look better. if ($book->getRepositoryPHID()) { $header->addTag( id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor(PHUITagView::COLOR_BLUE) ->setName($book->getRepository()->getMonogram())); } $document = new PHUIDocumentViewPro(); $document->setHeader($header); $document->addClass('diviner-view'); $atoms = id(new DivinerAtomQuery()) ->setViewer($viewer) ->withBookPHIDs(array($book->getPHID())) ->withGhosts(false) ->withIsDocumentable(true) ->execute(); $atoms = msort($atoms, 'getSortKey'); $group_spec = $book->getConfig('groups'); if (!is_array($group_spec)) { $group_spec = array(); } $groups = mgroup($atoms, 'getGroupName'); $groups = array_select_keys($groups, array_keys($group_spec)) + $groups; if (isset($groups[''])) { $no_group = $groups['']; unset($groups['']); $groups[''] = $no_group; } $out = array(); foreach ($groups as $group => $atoms) { $group_name = $book->getGroupName($group); if (!strlen($group_name)) { $group_name = pht('Free Radicals'); } $section = id(new DivinerSectionView()) ->setHeader($group_name); $section->addContent($this->renderAtomList($atoms)); $out[] = $section; } $preface = $book->getPreface(); $preface_view = null; if (strlen($preface)) { $preface_view = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($preface), 'default', $viewer); } $document->appendChild($preface_view); $document->appendChild($out); return $this->buildApplicationPage( array( $crumbs, $document, ), array( 'title' => $book->getTitle(), - 'class' => 'pro-white-background', )); } private function buildActionView( PhabricatorUser $user, DivinerLiveBook $book) { $can_edit = PhabricatorPolicyFilter::hasCapability( $user, $book, PhabricatorPolicyCapability::CAN_EDIT); $action_view = id(new PhabricatorActionListView()) ->setUser($user) ->setObject($book) ->setObjectURI($this->getRequest()->getRequestURI()); $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Book')) ->setIcon('fa-pencil') ->setHref('/book/'.$book->getName().'/edit/') ->setDisabled(!$can_edit)); return $action_view; } } diff --git a/src/applications/diviner/controller/DivinerMainController.php b/src/applications/diviner/controller/DivinerMainController.php index a68d900d8a..3050199c8a 100644 --- a/src/applications/diviner/controller/DivinerMainController.php +++ b/src/applications/diviner/controller/DivinerMainController.php @@ -1,85 +1,84 @@ getViewer(); $books = id(new DivinerBookQuery()) ->setViewer($viewer) ->execute(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); $crumbs->addTextCrumb(pht('Books')); $search_icon = id(new PHUIIconView()) ->setIconFont('fa-search'); $query_button = id(new PHUIButtonView()) ->setTag('a') ->setHref($this->getApplicationURI('query/')) ->setText(pht('Advanced Search')) ->setIcon($search_icon); $header = id(new PHUIHeaderView()) ->setHeader(pht('Documentation Books')) ->addActionLink($query_button); $document = new PHUIDocumentViewPro(); $document->setHeader($header); $document->addClass('diviner-view'); if ($books) { $books = msort($books, 'getTitle'); $list = array(); foreach ($books as $book) { $item = id(new DivinerBookItemView()) ->setTitle($book->getTitle()) ->setHref('/book/'.$book->getName().'/') ->setSubtitle($book->getPreface()); $list[] = $item; } $list = id(new PHUIBoxView()) ->addPadding(PHUI::PADDING_MEDIUM_TOP) ->appendChild($list); $document->appendChild($list); } else { $text = pht( "(NOTE) **Looking for Phabricator documentation?** ". "If you're looking for help and information about Phabricator, ". "you can [[https://secure.phabricator.com/diviner/ | ". "browse the public Phabricator documentation]] on the live site.\n\n". "Diviner is the documentation generator used to build the ". "Phabricator documentation.\n\n". "You haven't generated any Diviner documentation books yet, so ". "there's nothing to show here. If you'd like to generate your own ". "local copy of the Phabricator documentation and have it appear ". "here, run this command:\n\n". " %s\n\n", 'phabricator/ $ ./bin/diviner generate'); $text = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($text), 'default', $viewer); $document->appendChild($text); } return $this->buildApplicationPage( array( $crumbs, $document, ), array( 'title' => pht('Documentation Books'), - 'class' => 'pro-white-background', )); } } diff --git a/src/applications/draft/storage/PhabricatorVersionedDraft.php b/src/applications/draft/storage/PhabricatorVersionedDraft.php new file mode 100644 index 0000000000..145f24a3fb --- /dev/null +++ b/src/applications/draft/storage/PhabricatorVersionedDraft.php @@ -0,0 +1,83 @@ + array( + 'properties' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'version' => 'uint32', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_object' => array( + 'columns' => array('objectPHID', 'authorPHID', 'version'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function setProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + + public function getProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public static function loadDraft( + $object_phid, + $viewer_phid) { + + return id(new PhabricatorVersionedDraft())->loadOneWhere( + 'objectPHID = %s AND authorPHID = %s ORDER BY version DESC LIMIT 1', + $object_phid, + $viewer_phid); + } + + public static function loadOrCreateDraft( + $object_phid, + $viewer_phid, + $version) { + + $draft = self::loadDraft($object_phid, $viewer_phid); + if ($draft) { + return $draft; + } + + return id(new PhabricatorVersionedDraft()) + ->setObjectPHID($object_phid) + ->setAuthorPHID($viewer_phid) + ->setVersion($version) + ->save(); + } + + public static function purgeDrafts( + $object_phid, + $viewer_phid, + $version) { + + $draft = new PhabricatorVersionedDraft(); + $conn_w = $draft->establishConnection('w'); + + queryfx( + $conn_w, + 'DELETE FROM %T WHERE objectPHID = %s AND authorPHID = %s + AND version <= %d', + $draft->getTableName(), + $object_phid, + $viewer_phid, + $version); + } + +} diff --git a/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php b/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php index 61301bdae5..eb6288b40f 100644 --- a/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php +++ b/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php @@ -1,188 +1,188 @@ getWritableEngine(); } public function hasFilesizeLimit() { return false; } public function isChunkEngine() { return true; } public function writeFile($data, array $params) { // The chunk engine does not support direct writes. throw new PhutilMethodNotImplementedException(); } public function readFile($handle) { // This is inefficient, but makes the API work as expected. $chunks = $this->loadAllChunks($handle, true); $buffer = ''; foreach ($chunks as $chunk) { $data_file = $chunk->getDataFile(); if (!$data_file) { throw new Exception(pht('This file data is incomplete!')); } $buffer .= $chunk->getDataFile()->loadFileData(); } return $buffer; } public function deleteFile($handle) { $engine = new PhabricatorDestructionEngine(); $chunks = $this->loadAllChunks($handle, true); foreach ($chunks as $chunk) { $engine->destroyObject($chunk); } } private function loadAllChunks($handle, $need_files) { $chunks = id(new PhabricatorFileChunkQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withChunkHandles(array($handle)) ->needDataFiles($need_files) ->execute(); $chunks = msort($chunks, 'getByteStart'); return $chunks; } /** * Compute a chunked file hash for the viewer. * * We can not currently compute a real hash for chunked file uploads (because * no process sees all of the file data). * * We also can not trust the hash that the user claims to have computed. If * we trust the user, they can upload some `evil.exe` and claim it has the * same file hash as `good.exe`. When another user later uploads the real * `good.exe`, we'll just create a reference to the existing `evil.exe`. Users * who download `good.exe` will then receive `evil.exe`. * * Instead, we rehash the user's claimed hash with account secrets. This * allows users to resume file uploads, but not collide with other users. * * Ideally, we'd like to be able to verify hashes, but this is complicated * and time consuming and gives us a fairly small benefit. * * @param PhabricatorUser Viewing user. * @param string Claimed file hash. * @return string Rehashed file hash. */ public static function getChunkedHash(PhabricatorUser $viewer, $hash) { if (!$viewer->getPHID()) { throw new Exception( pht('Unable to compute chunked hash without real viewer!')); } $input = $viewer->getAccountSecret().':'.$hash.':'.$viewer->getPHID(); return self::getChunkedHashForInput($input); } public static function getChunkedHashForInput($input) { $rehash = PhabricatorHash::digest($input); // Add a suffix to identify this as a chunk hash. $rehash = substr($rehash, 0, -2).'-C'; return $rehash; } public function allocateChunks($length, array $properties) { $file = PhabricatorFile::newChunkedFile($this, $length, $properties); $chunk_size = $this->getChunkSize(); $handle = $file->getStorageHandle(); $chunks = array(); for ($ii = 0; $ii < $length; $ii += $chunk_size) { $chunks[] = PhabricatorFileChunk::initializeNewChunk( $handle, $ii, min($ii + $chunk_size, $length)); } $file->openTransaction(); foreach ($chunks as $chunk) { $chunk->save(); } $file->save(); $file->saveTransaction(); return $file; } /** * Find a storage engine which is suitable for storing chunks. * * This engine must be a writable engine, have a filesize limit larger than * the chunk limit, and must not be a chunk engine itself. */ private function getWritableEngine() { // NOTE: We can't just load writable engines or we'll loop forever. - $engines = PhabricatorFileStorageEngine::loadAllEngines(); + $engines = parent::loadAllEngines(); foreach ($engines as $engine) { if ($engine->isChunkEngine()) { continue; } if ($engine->isTestEngine()) { continue; } if (!$engine->canWriteFiles()) { continue; } if ($engine->hasFilesizeLimit()) { if ($engine->getFilesizeLimit() < $this->getChunkSize()) { continue; } } return true; } return false; } public function getChunkSize() { return (4 * 1024 * 1024); } public function getFileDataIterator(PhabricatorFile $file, $begin, $end) { $chunks = id(new PhabricatorFileChunkQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withChunkHandles(array($file->getStorageHandle())) ->withByteRange($begin, $end) ->needDataFiles(true) ->execute(); return new PhabricatorFileChunkIterator($chunks, $begin, $end); } } diff --git a/src/applications/flag/application/PhabricatorFlagsApplication.php b/src/applications/flag/application/PhabricatorFlagsApplication.php index 232f6f4dca..fad4c0bc8e 100644 --- a/src/applications/flag/application/PhabricatorFlagsApplication.php +++ b/src/applications/flag/application/PhabricatorFlagsApplication.php @@ -1,69 +1,72 @@ setViewer($user) ->withOwnerPHIDs(array($user->getPHID())) ->setLimit(self::MAX_STATUS_ITEMS) ->execute(); $count = count($flags); - $count_str = self::formatStatusCount( - $count, - '%s Flagged Objects', - '%d Flagged Object(s)'); + if ($count >= $limit) { + $count_str = pht('%s+ Flagged Object(s)', new PhutilNumber($limit - 1)); + } else { + $count_str = pht('%s Flagged Object(s)', new PhutilNumber($count)); + } + $type = PhabricatorApplicationStatusView::TYPE_WARNING; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($count_str) ->setCount($count); return $status; } public function getRoutes() { return array( '/flag/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorFlagListController', 'view/(?P[^/]+)/' => 'PhabricatorFlagListController', 'edit/(?P[^/]+)/' => 'PhabricatorFlagEditController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorFlagDeleteController', ), ); } } diff --git a/src/applications/herald/controller/HeraldController.php b/src/applications/herald/controller/HeraldController.php index b3ba2f17c3..2151c62aa9 100644 --- a/src/applications/herald/controller/HeraldController.php +++ b/src/applications/herald/controller/HeraldController.php @@ -1,45 +1,40 @@ buildSideNavView(true)->getMenu(); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Herald Rule')) ->setHref($this->getApplicationURI('new/')) ->setIcon('fa-plus-square')); return $crumbs; } - public function buildSideNavView($for_app = false) { - $user = $this->getRequest()->getUser(); + public function buildSideNavView() { + $viewer = $this->getViewer(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - if ($for_app) { - $nav->addFilter('new', pht('Create Rule')); - } - id(new HeraldRuleSearchEngine()) - ->setViewer($user) + ->setViewer($viewer) ->addNavigationItems($nav->getMenu()); - $nav - ->addLabel(pht('Utilities')) - ->addFilter('test', pht('Test Console')) - ->addFilter('transcript', pht('Transcripts')); + $nav->addLabel(pht('Utilities')) + ->addFilter('test', pht('Test Console')) + ->addFilter('transcript', pht('Transcripts')); $nav->selectFilter(null); return $nav; } } diff --git a/src/applications/herald/controller/HeraldNewController.php b/src/applications/herald/controller/HeraldNewController.php index 53e1faf7d6..c25b4c839b 100644 --- a/src/applications/herald/controller/HeraldNewController.php +++ b/src/applications/herald/controller/HeraldNewController.php @@ -1,316 +1,317 @@ getViewer(); $content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer); $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); $errors = array(); $e_type = null; $e_rule = null; $e_object = null; $step = $request->getInt('step'); if ($request->isFormPost()) { $content_type = $request->getStr('content_type'); if (empty($content_type_map[$content_type])) { $errors[] = pht('You must choose a content type for this rule.'); $e_type = pht('Required'); $step = 0; } if (!$errors && $step > 1) { $rule_type = $request->getStr('rule_type'); if (empty($rule_type_map[$rule_type])) { $errors[] = pht('You must choose a rule type for this rule.'); $e_rule = pht('Required'); $step = 1; } } if (!$errors && $step >= 2) { $target_phid = null; $object_name = $request->getStr('objectName'); $done = false; if ($rule_type != HeraldRuleTypeConfig::RULE_TYPE_OBJECT) { $done = true; } else if (strlen($object_name)) { $target_object = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withNames(array($object_name)) ->executeOne(); if ($target_object) { $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $target_object, PhabricatorPolicyCapability::CAN_EDIT); if (!$can_edit) { $errors[] = pht( 'You can not create a rule for that object, because you do '. 'not have permission to edit it. You can only create rules '. 'for objects you can edit.'); $e_object = pht('Not Editable'); $step = 2; } else { $adapter = HeraldAdapter::getAdapterForContentType($content_type); if (!$adapter->canTriggerOnObject($target_object)) { $errors[] = pht( 'This object is not of an allowed type for the rule. '. 'Rules can only trigger on certain objects.'); $e_object = pht('Invalid'); $step = 2; } else { $target_phid = $target_object->getPHID(); $done = true; } } } else { $errors[] = pht('No object exists by that name.'); $e_object = pht('Invalid'); $step = 2; } } else if ($step > 2) { $errors[] = pht( 'You must choose an object to associate this rule with.'); $e_object = pht('Required'); $step = 2; } if (!$errors && $done) { $uri = id(new PhutilURI('edit/')) ->setQueryParams( array( 'content_type' => $content_type, 'rule_type' => $rule_type, 'targetPHID' => $target_phid, )); $uri = $this->getApplicationURI($uri); return id(new AphrontRedirectResponse())->setURI($uri); } } } $content_type = $request->getStr('content_type'); $rule_type = $request->getStr('rule_type'); $form = id(new AphrontFormView()) ->setUser($viewer) ->setAction($this->getApplicationURI('new/')); switch ($step) { case 0: default: $content_types = $this->renderContentTypeControl( $content_type_map, $e_type); $form ->addHiddenInput('step', 1) ->appendChild($content_types); $cancel_text = null; $cancel_uri = $this->getApplicationURI(); break; case 1: $rule_types = $this->renderRuleTypeControl( $rule_type_map, $e_rule); $form ->addHiddenInput('content_type', $content_type) ->addHiddenInput('step', 2) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Rule for')) ->setValue( phutil_tag( 'strong', array(), idx($content_type_map, $content_type)))) ->appendChild($rule_types); $cancel_text = pht('Back'); $cancel_uri = id(new PhutilURI('new/')) ->setQueryParams( array( 'content_type' => $content_type, 'step' => 0, )); $cancel_uri = $this->getApplicationURI($cancel_uri); break; case 2: $adapter = HeraldAdapter::getAdapterForContentType($content_type); $form ->addHiddenInput('content_type', $content_type) ->addHiddenInput('rule_type', $rule_type) ->addHiddenInput('step', 3) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Rule for')) ->setValue( phutil_tag( 'strong', array(), idx($content_type_map, $content_type)))) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Rule Type')) ->setValue( phutil_tag( 'strong', array(), idx($rule_type_map, $rule_type)))) ->appendRemarkupInstructions( pht( 'Choose the object this rule will act on (for example, enter '. '`rX` to act on the `rX` repository, or `#project` to act on '. 'a project).')) ->appendRemarkupInstructions( $adapter->explainValidTriggerObjects()) ->appendChild( id(new AphrontFormTextControl()) ->setName('objectName') ->setError($e_object) ->setValue($request->getStr('objectName')) ->setLabel(pht('Object'))); $cancel_text = pht('Back'); $cancel_uri = id(new PhutilURI('new/')) ->setQueryParams( array( 'content_type' => $content_type, 'rule_type' => $rule_type, 'step' => 1, )); $cancel_uri = $this->getApplicationURI($cancel_uri); break; } $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Continue')) ->addCancelButton($cancel_uri, $cancel_text)); $form_box = id(new PHUIObjectBoxView()) ->setFormErrors($errors) ->setHeaderText(pht('Create Herald Rule')) ->setForm($form); $crumbs = $this ->buildApplicationCrumbs() ->addTextCrumb(pht('Create Rule')); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => pht('Create Herald Rule'), + $title = pht('Create Herald Rule'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $form_box, )); } private function renderContentTypeControl(array $content_type_map, $e_type) { $request = $this->getRequest(); $radio = id(new AphrontFormRadioButtonControl()) ->setLabel(pht('New Rule for')) ->setName('content_type') ->setValue($request->getStr('content_type')) ->setError($e_type); foreach ($content_type_map as $value => $name) { $adapter = HeraldAdapter::getAdapterForContentType($value); $radio->addButton( $value, $name, phutil_escape_html_newlines($adapter->getAdapterContentDescription())); } return $radio; } private function renderRuleTypeControl(array $rule_type_map, $e_rule) { $request = $this->getRequest(); // Reorder array to put less powerful rules first. $rule_type_map = array_select_keys( $rule_type_map, array( HeraldRuleTypeConfig::RULE_TYPE_PERSONAL, HeraldRuleTypeConfig::RULE_TYPE_OBJECT, HeraldRuleTypeConfig::RULE_TYPE_GLOBAL, )) + $rule_type_map; list($can_global, $global_link) = $this->explainApplicationCapability( HeraldManageGlobalRulesCapability::CAPABILITY, pht('You have permission to create and manage global rules.'), pht('You do not have permission to create or manage global rules.')); $captions = array( HeraldRuleTypeConfig::RULE_TYPE_PERSONAL => pht( 'Personal rules notify you about events. You own them, but they can '. 'only affect you. Personal rules only trigger for objects you have '. 'permission to see.'), HeraldRuleTypeConfig::RULE_TYPE_OBJECT => pht( 'Object rules notify anyone about events. They are bound to an '. 'object (like a repository) and can only act on that object. You '. 'must be able to edit an object to create object rules for it. '. 'Other users who can edit the object can edit its rules.'), HeraldRuleTypeConfig::RULE_TYPE_GLOBAL => array( pht( 'Global rules notify anyone about events. Global rules can '. 'bypass access control policies and act on any object.'), $global_link, ), ); $radio = id(new AphrontFormRadioButtonControl()) ->setLabel(pht('Rule Type')) ->setName('rule_type') ->setValue($request->getStr('rule_type')) ->setError($e_rule); $adapter = HeraldAdapter::getAdapterForContentType( $request->getStr('content_type')); foreach ($rule_type_map as $value => $name) { $caption = idx($captions, $value); $disabled = ($value == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) && (!$can_global); if (!$adapter->supportsRuleType($value)) { $disabled = true; $caption = array( $caption, "\n\n", phutil_tag( 'em', array(), pht( 'This rule type is not supported by the selected content type.')), ); } $radio->addButton( $value, $name, phutil_escape_html_newlines($caption), $disabled ? 'disabled' : null, $disabled); } return $radio; } } diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index 510113b0de..d8e5e42df3 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -1,692 +1,693 @@ getViewer(); $id = $request->getURIData('id'); $content_type_map = HeraldAdapter::getEnabledAdapterMap($viewer); $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); if ($id) { $rule = id(new HeraldRuleQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$rule) { return new Aphront404Response(); } $cancel_uri = '/'.$rule->getMonogram(); } else { $new_uri = $this->getApplicationURI('new/'); $rule = new HeraldRule(); $rule->setAuthorPHID($viewer->getPHID()); $rule->setMustMatchAll(1); $content_type = $request->getStr('content_type'); $rule->setContentType($content_type); $rule_type = $request->getStr('rule_type'); if (!isset($rule_type_map[$rule_type])) { return $this->newDialog() ->setTitle(pht('Invalid Rule Type')) ->appendParagraph( pht( 'The selected rule type ("%s") is not recognized by Herald.', $rule_type)) ->addCancelButton($new_uri); } $rule->setRuleType($rule_type); try { $adapter = HeraldAdapter::getAdapterForContentType( $rule->getContentType()); } catch (Exception $ex) { return $this->newDialog() ->setTitle(pht('Invalid Content Type')) ->appendParagraph( pht( 'The selected content type ("%s") is not recognized by '. 'Herald.', $rule->getContentType())) ->addCancelButton($new_uri); } if (!$adapter->supportsRuleType($rule->getRuleType())) { return $this->newDialog() ->setTitle(pht('Rule/Content Mismatch')) ->appendParagraph( pht( 'The selected rule type ("%s") is not supported by the selected '. 'content type ("%s").', $rule->getRuleType(), $rule->getContentType())) ->addCancelButton($new_uri); } if ($rule->isObjectRule()) { $rule->setTriggerObjectPHID($request->getStr('targetPHID')); $object = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withPHIDs(array($rule->getTriggerObjectPHID())) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$object) { throw new Exception( pht('No valid object provided for object rule!')); } if (!$adapter->canTriggerOnObject($object)) { throw new Exception( pht('Object is of wrong type for adapter!')); } } $cancel_uri = $this->getApplicationURI(); } if ($rule->isGlobalRule()) { $this->requireApplicationCapability( HeraldManageGlobalRulesCapability::CAPABILITY); } $adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType()); $local_version = id(new HeraldRule())->getConfigVersion(); if ($rule->getConfigVersion() > $local_version) { throw new Exception( pht( 'This rule was created with a newer version of Herald. You can not '. 'view or edit it in this older version. Upgrade your Phabricator '. 'deployment.')); } // Upgrade rule version to our version, since we might add newly-defined // conditions, etc. $rule->setConfigVersion($local_version); $rule_conditions = $rule->loadConditions(); $rule_actions = $rule->loadActions(); $rule->attachConditions($rule_conditions); $rule->attachActions($rule_actions); $e_name = true; $errors = array(); if ($request->isFormPost() && $request->getStr('save')) { list($e_name, $errors) = $this->saveRule($adapter, $rule, $request); if (!$errors) { $id = $rule->getID(); $uri = '/'.$rule->getMonogram(); return id(new AphrontRedirectResponse())->setURI($uri); } } $must_match_selector = $this->renderMustMatchSelector($rule); $repetition_selector = $this->renderRepetitionSelector($rule, $adapter); $handles = $this->loadHandlesForRule($rule); require_celerity_resource('herald-css'); $content_type_name = $content_type_map[$rule->getContentType()]; $rule_type_name = $rule_type_map[$rule->getRuleType()]; $form = id(new AphrontFormView()) ->setUser($viewer) ->setID('herald-rule-edit-form') ->addHiddenInput('content_type', $rule->getContentType()) ->addHiddenInput('rule_type', $rule->getRuleType()) ->addHiddenInput('save', 1) ->appendChild( // Build this explicitly (instead of using addHiddenInput()) // so we can add a sigil to it. javelin_tag( 'input', array( 'type' => 'hidden', 'name' => 'rule', 'sigil' => 'rule', ))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Rule Name')) ->setName('name') ->setError($e_name) ->setValue($rule->getName())); $trigger_object_control = false; if ($rule->isObjectRule()) { $trigger_object_control = id(new AphrontFormStaticControl()) ->setValue( pht( 'This rule triggers for %s.', $handles[$rule->getTriggerObjectPHID()]->renderLink())); } $form ->appendChild( id(new AphrontFormMarkupControl()) ->setValue(pht( 'This %s rule triggers for %s.', phutil_tag('strong', array(), $rule_type_name), phutil_tag('strong', array(), $content_type_name)))) ->appendChild($trigger_object_control) ->appendChild( id(new PHUIFormInsetView()) ->setTitle(pht('Conditions')) ->setRightButton(javelin_tag( 'a', array( 'href' => '#', 'class' => 'button green', 'sigil' => 'create-condition', 'mustcapture' => true, ), pht('New Condition'))) ->setDescription( pht('When %s these conditions are met:', $must_match_selector)) ->setContent(javelin_tag( 'table', array( 'sigil' => 'rule-conditions', 'class' => 'herald-condition-table', ), ''))) ->appendChild( id(new PHUIFormInsetView()) ->setTitle(pht('Action')) ->setRightButton(javelin_tag( 'a', array( 'href' => '#', 'class' => 'button green', 'sigil' => 'create-action', 'mustcapture' => true, ), pht('New Action'))) ->setDescription(pht( 'Take these actions %s this rule matches:', $repetition_selector)) ->setContent(javelin_tag( 'table', array( 'sigil' => 'rule-actions', 'class' => 'herald-action-table', ), ''))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save Rule')) ->addCancelButton($cancel_uri)); $this->setupEditorBehavior($rule, $handles, $adapter); $title = $rule->getID() ? pht('Edit Herald Rule') : pht('Create Herald Rule'); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormErrors($errors) ->setForm($form); $crumbs = $this ->buildApplicationCrumbs() ->addTextCrumb($title); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => pht('Edit Rule'), + $title = pht('Edit Rule'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $form_box, )); } private function saveRule(HeraldAdapter $adapter, $rule, $request) { $rule->setName($request->getStr('name')); $match_all = ($request->getStr('must_match') == 'all'); $rule->setMustMatchAll((int)$match_all); $repetition_policy_param = $request->getStr('repetition_policy'); $rule->setRepetitionPolicy( HeraldRepetitionPolicyConfig::toInt($repetition_policy_param)); $e_name = true; $errors = array(); if (!strlen($rule->getName())) { $e_name = pht('Required'); $errors[] = pht('Rule must have a name.'); } $data = null; try { $data = phutil_json_decode($request->getStr('rule')); } catch (PhutilJSONParserException $ex) { throw new PhutilProxyException( pht('Failed to decode rule data.'), $ex); } if (!is_array($data) || !$data['conditions'] || !$data['actions']) { throw new Exception(pht('Failed to decode rule data.')); } $conditions = array(); foreach ($data['conditions'] as $condition) { if ($condition === null) { // We manage this as a sparse array on the client, so may receive // NULL if conditions have been removed. continue; } $obj = new HeraldCondition(); $obj->setFieldName($condition[0]); $obj->setFieldCondition($condition[1]); if (is_array($condition[2])) { $obj->setValue(array_keys($condition[2])); } else { $obj->setValue($condition[2]); } try { $adapter->willSaveCondition($obj); } catch (HeraldInvalidConditionException $ex) { $errors[] = $ex->getMessage(); } $conditions[] = $obj; } $actions = array(); foreach ($data['actions'] as $action) { if ($action === null) { // Sparse on the client; removals can give us NULLs. continue; } if (!isset($action[1])) { // Legitimate for any action which doesn't need a target, like // "Do nothing". $action[1] = null; } $obj = new HeraldActionRecord(); $obj->setAction($action[0]); $obj->setTarget($action[1]); try { $adapter->willSaveAction($rule, $obj); } catch (HeraldInvalidActionException $ex) { $errors[] = $ex->getMessage(); } $actions[] = $obj; } $rule->attachConditions($conditions); $rule->attachActions($actions); if (!$errors) { $edit_action = $rule->getID() ? 'edit' : 'create'; $rule->openTransaction(); $rule->save(); $rule->saveConditions($conditions); $rule->saveActions($actions); $rule->saveTransaction(); } return array($e_name, $errors); } private function setupEditorBehavior( HeraldRule $rule, array $handles, HeraldAdapter $adapter) { $serial_conditions = array( array('default', 'default', ''), ); if ($rule->getConditions()) { $serial_conditions = array(); foreach ($rule->getConditions() as $condition) { $value = $adapter->getEditorValueForCondition( $this->getViewer(), $condition); $serial_conditions[] = array( $condition->getFieldName(), $condition->getFieldCondition(), $value, ); } } $serial_actions = array( array('default', ''), ); if ($rule->getActions()) { $serial_actions = array(); foreach ($rule->getActions() as $action) { $value = $adapter->getEditorValueForAction( $this->getViewer(), $action); $serial_actions[] = array( $action->getAction(), $value, ); } } $all_rules = $this->loadRulesThisRuleMayDependUpon($rule); $all_rules = mpull($all_rules, 'getName', 'getPHID'); asort($all_rules); $all_fields = $adapter->getFieldNameMap(); $all_conditions = $adapter->getConditionNameMap(); $all_actions = $adapter->getActionNameMap($rule->getRuleType()); $fields = $adapter->getFields(); $field_map = array_select_keys($all_fields, $fields); // Populate any fields which exist in the rule but which we don't know the // names of, so that saving a rule without touching anything doesn't change // it. foreach ($rule->getConditions() as $condition) { $field_name = $condition->getFieldName(); if (empty($field_map[$field_name])) { $field_map[$field_name] = pht('', $field_name); } } $actions = $adapter->getActions($rule->getRuleType()); $action_map = array_select_keys($all_actions, $actions); // Populate any actions which exist in the rule but which we don't know the // names of, so that saving a rule without touching anything doesn't change // it. foreach ($rule->getActions() as $action) { $action_name = $action->getAction(); if (empty($action_map[$action_name])) { $action_map[$action_name] = pht('', $action_name); } } $config_info = array(); $config_info['fields'] = $this->getFieldGroups($adapter, $field_map); $config_info['conditions'] = $all_conditions; $config_info['actions'] = $this->getActionGroups($adapter, $action_map); $config_info['valueMap'] = array(); foreach ($field_map as $field => $name) { try { $field_conditions = $adapter->getConditionsForField($field); } catch (Exception $ex) { $field_conditions = array(HeraldAdapter::CONDITION_UNCONDITIONALLY); } $config_info['conditionMap'][$field] = $field_conditions; } foreach ($field_map as $field => $fname) { foreach ($config_info['conditionMap'][$field] as $condition) { $value_key = $adapter->getValueTypeForFieldAndCondition( $field, $condition); if ($value_key instanceof HeraldFieldValue) { $value_key->setViewer($this->getViewer()); $spec = $value_key->getControlSpecificationDictionary(); $value_key = $value_key->getFieldValueKey(); $config_info['valueMap'][$value_key] = $spec; } $config_info['values'][$field][$condition] = $value_key; } } $config_info['rule_type'] = $rule->getRuleType(); foreach ($action_map as $action => $name) { try { $value_key = $adapter->getValueTypeForAction( $action, $rule->getRuleType()); } catch (Exception $ex) { $value_key = new HeraldEmptyFieldValue(); } if ($value_key instanceof HeraldFieldValue) { $value_key->setViewer($this->getViewer()); $spec = $value_key->getControlSpecificationDictionary(); $value_key = $value_key->getFieldValueKey(); $config_info['valueMap'][$value_key] = $spec; } $config_info['targets'][$action] = $value_key; } Javelin::initBehavior( 'herald-rule-editor', array( 'root' => 'herald-rule-edit-form', 'conditions' => (object)$serial_conditions, 'actions' => (object)$serial_actions, 'template' => $this->buildTokenizerTemplates() + array( 'rules' => $all_rules, ), 'info' => $config_info, )); } private function loadHandlesForRule($rule) { $phids = array(); foreach ($rule->getActions() as $action) { if (!is_array($action->getTarget())) { continue; } foreach ($action->getTarget() as $target) { $target = (array)$target; foreach ($target as $phid) { $phids[] = $phid; } } } foreach ($rule->getConditions() as $condition) { $value = $condition->getValue(); if (is_array($value)) { foreach ($value as $phid) { $phids[] = $phid; } } } $phids[] = $rule->getAuthorPHID(); if ($rule->isObjectRule()) { $phids[] = $rule->getTriggerObjectPHID(); } return $this->loadViewerHandles($phids); } /** * Render the selector for the "When (all of | any of) these conditions are * met:" element. */ private function renderMustMatchSelector($rule) { return AphrontFormSelectControl::renderSelectTag( $rule->getMustMatchAll() ? 'all' : 'any', array( 'all' => pht('all of'), 'any' => pht('any of'), ), array( 'name' => 'must_match', )); } /** * Render the selector for "Take these actions (every time | only the first * time) this rule matches..." element. */ private function renderRepetitionSelector($rule, HeraldAdapter $adapter) { $repetition_policy = HeraldRepetitionPolicyConfig::toString( $rule->getRepetitionPolicy()); $repetition_options = $adapter->getRepetitionOptions(); $repetition_names = HeraldRepetitionPolicyConfig::getMap(); $repetition_map = array_select_keys($repetition_names, $repetition_options); if (count($repetition_map) < 2) { return head($repetition_names); } else { return AphrontFormSelectControl::renderSelectTag( $repetition_policy, $repetition_map, array( 'name' => 'repetition_policy', )); } } protected function buildTokenizerTemplates() { $template = new AphrontTokenizerTemplateView(); $template = $template->render(); return array( 'markup' => $template, ); } /** * Load rules for the "Another Herald rule..." condition dropdown, which * allows one rule to depend upon the success or failure of another rule. */ private function loadRulesThisRuleMayDependUpon(HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); // Any rule can depend on a global rule. $all_rules = id(new HeraldRuleQuery()) ->setViewer($viewer) ->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_GLOBAL)) ->withContentTypes(array($rule->getContentType())) ->execute(); if ($rule->isObjectRule()) { // Object rules may depend on other rules for the same object. $all_rules += id(new HeraldRuleQuery()) ->setViewer($viewer) ->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_OBJECT)) ->withContentTypes(array($rule->getContentType())) ->withTriggerObjectPHIDs(array($rule->getTriggerObjectPHID())) ->execute(); } if ($rule->isPersonalRule()) { // Personal rules may depend upon your other personal rules. $all_rules += id(new HeraldRuleQuery()) ->setViewer($viewer) ->withRuleTypes(array(HeraldRuleTypeConfig::RULE_TYPE_PERSONAL)) ->withContentTypes(array($rule->getContentType())) ->withAuthorPHIDs(array($rule->getAuthorPHID())) ->execute(); } // mark disabled rules as disabled since they are not useful as such; // don't filter though to keep edit cases sane / expected foreach ($all_rules as $current_rule) { if ($current_rule->getIsDisabled()) { $current_rule->makeEphemeral(); $current_rule->setName($rule->getName().' '.pht('(Disabled)')); } } // A rule can not depend upon itself. unset($all_rules[$rule->getID()]); return $all_rules; } private function getFieldGroups(HeraldAdapter $adapter, array $field_map) { $group_map = array(); foreach ($field_map as $field_key => $field_name) { $group_key = $adapter->getFieldGroupKey($field_key); $group_map[$group_key][$field_key] = $field_name; } return $this->getGroups( $group_map, HeraldFieldGroup::getAllFieldGroups()); } private function getActionGroups(HeraldAdapter $adapter, array $action_map) { $group_map = array(); foreach ($action_map as $action_key => $action_name) { $group_key = $adapter->getActionGroupKey($action_key); $group_map[$group_key][$action_key] = $action_name; } return $this->getGroups( $group_map, HeraldActionGroup::getAllActionGroups()); } private function getGroups(array $item_map, array $group_list) { assert_instances_of($group_list, 'HeraldGroup'); $groups = array(); foreach ($item_map as $group_key => $options) { asort($options); $group_object = idx($group_list, $group_key); if ($group_object) { $group_label = $group_object->getGroupLabel(); $group_order = $group_object->getSortKey(); } else { $group_label = nonempty($group_key, pht('Other')); $group_order = 'Z'; } $groups[] = array( 'label' => $group_label, 'options' => $options, 'order' => $group_order, ); } return array_values(isort($groups, 'order')); } } diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index 2063ebcc2f..c8e0442dc5 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -1,156 +1,157 @@ getViewer(); $id = $request->getURIData('id'); $rule = id(new HeraldRuleQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needConditionsAndActions(true) ->executeOne(); if (!$rule) { return new Aphront404Response(); } $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($rule->getName()) ->setPolicyObject($rule); if ($rule->getIsDisabled()) { $header->setStatus( 'fa-ban', 'red', pht('Archived')); } else { $header->setStatus( 'fa-check', 'bluegrey', pht('Active')); } $actions = $this->buildActionView($rule); $properties = $this->buildPropertyView($rule, $actions); $id = $rule->getID(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb("H{$id}"); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); $timeline = $this->buildTransactionTimeline( $rule, new HeraldTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $timeline, - ), - array( - 'title' => $rule->getName(), + $title = $rule->getName(); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $object_box, + $timeline, )); } private function buildActionView(HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); $id = $rule->getID(); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($rule) ->setObjectURI('/'.$rule->getMonogram()); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $rule, PhabricatorPolicyCapability::CAN_EDIT); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Rule')) ->setHref($this->getApplicationURI("edit/{$id}/")) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); if ($rule->getIsDisabled()) { $disable_uri = "disable/{$id}/enable/"; $disable_icon = 'fa-check'; $disable_name = pht('Activate Rule'); } else { $disable_uri = "disable/{$id}/disable/"; $disable_icon = 'fa-ban'; $disable_name = pht('Archive Rule'); } $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Disable Rule')) ->setHref($this->getApplicationURI($disable_uri)) ->setIcon($disable_icon) ->setName($disable_name) ->setDisabled(!$can_edit) ->setWorkflow(true)); return $view; } private function buildPropertyView( HeraldRule $rule, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($rule) ->setActionList($actions); $view->addProperty( pht('Rule Type'), idx(HeraldRuleTypeConfig::getRuleTypeMap(), $rule->getRuleType())); if ($rule->isPersonalRule()) { $view->addProperty( pht('Author'), $viewer->renderHandle($rule->getAuthorPHID())); } $adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType()); if ($adapter) { $view->addProperty( pht('Applies To'), idx( HeraldAdapter::getEnabledAdapterMap($viewer), $rule->getContentType())); if ($rule->isObjectRule()) { $view->addProperty( pht('Trigger Object'), $viewer->renderHandle($rule->getTriggerObjectPHID())); } $view->invokeWillRenderEvent(); $view->addSectionHeader( pht('Rule Description'), PHUIPropertyListView::ICON_SUMMARY); $handles = $viewer->loadHandles(HeraldAdapter::getHandlePHIDs($rule)); $rule_text = $adapter->renderRuleAsText($rule, $handles, $viewer); $view->addTextContent($rule_text); } return $view; } } diff --git a/src/applications/herald/controller/HeraldTestConsoleController.php b/src/applications/herald/controller/HeraldTestConsoleController.php index 64b7c5867b..1e12825291 100644 --- a/src/applications/herald/controller/HeraldTestConsoleController.php +++ b/src/applications/herald/controller/HeraldTestConsoleController.php @@ -1,120 +1,120 @@ getViewer(); $object_name = trim($request->getStr('object_name')); $e_name = true; $errors = array(); if ($request->isFormPost()) { if (!$object_name) { $e_name = pht('Required'); $errors[] = pht('An object name is required.'); } if (!$errors) { $object = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withNames(array($object_name)) ->executeOne(); if (!$object) { $e_name = pht('Invalid'); $errors[] = pht('No object exists with that name.'); } if (!$errors) { // TODO: Let the adapters claim objects instead. if ($object instanceof DifferentialRevision) { $adapter = HeraldDifferentialRevisionAdapter::newLegacyAdapter( $object, $object->loadActiveDiff()); } else if ($object instanceof PhabricatorRepositoryCommit) { $adapter = id(new HeraldCommitAdapter()) ->setCommit($object); } else if ($object instanceof ManiphestTask) { $adapter = id(new HeraldManiphestTaskAdapter()) ->setTask($object); } else if ($object instanceof PholioMock) { $adapter = id(new HeraldPholioMockAdapter()) ->setMock($object); } else if ($object instanceof PhrictionDocument) { $adapter = id(new PhrictionDocumentHeraldAdapter()) ->setDocument($object); } else if ($object instanceof PonderQuestion) { $adapter = id(new HeraldPonderQuestionAdapter()) ->setQuestion($object); } else if ($object instanceof PhabricatorMetaMTAMail) { $adapter = id(new PhabricatorMailOutboundMailHeraldAdapter()) ->setObject($object); } else { throw new Exception(pht('Can not build adapter for object!')); } $adapter->setIsNewObject(false); $rules = id(new HeraldRuleQuery()) ->setViewer($viewer) ->withContentTypes(array($adapter->getAdapterContentType())) ->withDisabled(false) ->needConditionsAndActions(true) ->needAppliedToPHIDs(array($object->getPHID())) ->needValidateAuthors(true) ->execute(); $engine = id(new HeraldEngine()) ->setDryRun(true); $effects = $engine->applyRules($rules, $adapter); $engine->applyEffects($effects, $adapter, $rules); $xscript = $engine->getTranscript(); return id(new AphrontRedirectResponse()) ->setURI('/herald/transcript/'.$xscript->getID().'/'); } } } $form = id(new AphrontFormView()) ->setUser($viewer) ->appendRemarkupInstructions( pht( 'Enter an object to test rules for, like a Diffusion commit (e.g., '. '`rX123`) or a Differential revision (e.g., `D123`). You will be '. 'shown the results of a dry run on the object.')) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Object Name')) ->setName('object_name') ->setError($e_name) ->setValue($object_name)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Test Rules'))); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Herald Test Console')) ->setFormErrors($errors) ->setForm($form); - $nav = $this->buildSideNavView(); - $nav->selectFilter('test'); - $nav->appendChild($box); - $crumbs = id($this->buildApplicationCrumbs()) ->addTextCrumb(pht('Test Console')); - $nav->setCrumbs($crumbs); - return $this->buildApplicationPage( - $nav, - array( - 'title' => pht('Test Console'), + $title = pht('Test Console'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $box, )); + } } diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index 3e4b0b074c..afb7b36558 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -1,477 +1,478 @@ adapter; } public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $xscript = id(new HeraldTranscriptQuery()) ->setViewer($viewer) ->withIDs(array($request->getURIData('id'))) ->executeOne(); if (!$xscript) { return new Aphront404Response(); } require_celerity_resource('herald-test-css'); $content = array(); $object_xscript = $xscript->getObjectTranscript(); if (!$object_xscript) { $notice = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setTitle(pht('Old Transcript')) ->appendChild(phutil_tag( 'p', array(), pht('Details of this transcript have been garbage collected.'))); $content[] = $notice; } else { $map = HeraldAdapter::getEnabledAdapterMap($viewer); $object_type = $object_xscript->getType(); if (empty($map[$object_type])) { // TODO: We should filter these out in the Query, but we have to load // the objectTranscript right now, which is potentially enormous. We // should denormalize the object type, or move the data into a separate // table, and then filter this earlier (and thus raise a better error). // For now, just block access so we don't violate policies. throw new Exception( pht('This transcript has an invalid or inaccessible adapter.')); } $this->adapter = HeraldAdapter::getAdapterForContentType($object_type); $phids = $this->getTranscriptPHIDs($xscript); $phids = array_unique($phids); $phids = array_filter($phids); $handles = $this->loadViewerHandles($phids); $this->handles = $handles; if ($xscript->getDryRun()) { $notice = new PHUIInfoView(); $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); $notice->setTitle(pht('Dry Run')); $notice->appendChild( pht( 'This was a dry run to test Herald rules, '. 'no actions were executed.')); $content[] = $notice; } $warning_panel = $this->buildWarningPanel($xscript); $content[] = $warning_panel; $content[] = array( $this->buildActionTranscriptPanel($xscript), $this->buildObjectTranscriptPanel($xscript), ); } $crumbs = id($this->buildApplicationCrumbs()) ->addTextCrumb( pht('Transcripts'), $this->getApplicationURI('/transcript/')) ->addTextCrumb($xscript->getID()); - return $this->buildApplicationPage( - array( - $crumbs, - $content, - ), - array( - 'title' => pht('Transcript'), + $title = pht('Transcript'); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $content, )); } protected function renderConditionTestValue($condition, $handles) { // TODO: This is all a hacky mess and should be driven through FieldValue // eventually. switch ($condition->getFieldName()) { case HeraldAnotherRuleField::FIELDCONST: $value = array($condition->getTestValue()); break; default: $value = $condition->getTestValue(); break; } if (!is_scalar($value) && $value !== null) { foreach ($value as $key => $phid) { $handle = idx($handles, $phid); if ($handle && $handle->isComplete()) { $value[$key] = $handle->getName(); } else { // This happens for things like task priorities, statuses, and // custom fields. $value[$key] = $phid; } } sort($value); $value = implode(', ', $value); } return phutil_tag('span', array('class' => 'condition-test-value'), $value); } protected function getTranscriptPHIDs($xscript) { $phids = array(); $object_xscript = $xscript->getObjectTranscript(); if (!$object_xscript) { return array(); } $phids[] = $object_xscript->getPHID(); foreach ($xscript->getApplyTranscripts() as $apply_xscript) { // TODO: This is total hacks. Add another amazing layer of abstraction. $target = (array)$apply_xscript->getTarget(); foreach ($target as $phid) { if ($phid) { $phids[] = $phid; } } } foreach ($xscript->getRuleTranscripts() as $rule_xscript) { $phids[] = $rule_xscript->getRuleOwner(); } $condition_xscripts = $xscript->getConditionTranscripts(); if ($condition_xscripts) { $condition_xscripts = call_user_func_array( 'array_merge', $condition_xscripts); } foreach ($condition_xscripts as $condition_xscript) { switch ($condition_xscript->getFieldName()) { case HeraldAnotherRuleField::FIELDCONST: $phids[] = $condition_xscript->getTestValue(); break; default: $value = $condition_xscript->getTestValue(); // TODO: Also total hacks. if (is_array($value)) { foreach ($value as $phid) { if ($phid) { // TODO: Probably need to make sure this // "looks like" a PHID or decrease the level of hacks here; // this used to be an is_numeric() check in Facebook land. $phids[] = $phid; } } } break; } } return $phids; } private function buildWarningPanel(HeraldTranscript $xscript) { $request = $this->getRequest(); $panel = null; if ($xscript->getObjectTranscript()) { $handles = $this->handles; $object_xscript = $xscript->getObjectTranscript(); $handle = $handles[$object_xscript->getPHID()]; if ($handle->getType() == PhabricatorRepositoryCommitPHIDType::TYPECONST) { $commit = id(new DiffusionCommitQuery()) ->setViewer($request->getUser()) ->withPHIDs(array($handle->getPHID())) ->executeOne(); if ($commit) { $repository = $commit->getRepository(); if ($repository->isImporting()) { $title = pht( 'The %s repository is still importing.', $repository->getMonogram()); $body = pht( 'Herald rules will not trigger until import completes.'); } else if (!$repository->isTracked()) { $title = pht( 'The %s repository is not tracked.', $repository->getMonogram()); $body = pht( 'Herald rules will not trigger until tracking is enabled.'); } else { return $panel; } $panel = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setTitle($title) ->appendChild($body); } } } return $panel; } private function buildActionTranscriptPanel(HeraldTranscript $xscript) { $action_xscript = mgroup($xscript->getApplyTranscripts(), 'getRuleID'); $adapter = $this->getAdapter(); $field_names = $adapter->getFieldNameMap(); $condition_names = $adapter->getConditionNameMap(); $handles = $this->handles; $action_map = $xscript->getApplyTranscripts(); $action_map = mgroup($action_map, 'getRuleID'); $rule_list = id(new PHUIObjectItemListView()) ->setNoDataString(pht('No Herald rules applied to this object.')); $rule_xscripts = $xscript->getRuleTranscripts(); $rule_xscripts = msort($rule_xscripts, 'getRuleID'); foreach ($rule_xscripts as $rule_xscript) { $rule_id = $rule_xscript->getRuleID(); $rule_item = id(new PHUIObjectItemView()) ->setObjectName(pht('H%d', $rule_id)) ->setHeader($rule_xscript->getRuleName()); if (!$rule_xscript->getResult()) { $rule_item->setDisabled(true); } $rule_list->addItem($rule_item); // Build the field/condition transcript. $cond_xscripts = $xscript->getConditionTranscriptsForRule($rule_id); $cond_list = id(new PHUIStatusListView()); $cond_list->addItem( id(new PHUIStatusItemView()) ->setTarget(phutil_tag('strong', array(), pht('Conditions')))); foreach ($cond_xscripts as $cond_xscript) { if ($cond_xscript->getResult()) { $icon = 'fa-check'; $color = 'green'; $result = pht('Passed'); } else { $icon = 'fa-times'; $color = 'red'; $result = pht('Failed'); } if ($cond_xscript->getNote()) { $note = phutil_tag( 'div', array( 'class' => 'herald-condition-note', ), $cond_xscript->getNote()); } else { $note = null; } // TODO: This is not really translatable and should be driven through // HeraldField. $explanation = pht( '%s %s %s', idx($field_names, $cond_xscript->getFieldName(), pht('Unknown')), idx($condition_names, $cond_xscript->getCondition(), pht('Unknown')), $this->renderConditionTestValue($cond_xscript, $handles)); $cond_item = id(new PHUIStatusItemView()) ->setIcon($icon, $color) ->setTarget($result) ->setNote(array($explanation, $note)); $cond_list->addItem($cond_item); } if ($rule_xscript->getResult()) { $last_icon = 'fa-check-circle'; $last_color = 'green'; $last_result = pht('Passed'); $last_note = pht('Rule passed.'); } else { $last_icon = 'fa-times-circle'; $last_color = 'red'; $last_result = pht('Failed'); $last_note = pht('Rule failed.'); } $cond_last = id(new PHUIStatusItemView()) ->setIcon($last_icon, $last_color) ->setTarget(phutil_tag('strong', array(), $last_result)) ->setNote($last_note); $cond_list->addItem($cond_last); $cond_box = id(new PHUIBoxView()) ->appendChild($cond_list) ->addMargin(PHUI::MARGIN_LARGE_LEFT); $rule_item->appendChild($cond_box); if (!$rule_xscript->getResult()) { // If the rule didn't pass, don't generate an action transcript since // actions didn't apply. continue; } $cond_box->addMargin(PHUI::MARGIN_MEDIUM_BOTTOM); $action_xscripts = idx($action_map, $rule_id, array()); foreach ($action_xscripts as $action_xscript) { $action_key = $action_xscript->getAction(); $action = $adapter->getActionImplementation($action_key); if ($action) { $name = $action->getHeraldActionName(); $action->setViewer($this->getViewer()); } else { $name = pht('Unknown Action ("%s")', $action_key); } $name = pht('Action: %s', $name); $action_list = id(new PHUIStatusListView()); $action_list->addItem( id(new PHUIStatusItemView()) ->setTarget(phutil_tag('strong', array(), $name))); $action_box = id(new PHUIBoxView()) ->appendChild($action_list) ->addMargin(PHUI::MARGIN_LARGE_LEFT); $rule_item->appendChild($action_box); $log = $action_xscript->getAppliedReason(); // Handle older transcripts which used a static string to record // action results. if ($xscript->getDryRun()) { $action_list->addItem( id(new PHUIStatusItemView()) ->setIcon('fa-ban', 'grey') ->setTarget(pht('Dry Run')) ->setNote( pht( 'This was a dry run, so no actions were taken.'))); continue; } else if (!is_array($log)) { $action_list->addItem( id(new PHUIStatusItemView()) ->setIcon('fa-clock-o', 'grey') ->setTarget(pht('Old Transcript')) ->setNote( pht( 'This is an old transcript which uses an obsolete log '. 'format. Detailed action information is not available.'))); continue; } foreach ($log as $entry) { $type = idx($entry, 'type'); $data = idx($entry, 'data'); if ($action) { $icon = $action->renderActionEffectIcon($type, $data); $color = $action->renderActionEffectColor($type, $data); $name = $action->renderActionEffectName($type, $data); $note = $action->renderEffectDescription($type, $data); } else { $icon = 'fa-question-circle'; $color = 'indigo'; $name = pht('Unknown Effect ("%s")', $type); $note = null; } $action_item = id(new PHUIStatusItemView()) ->setIcon($icon, $color) ->setTarget($name) ->setNote($note); $action_list->addItem($action_item); } } } $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Rule Transcript')) ->appendChild($rule_list); return $box; } private function buildObjectTranscriptPanel(HeraldTranscript $xscript) { $adapter = $this->getAdapter(); $field_names = $adapter->getFieldNameMap(); $object_xscript = $xscript->getObjectTranscript(); $data = array(); if ($object_xscript) { $phid = $object_xscript->getPHID(); $handles = $this->handles; $data += array( pht('Object Name') => $object_xscript->getName(), pht('Object Type') => $object_xscript->getType(), pht('Object PHID') => $phid, pht('Object Link') => $handles[$phid]->renderLink(), ); } $data += $xscript->getMetadataMap(); if ($object_xscript) { foreach ($object_xscript->getFields() as $field => $value) { $field = idx($field_names, $field, '['.$field.'?]'); $data['Field: '.$field] = $value; } } $rows = array(); foreach ($data as $name => $value) { if (!($value instanceof PhutilSafeHTML)) { if (!is_scalar($value) && !is_null($value)) { $value = implode("\n", $value); } if (strlen($value) > 256) { $value = phutil_tag( 'textarea', array( 'class' => 'herald-field-value-transcript', ), $value); } } $rows[] = array($name, $value); } $property_list = new PHUIPropertyListView(); $property_list->setStacked(true); foreach ($rows as $row) { $property_list->addProperty($row[0], $row[1]); } $box = new PHUIObjectBoxView(); $box->setHeaderText(pht('Object Transcript')); $box->appendChild($property_list); return $box; } } diff --git a/src/applications/herald/controller/HeraldTranscriptListController.php b/src/applications/herald/controller/HeraldTranscriptListController.php index 81865eee4a..c1f2b3ea5d 100644 --- a/src/applications/herald/controller/HeraldTranscriptListController.php +++ b/src/applications/herald/controller/HeraldTranscriptListController.php @@ -1,44 +1,35 @@ getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - if ($for_app) { - $nav->addFilter('new', pht('Create Rule')); - } - id(new HeraldTranscriptSearchEngine()) ->setViewer($user) ->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); return $nav; } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $crumbs->addTextCrumb( pht('Transcripts'), $this->getApplicationURI('transcript/')); return $crumbs; } public function handleRequest(AphrontRequest $request) { - $querykey = $request->getURIData('queryKey'); - - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine(new HeraldTranscriptSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); + return id(new HeraldTranscriptSearchEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/herald/query/HeraldTranscriptSearchEngine.php b/src/applications/herald/query/HeraldTranscriptSearchEngine.php index c3e4b8d8e3..cc0d620394 100644 --- a/src/applications/herald/query/HeraldTranscriptSearchEngine.php +++ b/src/applications/herald/query/HeraldTranscriptSearchEngine.php @@ -1,143 +1,143 @@ getStrList('objectMonograms'); $saved->setParameter('objectMonograms', $object_monograms); $ids = $request->getStrList('ids'); foreach ($ids as $key => $id) { if (!$id || !is_numeric($id)) { unset($ids[$key]); } else { $ids[$key] = $id; } } $saved->setParameter('ids', $ids); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new HeraldTranscriptQuery()); $object_monograms = $saved->getParameter('objectMonograms'); if ($object_monograms) { $objects = id(new PhabricatorObjectQuery()) ->setViewer($this->requireViewer()) ->withNames($object_monograms) ->execute(); $query->withObjectPHIDs(mpull($objects, 'getPHID')); } $ids = $saved->getParameter('ids'); if ($ids) { $query->withIDs($ids); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved) { $object_monograms = $saved->getParameter('objectMonograms', array()); $ids = $saved->getParameter('ids', array()); $form ->appendChild( id(new AphrontFormTextControl()) ->setName('objectMonograms') ->setLabel(pht('Object Monograms')) ->setValue(implode(', ', $object_monograms))) ->appendChild( id(new AphrontFormTextControl()) ->setName('ids') ->setLabel(pht('Transcript IDs')) ->setValue(implode(', ', $ids))); } protected function getURI($path) { return '/herald/transcript/'.$path; } protected function getBuiltinQueryNames() { return array( - 'all' => pht('All'), + 'all' => pht('All Transcripts'), ); } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer_phid = $this->requireViewer()->getPHID(); switch ($query_key) { case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } protected function getRequiredHandlePHIDsForResultList( array $transcripts, PhabricatorSavedQuery $query) { return mpull($transcripts, 'getObjectPHID'); } protected function renderResultList( array $transcripts, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($transcripts, 'HeraldTranscript'); $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); foreach ($transcripts as $xscript) { $view_href = phutil_tag( 'a', array( 'href' => '/herald/transcript/'.$xscript->getID().'/', ), pht('View Full Transcript')); $item = new PHUIObjectItemView(); $item->setObjectName($xscript->getID()); $item->setHeader($view_href); if ($xscript->getDryRun()) { $item->addAttribute(pht('Dry Run')); } $item->addAttribute($handles[$xscript->getObjectPHID()]->renderLink()); $item->addAttribute( pht('%s ms', new PhutilNumber((int)(1000 * $xscript->getDuration())))); $item->addIcon( 'none', phabricator_datetime($xscript->getTime(), $viewer)); $list->addItem($item); } $result = new PhabricatorApplicationSearchResultView(); $result->setObjectList($list); $result->setNoDataString(pht('No transcripts found.')); return $result; } } diff --git a/src/applications/home/controller/PhabricatorHomeMainController.php b/src/applications/home/controller/PhabricatorHomeMainController.php index 330aecfca1..63ce763b69 100644 --- a/src/applications/home/controller/PhabricatorHomeMainController.php +++ b/src/applications/home/controller/PhabricatorHomeMainController.php @@ -1,422 +1,422 @@ getUser(); $dashboard = PhabricatorDashboardInstall::getDashboard( $user, $user->getPHID(), get_class($this->getCurrentApplication())); if (!$dashboard) { $dashboard = PhabricatorDashboardInstall::getDashboard( $user, PhabricatorHomeApplication::DASHBOARD_DEFAULT, get_class($this->getCurrentApplication())); } if ($dashboard) { $content = id(new PhabricatorDashboardRenderingEngine()) ->setViewer($user) ->setDashboard($dashboard) ->renderDashboard(); } else { $project_query = new PhabricatorProjectQuery(); $project_query->setViewer($user); $project_query->withMemberPHIDs(array($user->getPHID())); $projects = $project_query->execute(); $content = $this->buildMainResponse($projects); } if (!$request->getURIData('only')) { $nav = $this->buildNav(); $nav->appendChild( array( $content, id(new PhabricatorGlobalUploadTargetView())->setUser($user), )); $content = $nav; } return $this->buildApplicationPage( $content, array( 'title' => 'Phabricator', )); } private function buildMainResponse(array $projects) { assert_instances_of($projects, 'PhabricatorProject'); $viewer = $this->getRequest()->getUser(); $has_maniphest = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorManiphestApplication', $viewer); $has_audit = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorAuditApplication', $viewer); $has_differential = PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorDifferentialApplication', $viewer); if ($has_maniphest) { $unbreak_panel = $this->buildUnbreakNowPanel(); $triage_panel = $this->buildNeedsTriagePanel($projects); $tasks_panel = $this->buildTasksPanel(); } else { $unbreak_panel = null; $triage_panel = null; $tasks_panel = null; } if ($has_audit) { $audit_panel = $this->buildAuditPanel(); $commit_panel = $this->buildCommitPanel(); } else { $audit_panel = null; $commit_panel = null; } if (PhabricatorEnv::getEnvConfig('welcome.html') !== null) { $welcome_panel = $this->buildWelcomePanel(); } else { $welcome_panel = null; } if ($has_differential) { $revision_panel = $this->buildRevisionPanel(); } else { $revision_panel = null; } $home = phutil_tag( 'div', array( 'class' => 'homepage-panel', ), array( $welcome_panel, $unbreak_panel, $triage_panel, $revision_panel, $tasks_panel, $audit_panel, $commit_panel, $this->minipanels, )); return $home; } private function buildUnbreakNowPanel() { $unbreak_now = PhabricatorEnv::getEnvConfig( 'maniphest.priorities.unbreak-now'); if (!$unbreak_now) { return null; } $user = $this->getRequest()->getUser(); $task_query = id(new ManiphestTaskQuery()) ->setViewer($user) ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants()) ->withPriorities(array($unbreak_now)) ->needProjectPHIDs(true) ->setLimit(10); $tasks = $task_query->execute(); if (!$tasks) { return $this->renderMiniPanel( pht('No "Unbreak Now!" Tasks'), pht('Nothing appears to be critically broken right now.')); } $href = urisprintf( '/maniphest/?statuses=open()&priorities=%s#R', $unbreak_now); $title = pht('Unbreak Now!'); $panel = new PHUIObjectBoxView(); $panel->setHeader($this->renderSectionHeader($title, $href)); $panel->setObjectList($this->buildTaskListView($tasks)); return $panel; } private function buildNeedsTriagePanel(array $projects) { assert_instances_of($projects, 'PhabricatorProject'); $needs_triage = PhabricatorEnv::getEnvConfig( 'maniphest.priorities.needs-triage'); if (!$needs_triage) { return null; } $user = $this->getRequest()->getUser(); if (!$user->isLoggedIn()) { return null; } if ($projects) { $task_query = id(new ManiphestTaskQuery()) ->setViewer($user) ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants()) ->withPriorities(array($needs_triage)) ->withEdgeLogicPHIDs( PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, PhabricatorQueryConstraint::OPERATOR_OR, mpull($projects, 'getPHID')) ->needProjectPHIDs(true) ->setLimit(10); $tasks = $task_query->execute(); } else { $tasks = array(); } if (!$tasks) { return $this->renderMiniPanel( pht('No "Needs Triage" Tasks'), pht('No tasks in projects you are a member of need triage.')); } $title = pht('Needs Triage'); $href = urisprintf( '/maniphest/?statuses=open()&priorities=%s&projects=projects(%s)#R', $needs_triage, $user->getPHID()); $panel = new PHUIObjectBoxView(); $panel->setHeader($this->renderSectionHeader($title, $href)); $panel->setObjectList($this->buildTaskListView($tasks)); return $panel; } private function buildRevisionPanel() { $user = $this->getRequest()->getUser(); $user_phid = $user->getPHID(); $revision_query = id(new DifferentialRevisionQuery()) ->setViewer($user) ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) ->withResponsibleUsers(array($user_phid)) ->needRelationships(true) ->needFlags(true) ->needDrafts(true); $revisions = $revision_query->execute(); - list($blocking, $active,) = DifferentialRevisionQuery::splitResponsible( + list($blocking, $active) = DifferentialRevisionQuery::splitResponsible( $revisions, array($user_phid)); if (!$blocking && !$active) { return $this->renderMiniPanel( pht('No Waiting Revisions'), pht('No revisions are waiting on you.')); } $title = pht('Revisions Waiting on You'); $href = '/differential'; $panel = new PHUIObjectBoxView(); $panel->setHeader($this->renderSectionHeader($title, $href)); $revision_view = id(new DifferentialRevisionListView()) ->setHighlightAge(true) ->setRevisions(array_merge($blocking, $active)) ->setUser($user); $phids = array_merge( array($user_phid), $revision_view->getRequiredHandlePHIDs()); $handles = $this->loadViewerHandles($phids); $revision_view->setHandles($handles); $list_view = $revision_view->render(); $panel->setObjectList($list_view); return $panel; } private function buildWelcomePanel() { $panel = new PHUIObjectBoxView(); $panel->setHeaderText(pht('Welcome')); $panel->appendChild( phutil_safe_html( PhabricatorEnv::getEnvConfig('welcome.html'))); return $panel; } private function buildTasksPanel() { $user = $this->getRequest()->getUser(); $user_phid = $user->getPHID(); $task_query = id(new ManiphestTaskQuery()) ->setViewer($user) ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants()) ->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY) ->withOwners(array($user_phid)) ->needProjectPHIDs(true) ->setLimit(10); $tasks = $task_query->execute(); if (!$tasks) { return $this->renderMiniPanel( pht('No Assigned Tasks'), pht('You have no assigned tasks.')); } $title = pht('Assigned Tasks'); $href = '/maniphest/query/assigned/'; $panel = new PHUIObjectBoxView(); $panel->setHeader($this->renderSectionHeader($title, $href)); $panel->setObjectList($this->buildTaskListView($tasks)); return $panel; } private function buildTaskListView(array $tasks) { assert_instances_of($tasks, 'ManiphestTask'); $user = $this->getRequest()->getUser(); $phids = array_merge( array_filter(mpull($tasks, 'getOwnerPHID')), array_mergev(mpull($tasks, 'getProjectPHIDs'))); $handles = $this->loadViewerHandles($phids); $view = new ManiphestTaskListView(); $view->setTasks($tasks); $view->setUser($user); $view->setHandles($handles); return $view; } private function renderSectionHeader($title, $href) { $title = phutil_tag( 'a', array( 'href' => $href, ), $title); $icon = id(new PHUIIconView()) ->setIconFont('fa-search') ->setHref($href); $header = id(new PHUIHeaderView()) ->setHeader($title) ->addActionIcon($icon); return $header; } private function renderMiniPanel($title, $body) { $panel = new PHUIInfoView(); $panel->setSeverity(PHUIInfoView::SEVERITY_NODATA); $panel->appendChild( phutil_tag( 'p', array( ), array( phutil_tag('strong', array(), $title.': '), $body, ))); $this->minipanels[] = $panel; } public function buildAuditPanel() { $request = $this->getRequest(); $user = $request->getUser(); $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user); $query = id(new DiffusionCommitQuery()) ->setViewer($user) ->withNeedsAuditByPHIDs($phids) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_OPEN) ->needAuditRequests(true) ->needCommitData(true) ->setLimit(10); $commits = $query->execute(); if (!$commits) { return $this->renderMinipanel( pht('No Audits'), pht('No commits are waiting for you to audit them.')); } $view = id(new PhabricatorAuditListView()) ->setCommits($commits) ->setUser($user); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); $title = pht('Audits'); $href = '/audit/'; $panel = new PHUIObjectBoxView(); $panel->setHeader($this->renderSectionHeader($title, $href)); $panel->setObjectList($view); return $panel; } public function buildCommitPanel() { $request = $this->getRequest(); $user = $request->getUser(); $phids = array($user->getPHID()); $query = id(new DiffusionCommitQuery()) ->setViewer($user) ->withAuthorPHIDs($phids) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_CONCERN) ->needCommitData(true) ->needAuditRequests(true) ->setLimit(10); $commits = $query->execute(); if (!$commits) { return $this->renderMinipanel( pht('No Problem Commits'), pht('No one has raised concerns with your commits.')); } $view = id(new PhabricatorAuditListView()) ->setCommits($commits) ->setUser($user); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); $title = pht('Problem Commits'); $href = '/audit/'; $panel = new PHUIObjectBoxView(); $panel->setHeader($this->renderSectionHeader($title, $href)); $panel->setObjectList($view); return $panel; } } diff --git a/src/applications/legalpad/controller/LegalpadDocumentSignController.php b/src/applications/legalpad/controller/LegalpadDocumentSignController.php index b9c43e377b..ac24efbb5a 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentSignController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentSignController.php @@ -1,704 +1,703 @@ getUser(); $document = id(new LegalpadDocumentQuery()) ->setViewer($viewer) ->withIDs(array($request->getURIData('id'))) ->needDocumentBodies(true) ->executeOne(); if (!$document) { return new Aphront404Response(); } $information = $this->readSignerInformation( $document, $request); if ($information instanceof AphrontResponse) { return $information; } list($signer_phid, $signature_data) = $information; $signature = null; $type_individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL; $is_individual = ($document->getSignatureType() == $type_individual); switch ($document->getSignatureType()) { case LegalpadDocument::SIGNATURE_TYPE_NONE: // nothing to sign means this should be true $has_signed = true; // this is a status UI element $signed_status = null; break; case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: if ($signer_phid) { // TODO: This is odd and should probably be adjusted after // grey/external accounts work better, but use the omnipotent // viewer to check for a signature so we can pick up // anonymous/grey signatures. $signature = id(new LegalpadDocumentSignatureQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withDocumentPHIDs(array($document->getPHID())) ->withSignerPHIDs(array($signer_phid)) ->executeOne(); if ($signature && !$viewer->isLoggedIn()) { return $this->newDialog() ->setTitle(pht('Already Signed')) ->appendParagraph(pht('You have already signed this document!')) ->addCancelButton('/'.$document->getMonogram(), pht('Okay')); } } $signed_status = null; if (!$signature) { $has_signed = false; $signature = id(new LegalpadDocumentSignature()) ->setSignerPHID($signer_phid) ->setDocumentPHID($document->getPHID()) ->setDocumentVersion($document->getVersions()); // If the user is logged in, show a notice that they haven't signed. // If they aren't logged in, we can't be as sure, so don't show // anything. if ($viewer->isLoggedIn()) { $signed_status = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors( array( pht('You have not signed this document yet.'), )); } } else { $has_signed = true; $signature_data = $signature->getSignatureData(); // In this case, we know they've signed. $signed_at = $signature->getDateCreated(); if ($signature->getIsExemption()) { $exemption_phid = $signature->getExemptionPHID(); $handles = $this->loadViewerHandles(array($exemption_phid)); $exemption_handle = $handles[$exemption_phid]; $signed_text = pht( 'You do not need to sign this document. '. '%s added a signature exemption for you on %s.', $exemption_handle->renderLink(), phabricator_datetime($signed_at, $viewer)); } else { $signed_text = pht( 'You signed this document on %s.', phabricator_datetime($signed_at, $viewer)); } $signed_status = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setErrors(array($signed_text)); } $field_errors = array( 'name' => true, 'email' => true, 'agree' => true, ); $signature->setSignatureData($signature_data); break; case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: $signature = id(new LegalpadDocumentSignature()) ->setDocumentPHID($document->getPHID()) ->setDocumentVersion($document->getVersions()); if ($viewer->isLoggedIn()) { $has_signed = false; $signed_status = null; } else { // This just hides the form. $has_signed = true; $login_text = pht( 'This document requires a corporate signatory. You must log in to '. 'accept this document on behalf of a company you represent.'); $signed_status = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors(array($login_text)); } $field_errors = array( 'name' => true, 'address' => true, 'contact.name' => true, 'email' => true, ); $signature->setSignatureData($signature_data); break; } $errors = array(); if ($request->isFormOrHisecPost() && !$has_signed) { // Require two-factor auth to sign legal documents. if ($viewer->isLoggedIn()) { $engine = new PhabricatorAuthSessionEngine(); $engine->requireHighSecuritySession( $viewer, $request, '/'.$document->getMonogram()); } list($form_data, $errors, $field_errors) = $this->readSignatureForm( $document, $request); $signature_data = $form_data + $signature_data; $signature->setSignatureData($signature_data); $signature->setSignatureType($document->getSignatureType()); $signature->setSignerName((string)idx($signature_data, 'name')); $signature->setSignerEmail((string)idx($signature_data, 'email')); $agree = $request->getExists('agree'); if (!$agree) { $errors[] = pht( 'You must check "I agree to the terms laid forth above."'); $field_errors['agree'] = pht('Required'); } if ($viewer->isLoggedIn() && $is_individual) { $verified = LegalpadDocumentSignature::VERIFIED; } else { $verified = LegalpadDocumentSignature::UNVERIFIED; } $signature->setVerified($verified); if (!$errors) { $signature->save(); // If the viewer is logged in, signing for themselves, send them to // the document page, which will show that they have signed the // document. Unless of course they were required to sign the // document to use Phabricator; in that case try really hard to // re-direct them to where they wanted to go. // // Otherwise, send them to a completion page. if ($viewer->isLoggedIn() && $is_individual) { $next_uri = '/'.$document->getMonogram(); if ($document->getRequireSignature()) { $request_uri = $request->getRequestURI(); $next_uri = (string)$request_uri; } } else { $this->sendVerifySignatureEmail( $document, $signature); $next_uri = $this->getApplicationURI('done/'); } return id(new AphrontRedirectResponse())->setURI($next_uri); } } $document_body = $document->getDocumentBody(); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($viewer); $engine->addObject( $document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT); $engine->process(); $document_markup = $engine->getOutput( $document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT); $title = $document_body->getTitle(); $manage_uri = $this->getApplicationURI('view/'.$document->getID().'/'); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $document, PhabricatorPolicyCapability::CAN_EDIT); // Use the last content update as the modified date. We don't want to // show that a document like a TOS was "updated" by an incidental change // to a field like the preamble or privacy settings which does not acutally // affect the content of the agreement. $content_updated = $document_body->getDateCreated(); // NOTE: We're avoiding `setPolicyObject()` here so we don't pick up // extra UI elements that are unnecessary and clutter the signature page. // These details are available on the "Manage" page. $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) ->setEpoch($content_updated) ->addActionLink( id(new PHUIButtonView()) ->setTag('a') ->setIcon( id(new PHUIIconView()) ->setIconFont('fa-pencil')) ->setText(pht('Manage')) ->setHref($manage_uri) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $preamble_box = null; if (strlen($document->getPreamble())) { $preamble_text = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent( $document->getPreamble()), 'default', $viewer); // NOTE: We're avoiding `setObject()` here so we don't pick up extra UI // elements like "Subscribers". This information is available on the // "Manage" page, but just clutters up the "Signature" page. $preamble = id(new PHUIPropertyListView()) ->setUser($viewer) ->addSectionHeader(pht('Preamble')) ->addTextContent($preamble_text); $preamble_box = new PHUIPropertyGroupView(); $preamble_box->addPropertyList($preamble); } $content = id(new PHUIDocumentViewPro()) ->addClass('legalpad') ->setHeader($header) ->appendChild( array( $signed_status, $preamble_box, $document_markup, )); $signature_box = null; if (!$has_signed) { $error_view = null; if ($errors) { $error_view = id(new PHUIInfoView()) ->setErrors($errors); } $signature_form = $this->buildSignatureForm( $document, $signature, $field_errors); switch ($document->getSignatureType()) { default: break; case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Agree and Sign Document')) ->setForm($signature_form); if ($error_view) { $box->setInfoView($error_view); } $signature_box = phutil_tag_div('phui-document-view-pro-box', $box); break; } } $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); $crumbs->addTextCrumb($document->getMonogram()); return $this->buildApplicationPage( array( $crumbs, $content, $signature_box, ), array( 'title' => $title, - 'class' => 'pro-white-background', 'pageObjects' => array($document->getPHID()), )); } private function readSignerInformation( LegalpadDocument $document, AphrontRequest $request) { $viewer = $request->getUser(); $signer_phid = null; $signature_data = array(); switch ($document->getSignatureType()) { case LegalpadDocument::SIGNATURE_TYPE_NONE: break; case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: if ($viewer->isLoggedIn()) { $signer_phid = $viewer->getPHID(); $signature_data = array( 'name' => $viewer->getRealName(), 'email' => $viewer->loadPrimaryEmailAddress(), ); } else if ($request->isFormPost()) { $email = new PhutilEmailAddress($request->getStr('email')); if (strlen($email->getDomainName())) { $email_obj = id(new PhabricatorUserEmail()) ->loadOneWhere('address = %s', $email->getAddress()); if ($email_obj) { return $this->signInResponse(); } $external_account = id(new PhabricatorExternalAccountQuery()) ->setViewer($viewer) ->withAccountTypes(array('email')) ->withAccountDomains(array($email->getDomainName())) ->withAccountIDs(array($email->getAddress())) ->loadOneOrCreate(); if ($external_account->getUserPHID()) { return $this->signInResponse(); } $signer_phid = $external_account->getPHID(); } } break; case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: $signer_phid = $viewer->getPHID(); if ($signer_phid) { $signature_data = array( 'contact.name' => $viewer->getRealName(), 'email' => $viewer->loadPrimaryEmailAddress(), 'actorPHID' => $viewer->getPHID(), ); } break; } return array($signer_phid, $signature_data); } private function buildSignatureForm( LegalpadDocument $document, LegalpadDocumentSignature $signature, array $errors) { $viewer = $this->getRequest()->getUser(); $data = $signature->getSignatureData(); $form = id(new AphrontFormView()) ->setUser($viewer); $signature_type = $document->getSignatureType(); switch ($signature_type) { case LegalpadDocument::SIGNATURE_TYPE_NONE: // bail out of here quick return; case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: $this->buildIndividualSignatureForm( $form, $document, $signature, $errors); break; case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: $this->buildCorporateSignatureForm( $form, $document, $signature, $errors); break; default: throw new Exception( pht( 'This document has an unknown signature type ("%s").', $signature_type)); } $form ->appendChild( id(new AphrontFormCheckboxControl()) ->setError(idx($errors, 'agree', null)) ->addCheckbox( 'agree', 'agree', pht('I agree to the terms laid forth above.'), false)); if ($document->getRequireSignature()) { $cancel_uri = '/logout/'; $cancel_text = pht('Log Out'); } else { $cancel_uri = $this->getApplicationURI(); $cancel_text = pht('Cancel'); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Sign Document')) ->addCancelButton($cancel_uri, $cancel_text)); return $form; } private function buildIndividualSignatureForm( AphrontFormView $form, LegalpadDocument $document, LegalpadDocumentSignature $signature, array $errors) { $data = $signature->getSignatureData(); $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setValue(idx($data, 'name', '')) ->setName('name') ->setError(idx($errors, 'name', null))); $viewer = $this->getRequest()->getUser(); if (!$viewer->isLoggedIn()) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Email')) ->setValue(idx($data, 'email', '')) ->setName('email') ->setError(idx($errors, 'email', null))); } return $form; } private function buildCorporateSignatureForm( AphrontFormView $form, LegalpadDocument $document, LegalpadDocumentSignature $signature, array $errors) { $data = $signature->getSignatureData(); $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Company Name')) ->setValue(idx($data, 'name', '')) ->setName('name') ->setError(idx($errors, 'name', null))) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel(pht('Company Address')) ->setValue(idx($data, 'address', '')) ->setName('address') ->setError(idx($errors, 'address', null))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Contact Name')) ->setValue(idx($data, 'contact.name', '')) ->setName('contact.name') ->setError(idx($errors, 'contact.name', null))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Contact Email')) ->setValue(idx($data, 'email', '')) ->setName('email') ->setError(idx($errors, 'email', null))); return $form; } private function readSignatureForm( LegalpadDocument $document, AphrontRequest $request) { $signature_type = $document->getSignatureType(); switch ($signature_type) { case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL: $result = $this->readIndividualSignatureForm( $document, $request); break; case LegalpadDocument::SIGNATURE_TYPE_CORPORATION: $result = $this->readCorporateSignatureForm( $document, $request); break; default: throw new Exception( pht( 'This document has an unknown signature type ("%s").', $signature_type)); } return $result; } private function readIndividualSignatureForm( LegalpadDocument $document, AphrontRequest $request) { $signature_data = array(); $errors = array(); $field_errors = array(); $name = $request->getStr('name'); if (!strlen($name)) { $field_errors['name'] = pht('Required'); $errors[] = pht('Name field is required.'); } else { $field_errors['name'] = null; } $signature_data['name'] = $name; $viewer = $request->getUser(); if ($viewer->isLoggedIn()) { $email = $viewer->loadPrimaryEmailAddress(); } else { $email = $request->getStr('email'); $addr_obj = null; if (!strlen($email)) { $field_errors['email'] = pht('Required'); $errors[] = pht('Email field is required.'); } else { $addr_obj = new PhutilEmailAddress($email); $domain = $addr_obj->getDomainName(); if (!$domain) { $field_errors['email'] = pht('Invalid'); $errors[] = pht('A valid email is required.'); } else { $field_errors['email'] = null; } } } $signature_data['email'] = $email; return array($signature_data, $errors, $field_errors); } private function readCorporateSignatureForm( LegalpadDocument $document, AphrontRequest $request) { $viewer = $request->getUser(); if (!$viewer->isLoggedIn()) { throw new Exception( pht( 'You can not sign a document on behalf of a corporation unless '. 'you are logged in.')); } $signature_data = array(); $errors = array(); $field_errors = array(); $name = $request->getStr('name'); if (!strlen($name)) { $field_errors['name'] = pht('Required'); $errors[] = pht('Company name is required.'); } else { $field_errors['name'] = null; } $signature_data['name'] = $name; $address = $request->getStr('address'); if (!strlen($address)) { $field_errors['address'] = pht('Required'); $errors[] = pht('Company address is required.'); } else { $field_errors['address'] = null; } $signature_data['address'] = $address; $contact_name = $request->getStr('contact.name'); if (!strlen($contact_name)) { $field_errors['contact.name'] = pht('Required'); $errors[] = pht('Contact name is required.'); } else { $field_errors['contact.name'] = null; } $signature_data['contact.name'] = $contact_name; $email = $request->getStr('email'); $addr_obj = null; if (!strlen($email)) { $field_errors['email'] = pht('Required'); $errors[] = pht('Contact email is required.'); } else { $addr_obj = new PhutilEmailAddress($email); $domain = $addr_obj->getDomainName(); if (!$domain) { $field_errors['email'] = pht('Invalid'); $errors[] = pht('A valid email is required.'); } else { $field_errors['email'] = null; } } $signature_data['email'] = $email; return array($signature_data, $errors, $field_errors); } private function sendVerifySignatureEmail( LegalpadDocument $doc, LegalpadDocumentSignature $signature) { $signature_data = $signature->getSignatureData(); $email = new PhutilEmailAddress($signature_data['email']); $doc_name = $doc->getTitle(); $doc_link = PhabricatorEnv::getProductionURI('/'.$doc->getMonogram()); $path = $this->getApplicationURI(sprintf( '/verify/%s/', $signature->getSecretKey())); $link = PhabricatorEnv::getProductionURI($path); $name = idx($signature_data, 'name'); $body = pht( "%s:\n\n". "This email address was used to sign a Legalpad document ". "in Phabricator:\n\n". " %s\n\n". "Please verify you own this email address and accept the ". "agreement by clicking this link:\n\n". " %s\n\n". "Your signature is not valid until you complete this ". "verification step.\n\nYou can review the document here:\n\n". " %s\n", $name, $doc_name, $link, $doc_link); id(new PhabricatorMetaMTAMail()) ->addRawTos(array($email->getAddress())) ->setSubject(pht('[Legalpad] Signature Verification')) ->setForceDelivery(true) ->setBody($body) ->setRelatedPHID($signature->getDocumentPHID()) ->saveAndSend(); } private function signInResponse() { return id(new Aphront403Response()) ->setForbiddenText( pht( 'The email address specified is associated with an account. '. 'Please login to that account and sign this document again.')); } } diff --git a/src/applications/maniphest/application/PhabricatorManiphestApplication.php b/src/applications/maniphest/application/PhabricatorManiphestApplication.php index 758d85f879..7edb1a12d9 100644 --- a/src/applications/maniphest/application/PhabricatorManiphestApplication.php +++ b/src/applications/maniphest/application/PhabricatorManiphestApplication.php @@ -1,171 +1,175 @@ [1-9]\d*)' => 'ManiphestTaskDetailController', '/maniphest/' => array( '(?:query/(?P[^/]+)/)?' => 'ManiphestTaskListController', 'report/(?:(?P\w+)/)?' => 'ManiphestReportController', 'batch/' => 'ManiphestBatchEditController', 'task/' => array( 'create/' => 'ManiphestTaskEditController', 'edit/(?P[1-9]\d*)/' => 'ManiphestTaskEditController', 'descriptionpreview/' => 'PhabricatorMarkupPreviewController', ), 'transaction/' => array( 'save/' => 'ManiphestTransactionSaveController', 'preview/(?P[1-9]\d*)/' => 'ManiphestTransactionPreviewController', ), 'export/(?P[^/]+)/' => 'ManiphestExportController', 'subpriority/' => 'ManiphestSubpriorityController', ), ); } public function loadStatus(PhabricatorUser $user) { $status = array(); if (!$user->isLoggedIn()) { return $status; } + $limit = self::MAX_STATUS_ITEMS; + $query = id(new ManiphestTaskQuery()) ->setViewer($user) ->withStatuses(ManiphestTaskStatus::getOpenStatusConstants()) ->withOwners(array($user->getPHID())) - ->setLimit(self::MAX_STATUS_ITEMS); + ->setLimit($limit); + $count = count($query->execute()); - $count_str = self::formatStatusCount( - $count, - '%s Assigned Tasks', - '%d Assigned Task(s)'); + if ($count >= $limit) { + $count_str = pht('%s+ Assigned Task(s)', new PhutilNumber($limit - 1)); + } else { + $count_str = pht('%s Assigned Task(s)', new PhutilNumber($count)); + } $type = PhabricatorApplicationStatusView::TYPE_WARNING; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($count_str) ->setCount($count); return $status; } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); $item = id(new PHUIListItemView()) ->setName(pht('Maniphest Task')) ->setIcon('fa-anchor') ->setHref($this->getBaseURI().'task/create/'); $items[] = $item; return $items; } public function supportsEmailIntegration() { return true; } public function getAppEmailBlurb() { return pht( 'Send email to these addresses to create tasks. %s', phutil_tag( 'a', array( 'href' => $this->getInboundEmailSupportLink(), ), pht('Learn More'))); } protected function getCustomCapabilities() { return array( ManiphestDefaultViewCapability::CAPABILITY => array( 'caption' => pht('Default view policy for newly created tasks.'), 'template' => ManiphestTaskPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_VIEW, ), ManiphestDefaultEditCapability::CAPABILITY => array( 'caption' => pht('Default edit policy for newly created tasks.'), 'template' => ManiphestTaskPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_EDIT, ), ManiphestEditStatusCapability::CAPABILITY => array(), ManiphestEditAssignCapability::CAPABILITY => array(), ManiphestEditPoliciesCapability::CAPABILITY => array(), ManiphestEditPriorityCapability::CAPABILITY => array(), ManiphestEditProjectsCapability::CAPABILITY => array(), ManiphestBulkEditCapability::CAPABILITY => array(), ); } public function getMailCommandObjects() { return array( 'task' => array( 'name' => pht('Email Commands: Tasks'), 'header' => pht('Interacting with Maniphest Tasks'), 'object' => new ManiphestTask(), 'summary' => pht( 'This page documents the commands you can use to interact with '. 'tasks in Maniphest. These commands work when creating new tasks '. 'via email and when replying to existing tasks.'), ), ); } public function getApplicationSearchDocumentTypes() { return array( ManiphestTaskPHIDType::TYPECONST, ); } } diff --git a/src/applications/maniphest/controller/ManiphestController.php b/src/applications/maniphest/controller/ManiphestController.php index 87eaf69706..b65811128d 100644 --- a/src/applications/maniphest/controller/ManiphestController.php +++ b/src/applications/maniphest/controller/ManiphestController.php @@ -1,70 +1,66 @@ buildSideNavView(true)->getMenu(); } - public function buildSideNavView($for_app = false) { - $user = $this->getRequest()->getUser(); + public function buildSideNavView() { + $viewer = $this->getViewer(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - if ($for_app) { - $nav->addFilter('task/create/', pht('Create Task')); - } - id(new ManiphestTaskSearchEngine()) - ->setViewer($user) + ->setViewer($viewer) ->addNavigationItems($nav->getMenu()); - if ($user->isLoggedIn()) { + if ($viewer->isLoggedIn()) { // For now, don't give logged-out users access to reports. $nav->addLabel(pht('Reports')); $nav->addFilter('report', pht('Reports')); } $nav->selectFilter(null); return $nav; } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Task')) ->setHref($this->getApplicationURI('task/create/')) ->setIcon('fa-plus-square')); return $crumbs; } protected function renderSingleTask(ManiphestTask $task) { $request = $this->getRequest(); $user = $request->getUser(); $phids = $task->getProjectPHIDs(); if ($task->getOwnerPHID()) { $phids[] = $task->getOwnerPHID(); } $handles = id(new PhabricatorHandleQuery()) ->setViewer($user) ->withPHIDs($phids) ->execute(); $view = id(new ManiphestTaskListView()) ->setUser($user) ->setShowSubpriorityControls(!$request->getStr('ungrippable')) ->setShowBatchControls(true) ->setHandles($handles) ->setTasks(array($task)); return $view; } } diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index 090bdb9bb7..d5fab7f9f5 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -1,586 +1,590 @@ getViewer(); $id = $request->getURIData('id'); $e_title = null; $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); $task = id(new ManiphestTaskQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needSubscriberPHIDs(true) ->executeOne(); if (!$task) { return new Aphront404Response(); } $workflow = $request->getStr('workflow'); $parent_task = null; if ($workflow && is_numeric($workflow)) { $parent_task = id(new ManiphestTaskQuery()) ->setViewer($viewer) ->withIDs(array($workflow)) ->executeOne(); } $field_list = PhabricatorCustomField::getObjectFields( $task, PhabricatorCustomField::ROLE_VIEW); $field_list ->setViewer($viewer) ->readFieldsFromStorage($task); $e_commit = ManiphestTaskHasCommitEdgeType::EDGECONST; $e_dep_on = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; $e_dep_by = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; $e_rev = ManiphestTaskHasRevisionEdgeType::EDGECONST; $e_mock = ManiphestTaskHasMockEdgeType::EDGECONST; $phid = $task->getPHID(); $query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($phid)) ->withEdgeTypes( array( $e_commit, $e_dep_on, $e_dep_by, $e_rev, $e_mock, )); $edges = idx($query->execute(), $phid); $phids = array_fill_keys($query->getDestinationPHIDs(), true); if ($task->getOwnerPHID()) { $phids[$task->getOwnerPHID()] = true; } $phids[$task->getAuthorPHID()] = true; $attached = $task->getAttached(); foreach ($attached as $type => $list) { foreach ($list as $phid => $info) { $phids[$phid] = true; } } if ($parent_task) { $phids[$parent_task->getPHID()] = true; } $phids = array_keys($phids); $handles = $viewer->loadHandles($phids); $info_view = null; if ($parent_task) { $info_view = new PHUIInfoView(); $info_view->setSeverity(PHUIInfoView::SEVERITY_NOTICE); $info_view->addButton( id(new PHUIButtonView()) ->setTag('a') ->setHref('/maniphest/task/create/?parent='.$parent_task->getID()) ->setText(pht('Create Another Subtask'))); $info_view->appendChild(hsprintf( 'Created a subtask of %s.', $handles->renderHandle($parent_task->getPHID()))); } else if ($workflow == 'create') { $info_view = new PHUIInfoView(); $info_view->setSeverity(PHUIInfoView::SEVERITY_NOTICE); $info_view->addButton( id(new PHUIButtonView()) ->setTag('a') ->setHref('/maniphest/task/create/?template='.$task->getID()) ->setText(pht('Similar Task'))); $info_view->addButton( id(new PHUIButtonView()) ->setTag('a') ->setHref('/maniphest/task/create/') ->setText(pht('Empty Task'))); $info_view->appendChild(pht('New task created. Create another?')); } $engine = new PhabricatorMarkupEngine(); $engine->setViewer($viewer); $engine->setContextObject($task); $engine->addObject($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION); $timeline = $this->buildTransactionTimeline( $task, new ManiphestTransactionQuery(), $engine); $resolution_types = ManiphestTaskStatus::getTaskStatusMap(); $transaction_types = array( PhabricatorTransactions::TYPE_COMMENT => pht('Comment'), ManiphestTransaction::TYPE_STATUS => pht('Change Status'), ManiphestTransaction::TYPE_OWNER => pht('Reassign / Claim'), PhabricatorTransactions::TYPE_SUBSCRIBERS => pht('Add CCs'), ManiphestTransaction::TYPE_PRIORITY => pht('Change Priority'), PhabricatorTransactions::TYPE_EDGE => pht('Associate Projects'), ); // Remove actions the user doesn't have permission to take. $requires = array( ManiphestTransaction::TYPE_OWNER => ManiphestEditAssignCapability::CAPABILITY, ManiphestTransaction::TYPE_PRIORITY => ManiphestEditPriorityCapability::CAPABILITY, PhabricatorTransactions::TYPE_EDGE => ManiphestEditProjectsCapability::CAPABILITY, ManiphestTransaction::TYPE_STATUS => ManiphestEditStatusCapability::CAPABILITY, ); foreach ($transaction_types as $type => $name) { if (isset($requires[$type])) { if (!$this->hasApplicationCapability($requires[$type])) { unset($transaction_types[$type]); } } } // Don't show an option to change to the current status, or to change to // the duplicate status explicitly. unset($resolution_types[$task->getStatus()]); unset($resolution_types[ManiphestTaskStatus::getDuplicateStatus()]); // Don't show owner/priority changes for closed tasks, as they don't make // much sense. if ($task->isClosed()) { unset($transaction_types[ManiphestTransaction::TYPE_PRIORITY]); unset($transaction_types[ManiphestTransaction::TYPE_OWNER]); } $default_claim = array( $viewer->getPHID() => $viewer->getUsername(). ' ('.$viewer->getRealName().')', ); $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $viewer->getPHID(), $task->getPHID()); if ($draft) { $draft_text = $draft->getDraft(); } else { $draft_text = null; } $projects_source = new PhabricatorProjectDatasource(); $users_source = new PhabricatorPeopleDatasource(); $mailable_source = new PhabricatorMetaMTAMailableDatasource(); $comment_form = new AphrontFormView(); $comment_form ->setUser($viewer) ->setWorkflow(true) ->setAction('/maniphest/transaction/save/') ->setEncType('multipart/form-data') ->addHiddenInput('taskID', $task->getID()) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Action')) ->setName('action') ->setOptions($transaction_types) ->setID('transaction-action')) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Status')) ->setName('resolution') ->setControlID('resolution') ->setControlStyle('display: none') ->setOptions($resolution_types)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Assign To')) ->setName('assign_to') ->setControlID('assign_to') ->setControlStyle('display: none') ->setID('assign-tokenizer') ->setDisableBehavior(true) ->setDatasource($users_source)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('CCs')) ->setName('ccs') ->setControlID('ccs') ->setControlStyle('display: none') ->setID('cc-tokenizer') ->setDisableBehavior(true) ->setDatasource($mailable_source)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Priority')) ->setName('priority') ->setOptions($priority_map) ->setControlID('priority') ->setControlStyle('display: none') ->setValue($task->getPriority())) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setControlID('projects') ->setControlStyle('display: none') ->setID('projects-tokenizer') ->setDisableBehavior(true) ->setDatasource($projects_source)) ->appendChild( id(new AphrontFormFileControl()) ->setLabel(pht('File')) ->setName('file') ->setControlID('file') ->setControlStyle('display: none')) ->appendChild( id(new PhabricatorRemarkupControl()) ->setUser($viewer) ->setLabel(pht('Comments')) ->setName('comments') ->setValue($draft_text) ->setID('transaction-comments') ->setUser($viewer)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Submit'))); $control_map = array( ManiphestTransaction::TYPE_STATUS => 'resolution', ManiphestTransaction::TYPE_OWNER => 'assign_to', PhabricatorTransactions::TYPE_SUBSCRIBERS => 'ccs', ManiphestTransaction::TYPE_PRIORITY => 'priority', PhabricatorTransactions::TYPE_EDGE => 'projects', ); $tokenizer_map = array( PhabricatorTransactions::TYPE_EDGE => array( 'id' => 'projects-tokenizer', 'src' => $projects_source->getDatasourceURI(), 'placeholder' => $projects_source->getPlaceholderText(), ), ManiphestTransaction::TYPE_OWNER => array( 'id' => 'assign-tokenizer', 'src' => $users_source->getDatasourceURI(), 'value' => $default_claim, 'limit' => 1, 'placeholder' => $users_source->getPlaceholderText(), ), PhabricatorTransactions::TYPE_SUBSCRIBERS => array( 'id' => 'cc-tokenizer', 'src' => $mailable_source->getDatasourceURI(), 'placeholder' => $mailable_source->getPlaceholderText(), ), ); // TODO: Initializing these behaviors for logged out users fatals things. if ($viewer->isLoggedIn()) { Javelin::initBehavior('maniphest-transaction-controls', array( 'select' => 'transaction-action', 'controlMap' => $control_map, 'tokenizers' => $tokenizer_map, )); Javelin::initBehavior('maniphest-transaction-preview', array( 'uri' => '/maniphest/transaction/preview/'.$task->getID().'/', 'preview' => 'transaction-preview', 'comments' => 'transaction-comments', 'action' => 'transaction-action', 'map' => $control_map, 'tokenizers' => $tokenizer_map, )); } $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $comment_header = $is_serious ? pht('Add Comment') : pht('Weigh In'); $preview_panel = phutil_tag_div( 'aphront-panel-preview', phutil_tag( 'div', array('id' => 'transaction-preview'), phutil_tag_div( 'aphront-panel-preview-loading-text', pht('Loading preview...')))); $object_name = 'T'.$task->getID(); $actions = $this->buildActionView($task); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb($object_name, '/'.$object_name); $header = $this->buildHeaderView($task); $properties = $this->buildPropertyView( $task, $field_list, $edges, $actions, $handles); $description = $this->buildDescriptionView($task, $engine); if (!$viewer->isLoggedIn()) { // TODO: Eventually, everything should run through this. For now, we're // only using it to get a consistent "Login to Comment" button. $comment_box = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setRequestURI($request->getRequestURI()); $preview_panel = null; } else { $comment_box = id(new PHUIObjectBoxView()) ->setFlush(true) ->setHeaderText($comment_header) ->setForm($comment_form); $timeline->setQuoteTargetID('transaction-comments'); $timeline->setQuoteRef($object_name); } $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); if ($description) { $object_box->addPropertyList($description); } - return $this->buildApplicationPage( - array( - $crumbs, - $info_view, - $object_box, - $timeline, - $comment_box, - $preview_panel, - ), - array( - 'title' => 'T'.$task->getID().' '.$task->getTitle(), - 'pageObjects' => array($task->getPHID()), - )); + $title = 'T'.$task->getID().' '.$task->getTitle(); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs( + array( + $task->getPHID(), + )) + ->appendChild( + array( + $info_view, + $object_box, + $timeline, + $comment_box, + $preview_panel, + )); } private function buildHeaderView(ManiphestTask $task) { $view = id(new PHUIHeaderView()) ->setHeader($task->getTitle()) ->setUser($this->getRequest()->getUser()) ->setPolicyObject($task); $status = $task->getStatus(); $status_name = ManiphestTaskStatus::renderFullDescription($status); $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name); return $view; } private function buildActionView(ManiphestTask $task) { $viewer = $this->getRequest()->getUser(); $id = $task->getID(); $phid = $task->getPHID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $task, PhabricatorPolicyCapability::CAN_EDIT); $can_create = $viewer->isLoggedIn(); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($task) ->setObjectURI($this->getRequest()->getRequestURI()); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Task')) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI("/task/edit/{$id}/")) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Merge Duplicates In')) ->setHref("/search/attach/{$phid}/TASK/merge/") ->setWorkflow(true) ->setIcon('fa-compress') ->setDisabled(!$can_edit) ->setWorkflow(true)); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Create Subtask')) ->setHref($this->getApplicationURI("/task/create/?parent={$id}")) ->setIcon('fa-level-down') ->setDisabled(!$can_create) ->setWorkflow(!$can_create)); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Blocking Tasks')) ->setHref("/search/attach/{$phid}/TASK/blocks/") ->setWorkflow(true) ->setIcon('fa-link') ->setDisabled(!$can_edit) ->setWorkflow(true)); return $view; } private function buildPropertyView( ManiphestTask $task, PhabricatorCustomFieldList $field_list, array $edges, PhabricatorActionListView $actions, $handles) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($task) ->setActionList($actions); $view->addProperty( pht('Assigned To'), $task->getOwnerPHID() ? $handles->renderHandle($task->getOwnerPHID()) : phutil_tag('em', array(), pht('None'))); $view->addProperty( pht('Priority'), ManiphestTaskPriority::getTaskPriorityName($task->getPriority())); $view->addProperty( pht('Author'), $handles->renderHandle($task->getAuthorPHID())); $source = $task->getOriginalEmailSource(); if ($source) { $subject = '[T'.$task->getID().'] '.$task->getTitle(); $view->addProperty( pht('From Email'), phutil_tag( 'a', array( 'href' => 'mailto:'.$source.'?subject='.$subject, ), $source)); } $edge_types = array( ManiphestTaskDependedOnByTaskEdgeType::EDGECONST => pht('Blocks'), ManiphestTaskDependsOnTaskEdgeType::EDGECONST => pht('Blocked By'), ManiphestTaskHasRevisionEdgeType::EDGECONST => pht('Differential Revisions'), ManiphestTaskHasMockEdgeType::EDGECONST => pht('Pholio Mocks'), ); $revisions_commits = array(); $commit_phids = array_keys( $edges[ManiphestTaskHasCommitEdgeType::EDGECONST]); if ($commit_phids) { $commit_drev = DiffusionCommitHasRevisionEdgeType::EDGECONST; $drev_edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs($commit_phids) ->withEdgeTypes(array($commit_drev)) ->execute(); foreach ($commit_phids as $phid) { $revisions_commits[$phid] = $handles->renderHandle($phid); $revision_phid = key($drev_edges[$phid][$commit_drev]); $revision_handle = $handles->getHandleIfExists($revision_phid); if ($revision_handle) { $task_drev = ManiphestTaskHasRevisionEdgeType::EDGECONST; unset($edges[$task_drev][$revision_phid]); $revisions_commits[$phid] = hsprintf( '%s / %s', $revision_handle->renderLink($revision_handle->getName()), $revisions_commits[$phid]); } } } foreach ($edge_types as $edge_type => $edge_name) { if ($edges[$edge_type]) { $edge_handles = $viewer->loadHandles(array_keys($edges[$edge_type])); $view->addProperty( $edge_name, $edge_handles->renderList()); } } if ($revisions_commits) { $view->addProperty( pht('Commits'), phutil_implode_html(phutil_tag('br'), $revisions_commits)); } $attached = $task->getAttached(); if (!is_array($attached)) { $attached = array(); } $file_infos = idx($attached, PhabricatorFileFilePHIDType::TYPECONST); if ($file_infos) { $file_phids = array_keys($file_infos); // TODO: These should probably be handles or something; clean this up // as we sort out file attachments. $files = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs($file_phids) ->execute(); $file_view = new PhabricatorFileLinkListView(); $file_view->setFiles($files); $view->addProperty( pht('Files'), $file_view->render()); } $view->invokeWillRenderEvent(); $field_list->appendFieldsToPropertyList( $task, $viewer, $view); return $view; } private function buildDescriptionView( ManiphestTask $task, PhabricatorMarkupEngine $engine) { $section = null; if (strlen($task->getDescription())) { $section = new PHUIPropertyListView(); $section->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $section->addTextContent( phutil_tag( 'div', array( 'class' => 'phabricator-remarkup', ), $engine->getOutput($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION))); } return $section; } } diff --git a/src/applications/maniphest/controller/ManiphestTaskEditController.php b/src/applications/maniphest/controller/ManiphestTaskEditController.php index 5c4a3b4862..e9ce5a567a 100644 --- a/src/applications/maniphest/controller/ManiphestTaskEditController.php +++ b/src/applications/maniphest/controller/ManiphestTaskEditController.php @@ -1,756 +1,757 @@ getViewer(); $id = $request->getURIData('id'); $response_type = $request->getStr('responseType', 'task'); $order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER); $can_edit_assign = $this->hasApplicationCapability( ManiphestEditAssignCapability::CAPABILITY); $can_edit_policies = $this->hasApplicationCapability( ManiphestEditPoliciesCapability::CAPABILITY); $can_edit_priority = $this->hasApplicationCapability( ManiphestEditPriorityCapability::CAPABILITY); $can_edit_projects = $this->hasApplicationCapability( ManiphestEditProjectsCapability::CAPABILITY); $can_edit_status = $this->hasApplicationCapability( ManiphestEditStatusCapability::CAPABILITY); $can_create_projects = PhabricatorPolicyFilter::hasCapability( $viewer, PhabricatorApplication::getByClass('PhabricatorProjectApplication'), ProjectCreateProjectsCapability::CAPABILITY); $parent_task = null; $template_id = null; if ($id) { $task = id(new ManiphestTaskQuery()) ->setViewer($viewer) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($id)) ->needSubscriberPHIDs(true) ->needProjectPHIDs(true) ->executeOne(); if (!$task) { return new Aphront404Response(); } } else { $task = ManiphestTask::initializeNewTask($viewer); // We currently do not allow you to set the task status when creating // a new task, although now that statuses are custom it might make // sense. $can_edit_status = false; // These allow task creation with defaults. if (!$request->isFormPost()) { $task->setTitle($request->getStr('title')); if ($can_edit_projects) { $projects = $request->getStr('projects'); if ($projects) { $tokens = $request->getStrList('projects'); $type_project = PhabricatorProjectProjectPHIDType::TYPECONST; foreach ($tokens as $key => $token) { if (phid_get_type($token) == $type_project) { // If this is formatted like a PHID, leave it as-is. continue; } if (preg_match('/^#/', $token)) { // If this already has a "#", leave it as-is. continue; } // Add a "#" prefix. $tokens[$key] = '#'.$token; } $default_projects = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withNames($tokens) ->execute(); $default_projects = mpull($default_projects, 'getPHID'); if ($default_projects) { $task->attachProjectPHIDs($default_projects); } } } if ($can_edit_priority) { $priority = $request->getInt('priority'); if ($priority !== null) { $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); if (isset($priority_map[$priority])) { $task->setPriority($priority); } } } $task->setDescription($request->getStr('description')); if ($can_edit_assign) { $assign = $request->getStr('assign'); if (strlen($assign)) { $assign_user = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) ->withUsernames(array($assign)) ->executeOne(); if (!$assign_user) { $assign_user = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) ->withPHIDs(array($assign)) ->executeOne(); } if ($assign_user) { $task->setOwnerPHID($assign_user->getPHID()); } } } } $template_id = $request->getInt('template'); // You can only have a parent task if you're creating a new task. $parent_id = $request->getInt('parent'); if (strlen($parent_id)) { $parent_task = id(new ManiphestTaskQuery()) ->setViewer($viewer) ->withIDs(array($parent_id)) ->executeOne(); if (!$parent_task) { return new Aphront404Response(); } if (!$template_id) { $template_id = $parent_id; } } } $errors = array(); $e_title = true; $field_list = PhabricatorCustomField::getObjectFields( $task, PhabricatorCustomField::ROLE_EDIT); $field_list->setViewer($viewer); $field_list->readFieldsFromStorage($task); $aux_fields = $field_list->getFields(); $v_space = $task->getSpacePHID(); if ($request->isFormPost()) { $changes = array(); $new_title = $request->getStr('title'); $new_desc = $request->getStr('description'); $new_status = $request->getStr('status'); $v_space = $request->getStr('spacePHID'); if (!$task->getID()) { $workflow = 'create'; } else { $workflow = ''; } $changes[ManiphestTransaction::TYPE_TITLE] = $new_title; $changes[ManiphestTransaction::TYPE_DESCRIPTION] = $new_desc; if ($can_edit_status) { $changes[ManiphestTransaction::TYPE_STATUS] = $new_status; } else if (!$task->getID()) { // Create an initial status transaction for the burndown chart. // TODO: We can probably remove this once Facts comes online. $changes[ManiphestTransaction::TYPE_STATUS] = $task->getStatus(); } $owner_tokenizer = $request->getArr('assigned_to'); $owner_phid = reset($owner_tokenizer); if (!strlen($new_title)) { $e_title = pht('Required'); $errors[] = pht('Title is required.'); } $old_values = array(); foreach ($aux_fields as $aux_arr_key => $aux_field) { // TODO: This should be buildFieldTransactionsFromRequest() once we // switch to ApplicationTransactions properly. $aux_old_value = $aux_field->getOldValueForApplicationTransactions(); $aux_field->readValueFromRequest($request); $aux_new_value = $aux_field->getNewValueForApplicationTransactions(); // TODO: We're faking a call to the ApplicaitonTransaction validation // logic here. We need valid objects to pass, but they aren't used // in a meaningful way. For now, build User objects. Once the Maniphest // objects exist, this will switch over automatically. This is a big // hack but shouldn't be long for this world. $placeholder_editor = id(new PhabricatorUserProfileEditor()) ->setActor($viewer); $field_errors = $aux_field->validateApplicationTransactions( $placeholder_editor, PhabricatorTransactions::TYPE_CUSTOMFIELD, array( id(new ManiphestTransaction()) ->setOldValue($aux_old_value) ->setNewValue($aux_new_value), )); foreach ($field_errors as $error) { $errors[] = $error->getMessage(); } $old_values[$aux_field->getFieldKey()] = $aux_old_value; } if ($errors) { $task->setTitle($new_title); $task->setDescription($new_desc); $task->setPriority($request->getInt('priority')); $task->setOwnerPHID($owner_phid); $task->attachSubscriberPHIDs($request->getArr('cc')); $task->attachProjectPHIDs($request->getArr('projects')); } else { if ($can_edit_priority) { $changes[ManiphestTransaction::TYPE_PRIORITY] = $request->getInt('priority'); } if ($can_edit_assign) { $changes[ManiphestTransaction::TYPE_OWNER] = $owner_phid; } $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = array('=' => $request->getArr('cc')); if ($can_edit_projects) { $projects = $request->getArr('projects'); $changes[PhabricatorTransactions::TYPE_EDGE] = $projects; $column_phid = $request->getStr('columnPHID'); // allow for putting a task in a project column at creation -only- if (!$task->getID() && $column_phid && $projects) { $column = id(new PhabricatorProjectColumnQuery()) ->setViewer($viewer) ->withProjectPHIDs($projects) ->withPHIDs(array($column_phid)) ->executeOne(); if ($column) { $changes[ManiphestTransaction::TYPE_PROJECT_COLUMN] = array( 'new' => array( 'projectPHID' => $column->getProjectPHID(), 'columnPHIDs' => array($column_phid), ), 'old' => array( 'projectPHID' => $column->getProjectPHID(), 'columnPHIDs' => array(), ), ); } } } if ($can_edit_policies) { $changes[PhabricatorTransactions::TYPE_SPACE] = $v_space; $changes[PhabricatorTransactions::TYPE_VIEW_POLICY] = $request->getStr('viewPolicy'); $changes[PhabricatorTransactions::TYPE_EDIT_POLICY] = $request->getStr('editPolicy'); } $template = new ManiphestTransaction(); $transactions = array(); foreach ($changes as $type => $value) { $transaction = clone $template; $transaction->setTransactionType($type); if ($type == ManiphestTransaction::TYPE_PROJECT_COLUMN) { $transaction->setNewValue($value['new']); $transaction->setOldValue($value['old']); } else if ($type == PhabricatorTransactions::TYPE_EDGE) { $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $transaction ->setMetadataValue('edge:type', $project_type) ->setNewValue( array( '=' => array_fuse($value), )); } else { $transaction->setNewValue($value); } $transactions[] = $transaction; } if ($aux_fields) { foreach ($aux_fields as $aux_field) { $transaction = clone $template; $transaction->setTransactionType( PhabricatorTransactions::TYPE_CUSTOMFIELD); $aux_key = $aux_field->getFieldKey(); $transaction->setMetadataValue('customfield:key', $aux_key); $old = idx($old_values, $aux_key); $new = $aux_field->getNewValueForApplicationTransactions(); $transaction->setOldValue($old); $transaction->setNewValue($new); $transactions[] = $transaction; } } if ($transactions) { $is_new = !$task->getID(); $event = new PhabricatorEvent( PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK, array( 'task' => $task, 'new' => $is_new, 'transactions' => $transactions, )); $event->setUser($viewer); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); $task = $event->getValue('task'); $transactions = $event->getValue('transactions'); $editor = id(new ManiphestTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->applyTransactions($task, $transactions); $event = new PhabricatorEvent( PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK, array( 'task' => $task, 'new' => $is_new, 'transactions' => $transactions, )); $event->setUser($viewer); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); } if ($parent_task) { // TODO: This should be transactional now. id(new PhabricatorEdgeEditor()) ->addEdge( $parent_task->getPHID(), ManiphestTaskDependsOnTaskEdgeType::EDGECONST, $task->getPHID()) ->save(); $workflow = $parent_task->getID(); } if ($request->isAjax()) { switch ($response_type) { case 'card': $owner = null; if ($task->getOwnerPHID()) { $owner = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs(array($task->getOwnerPHID())) ->executeOne(); } $tasks = id(new ProjectBoardTaskCard()) ->setViewer($viewer) ->setTask($task) ->setOwner($owner) ->setCanEdit(true) ->getItem(); $column = id(new PhabricatorProjectColumnQuery()) ->setViewer($viewer) ->withPHIDs(array($request->getStr('columnPHID'))) ->executeOne(); if (!$column) { return new Aphront404Response(); } // re-load projects for accuracy as they are not re-loaded via // the editor $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $task->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $task->attachProjectPHIDs($project_phids); $remove_from_board = false; if (!in_array($column->getProjectPHID(), $project_phids)) { $remove_from_board = true; } $positions = id(new PhabricatorProjectColumnPositionQuery()) ->setViewer($viewer) ->withColumns(array($column)) ->execute(); $task_phids = mpull($positions, 'getObjectPHID'); $column_tasks = id(new ManiphestTaskQuery()) ->setViewer($viewer) ->withPHIDs($task_phids) ->execute(); if ($order == PhabricatorProjectColumn::ORDER_NATURAL) { // TODO: This is a little bit awkward, because PHP and JS use // slightly different sort order parameters to achieve the same // effect. It would be good to unify this a bit at some point. $sort_map = array(); foreach ($positions as $position) { $sort_map[$position->getObjectPHID()] = array( -$position->getSequence(), $position->getID(), ); } } else { $sort_map = mpull( $column_tasks, 'getPrioritySortVector', 'getPHID'); } $data = array( 'sortMap' => $sort_map, 'removeFromBoard' => $remove_from_board, ); break; case 'task': default: $tasks = $this->renderSingleTask($task); $data = array(); break; } return id(new AphrontAjaxResponse())->setContent( array( 'tasks' => $tasks, 'data' => $data, )); } $redirect_uri = '/T'.$task->getID(); if ($workflow) { $redirect_uri .= '?workflow='.$workflow; } return id(new AphrontRedirectResponse()) ->setURI($redirect_uri); } } else { if (!$task->getID()) { $task->attachSubscriberPHIDs(array( $viewer->getPHID(), )); if ($template_id) { $template_task = id(new ManiphestTaskQuery()) ->setViewer($viewer) ->withIDs(array($template_id)) ->needSubscriberPHIDs(true) ->needProjectPHIDs(true) ->executeOne(); if ($template_task) { $cc_phids = array_unique(array_merge( $template_task->getSubscriberPHIDs(), array($viewer->getPHID()))); $task->attachSubscriberPHIDs($cc_phids); $task->attachProjectPHIDs($template_task->getProjectPHIDs()); $task->setOwnerPHID($template_task->getOwnerPHID()); $task->setPriority($template_task->getPriority()); $task->setViewPolicy($template_task->getViewPolicy()); $task->setEditPolicy($template_task->getEditPolicy()); $v_space = $template_task->getSpacePHID(); $template_fields = PhabricatorCustomField::getObjectFields( $template_task, PhabricatorCustomField::ROLE_EDIT); $fields = $template_fields->getFields(); foreach ($fields as $key => $field) { if (!$field->shouldCopyWhenCreatingSimilarTask()) { unset($fields[$key]); } if (empty($aux_fields[$key])) { unset($fields[$key]); } } if ($fields) { id(new PhabricatorCustomFieldList($fields)) ->setViewer($viewer) ->readFieldsFromStorage($template_task); foreach ($fields as $key => $field) { $aux_fields[$key]->setValueFromStorage( $field->getValueForStorage()); } } } } } } $error_view = null; if ($errors) { $error_view = new PHUIInfoView(); $error_view->setErrors($errors); } $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); if ($task->getOwnerPHID()) { $assigned_value = array($task->getOwnerPHID()); } else { $assigned_value = array(); } if ($task->getSubscriberPHIDs()) { $cc_value = $task->getSubscriberPHIDs(); } else { $cc_value = array(); } if ($task->getProjectPHIDs()) { $projects_value = $task->getProjectPHIDs(); } else { $projects_value = array(); } $cancel_id = nonempty($task->getID(), $template_id); if ($cancel_id) { $cancel_uri = '/T'.$cancel_id; } else { $cancel_uri = '/maniphest/'; } if ($task->getID()) { $button_name = pht('Save Task'); $header_name = pht('Edit Task'); } else if ($parent_task) { $cancel_uri = '/T'.$parent_task->getID(); $button_name = pht('Create Task'); $header_name = pht('Create New Subtask'); } else { $button_name = pht('Create Task'); $header_name = pht('Create New Task'); } require_celerity_resource('maniphest-task-edit-css'); $project_tokenizer_id = celerity_generate_unique_node_id(); $form = new AphrontFormView(); $form ->setUser($viewer) ->addHiddenInput('template', $template_id) ->addHiddenInput('responseType', $response_type) ->addHiddenInput('order', $order) ->addHiddenInput('ungrippable', $request->getStr('ungrippable')) ->addHiddenInput('columnPHID', $request->getStr('columnPHID')); if ($parent_task) { $form ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Parent Task')) ->setValue($viewer->renderHandle($parent_task->getPHID()))) ->addHiddenInput('parent', $parent_task->getID()); } $form ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel(pht('Title')) ->setName('title') ->setError($e_title) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) ->setValue($task->getTitle())); if ($can_edit_status) { // See T4819. $status_map = ManiphestTaskStatus::getTaskStatusMap(); $dup_status = ManiphestTaskStatus::getDuplicateStatus(); if ($task->getStatus() != $dup_status) { unset($status_map[$dup_status]); } $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Status')) ->setName('status') ->setValue($task->getStatus()) ->setOptions($status_map)); } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($task) ->execute(); if ($can_edit_assign) { $form->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Assigned To')) ->setName('assigned_to') ->setValue($assigned_value) ->setUser($viewer) ->setDatasource(new PhabricatorPeopleDatasource()) ->setLimit(1)); } $form ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('CC')) ->setName('cc') ->setValue($cc_value) ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource())); if ($can_edit_priority) { $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Priority')) ->setName('priority') ->setOptions($priority_map) ->setValue($task->getPriority())); } if ($can_edit_policies) { $form ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($task) ->setPolicies($policies) ->setSpacePHID($v_space) ->setName('viewPolicy')) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($task) ->setPolicies($policies) ->setName('editPolicy')); } if ($can_edit_projects) { $caption = null; if ($can_create_projects) { $caption = javelin_tag( 'a', array( 'href' => '/project/create/', 'mustcapture' => true, 'sigil' => 'project-create', ), pht('Create New Project')); } $form ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setValue($projects_value) ->setID($project_tokenizer_id) ->setCaption($caption) ->setDatasource(new PhabricatorProjectDatasource())); } $field_list->appendFieldsToForm($form); require_celerity_resource('phui-info-view-css'); Javelin::initBehavior('project-create', array( 'tokenizerID' => $project_tokenizer_id, )); $description_control = id(new PhabricatorRemarkupControl()) ->setLabel(pht('Description')) ->setName('description') ->setID('description-textarea') ->setValue($task->getDescription()) ->setUser($viewer); $form ->appendChild($description_control); if ($request->isAjax()) { $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setWidth(AphrontDialogView::WIDTH_FULL) ->setTitle($header_name) ->appendChild( array( $error_view, $form->buildLayoutView(), )) ->addCancelButton($cancel_uri) ->addSubmitButton($button_name); return id(new AphrontDialogResponse())->setDialog($dialog); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($button_name)); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($header_name) ->setFormErrors($errors) ->setForm($form); $preview = id(new PHUIRemarkupPreviewPanel()) ->setHeader(pht('Description Preview')) ->setControlID('description-textarea') ->setPreviewURI($this->getApplicationURI('task/descriptionpreview/')); if ($task->getID()) { $page_objects = array($task->getPHID()); } else { $page_objects = array(); } $crumbs = $this->buildApplicationCrumbs(); if ($task->getID()) { $crumbs->addTextCrumb('T'.$task->getID(), '/T'.$task->getID()); } $crumbs->addTextCrumb($header_name); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - $preview, - ), - array( - 'title' => $header_name, - 'pageObjects' => $page_objects, - )); + $title = $header_name; + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs($page_objects) + ->appendChild( + array( + $form_box, + $preview, + )); } } diff --git a/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php b/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php index 63f2d256e1..060ca951ba 100644 --- a/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php +++ b/src/applications/meta/controller/PhabricatorApplicationEmailCommandsController.php @@ -1,156 +1,155 @@ getViewer(); $application = $request->getURIData('application'); $selected = id(new PhabricatorApplicationQuery()) ->setViewer($viewer) ->withClasses(array($application)) ->executeOne(); if (!$selected) { return new Aphront404Response(); } $specs = $selected->getMailCommandObjects(); $type = $request->getURIData('type'); if (empty($specs[$type])) { return new Aphront404Response(); } $spec = $specs[$type]; $commands = MetaMTAEmailTransactionCommand::getAllCommandsForObject( $spec['object']); $commands = msort($commands, 'getCommand'); $content = array(); $content[] = '= '.pht('Mail Commands Overview'); $content[] = pht( 'After configuring Phabricator to process inbound mail, you can '. 'interact with objects (like tasks and revisions) over email. For '. 'information on configuring Phabricator, see '. '**[[ %s | Configuring Inbound Email ]]**.'. "\n\n". 'In most cases, you can reply to email you receive from Phabricator '. 'to leave comments. You can also use **mail commands** to take a '. 'greater range of actions (like claiming a task or requesting changes '. 'to a revision) without needing to log in to the web UI.'. "\n\n". 'Mail commands are keywords which start with an exclamation point, '. 'like `!claim`. Some commands may take parameters, like '. "`!assign alincoln`.\n\n". 'To use mail commands, write one command per line at the beginning '. 'or end of your mail message. For example, you could write this in a '. 'reply to task email to claim the task:'. "\n\n```\n!claim\n\nI'll take care of this.\n```\n\n\n". "When Phabricator receives your mail, it will process any commands ". "first, then post the remaining message body as a comment. You can ". "execute multiple commands at once:". "\n\n```\n!assign alincoln\n!close\n\nI just talked to @alincoln, ". "and he showed me that he fixed this.\n```\n", PhabricatorEnv::getDoclink('Configuring Inbound Email')); $content[] = '= '.$spec['header']; $content[] = $spec['summary']; $content[] = '= '.pht('Quick Reference'); $content[] = pht( 'This table summarizes the available mail commands. For details on a '. 'specific command, see the command section below.'); $table = array(); $table[] = '| '.pht('Command').' | '.pht('Summary').' |'; $table[] = '|---|---|'; foreach ($commands as $command) { $summary = $command->getCommandSummary(); $table[] = '| '.$command->getCommandSyntax().' | '.$summary; } $table = implode("\n", $table); $content[] = $table; foreach ($commands as $command) { $content[] = '== !'.$command->getCommand().' =='; $content[] = $command->getCommandSummary(); $aliases = $command->getCommandAliases(); if ($aliases) { foreach ($aliases as $key => $alias) { $aliases[$key] = '!'.$alias; } $aliases = implode(', ', $aliases); } else { $aliases = '//None//'; } $syntax = $command->getCommandSyntax(); $table = array(); $table[] = '| '.pht('Property').' | '.pht('Value'); $table[] = '|---|---|'; $table[] = '| **'.pht('Syntax').'** | '.$syntax; $table[] = '| **'.pht('Aliases').'** | '.$aliases; $table[] = '| **'.pht('Class').'** | `'.get_class($command).'`'; $table = implode("\n", $table); $content[] = $table; $description = $command->getCommandDescription(); if ($description) { $content[] = $description; } } $content = implode("\n\n", $content); $title = $spec['name']; $crumbs = $this->buildApplicationCrumbs(); $this->addApplicationCrumb($crumbs, $selected); $crumbs->addTextCrumb($title); $crumbs->setBorder(true); $content_box = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($content), 'default', $viewer); $info_view = null; if (!PhabricatorEnv::getEnvConfig('metamta.reply-handler-domain')) { $error = pht( "Phabricator is not currently configured to accept inbound mail. ". "You won't be able to interact with objects over email until ". "inbound mail is set up."); $info_view = id(new PHUIInfoView()) ->setErrors(array($error)); } $header = id(new PHUIHeaderView()) ->setHeader($title); $document = id(new PHUIDocumentViewPro()) ->setHeader($header) ->appendChild($info_view) ->appendChild($content_box); return $this->buildApplicationPage( array( $crumbs, $document, ), array( 'title' => $title, - 'class' => 'pro-white-background', )); } } diff --git a/src/applications/meta/view/PhabricatorApplicationLaunchView.php b/src/applications/meta/view/PhabricatorApplicationLaunchView.php index 397838882e..c714e0daa6 100644 --- a/src/applications/meta/view/PhabricatorApplicationLaunchView.php +++ b/src/applications/meta/view/PhabricatorApplicationLaunchView.php @@ -1,135 +1,145 @@ application = $application; return $this; } public function setApplicationStatus(array $status) { $this->status = $status; return $this; } protected function getTagName() { return $this->application ? 'a' : 'div'; } protected function getTagAttributes() { $application = $this->application; return array( 'class' => array('phabricator-application-launch-container'), 'href' => $application ? $application->getBaseURI() : null, ); } protected function getTagContent() { $application = $this->application; require_celerity_resource('phabricator-application-launch-view-css'); $content = array(); $icon = null; if ($application) { $content[] = phutil_tag( 'span', array( 'class' => 'phabricator-application-launch-name', ), $application->getName()); $content[] = phutil_tag( 'span', array( 'class' => 'phabricator-application-launch-description', ), $application->getShortDescription()); $counts = array(); $text = array(); if ($this->status) { foreach ($this->status as $status) { $type = $status->getType(); $counts[$type] = idx($counts, $type, 0) + $status->getCount(); if ($status->getCount()) { $text[] = $status->getText(); } } } $attention = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; $warning = PhabricatorApplicationStatusView::TYPE_WARNING; if (!empty($counts[$attention]) || !empty($counts[$warning])) { $count = idx($counts, $attention, 0); $count1 = $count2 = ''; if ($count > 0) { $count1 = phutil_tag( 'span', array( 'class' => 'phabricator-application-attention-count', ), - PhabricatorApplication::formatStatusCount($count)); + $this->formatStatusItemCount($count)); } if (!empty($counts[$warning])) { $count2 = phutil_tag( 'span', array( 'class' => 'phabricator-application-warning-count', ), - PhabricatorApplication::formatStatusCount($counts[$warning])); + $this->formatStatusItemCount($counts[$warning])); } + if (nonempty($count1) && nonempty($count2)) { $numbers = array($count1, ' / ', $count2); } else { $numbers = array($count1, $count2); } Javelin::initBehavior('phabricator-tooltips'); $content[] = javelin_tag( 'span', array( 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => implode("\n", $text), 'size' => 300, 'align' => 'E', ), 'class' => 'phabricator-application-launch-attention', ), $numbers); } $classes = array(); $classes[] = 'phabricator-application-launch-icon'; $styles = array(); if ($application->getIconURI()) { $styles[] = 'background-image: url('.$application->getIconURI().')'; } else { $classes[] = $application->getFontIcon(); $classes[] = 'phui-icon-view'; $classes[] = 'phui-font-fa'; } $icon = phutil_tag( 'span', array( 'class' => implode(' ', $classes), 'style' => nonempty(implode('; ', $styles), null), ), ''); } return array( $icon, $content, ); } + private function formatStatusItemCount($count) { + $limit = PhabricatorApplication::MAX_STATUS_ITEMS; + if ($count >= $limit) { + return pht('%s+', new PhutilNumber($limit - 1)); + } else { + return pht('%s', new PhutilNumber($count)); + } + } + } diff --git a/src/applications/oauthserver/PhabricatorOAuthResponse.php b/src/applications/oauthserver/PhabricatorOAuthResponse.php index 4e2c86c861..62c0fc9821 100644 --- a/src/applications/oauthserver/PhabricatorOAuthResponse.php +++ b/src/applications/oauthserver/PhabricatorOAuthResponse.php @@ -1,107 +1,106 @@ state; } public function setState($state) { $this->state = $state; return $this; } private function getContent() { return $this->content; } public function setContent($content) { $this->content = $content; return $this; } private function getClientURI() { return $this->clientURI; } public function setClientURI(PhutilURI $uri) { $this->setHTTPResponseCode(302); $this->clientURI = $uri; return $this; } private function getFullURI() { $base_uri = $this->getClientURI(); $query_params = $this->buildResponseDict(); foreach ($query_params as $key => $value) { $base_uri->setQueryParam($key, $value); } return $base_uri; } private function getError() { return $this->error; } public function setError($error) { // errors sometimes redirect to the client (302) but otherwise // the spec says all code 400 if (!$this->getClientURI()) { $this->setHTTPResponseCode(400); } $this->error = $error; return $this; } private function getErrorDescription() { return $this->errorDescription; } public function setErrorDescription($error_description) { $this->errorDescription = $error_description; return $this; } public function __construct() { - $this->setHTTPResponseCode(200); // assume the best - return $this; + $this->setHTTPResponseCode(200); // assume the best } public function getHeaders() { $headers = array( array('Content-Type', 'application/json'), ); if ($this->getClientURI()) { $headers[] = array('Location', $this->getFullURI()); } // TODO -- T844 set headers with X-Auth-Scopes, etc $headers = array_merge(parent::getHeaders(), $headers); return $headers; } private function buildResponseDict() { if ($this->getError()) { $content = array( 'error' => $this->getError(), 'error_description' => $this->getErrorDescription(), ); $this->setContent($content); } $content = $this->getContent(); if (!$content) { return ''; } if ($this->getState()) { $content['state'] = $this->getState(); } return $content; } public function buildResponseString() { return $this->encodeJSONForHTTPResponse($this->buildResponseDict()); } } diff --git a/src/applications/oauthserver/PhabricatorOAuthServer.php b/src/applications/oauthserver/PhabricatorOAuthServer.php index e1b9f516ba..38b2e34623 100644 --- a/src/applications/oauthserver/PhabricatorOAuthServer.php +++ b/src/applications/oauthserver/PhabricatorOAuthServer.php @@ -1,272 +1,272 @@ user) { throw new PhutilInvalidStateException('setUser'); } return $this->user; } public function setUser(PhabricatorUser $user) { $this->user = $user; return $this; } private function getClient() { if (!$this->client) { throw new PhutilInvalidStateException('setClient'); } return $this->client; } public function setClient(PhabricatorOAuthServerClient $client) { $this->client = $client; return $this; } /** * @task auth * @return tuple */ public function userHasAuthorizedClient(array $scope) { - $authorization = id(new PhabricatorOAuthClientAuthorization())-> - loadOneWhere( + $authorization = id(new PhabricatorOAuthClientAuthorization()) + ->loadOneWhere( 'userPHID = %s AND clientPHID = %s', $this->getUser()->getPHID(), $this->getClient()->getPHID()); if (empty($authorization)) { return array(false, null); } if ($scope) { $missing_scope = array_diff_key($scope, $authorization->getScope()); } else { $missing_scope = false; } if ($missing_scope) { return array(false, $authorization); } return array(true, $authorization); } /** * @task auth */ public function authorizeClient(array $scope) { $authorization = new PhabricatorOAuthClientAuthorization(); $authorization->setUserPHID($this->getUser()->getPHID()); $authorization->setClientPHID($this->getClient()->getPHID()); $authorization->setScope($scope); $authorization->save(); return $authorization; } /** * @task auth */ public function generateAuthorizationCode(PhutilURI $redirect_uri) { $code = Filesystem::readRandomCharacters(32); $client = $this->getClient(); $authorization_code = new PhabricatorOAuthServerAuthorizationCode(); $authorization_code->setCode($code); $authorization_code->setClientPHID($client->getPHID()); $authorization_code->setClientSecret($client->getSecret()); $authorization_code->setUserPHID($this->getUser()->getPHID()); $authorization_code->setRedirectURI((string)$redirect_uri); $authorization_code->save(); return $authorization_code; } /** * @task token */ public function generateAccessToken() { $token = Filesystem::readRandomCharacters(32); $access_token = new PhabricatorOAuthServerAccessToken(); $access_token->setToken($token); $access_token->setUserPHID($this->getUser()->getPHID()); $access_token->setClientPHID($this->getClient()->getPHID()); $access_token->save(); return $access_token; } /** * @task token */ public function validateAuthorizationCode( PhabricatorOAuthServerAuthorizationCode $test_code, PhabricatorOAuthServerAuthorizationCode $valid_code) { // check that all the meta data matches if ($test_code->getClientPHID() != $valid_code->getClientPHID()) { return false; } if ($test_code->getClientSecret() != $valid_code->getClientSecret()) { return false; } // check that the authorization code hasn't timed out $created_time = $test_code->getDateCreated(); $must_be_used_by = $created_time + self::AUTHORIZATION_CODE_TIMEOUT; return (time() < $must_be_used_by); } /** * @task token */ public function validateAccessToken( PhabricatorOAuthServerAccessToken $token, $required_scope) { $created_time = $token->getDateCreated(); $must_be_used_by = $created_time + self::ACCESS_TOKEN_TIMEOUT; $expired = time() > $must_be_used_by; $authorization = id(new PhabricatorOAuthClientAuthorization()) ->loadOneWhere( 'userPHID = %s AND clientPHID = %s', $token->getUserPHID(), $token->getClientPHID()); if (!$authorization) { return false; } $token_scope = $authorization->getScope(); if (!isset($token_scope[$required_scope])) { return false; } $valid = true; if ($expired) { $valid = false; // check if the scope includes "offline_access", which makes the // token valid despite being expired if (isset( $token_scope[PhabricatorOAuthServerScope::SCOPE_OFFLINE_ACCESS])) { $valid = true; } } return $valid; } /** * See http://tools.ietf.org/html/draft-ietf-oauth-v2-23#section-3.1.2 * for details on what makes a given redirect URI "valid". */ public function validateRedirectURI(PhutilURI $uri) { if (!PhabricatorEnv::isValidRemoteURIForLink($uri)) { return false; } if ($uri->getFragment()) { return false; } if (!$uri->getDomain()) { return false; } return true; } /** * If there's a URI specified in an OAuth request, it must be validated in * its own right. Further, it must have the same domain, the same path, the * same port, and (at least) the same query parameters as the primary URI. */ public function validateSecondaryRedirectURI( PhutilURI $secondary_uri, PhutilURI $primary_uri) { // The secondary URI must be valid. if (!$this->validateRedirectURI($secondary_uri)) { return false; } // Both URIs must point at the same domain. if ($secondary_uri->getDomain() != $primary_uri->getDomain()) { return false; } // Both URIs must have the same path if ($secondary_uri->getPath() != $primary_uri->getPath()) { return false; } // Both URIs must have the same port if ($secondary_uri->getPort() != $primary_uri->getPort()) { return false; } // Any query parameters present in the first URI must be exactly present // in the second URI. $need_params = $primary_uri->getQueryParams(); $have_params = $secondary_uri->getQueryParams(); foreach ($need_params as $key => $value) { if (!array_key_exists($key, $have_params)) { return false; } if ((string)$have_params[$key] != (string)$value) { return false; } } // If the first URI is HTTPS, the second URI must also be HTTPS. This // defuses an attack where a third party with control over the network // tricks you into using HTTP to authenticate over a link which is supposed // to be HTTPS only and sniffs all your token cookies. if (strtolower($primary_uri->getProtocol()) == 'https') { if (strtolower($secondary_uri->getProtocol()) != 'https') { return false; } } return true; } } diff --git a/src/applications/owners/application/PhabricatorOwnersApplication.php b/src/applications/owners/application/PhabricatorOwnersApplication.php index 79f63b26b8..39933f3dce 100644 --- a/src/applications/owners/application/PhabricatorOwnersApplication.php +++ b/src/applications/owners/application/PhabricatorOwnersApplication.php @@ -1,54 +1,56 @@ pht('Owners User Guide'), 'href' => PhabricatorEnv::getDoclink('Owners User Guide'), ), ); } public function getFlavorText() { return pht('Adopt today!'); } public function getApplicationGroup() { return self::GROUP_UTILITIES; } public function getRoutes() { return array( '/owners/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorOwnersListController', - 'edit/(?P[1-9]\d*)/' => 'PhabricatorOwnersEditController', 'new/' => 'PhabricatorOwnersEditController', 'package/(?P[1-9]\d*)/' => 'PhabricatorOwnersDetailController', 'paths/(?P[1-9]\d*)/' => 'PhabricatorOwnersPathsController', + + $this->getEditRoutePattern('edit/') + => 'PhabricatorOwnersEditController', ), ); } } diff --git a/src/applications/owners/conduit/OwnersEditConduitAPIMethod.php b/src/applications/owners/conduit/OwnersEditConduitAPIMethod.php new file mode 100644 index 0000000000..4715f7695d --- /dev/null +++ b/src/applications/owners/conduit/OwnersEditConduitAPIMethod.php @@ -0,0 +1,20 @@ +getUser(); - - $id = $request->getURIData('id'); - if ($id) { - $package = id(new PhabricatorOwnersPackageQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - // TODO: Support this capability. - // PhabricatorPolicyCapability::CAN_EDIT, - )) - ->needOwners(true) - ->executeOne(); - if (!$package) { - return new Aphront404Response(); - } - $is_new = false; - } else { - $package = PhabricatorOwnersPackage::initializeNewPackage($viewer); - $is_new = true; - } - - $e_name = true; - - $v_name = $package->getName(); - $v_owners = mpull($package->getOwners(), 'getUserPHID'); - $v_auditing = $package->getAuditingEnabled(); - $v_description = $package->getDescription(); - $v_status = $package->getStatus(); - - $field_list = PhabricatorCustomField::getObjectFields( - $package, - PhabricatorCustomField::ROLE_EDIT); - $field_list->setViewer($viewer); - $field_list->readFieldsFromStorage($package); - - $errors = array(); - if ($request->isFormPost()) { - $xactions = array(); - - $v_name = $request->getStr('name'); - $v_owners = $request->getArr('owners'); - $v_auditing = ($request->getStr('auditing') == 'enabled'); - $v_description = $request->getStr('description'); - $v_status = $request->getStr('status'); - - $type_name = PhabricatorOwnersPackageTransaction::TYPE_NAME; - $type_owners = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; - $type_auditing = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; - $type_description = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; - $type_status = PhabricatorOwnersPackageTransaction::TYPE_STATUS; - - $xactions[] = id(new PhabricatorOwnersPackageTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new PhabricatorOwnersPackageTransaction()) - ->setTransactionType($type_owners) - ->setNewValue($v_owners); - - $xactions[] = id(new PhabricatorOwnersPackageTransaction()) - ->setTransactionType($type_auditing) - ->setNewValue($v_auditing); - - $xactions[] = id(new PhabricatorOwnersPackageTransaction()) - ->setTransactionType($type_description) - ->setNewValue($v_description); - - if (!$is_new) { - $xactions[] = id(new PhabricatorOwnersPackageTransaction()) - ->setTransactionType($type_status) - ->setNewValue($v_status); - } - - $field_xactions = $field_list->buildFieldTransactionsFromRequest( - new PhabricatorOwnersPackageTransaction(), - $request); - - $xactions = array_merge($xactions, $field_xactions); - - $editor = id(new PhabricatorOwnersPackageTransactionEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($package, $xactions); - - $id = $package->getID(); - if ($is_new) { - $next_uri = '/owners/paths/'.$id.'/'; - } else { - $next_uri = '/owners/package/'.$id.'/'; - } - - return id(new AphrontRedirectResponse())->setURI($next_uri); - } catch (AphrontDuplicateKeyQueryException $ex) { - $e_name = pht('Duplicate'); - $errors[] = pht('Package name must be unique.'); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $ex->getShortMessage($type_name); - } - } - - if ($is_new) { - $cancel_uri = '/owners/'; - $title = pht('New Package'); - $button_text = pht('Continue'); - } else { - $cancel_uri = '/owners/package/'.$package->getID().'/'; - $title = pht('Edit Package'); - $button_text = pht('Save Package'); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($v_name) - ->setError($e_name)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorProjectOrUserDatasource()) - ->setLabel(pht('Owners')) - ->setName('owners') - ->setValue($v_owners)); - - if (!$is_new) { - $form->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Status')) - ->setName('status') - ->setValue($v_status) - ->setOptions($package->getStatusNameMap())); - } - - $form->appendChild( - id(new AphrontFormSelectControl()) - ->setName('auditing') - ->setLabel(pht('Auditing')) - ->setCaption( - pht( - 'With auditing enabled, all future commits that touch '. - 'this package will be reviewed to make sure an owner '. - 'of the package is involved and the commit message has '. - 'a valid revision, reviewed by, and author.')) - ->setOptions( - array( - 'disabled' => pht('Disabled'), - 'enabled' => pht('Enabled'), - )) - ->setValue(($v_auditing ? 'enabled' : 'disabled'))) - ->appendChild( - id(new PhabricatorRemarkupControl()) - ->setUser($viewer) - ->setLabel(pht('Description')) - ->setName('description') - ->setValue($v_description)); - - $field_list->appendFieldsToForm($form); - - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue($button_text)); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setFormErrors($errors) - ->setForm($form); - - $crumbs = $this->buildApplicationCrumbs(); - if ($package->getID()) { - $crumbs->addTextCrumb( - $package->getName(), - $this->getApplicationURI('package/'.$package->getID().'/')); - $crumbs->addTextCrumb(pht('Edit')); - } else { - $crumbs->addTextCrumb(pht('New Package')); - } - - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, - )); + return id(new PhabricatorOwnersPackageEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/owners/controller/PhabricatorOwnersListController.php b/src/applications/owners/controller/PhabricatorOwnersListController.php index 7beb842c82..6392922577 100644 --- a/src/applications/owners/controller/PhabricatorOwnersListController.php +++ b/src/applications/owners/controller/PhabricatorOwnersListController.php @@ -1,54 +1,26 @@ setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new PhabricatorOwnersPackageSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView($for_app = false) { - $viewer = $this->getViewer(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - if ($for_app) { - $nav->addFilter('new/', pht('Create Package')); - } - - id(new PhabricatorOwnersPackageSearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; - } - - public function buildApplicationMenu() { - return $this->buildSideNavView(true)->getMenu(); + return id(new PhabricatorOwnersPackageSearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Package')) - ->setHref($this->getApplicationURI('new/')) - ->setIcon('fa-plus-square')); + id(new PhabricatorOwnersPackageEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } } diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php new file mode 100644 index 0000000000..c753556e89 --- /dev/null +++ b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php @@ -0,0 +1,93 @@ +getViewer()); + } + + protected function newObjectQuery() { + return id(new PhabricatorOwnersPackageQuery()) + ->needOwners(true); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create New Package'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Package %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Package %d', $object->getID()); + } + + protected function getObjectCreateShortText() { + return pht('Create Package'); + } + + protected function getObjectViewURI($object) { + $id = $object->getID(); + return "/owners/package/{$id}/"; + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the package.')) + ->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_NAME) + ->setIsRequired(true) + ->setValue($object->getName()), + id(new PhabricatorDatasourceEditField()) + ->setKey('owners') + ->setLabel(pht('Owners')) + ->setDescription(pht('Users and projects which own the package.')) + ->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_OWNERS) + ->setDatasource(new PhabricatorProjectOrUserDatasource()) + ->setValue($object->getOwnerPHIDs()), + id(new PhabricatorSelectEditField()) + ->setKey('status') + ->setLabel(pht('Status')) + ->setDescription(pht('Archive or enable the package.')) + ->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_STATUS) + ->setValue($object->getStatus()) + ->setOptions($object->getStatusNameMap()), + id(new PhabricatorSelectEditField()) + ->setKey('auditing') + ->setLabel(pht('Auditing')) + ->setDescription( + pht( + 'Automatically trigger audits for commits affecting files in '. + 'this package.')) + ->setTransactionType(PhabricatorOwnersPackageTransaction::TYPE_AUDITING) + ->setValue($object->getAuditingEnabled()) + ->setOptions( + array( + '' => pht('Disabled'), + '1' => pht('Enabled'), + )), + id(new PhabricatorRemarkupEditField()) + ->setKey('description') + ->setLabel(pht('Description')) + ->setDescription(pht('Human-readable description of the package.')) + ->setTransactionType( + PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION) + ->setValue($object->getDescription()), + ); + } + +} diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index 1a26bb6814..09e2a3be26 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -1,330 +1,341 @@ setAuditingEnabled(0) ->attachPaths(array()) ->setStatus(self::STATUS_ACTIVE) - ->attachOwners(array()); - } - - public function getCapabilities() { - return array( - PhabricatorPolicyCapability::CAN_VIEW, - ); - } - - public function getPolicy($capability) { - return PhabricatorPolicies::POLICY_USER; - } - - public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return false; - } - - public function describeAutomaticCapability($capability) { - return null; + ->attachOwners(array()) + ->setDescription(''); } public static function getStatusNameMap() { return array( self::STATUS_ACTIVE => pht('Active'), self::STATUS_ARCHIVED => pht('Archived'), ); } protected function getConfiguration() { return array( // This information is better available from the history table. self::CONFIG_TIMESTAMPS => false, self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text128', 'originalName' => 'text255', 'description' => 'text', 'primaryOwnerPHID' => 'phid?', 'auditingEnabled' => 'bool', 'mailKey' => 'bytes20', 'status' => 'text32', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'name' => array( 'columns' => array('name'), 'unique' => true, ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorOwnersPackagePHIDType::TYPECONST); } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } public function isArchived() { return ($this->getStatus() == self::STATUS_ARCHIVED); } public function setName($name) { $this->name = $name; if (!$this->getID()) { $this->originalName = $name; } return $this; } public function loadOwners() { if (!$this->getID()) { return array(); } return id(new PhabricatorOwnersOwner())->loadAllWhere( 'packageID = %d', $this->getID()); } public function loadPaths() { if (!$this->getID()) { return array(); } return id(new PhabricatorOwnersPath())->loadAllWhere( 'packageID = %d', $this->getID()); } public static function loadAffectedPackages( PhabricatorRepository $repository, array $paths) { if (!$paths) { return array(); } return self::loadPackagesForPaths($repository, $paths); } public static function loadOwningPackages($repository, $path) { if (empty($path)) { return array(); } return self::loadPackagesForPaths($repository, array($path), 1); } private static function loadPackagesForPaths( PhabricatorRepository $repository, array $paths, $limit = 0) { $fragments = array(); foreach ($paths as $path) { foreach (self::splitPath($path) as $fragment) { $fragments[$fragment][$path] = true; } } $package = new PhabricatorOwnersPackage(); $path = new PhabricatorOwnersPath(); $conn = $package->establishConnection('r'); $repository_clause = qsprintf( $conn, 'AND p.repositoryPHID = %s', $repository->getPHID()); // NOTE: The list of $paths may be very large if we're coming from // the OwnersWorker and processing, e.g., an SVN commit which created a new // branch. Break it apart so that it will fit within 'max_allowed_packet', // and then merge results in PHP. $rows = array(); foreach (array_chunk(array_keys($fragments), 128) as $chunk) { $rows[] = queryfx_all( $conn, 'SELECT pkg.id, p.excluded, p.path FROM %T pkg JOIN %T p ON p.packageID = pkg.id WHERE p.path IN (%Ls) %Q', $package->getTableName(), $path->getTableName(), $chunk, $repository_clause); } $rows = array_mergev($rows); $ids = self::findLongestPathsPerPackage($rows, $fragments); if (!$ids) { return array(); } arsort($ids); if ($limit) { $ids = array_slice($ids, 0, $limit, $preserve_keys = true); } $ids = array_keys($ids); $packages = $package->loadAllWhere('id in (%Ld)', $ids); $packages = array_select_keys($packages, $ids); return $packages; } public static function loadPackagesForRepository($repository) { $package = new PhabricatorOwnersPackage(); $ids = ipull( queryfx_all( $package->establishConnection('r'), 'SELECT DISTINCT packageID FROM %T WHERE repositoryPHID = %s', id(new PhabricatorOwnersPath())->getTableName(), $repository->getPHID()), 'packageID'); return $package->loadAllWhere('id in (%Ld)', $ids); } public static function findLongestPathsPerPackage(array $rows, array $paths) { $ids = array(); foreach (igroup($rows, 'id') as $id => $package_paths) { $relevant_paths = array_select_keys( $paths, ipull($package_paths, 'path')); // For every package, remove all excluded paths. $remove = array(); foreach ($package_paths as $package_path) { if ($package_path['excluded']) { $remove += idx($relevant_paths, $package_path['path'], array()); unset($relevant_paths[$package_path['path']]); } } if ($remove) { foreach ($relevant_paths as $fragment => $fragment_paths) { $relevant_paths[$fragment] = array_diff_key($fragment_paths, $remove); } } $relevant_paths = array_filter($relevant_paths); if ($relevant_paths) { $ids[$id] = max(array_map('strlen', array_keys($relevant_paths))); } } return $ids; } public static function splitPath($path) { $trailing_slash = preg_match('@/$@', $path) ? '/' : ''; $path = trim($path, '/'); $parts = explode('/', $path); $result = array(); while (count($parts)) { $result[] = '/'.implode('/', $parts).$trailing_slash; $trailing_slash = '/'; array_pop($parts); } $result[] = '/'; return array_reverse($result); } public function attachPaths(array $paths) { assert_instances_of($paths, 'PhabricatorOwnersPath'); $this->paths = $paths; return $this; } public function getPaths() { return $this->assertAttached($this->paths); } public function attachOwners(array $owners) { assert_instances_of($owners, 'PhabricatorOwnersOwner'); $this->owners = $owners; return $this; } public function getOwners() { return $this->assertAttached($this->owners); } + public function getOwnerPHIDs() { + return mpull($this->getOwners(), 'getUserPHID'); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + // TODO: Implement proper policies. + return PhabricatorPolicies::POLICY_USER; + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhabricatorOwnersPackageTransactionEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PhabricatorOwnersPackageTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorCustomFieldInterface )------------------------------------ */ public function getCustomFieldSpecificationForRole($role) { return PhabricatorEnv::getEnvConfig('owners.fields'); } public function getCustomFieldBaseClass() { return 'PhabricatorOwnersCustomField'; } public function getCustomFields() { return $this->assertAttached($this->customFields); } public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { $this->customFields = $fields; return $this; } } diff --git a/src/applications/paste/application/PhabricatorPasteApplication.php b/src/applications/paste/application/PhabricatorPasteApplication.php index e066d4cd9d..4ffc18ccc6 100644 --- a/src/applications/paste/application/PhabricatorPasteApplication.php +++ b/src/applications/paste/application/PhabricatorPasteApplication.php @@ -1,104 +1,103 @@ [1-9]\d*)(?:\$(?P\d+(?:-\d+)?))?' => 'PhabricatorPasteViewController', '/paste/' => array( '(query/(?P[^/]+)/)?' => 'PhabricatorPasteListController', 'create/' => 'PhabricatorPasteEditController', $this->getEditRoutePattern('edit/') => 'PhabricatorPasteEditController', 'raw/(?P[1-9]\d*)/' => 'PhabricatorPasteRawController', - 'comment/(?P[1-9]\d*)/' => 'PhabricatorPasteCommentController', ), ); } public function supportsEmailIntegration() { return true; } public function getAppEmailBlurb() { return pht( 'Send email to these addresses to create pastes. %s', phutil_tag( 'a', array( 'href' => $this->getInboundEmailSupportLink(), ), pht('Learn More'))); } protected function getCustomCapabilities() { return array( PasteDefaultViewCapability::CAPABILITY => array( 'caption' => pht('Default view policy for newly created pastes.'), 'template' => PhabricatorPastePastePHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_VIEW, ), PasteDefaultEditCapability::CAPABILITY => array( 'caption' => pht('Default edit policy for newly created pastes.'), 'template' => PhabricatorPastePastePHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_EDIT, ), ); } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); $item = id(new PHUIListItemView()) ->setName(pht('Paste')) ->setIcon('fa-clipboard') ->setHref($this->getBaseURI().'create/'); $items[] = $item; return $items; } public function getMailCommandObjects() { return array( 'paste' => array( 'name' => pht('Email Commands: Pastes'), 'header' => pht('Interacting with Pastes'), 'object' => new PhabricatorPaste(), 'summary' => pht( 'This page documents the commands you can use to interact with '. 'pastes.'), ), ); } } diff --git a/src/applications/paste/controller/PhabricatorPasteCommentController.php b/src/applications/paste/controller/PhabricatorPasteCommentController.php deleted file mode 100644 index 06596e5be7..0000000000 --- a/src/applications/paste/controller/PhabricatorPasteCommentController.php +++ /dev/null @@ -1,63 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if (!$request->isFormPost()) { - return new Aphront400Response(); - } - - $paste = id(new PhabricatorPasteQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$paste) { - return new Aphront404Response(); - } - - $is_preview = $request->isPreviewRequest(); - $draft = PhabricatorDraft::buildFromRequest($request); - - $view_uri = $paste->getURI(); - - $xactions = array(); - $xactions[] = id(new PhabricatorPasteTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) - ->attachComment( - id(new PhabricatorPasteTransactionComment()) - ->setContent($request->getStr('comment'))); - - $editor = id(new PhabricatorPasteEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect($request->isContinueRequest()) - ->setContentSourceFromRequest($request) - ->setIsPreview($is_preview); - - try { - $xactions = $editor->applyTransactions($paste, $xactions); - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { - return id(new PhabricatorApplicationTransactionNoEffectResponse()) - ->setCancelURI($view_uri) - ->setException($ex); - } - - if ($draft) { - $draft->replaceOrDelete(); - } - - if ($request->isAjax() && $is_preview) { - return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($viewer) - ->setTransactions($xactions) - ->setIsPreview($is_preview); - } else { - return id(new AphrontRedirectResponse()) - ->setURI($view_uri); - } - } - -} diff --git a/src/applications/paste/controller/PhabricatorPasteListController.php b/src/applications/paste/controller/PhabricatorPasteListController.php index e867b4e773..352aa78b1a 100644 --- a/src/applications/paste/controller/PhabricatorPasteListController.php +++ b/src/applications/paste/controller/PhabricatorPasteListController.php @@ -1,27 +1,25 @@ setController($this) ->buildResponse(); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Paste')) - ->setHref($this->getApplicationURI('edit/')) - ->setIcon('fa-plus-square')); + id(new PhabricatorPasteEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } } diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index 43a7058f59..4e291bcade 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -1,198 +1,184 @@ highlightMap = $map; } public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); $paste = id(new PhabricatorPasteQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needContent(true) + ->needRawContent(true) ->executeOne(); if (!$paste) { return new Aphront404Response(); } $forks = id(new PhabricatorPasteQuery()) ->setViewer($viewer) ->withParentPHIDs(array($paste->getPHID())) ->execute(); $fork_phids = mpull($forks, 'getPHID'); $header = $this->buildHeaderView($paste); $actions = $this->buildActionView($viewer, $paste); $properties = $this->buildPropertyView($paste, $fork_phids, $actions); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); $source_code = $this->buildSourceCodeView($paste, $this->highlightMap); require_celerity_resource('paste-css'); $source_code = phutil_tag( 'div', array( 'class' => 'container-of-paste', ), $source_code); $crumbs = $this->buildApplicationCrumbs() ->addTextCrumb('P'.$paste->getID(), '/P'.$paste->getID()); $timeline = $this->buildTransactionTimeline( $paste, new PhabricatorPasteTransactionQuery()); - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - - $add_comment_header = $is_serious - ? pht('Add Comment') - : pht('Eat Paste'); - - $draft = PhabricatorDraft::newFromUserAndKey($viewer, $paste->getPHID()); - - $add_comment_form = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setObjectPHID($paste->getPHID()) - ->setDraft($draft) - ->setHeaderText($add_comment_header) - ->setAction($this->getApplicationURI('/comment/'.$paste->getID().'/')) - ->setSubmitButtonName(pht('Add Comment')); + $comment_view = id(new PhabricatorPasteEditEngine()) + ->setViewer($viewer) + ->buildEditEngineCommentView($paste); return $this->newPage() ->setTitle($paste->getFullName()) ->setCrumbs($crumbs) ->setPageObjectPHIDs( array( $paste->getPHID(), )) ->appendChild( array( $object_box, $source_code, $timeline, - $add_comment_form, + $comment_view, )); } private function buildHeaderView(PhabricatorPaste $paste) { $title = (nonempty($paste->getTitle())) ? $paste->getTitle() : pht('(An Untitled Masterwork)'); if ($paste->isArchived()) { $header_icon = 'fa-ban'; $header_name = pht('Archived'); $header_color = 'dark'; } else { $header_icon = 'fa-check'; $header_name = pht('Active'); $header_color = 'bluegrey'; } $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($this->getRequest()->getUser()) ->setStatus($header_icon, $header_color, $header_name) ->setPolicyObject($paste); return $header; } private function buildActionView( PhabricatorUser $viewer, PhabricatorPaste $paste) { $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $paste, PhabricatorPolicyCapability::CAN_EDIT); $id = $paste->getID(); return id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($paste) ->setObjectURI($this->getRequest()->getRequestURI()) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Paste')) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setHref($this->getApplicationURI("edit/{$id}/"))) ->addAction( id(new PhabricatorActionView()) ->setName(pht('View Raw File')) ->setIcon('fa-file-text-o') ->setHref($this->getApplicationURI("raw/{$id}/"))); } private function buildPropertyView( PhabricatorPaste $paste, array $child_phids, PhabricatorActionListView $actions) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($paste) ->setActionList($actions); $properties->addProperty( pht('Author'), $viewer->renderHandle($paste->getAuthorPHID())); $properties->addProperty( pht('Created'), phabricator_datetime($paste->getDateCreated(), $viewer)); if ($paste->getParentPHID()) { $properties->addProperty( pht('Forked From'), $viewer->renderHandle($paste->getParentPHID())); } if ($child_phids) { $properties->addProperty( pht('Forks'), $viewer->renderHandleList($child_phids)); } $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, $paste); return $properties; } } diff --git a/src/applications/paste/editor/PhabricatorPasteEditEngine.php b/src/applications/paste/editor/PhabricatorPasteEditEngine.php index ce03a7ff06..2a8ebb4798 100644 --- a/src/applications/paste/editor/PhabricatorPasteEditEngine.php +++ b/src/applications/paste/editor/PhabricatorPasteEditEngine.php @@ -1,87 +1,96 @@ getViewer()); } protected function newObjectQuery() { return id(new PhabricatorPasteQuery()) ->needRawContent(true); } protected function getObjectCreateTitleText($object) { return pht('Create New Paste'); } protected function getObjectEditTitleText($object) { return pht('Edit %s %s', $object->getMonogram(), $object->getTitle()); } protected function getObjectEditShortText($object) { return $object->getMonogram(); } protected function getObjectCreateShortText() { return pht('Create Paste'); } + protected function getCommentViewHeaderText($object) { + $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); + if (!$is_serious) { + return pht('Eat Paste'); + } + + return parent::getCommentViewHeaderText($object); + } + protected function getObjectViewURI($object) { return '/P'.$object->getID(); } protected function buildCustomEditFields($object) { $langs = array( '' => pht('(Detect From Filename in Title)'), ) + PhabricatorEnv::getEnvConfig('pygments.dropdown-choices'); return array( id(new PhabricatorTextEditField()) ->setKey('title') ->setLabel(pht('Title')) ->setDescription(pht('Name of the paste.')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) ->setValue($object->getTitle()), id(new PhabricatorSelectEditField()) ->setKey('language') ->setLabel(pht('Language')) ->setDescription( pht( 'Programming language to interpret the paste as for syntax '. 'highlighting. By default, the language is inferred from the '. 'title.')) ->setAliases(array('lang')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE) ->setValue($object->getLanguage()) ->setOptions($langs), id(new PhabricatorSelectEditField()) ->setKey('status') ->setLabel(pht('Status')) ->setDescription(pht('Archive the paste.')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS) ->setValue($object->getStatus()) ->setOptions(PhabricatorPaste::getStatusNameMap()), id(new PhabricatorTextAreaEditField()) ->setKey('text') ->setLabel(pht('Text')) ->setDescription(pht('The main body text of the paste.')) ->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) ->setMonospaced(true) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setValue($object->getRawContent()), ); } } diff --git a/src/applications/people/application/PhabricatorPeopleApplication.php b/src/applications/people/application/PhabricatorPeopleApplication.php index b3a36687cb..9ab8a61bfd 100644 --- a/src/applications/people/application/PhabricatorPeopleApplication.php +++ b/src/applications/people/application/PhabricatorPeopleApplication.php @@ -1,199 +1,205 @@ getIsAdmin(); } public function getFlavorText() { return pht('Sort of a social utility.'); } public function canUninstall() { return false; } public function getEventListeners() { return array( new PhabricatorPeopleHovercardEventListener(), ); } public function getRoutes() { return array( '/people/' => array( '(query/(?P[^/]+)/)?' => 'PhabricatorPeopleListController', 'logs/(?:query/(?P[^/]+)/)?' => 'PhabricatorPeopleLogsController', 'invite/' => array( '(?:query/(?P[^/]+)/)?' => 'PhabricatorPeopleInviteListController', 'send/' => 'PhabricatorPeopleInviteSendController', ), 'approve/(?P[1-9]\d*)/' => 'PhabricatorPeopleApproveController', '(?Pdisapprove)/(?P[1-9]\d*)/' => 'PhabricatorPeopleDisableController', '(?Pdisable)/(?P[1-9]\d*)/' => 'PhabricatorPeopleDisableController', 'empower/(?P[1-9]\d*)/' => 'PhabricatorPeopleEmpowerController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorPeopleDeleteController', 'rename/(?P[1-9]\d*)/' => 'PhabricatorPeopleRenameController', 'welcome/(?P[1-9]\d*)/' => 'PhabricatorPeopleWelcomeController', 'create/' => 'PhabricatorPeopleCreateController', 'new/(?P[^/]+)/' => 'PhabricatorPeopleNewController', 'ldap/' => 'PhabricatorPeopleLdapController', 'editprofile/(?P[1-9]\d*)/' => 'PhabricatorPeopleProfileEditController', 'picture/(?P[1-9]\d*)/' => 'PhabricatorPeopleProfilePictureController', ), '/p/(?P[\w._-]+)/' => 'PhabricatorPeopleProfileController', '/p/(?P[\w._-]+)/calendar/' => 'PhabricatorPeopleCalendarController', ); } public function getRemarkupRules() { return array( new PhabricatorMentionRemarkupRule(), ); } protected function getCustomCapabilities() { return array( PeopleCreateUsersCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, ), PeopleBrowseUserDirectoryCapability::CAPABILITY => array(), ); } public function loadStatus(PhabricatorUser $user) { if (!$user->getIsAdmin()) { return array(); } + $limit = self::MAX_STATUS_ITEMS; $need_approval = id(new PhabricatorPeopleQuery()) ->setViewer($user) ->withIsApproved(false) ->withIsDisabled(false) - ->setLimit(self::MAX_STATUS_ITEMS) + ->setLimit($limit) ->execute(); - if (!$need_approval) { return array(); } $status = array(); $count = count($need_approval); - $count_str = self::formatStatusCount( - $count, - '%s Users Need Approval', - '%d User(s) Need Approval'); + if ($count >= $limit) { + $count_str = pht( + '%s+ User(s) Need Approval', + new PhutilNumber($limit - 1)); + } else { + $count_str = pht( + '%s User(s) Need Approval', + new PhutilNumber($count)); + } + $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($count_str) ->setCount($count); return $status; } public function buildMainMenuItems( PhabricatorUser $user, PhabricatorController $controller = null) { $items = array(); if ($user->isLoggedIn() && $user->isUserActivated()) { $profile = id(new PhabricatorPeopleQuery()) ->setViewer($user) ->needProfileImage(true) ->withPHIDs(array($user->getPHID())) ->executeOne(); $image = $profile->getProfileImageURI(); $item = id(new PHUIListItemView()) ->setName($user->getUsername()) ->setHref('/p/'.$user->getUsername().'/') ->addClass('core-menu-item') ->setAural(pht('Profile')) ->setOrder(100); $classes = array( 'phabricator-core-menu-icon', 'phabricator-core-menu-profile-image', ); $item->appendChild( phutil_tag( 'span', array( 'class' => implode(' ', $classes), 'style' => 'background-image: url('.$image.')', ), '')); $items[] = $item; } return $items; } public function getQuickCreateItems(PhabricatorUser $viewer) { $items = array(); $can_create = PhabricatorPolicyFilter::hasCapability( $viewer, $this, PeopleCreateUsersCapability::CAPABILITY); if ($can_create) { $item = id(new PHUIListItemView()) ->setName(pht('User Account')) ->setIcon('fa-users') ->setHref($this->getBaseURI().'create/'); $items[] = $item; } else if ($viewer->getIsAdmin()) { $item = id(new PHUIListItemView()) ->setName(pht('Bot Account')) ->setIcon('fa-android') ->setHref($this->getBaseURI().'new/bot/'); $items[] = $item; } return $items; } public function getApplicationSearchDocumentTypes() { return array( PhabricatorPeopleUserPHIDType::TYPECONST, ); } } diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php index 0f59e23286..9dabd50f66 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php @@ -1,251 +1,270 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $user = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->needProfileImage(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$user) { return new Aphront404Response(); } $profile_uri = '/p/'.$user->getUsername().'/'; $supported_formats = PhabricatorFile::getTransformableImageFormats(); $e_file = true; $errors = array(); if ($request->isFormPost()) { $phid = $request->getStr('phid'); $is_default = false; if ($phid == PhabricatorPHIDConstants::PHID_VOID) { $phid = null; $is_default = true; } else if ($phid) { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($phid)) ->executeOne(); } else { if ($request->getFileExists('picture')) { $file = PhabricatorFile::newFromPHPUpload( $_FILES['picture'], array( 'authorPHID' => $viewer->getPHID(), 'canCDN' => true, )); } else { $e_file = pht('Required'); $errors[] = pht( 'You must choose a file when uploading a new profile picture.'); } } if (!$errors && !$is_default) { if (!$file->isTransformableImage()) { $e_file = pht('Not Supported'); $errors[] = pht( 'This server only supports these image formats: %s.', implode(', ', $supported_formats)); } else { $xform = PhabricatorFileTransform::getTransformByKey( PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE); $xformed = $xform->executeTransform($file); } } if (!$errors) { if ($is_default) { $user->setProfileImagePHID(null); } else { $user->setProfileImagePHID($xformed->getPHID()); $xformed->attachToObject($user->getPHID()); } $user->save(); return id(new AphrontRedirectResponse())->setURI($profile_uri); } } $title = pht('Edit Profile Picture'); $form = id(new PHUIFormLayoutView()) ->setUser($viewer); $default_image = PhabricatorFile::loadBuiltin($viewer, 'profile.png'); $images = array(); $current = $user->getProfileImagePHID(); $has_current = false; if ($current) { $files = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($current)) ->execute(); if ($files) { $file = head($files); if ($file->isTransformableImage()) { $has_current = true; $images[$current] = array( 'uri' => $file->getBestURI(), 'tip' => pht('Current Picture'), ); } } } + $builtins = array( + 'user1.png', + 'user2.png', + 'user3.png', + 'user4.png', + 'user5.png', + 'user6.png', + 'user7.png', + 'user8.png', + 'user9.png', + ); + foreach ($builtins as $builtin) { + $file = PhabricatorFile::loadBuiltin($viewer, $builtin); + $images[$file->getPHID()] = array( + 'uri' => $file->getBestURI(), + 'tip' => pht('Builtin Image'), + ); + } + // Try to add external account images for any associated external accounts. $accounts = id(new PhabricatorExternalAccountQuery()) ->setViewer($viewer) ->withUserPHIDs(array($user->getPHID())) ->needImages(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->execute(); foreach ($accounts as $account) { $file = $account->getProfileImageFile(); if ($account->getProfileImagePHID() != $file->getPHID()) { // This is a default image, just skip it. continue; } $provider = PhabricatorAuthProvider::getEnabledProviderByKey( $account->getProviderKey()); if ($provider) { $tip = pht('Picture From %s', $provider->getProviderName()); } else { $tip = pht('Picture From External Account'); } if ($file->isTransformableImage()) { $images[$file->getPHID()] = array( 'uri' => $file->getBestURI(), 'tip' => $tip, ); } } $images[PhabricatorPHIDConstants::PHID_VOID] = array( 'uri' => $default_image->getBestURI(), 'tip' => pht('Default Picture'), ); require_celerity_resource('people-profile-css'); Javelin::initBehavior('phabricator-tooltips', array()); $buttons = array(); foreach ($images as $phid => $spec) { $button = javelin_tag( 'button', array( 'class' => 'grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], 'size' => 300, ), ), phutil_tag( 'img', array( 'height' => 50, 'width' => 50, 'src' => $spec['uri'], ))); $button = array( phutil_tag( 'input', array( 'type' => 'hidden', 'name' => 'phid', 'value' => $phid, )), $button, ); $button = phabricator_form( $viewer, array( 'class' => 'profile-image-form', 'method' => 'POST', ), $button); $buttons[] = $button; } if ($has_current) { $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Current Picture')) ->setValue(array_shift($buttons))); } $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Use Picture')) ->setValue($buttons)); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormErrors($errors) ->setForm($form); $upload_form = id(new AphrontFormView()) ->setUser($viewer) ->setEncType('multipart/form-data') ->appendChild( id(new AphrontFormFileControl()) ->setName('picture') ->setLabel(pht('Upload Picture')) ->setError($e_file) ->setCaption( pht('Supported formats: %s', implode(', ', $supported_formats)))) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($profile_uri) ->setValue(pht('Upload Picture'))); $upload_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Upload New Picture')) ->setForm($upload_form); $nav = $this->buildIconNavView($user); $nav->selectFilter('/'); $nav->appendChild($form_box); $nav->appendChild($upload_box); return $this->buildApplicationPage( $nav, array( 'title' => $title, )); } } diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php index 8c6516dd96..ef6a907b2c 100644 --- a/src/applications/phame/application/PhabricatorPhameApplication.php +++ b/src/applications/phame/application/PhabricatorPhameApplication.php @@ -1,114 +1,118 @@ pht('Phame User Guide'), 'href' => PhabricatorEnv::getDoclink('Phame User Guide'), ), ); } public function isPrototype() { return true; } public function getRoutes() { return array( '/phame/' => array( - '' => 'PhamePostListController', + '' => 'PhameHomeController', 'live/(?P[^/]+)/(?P.*)' => 'PhameBlogLiveController', 'post/' => array( '(?:(?Pdraft|all)/)?' => 'PhamePostListController', '(?:query/(?P[^/]+)/)?' => 'PhamePostListController', 'blogger/(?P[\w\.-_]+)/' => 'PhamePostListController', 'edit/(?:(?P[^/]+)/)?' => 'PhamePostEditController', + 'history/(?P\d+)/' => 'PhamePostHistoryController', 'view/(?P\d+)/' => 'PhamePostViewController', 'publish/(?P\d+)/' => 'PhamePostPublishController', + 'preview/(?P\d+)/' => 'PhamePostPreviewController', 'unpublish/(?P\d+)/' => 'PhamePostUnpublishController', 'notlive/(?P\d+)/' => 'PhamePostNotLiveController', 'preview/' => 'PhabricatorMarkupPreviewController', 'framed/(?P\d+)/' => 'PhamePostFramedController', 'new/' => 'PhamePostNewController', 'move/(?P\d+)/' => 'PhamePostNewController', 'comment/(?P[1-9]\d*)/' => 'PhamePostCommentController', ), 'blog/' => array( '(?:(?Puser|all)/)?' => 'PhameBlogListController', '(?:query/(?P[^/]+)/)?' => 'PhameBlogListController', 'archive/(?P[^/]+)/' => 'PhameBlogArchiveController', 'edit/(?P[^/]+)/' => 'PhameBlogEditController', 'view/(?P[^/]+)/' => 'PhameBlogViewController', + 'manage/(?P[^/]+)/' => 'PhameBlogManageController', 'feed/(?P[^/]+)/' => 'PhameBlogFeedController', 'new/' => 'PhameBlogEditController', + 'picture/(?P[1-9]\d*)/' => 'PhameBlogProfilePictureController', ), ) + $this->getResourceSubroutes(), ); } public function getResourceRoutes() { return array( '/phame/' => $this->getResourceSubroutes(), ); } private function getResourceSubroutes() { return array( 'r/(?P\d+)/(?P[^/]+)/(?P.*)' => 'PhameResourceController', ); } public function getBlogRoutes() { return array( '/(?P.*)' => 'PhameBlogLiveController', ); } public function getBlogCDNRoutes() { return array( '/phame/' => array( 'r/(?P\d+)/(?P[^/]+)/(?P.*)' => 'PhameResourceController', ), ); } public function getQuicksandURIPatternBlacklist() { return array( '/phame/live/.*', ); } protected function getCustomCapabilities() { return array( PhameBlogCreateCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_USER, 'caption' => pht('Default create policy for blogs.'), ), ); } } diff --git a/src/applications/phame/controller/PhameController.php b/src/applications/phame/controller/PhameController.php index 6606f7d30c..20acd8f098 100644 --- a/src/applications/phame/controller/PhameController.php +++ b/src/applications/phame/controller/PhameController.php @@ -1,114 +1,3 @@ getBloggerPHID(); - if ($post->getBlog()) { - $handle_phids[] = $post->getBlog()->getPHID(); - } - } - $handles = $viewer->loadHandles($handle_phids); - - $stories = array(); - foreach ($posts as $post) { - $blogger = $handles[$post->getBloggerPHID()]->renderLink(); - $blogger_uri = $handles[$post->getBloggerPHID()]->getURI(); - $blogger_image = $handles[$post->getBloggerPHID()]->getImageURI(); - - $blog = null; - if ($post->getBlog()) { - $blog = $handles[$post->getBlog()->getPHID()]->renderLink(); - } - - $phame_post = ''; - if ($post->getBody()) { - $phame_post = PhabricatorMarkupEngine::summarize($post->getBody()); - } - - $blog_view = $post->getViewURI(); - $phame_title = phutil_tag('a', array('href' => $blog_view), - $post->getTitle()); - - $blogger = phutil_tag('strong', array(), $blogger); - if ($post->isDraft()) { - $title = pht( - '%s drafted a blog post on %s.', - $blogger, - $blog); - $title = phutil_tag('em', array(), $title); - } else { - $title = pht( - '%s wrote a blog post on %s.', - $blogger, - $blog); - } - - $item = id(new PHUIObjectItemView()) - ->setObject($post) - ->setHeader($post->getTitle()) - ->setHref($this->getApplicationURI('post/view/'.$post->getID().'/')); - - $story = id(new PHUIFeedStoryView()) - ->setTitle($title) - ->setImage($blogger_image) - ->setImageHref($blogger_uri) - ->setAppIcon('fa-star') - ->setUser($viewer) - ->setPontification($phame_post, $phame_title); - - if (PhabricatorPolicyFilter::hasCapability( - $viewer, - $post, - PhabricatorPolicyCapability::CAN_EDIT)) { - - $story->addAction(id(new PHUIIconView()) - ->setHref($this->getApplicationURI('post/edit/'.$post->getID().'/')) - ->setIconFont('fa-pencil')); - } - - if ($post->getDatePublished()) { - $story->setEpoch($post->getDatePublished()); - } - - $stories[] = $story; - } - - if (empty($stories)) { - return id(new PHUIBoxView()) - ->appendChild($nodata) - ->addClass('mlt mlb msr msl'); - } - - return $stories; - } - - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $can_create = $this->hasApplicationCapability( - PhameBlogCreateCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('New Blog')) - ->setHref($this->getApplicationURI('/blog/new/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create)); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('New Post')) - ->setHref($this->getApplicationURI('/post/new/')) - ->setIcon('fa-pencil')); - return $crumbs; - } -} +abstract class PhameController extends PhabricatorController {} diff --git a/src/applications/phame/controller/PhameHomeController.php b/src/applications/phame/controller/PhameHomeController.php new file mode 100644 index 0000000000..fd3ab41cf7 --- /dev/null +++ b/src/applications/phame/controller/PhameHomeController.php @@ -0,0 +1,98 @@ +getViewer(); + + $pager = id(new AphrontCursorPagerView()) + ->readFromRequest($request); + + $posts = id(new PhamePostQuery()) + ->setViewer($viewer) + ->withVisibility(PhameConstants::VISIBILITY_PUBLISHED) + ->executeWithCursorPager($pager); + + $actions = $this->renderActions($viewer); + $action_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Search')) + ->setHref('#') + ->setIconFont('fa-search') + ->addClass('phui-mobile-menu') + ->setDropdownMenu($actions); + + $title = pht('Recent Posts'); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->addActionLink($action_button); + + $post_list = id(new PhamePostListView()) + ->setPosts($posts) + ->setViewer($viewer) + ->showBlog(true) + ->setNodata(pht('No Recent Visible Posts.')); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setBorder(true); + $crumbs->addTextCrumb( + pht('Recent Posts'), + $this->getApplicationURI('post/')); + + $page = id(new PHUIDocumentViewPro()) + ->setHeader($header) + ->appendChild($post_list); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $page, + )); + + + } + + private function renderActions($viewer) { + $actions = id(new PhabricatorActionListView()) + ->setUser($viewer); + + $actions->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil-square-o') + ->setHref($this->getApplicationURI('post/')) + ->setName(pht('Find Posts'))); + + $actions->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-star') + ->setHref($this->getApplicationURI('blog/')) + ->setName(pht('Find Blogs'))); + + return $actions; + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $can_create = $this->hasApplicationCapability( + PhameBlogCreateCapability::CAPABILITY); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('New Blog')) + ->setHref($this->getApplicationURI('/blog/new/')) + ->setIcon('fa-plus-square') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create)); + + return $crumbs; + } + +} diff --git a/src/applications/phame/controller/blog/PhameBlogListController.php b/src/applications/phame/controller/blog/PhameBlogListController.php index c162b82b6b..9bef5c1cf9 100644 --- a/src/applications/phame/controller/blog/PhameBlogListController.php +++ b/src/applications/phame/controller/blog/PhameBlogListController.php @@ -1,34 +1,51 @@ getURIData('queryKey'); $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($query_key) ->setSearchEngine(new PhameBlogSearchEngine()) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); } public function buildSideNavView() { $viewer = $this->getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); id(new PhameBlogSearchEngine()) ->setViewer($viewer) ->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); return $nav; } + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $can_create = $this->hasApplicationCapability( + PhameBlogCreateCapability::CAPABILITY); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('New Blog')) + ->setHref($this->getApplicationURI('/blog/new/')) + ->setIcon('fa-plus-square') + ->setDisabled(!$can_create) + ->setWorkflow(!$can_create)); + + return $crumbs; + } + } diff --git a/src/applications/phame/controller/blog/PhameBlogViewController.php b/src/applications/phame/controller/blog/PhameBlogManageController.php similarity index 82% copy from src/applications/phame/controller/blog/PhameBlogViewController.php copy to src/applications/phame/controller/blog/PhameBlogManageController.php index f74b838881..42aec0223c 100644 --- a/src/applications/phame/controller/blog/PhameBlogViewController.php +++ b/src/applications/phame/controller/blog/PhameBlogManageController.php @@ -1,195 +1,205 @@ getViewer(); $id = $request->getURIData('id'); $blog = id(new PhameBlogQuery()) ->setViewer($viewer) ->withIDs(array($id)) + ->needProfileImage(true) ->executeOne(); if (!$blog) { return new Aphront404Response(); } - $pager = id(new AphrontCursorPagerView()) - ->readFromRequest($request); - - $posts = id(new PhamePostQuery()) - ->setViewer($viewer) - ->withBlogPHIDs(array($blog->getPHID())) - ->executeWithCursorPager($pager); - if ($blog->isArchived()) { $header_icon = 'fa-ban'; $header_name = pht('Archived'); $header_color = 'dark'; } else { $header_icon = 'fa-check'; $header_name = pht('Active'); $header_color = 'bluegrey'; } + $picture = $blog->getProfileImageURI(); + $header = id(new PHUIHeaderView()) ->setHeader($blog->getName()) ->setUser($viewer) ->setPolicyObject($blog) + ->setImage($picture) ->setStatus($header_icon, $header_color, $header_name); $actions = $this->renderActions($blog, $viewer); $properties = $this->renderProperties($blog, $viewer, $actions); - $post_list = $this->renderPostList( - $posts, - $viewer, - pht('This blog has no visible posts.')); - - $post_list = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Latest Posts')) - ->appendChild($post_list); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( pht('Blogs'), $this->getApplicationURI('blog/')); $crumbs->addTextCrumb( $blog->getName()); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); + $timeline = $this->buildTransactionTimeline( + $blog, + new PhameBlogTransactionQuery()); + $timeline->setShouldTerminate(true); + return $this->newPage() ->setTitle($blog->getName()) ->setCrumbs($crumbs) ->appendChild( array( $object_box, - $post_list, + $timeline, )); } private function renderProperties( PhameBlog $blog, PhabricatorUser $viewer, PhabricatorActionListView $actions) { require_celerity_resource('aphront-tooltip-css'); Javelin::initBehavior('phabricator-tooltips'); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($blog) ->setActionList($actions); - $properties->addProperty( - pht('Skin'), - $blog->getSkin()); + $skin = $blog->getSkin(); + if (!$skin) { + $skin = pht('(No external skin)'); + } - $properties->addProperty( - pht('Domain'), - $blog->getDomain()); + $domain = $blog->getDomain(); + if (!$domain) { + $domain = pht('(No external domain)'); + } + + $properties->addProperty(pht('Skin'), $skin); + $properties->addProperty(pht('Domain'), $domain); $feed_uri = PhabricatorEnv::getProductionURI( $this->getApplicationURI('blog/feed/'.$blog->getID().'/')); $properties->addProperty( pht('Atom URI'), javelin_tag('a', array( 'href' => $feed_uri, 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => pht('Atom URI does not support custom domains.'), 'size' => 320, ), ), $feed_uri)); $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, $blog); $properties->addProperty( pht('Editable By'), $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($viewer) ->addObject($blog, PhameBlog::MARKUP_FIELD_DESCRIPTION) ->process(); $properties->invokeWillRenderEvent(); if (strlen($blog->getDescription())) { $description = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($blog->getDescription()), 'default', $viewer); $properties->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $properties->addTextContent($description); } return $properties; } private function renderActions(PhameBlog $blog, PhabricatorUser $viewer) { $actions = id(new PhabricatorActionListView()) ->setObject($blog) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $blog, PhabricatorPolicyCapability::CAN_EDIT); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-plus') ->setHref($this->getApplicationURI('post/edit/?blog='.$blog->getID())) ->setName(pht('Write Post')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $actions->addAction( id(new PhabricatorActionView()) ->setUser($viewer) ->setIcon('fa-globe') ->setHref($blog->getLiveURI()) ->setName(pht('View Live'))); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI('blog/edit/'.$blog->getID().'/')) ->setName(pht('Edit Blog')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); + $actions->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-picture-o') + ->setHref($this->getApplicationURI('blog/picture/'.$blog->getID().'/')) + ->setName(pht('Edit Blog Picture')) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + if ($blog->isArchived()) { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Activate Blog')) ->setIcon('fa-check') ->setHref( $this->getApplicationURI('blog/archive/'.$blog->getID().'/')) ->setDisabled(!$can_edit) ->setWorkflow(true)); } else { $actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Archive Blog')) ->setIcon('fa-ban') ->setHref( $this->getApplicationURI('blog/archive/'.$blog->getID().'/')) ->setDisabled(!$can_edit) ->setWorkflow(true)); } return $actions; } } diff --git a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php b/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php similarity index 66% copy from src/applications/people/controller/PhabricatorPeopleProfilePictureController.php copy to src/applications/phame/controller/blog/PhameBlogProfilePictureController.php index 0f59e23286..b41e7a5187 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfilePictureController.php +++ b/src/applications/phame/controller/blog/PhameBlogProfilePictureController.php @@ -1,251 +1,218 @@ id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $id = $request->getURIData('id'); - $user = id(new PhabricatorPeopleQuery()) + $blog = id(new PhameBlogQuery()) ->setViewer($viewer) - ->withIDs(array($this->id)) + ->withIDs(array($id)) ->needProfileImage(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); - if (!$user) { + if (!$blog) { return new Aphront404Response(); } - $profile_uri = '/p/'.$user->getUsername().'/'; + $blog_uri = '/phame/blog/manage/'.$id; $supported_formats = PhabricatorFile::getTransformableImageFormats(); $e_file = true; $errors = array(); if ($request->isFormPost()) { $phid = $request->getStr('phid'); $is_default = false; if ($phid == PhabricatorPHIDConstants::PHID_VOID) { $phid = null; $is_default = true; } else if ($phid) { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($phid)) ->executeOne(); } else { if ($request->getFileExists('picture')) { $file = PhabricatorFile::newFromPHPUpload( $_FILES['picture'], array( 'authorPHID' => $viewer->getPHID(), 'canCDN' => true, )); } else { $e_file = pht('Required'); $errors[] = pht( - 'You must choose a file when uploading a new profile picture.'); + 'You must choose a file when uploading a new blog picture.'); } } if (!$errors && !$is_default) { if (!$file->isTransformableImage()) { $e_file = pht('Not Supported'); $errors[] = pht( 'This server only supports these image formats: %s.', implode(', ', $supported_formats)); } else { $xform = PhabricatorFileTransform::getTransformByKey( PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE); $xformed = $xform->executeTransform($file); } } if (!$errors) { if ($is_default) { - $user->setProfileImagePHID(null); + $blog->setProfileImagePHID(null); } else { - $user->setProfileImagePHID($xformed->getPHID()); - $xformed->attachToObject($user->getPHID()); + $blog->setProfileImagePHID($xformed->getPHID()); + $xformed->attachToObject($blog->getPHID()); } - $user->save(); - return id(new AphrontRedirectResponse())->setURI($profile_uri); + $blog->save(); + return id(new AphrontRedirectResponse())->setURI($blog_uri); } } - $title = pht('Edit Profile Picture'); + $title = pht('Edit Blog Picture'); $form = id(new PHUIFormLayoutView()) ->setUser($viewer); - $default_image = PhabricatorFile::loadBuiltin($viewer, 'profile.png'); + $default_image = PhabricatorFile::loadBuiltin($viewer, 'blog.png'); $images = array(); - $current = $user->getProfileImagePHID(); + $current = $blog->getProfileImagePHID(); $has_current = false; if ($current) { $files = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($current)) ->execute(); if ($files) { $file = head($files); if ($file->isTransformableImage()) { $has_current = true; $images[$current] = array( 'uri' => $file->getBestURI(), 'tip' => pht('Current Picture'), ); } } } - // Try to add external account images for any associated external accounts. - $accounts = id(new PhabricatorExternalAccountQuery()) - ->setViewer($viewer) - ->withUserPHIDs(array($user->getPHID())) - ->needImages(true) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->execute(); - - foreach ($accounts as $account) { - $file = $account->getProfileImageFile(); - if ($account->getProfileImagePHID() != $file->getPHID()) { - // This is a default image, just skip it. - continue; - } - - $provider = PhabricatorAuthProvider::getEnabledProviderByKey( - $account->getProviderKey()); - if ($provider) { - $tip = pht('Picture From %s', $provider->getProviderName()); - } else { - $tip = pht('Picture From External Account'); - } - - if ($file->isTransformableImage()) { - $images[$file->getPHID()] = array( - 'uri' => $file->getBestURI(), - 'tip' => $tip, - ); - } - } - $images[PhabricatorPHIDConstants::PHID_VOID] = array( 'uri' => $default_image->getBestURI(), 'tip' => pht('Default Picture'), ); require_celerity_resource('people-profile-css'); Javelin::initBehavior('phabricator-tooltips', array()); $buttons = array(); foreach ($images as $phid => $spec) { $button = javelin_tag( 'button', array( 'class' => 'grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], 'size' => 300, ), ), phutil_tag( 'img', array( 'height' => 50, 'width' => 50, 'src' => $spec['uri'], ))); $button = array( phutil_tag( 'input', array( 'type' => 'hidden', 'name' => 'phid', 'value' => $phid, )), $button, ); $button = phabricator_form( $viewer, array( 'class' => 'profile-image-form', 'method' => 'POST', ), $button); $buttons[] = $button; } if ($has_current) { $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Current Picture')) ->setValue(array_shift($buttons))); } $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Use Picture')) ->setValue($buttons)); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormErrors($errors) ->setForm($form); $upload_form = id(new AphrontFormView()) ->setUser($viewer) ->setEncType('multipart/form-data') ->appendChild( id(new AphrontFormFileControl()) ->setName('picture') ->setLabel(pht('Upload Picture')) ->setError($e_file) ->setCaption( pht('Supported formats: %s', implode(', ', $supported_formats)))) ->appendChild( id(new AphrontFormSubmitControl()) - ->addCancelButton($profile_uri) + ->addCancelButton($blog_uri) ->setValue(pht('Upload Picture'))); $upload_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Upload New Picture')) ->setForm($upload_form); - $nav = $this->buildIconNavView($user); - $nav->selectFilter('/'); - $nav->appendChild($form_box); - $nav->appendChild($upload_box); - - return $this->buildApplicationPage( - $nav, - array( - 'title' => $title, + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + pht('Blogs'), + $this->getApplicationURI('blog/')); + $crumbs->addTextCrumb( + $blog->getName(), + $this->getApplicationURI('blog/view/'.$id)); + $crumbs->addTextCrumb(pht('Blog Picture')); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $form_box, + $upload_box, )); + } } diff --git a/src/applications/phame/controller/blog/PhameBlogViewController.php b/src/applications/phame/controller/blog/PhameBlogViewController.php index f74b838881..e822e6b933 100644 --- a/src/applications/phame/controller/blog/PhameBlogViewController.php +++ b/src/applications/phame/controller/blog/PhameBlogViewController.php @@ -1,195 +1,136 @@ getViewer(); $id = $request->getURIData('id'); $blog = id(new PhameBlogQuery()) ->setViewer($viewer) ->withIDs(array($id)) + ->needProfileImage(true) ->executeOne(); if (!$blog) { return new Aphront404Response(); } + $this->blog = $blog; $pager = id(new AphrontCursorPagerView()) ->readFromRequest($request); $posts = id(new PhamePostQuery()) ->setViewer($viewer) ->withBlogPHIDs(array($blog->getPHID())) ->executeWithCursorPager($pager); if ($blog->isArchived()) { $header_icon = 'fa-ban'; $header_name = pht('Archived'); $header_color = 'dark'; } else { $header_icon = 'fa-check'; $header_name = pht('Active'); $header_color = 'bluegrey'; } + $actions = $this->renderActions($blog, $viewer); + $action_button = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Actions')) + ->setHref('#') + ->setIconFont('fa-bars') + ->addClass('phui-mobile-menu') + ->setDropdownMenu($actions); + $header = id(new PHUIHeaderView()) ->setHeader($blog->getName()) ->setUser($viewer) ->setPolicyObject($blog) - ->setStatus($header_icon, $header_color, $header_name); - - $actions = $this->renderActions($blog, $viewer); - $properties = $this->renderProperties($blog, $viewer, $actions); - $post_list = $this->renderPostList( - $posts, - $viewer, - pht('This blog has no visible posts.')); + ->setStatus($header_icon, $header_color, $header_name) + ->addActionLink($action_button); - $post_list = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Latest Posts')) - ->appendChild($post_list); + $post_list = id(new PhamePostListView()) + ->setPosts($posts) + ->setViewer($viewer) + ->setNodata(pht('This blog has no visible posts.')); $crumbs = $this->buildApplicationCrumbs(); + $crumbs->setBorder(true); $crumbs->addTextCrumb( pht('Blogs'), $this->getApplicationURI('blog/')); $crumbs->addTextCrumb( $blog->getName()); - $object_box = id(new PHUIObjectBoxView()) + $page = id(new PHUIDocumentViewPro()) ->setHeader($header) - ->addPropertyList($properties); - - return $this->newPage() - ->setTitle($blog->getName()) - ->setCrumbs($crumbs) - ->appendChild( - array( - $object_box, - $post_list, - )); - } - - private function renderProperties( - PhameBlog $blog, - PhabricatorUser $viewer, - PhabricatorActionListView $actions) { - - require_celerity_resource('aphront-tooltip-css'); - Javelin::initBehavior('phabricator-tooltips'); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($blog) - ->setActionList($actions); - - $properties->addProperty( - pht('Skin'), - $blog->getSkin()); - - $properties->addProperty( - pht('Domain'), - $blog->getDomain()); - - $feed_uri = PhabricatorEnv::getProductionURI( - $this->getApplicationURI('blog/feed/'.$blog->getID().'/')); - $properties->addProperty( - pht('Atom URI'), - javelin_tag('a', - array( - 'href' => $feed_uri, - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => pht('Atom URI does not support custom domains.'), - 'size' => 320, - ), - ), - $feed_uri)); - - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $blog); - - $properties->addProperty( - pht('Editable By'), - $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); - - $engine = id(new PhabricatorMarkupEngine()) - ->setViewer($viewer) - ->addObject($blog, PhameBlog::MARKUP_FIELD_DESCRIPTION) - ->process(); - - $properties->invokeWillRenderEvent(); + ->appendChild($post_list); + $description = null; if (strlen($blog->getDescription())) { $description = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($blog->getDescription()), 'default', $viewer); - $properties->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); - $properties->addTextContent($description); + } else { + $description = phutil_tag('em', array(), pht('No description.')); } - return $properties; + $about = id(new PhameDescriptionView()) + ->setTitle(pht('About %s', $blog->getName())) + ->setDescription($description) + ->setImage($blog->getProfileImageURI()); + + return $this->newPage() + ->setTitle($blog->getName()) + ->setCrumbs($crumbs) + ->appendChild( + array( + $page, + $about, + )); } private function renderActions(PhameBlog $blog, PhabricatorUser $viewer) { $actions = id(new PhabricatorActionListView()) ->setObject($blog) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $blog, PhabricatorPolicyCapability::CAN_EDIT); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-plus') ->setHref($this->getApplicationURI('post/edit/?blog='.$blog->getID())) ->setName(pht('Write Post')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $actions->addAction( id(new PhabricatorActionView()) ->setUser($viewer) ->setIcon('fa-globe') ->setHref($blog->getLiveURI()) ->setName(pht('View Live'))); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI('blog/edit/'.$blog->getID().'/')) - ->setName(pht('Edit Blog')) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); - - if ($blog->isArchived()) { - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Activate Blog')) - ->setIcon('fa-check') - ->setHref( - $this->getApplicationURI('blog/archive/'.$blog->getID().'/')) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); - } else { - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Archive Blog')) - ->setIcon('fa-ban') - ->setHref( - $this->getApplicationURI('blog/archive/'.$blog->getID().'/')) - ->setDisabled(!$can_edit) - ->setWorkflow(true)); - } + ->setHref($this->getApplicationURI('blog/manage/'.$blog->getID().'/')) + ->setName(pht('Manage Blog'))); return $actions; } } diff --git a/src/applications/phame/controller/post/PhamePostEditController.php b/src/applications/phame/controller/post/PhamePostEditController.php index 145c291efb..2f7d8572de 100644 --- a/src/applications/phame/controller/post/PhamePostEditController.php +++ b/src/applications/phame/controller/post/PhamePostEditController.php @@ -1,211 +1,211 @@ getViewer(); $id = $request->getURIData('id'); if ($id) { $post = id(new PhamePostQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$post) { return new Aphront404Response(); } $cancel_uri = $this->getApplicationURI('/post/view/'.$id.'/'); $submit_button = pht('Save Changes'); $page_title = pht('Edit Post'); $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( $post->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $v_projects = array_reverse($v_projects); $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID( $post->getPHID()); } else { $blog = id(new PhameBlogQuery()) ->setViewer($viewer) ->withIDs(array($request->getInt('blog'))) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$blog) { return new Aphront404Response(); } $v_projects = array(); $v_cc = array(); $post = PhamePost::initializePost($viewer, $blog); $cancel_uri = $this->getApplicationURI('/blog/view/'.$blog->getID().'/'); $submit_button = pht('Create Post'); $page_title = pht('Create Post'); } $title = $post->getTitle(); $phame_title = $post->getPhameTitle(); $body = $post->getBody(); $visibility = $post->getVisibility(); $e_title = true; $e_phame_title = true; $validation_exception = null; if ($request->isFormPost()) { $title = $request->getStr('title'); $phame_title = $request->getStr('phame_title'); $phame_title = PhabricatorSlug::normalize($phame_title); $body = $request->getStr('body'); $v_projects = $request->getArr('projects'); $v_cc = $request->getArr('cc'); $visibility = $request->getInt('visibility'); $xactions = array( id(new PhamePostTransaction()) ->setTransactionType(PhamePostTransaction::TYPE_TITLE) ->setNewValue($title), id(new PhamePostTransaction()) ->setTransactionType(PhamePostTransaction::TYPE_PHAME_TITLE) ->setNewValue($phame_title), id(new PhamePostTransaction()) ->setTransactionType(PhamePostTransaction::TYPE_BODY) ->setNewValue($body), id(new PhamePostTransaction()) ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) ->setNewValue($visibility), id(new PhamePostTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) ->setNewValue(array('=' => $v_cc)), ); $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new PhamePostTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $proj_edge_type) ->setNewValue(array('=' => array_fuse($v_projects))); $editor = id(new PhamePostEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($post, $xactions); $uri = $this->getApplicationURI('/post/view/'.$post->getID().'/'); return id(new AphrontRedirectResponse())->setURI($uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_title = $validation_exception->getShortMessage( PhamePostTransaction::TYPE_TITLE); $e_phame_title = $validation_exception->getShortMessage( PhamePostTransaction::TYPE_PHAME_TITLE); } } $handle = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs(array($post->getBlogPHID())) ->executeOne(); $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('blog', $request->getInt('blog')) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Blog')) ->setValue($handle->renderLink())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Title')) ->setName('title') ->setValue($title) ->setID('post-title') ->setError($e_title)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Phame Title')) ->setName('phame_title') ->setValue(rtrim($phame_title, '/')) ->setID('post-phame-title') ->setCaption(pht('Up to 64 alphanumeric characters '. 'with underscores for spaces. '. 'Formatting is enforced.')) ->setError($e_phame_title)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Visibility')) ->setName('visibility') - ->setvalue($visibility) + ->setValue($visibility) ->setOptions(PhameConstants::getPhamePostStatusMap())) ->appendChild( id(new PhabricatorRemarkupControl()) ->setLabel(pht('Body')) ->setName('body') ->setValue($body) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setID('post-body') ->setUser($viewer) ->setDisableMacros(true)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Subscribers')) ->setName('cc') ->setValue($v_cc) ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource())) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setValue($v_projects) ->setDatasource(new PhabricatorProjectDatasource())) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($submit_button)); $preview = id(new PHUIRemarkupPreviewPanel()) ->setHeader($post->getTitle()) ->setPreviewURI($this->getApplicationURI('post/preview/')) ->setControlID('post-body') ->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT); Javelin::initBehavior( 'phame-post-preview', array( 'title' => 'post-title', 'phame_title' => 'post-phame-title', )); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setValidationException($validation_exception) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( $page_title, $this->getApplicationURI('/post/view/'.$id.'/')); return $this->newPage() ->setTitle($page_title) ->setCrumbs($crumbs) ->appendChild( array( $form_box, $preview, )); } } diff --git a/src/applications/phame/controller/post/PhamePostHistoryController.php b/src/applications/phame/controller/post/PhamePostHistoryController.php new file mode 100644 index 0000000000..616538f4bc --- /dev/null +++ b/src/applications/phame/controller/post/PhamePostHistoryController.php @@ -0,0 +1,55 @@ +getViewer(); + + $post = id(new PhamePostQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); + + if (!$post) { + return new Aphront404Response(); + } + + $blog = $post->getBlog(); + + $crumbs = $this->buildApplicationCrumbs(); + if ($blog) { + $crumbs->addTextCrumb( + $blog->getName(), + $this->getApplicationURI('blog/view/'.$blog->getID().'/')); + } else { + $crumbs->addTextCrumb( + pht('[No Blog]'), + null); + } + $crumbs->addTextCrumb( + $post->getTitle(), + $this->getApplicationURI('post/view/'.$post->getID().'/')); + $crumbs->addTextCrumb(pht('Post History')); + $crumbs->setBorder(true); + + $timeline = $this->buildTransactionTimeline( + $post, + new PhamePostTransactionQuery()); + $timeline->setShouldTerminate(true); + + return $this->newPage() + ->setTitle($post->getTitle()) + ->setPageObjectPHIDs(array($post->getPHID())) + ->setCrumbs($crumbs) + ->appendChild( + array( + $timeline, + )); + } + + +} diff --git a/src/applications/phame/controller/post/PhamePostPublishController.php b/src/applications/phame/controller/post/PhamePostPreviewController.php similarity index 86% copy from src/applications/phame/controller/post/PhamePostPublishController.php copy to src/applications/phame/controller/post/PhamePostPreviewController.php index 9b593b1313..c9e9c0ee40 100644 --- a/src/applications/phame/controller/post/PhamePostPublishController.php +++ b/src/applications/phame/controller/post/PhamePostPreviewController.php @@ -1,84 +1,89 @@ getViewer(); $id = $request->getURIData('id'); $post = id(new PhamePostQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$post) { return new Aphront404Response(); } $view_uri = $this->getApplicationURI('/post/view/'.$post->getID().'/'); if ($request->isFormPost()) { $xactions = array(); $xactions[] = id(new PhamePostTransaction()) ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) ->setNewValue(PhameConstants::VISIBILITY_PUBLISHED); id(new PhamePostEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->applyTransactions($post, $xactions); return id(new AphrontRedirectResponse())->setURI($view_uri); } $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Publish Post')) ->addCancelButton($view_uri)); $frame = $this->renderPreviewFrame($post); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Preview Post')) ->setForm($form); + $blog = $post->getBlog(); + $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Preview'), $view_uri); + $crumbs->addTextCrumb( + $blog->getName(), + $this->getApplicationURI('blog/view/'.$blog->getID().'/')); + $crumbs->addTextCrumb(pht('Preview Post'), $view_uri); return $this->newPage() ->setTitle(pht('Preview Post')) ->setCrumbs($crumbs) ->appendChild( array( $form_box, $frame, )); } private function renderPreviewFrame(PhamePost $post) { return phutil_tag( 'div', array( 'style' => 'text-align: center; padding: 16px;', ), phutil_tag( 'iframe', array( - 'style' => 'width: 100%; height: 600px; '. + 'style' => 'width: 100%; height: 800px; '. 'border: 1px solid #BFCFDA; '. 'background-color: #fff; '. 'border-radius: 3px; ', 'src' => $this->getApplicationURI('/post/framed/'.$post->getID().'/'), ), '')); } } diff --git a/src/applications/phame/controller/post/PhamePostPublishController.php b/src/applications/phame/controller/post/PhamePostPublishController.php index 9b593b1313..5e87b3acb6 100644 --- a/src/applications/phame/controller/post/PhamePostPublishController.php +++ b/src/applications/phame/controller/post/PhamePostPublishController.php @@ -1,84 +1,52 @@ getViewer(); $id = $request->getURIData('id'); $post = id(new PhamePostQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$post) { return new Aphront404Response(); } - $view_uri = $this->getApplicationURI('/post/view/'.$post->getID().'/'); - if ($request->isFormPost()) { $xactions = array(); $xactions[] = id(new PhamePostTransaction()) ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) ->setNewValue(PhameConstants::VISIBILITY_PUBLISHED); id(new PhamePostEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->applyTransactions($post, $xactions); - return id(new AphrontRedirectResponse())->setURI($view_uri); + return id(new AphrontRedirectResponse()) + ->setURI($this->getApplicationURI('/post/view/'.$post->getID().'/')); } - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Publish Post')) - ->addCancelButton($view_uri)); - - $frame = $this->renderPreviewFrame($post); - - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Preview Post')) - ->setForm($form); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Preview'), $view_uri); + $cancel_uri = $this->getApplicationURI('/post/view/'.$post->getID().'/'); - return $this->newPage() - ->setTitle(pht('Preview Post')) - ->setCrumbs($crumbs) + $dialog = $this->newDialog() + ->setTitle(pht('Publish Post?')) ->appendChild( - array( - $form_box, - $frame, - )); - } + pht( + 'The post "%s" will go live once you publish it.', + $post->getTitle())) + ->addSubmitButton(pht('Publish')) + ->addCancelButton($cancel_uri); - private function renderPreviewFrame(PhamePost $post) { - - return phutil_tag( - 'div', - array( - 'style' => 'text-align: center; padding: 16px;', - ), - phutil_tag( - 'iframe', - array( - 'style' => 'width: 100%; height: 600px; '. - 'border: 1px solid #BFCFDA; '. - 'background-color: #fff; '. - 'border-radius: 3px; ', - 'src' => $this->getApplicationURI('/post/framed/'.$post->getID().'/'), - ), - '')); + return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/phame/controller/post/PhamePostUnpublishController.php b/src/applications/phame/controller/post/PhamePostUnpublishController.php index ca09ef8879..9a06c47447 100644 --- a/src/applications/phame/controller/post/PhamePostUnpublishController.php +++ b/src/applications/phame/controller/post/PhamePostUnpublishController.php @@ -1,54 +1,53 @@ getViewer(); $id = $request->getURIData('id'); $post = id(new PhamePostQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$post) { return new Aphront404Response(); } if ($request->isFormPost()) { $xactions = array(); $xactions[] = id(new PhamePostTransaction()) ->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY) ->setNewValue(PhameConstants::VISIBILITY_DRAFT); id(new PhamePostEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->applyTransactions($post, $xactions); return id(new AphrontRedirectResponse()) ->setURI($this->getApplicationURI('/post/view/'.$post->getID().'/')); } $cancel_uri = $this->getApplicationURI('/post/view/'.$post->getID().'/'); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + $dialog = $this->newDialog() ->setTitle(pht('Unpublish Post?')) ->appendChild( pht( 'The post "%s" will no longer be visible to other users until you '. 'republish it.', $post->getTitle())) ->addSubmitButton(pht('Unpublish')) ->addCancelButton($cancel_uri); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index bff1113efe..76b33f1b03 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -1,232 +1,249 @@ getViewer(); $post = id(new PhamePostQuery()) ->setViewer($viewer) ->withIDs(array($request->getURIData('id'))) ->executeOne(); if (!$post) { return new Aphront404Response(); } $blog = $post->getBlog(); $crumbs = $this->buildApplicationCrumbs(); if ($blog) { $crumbs->addTextCrumb( $blog->getName(), $this->getApplicationURI('blog/view/'.$blog->getID().'/')); } else { $crumbs->addTextCrumb( pht('[No Blog]'), null); } $crumbs->addTextCrumb( $post->getTitle(), $this->getApplicationURI('post/view/'.$post->getID().'/')); $crumbs->setBorder(true); $actions = $this->renderActions($post, $viewer); - $properties = $this->renderProperties($post, $viewer); $action_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Actions')) ->setHref('#') ->setIconFont('fa-bars') ->addClass('phui-mobile-menu') ->setDropdownMenu($actions); $header = id(new PHUIHeaderView()) ->setHeader($post->getTitle()) ->setUser($viewer) ->setPolicyObject($post) ->addActionLink($action_button); $document = id(new PHUIDocumentViewPro()) - ->setHeader($header) - ->setPropertyList($properties); + ->setHeader($header); if ($post->isDraft()) { $document->appendChild( id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setTitle(pht('Draft Post')) ->appendChild( pht( 'Only you can see this draft until you publish it. '. - 'Use "Preview / Publish" to publish this post.'))); + 'Use "Preview or Publish" to publish this post.'))); } if (!$post->getBlog()) { $document->appendChild( id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setTitle(pht('Not On A Blog')) ->appendChild( pht( 'This post is not associated with a blog (the blog may have '. 'been deleted). Use "Move Post" to move it to a new blog.'))); } $engine = id(new PhabricatorMarkupEngine()) ->setViewer($viewer) ->addObject($post, PhamePost::MARKUP_FIELD_BODY) ->process(); $document->appendChild( phutil_tag( 'div', array( 'class' => 'phabricator-remarkup', ), $engine->getOutput($post, PhamePost::MARKUP_FIELD_BODY))); + $blogger = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($post->getBloggerPHID())) + ->needProfileImage(true) + ->executeOne(); + $blogger_profile = $blogger->loadUserProfile(); + + $author = phutil_tag( + 'a', + array( + 'href' => '/p/'.$blogger->getUsername().'/', + ), + $blogger->getUsername()); + + $date = phabricator_datetime($post->getDatePublished(), $viewer); + if ($post->isDraft()) { + $subtitle = pht('Unpublished draft by %s.', $author); + } else { + $subtitle = pht('Written by %s on %s.', $author, $date); + } + + $about = id(new PhameDescriptionView()) + ->setTitle($subtitle) + ->setDescription($blogger_profile->getTitle()) + ->setImage($blogger->getProfileImageURI()) + ->setImageHref('/p/'.$blogger->getUsername()); + $timeline = $this->buildTransactionTimeline( $post, id(new PhamePostTransactionQuery()) ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))); $timeline = phutil_tag_div('phui-document-view-pro-box', $timeline); $add_comment = $this->buildCommentForm($post); + $add_comment = phutil_tag_div('mlb mlt', $add_comment); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($post); + + $properties->invokeWillRenderEvent(); return $this->newPage() ->setTitle($post->getTitle()) - ->addClass('pro-white-background') ->setPageObjectPHIDs(array($post->getPHID())) ->setCrumbs($crumbs) ->appendChild( array( $document, + $about, + $properties, $timeline, $add_comment, )); } private function renderActions( PhamePost $post, PhabricatorUser $viewer) { $actions = id(new PhabricatorActionListView()) ->setObject($post) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $post, PhabricatorPolicyCapability::CAN_EDIT); $id = $post->getID(); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI('post/edit/'.$id.'/')) ->setName(pht('Edit Post')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-arrows') ->setHref($this->getApplicationURI('post/move/'.$id.'/')) ->setName(pht('Move Post')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); + $actions->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-history') + ->setHref($this->getApplicationURI('post/history/'.$id.'/')) + ->setName(pht('View History'))); + if ($post->isDraft()) { $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-eye') ->setHref($this->getApplicationURI('post/publish/'.$id.'/')) ->setDisabled(!$can_edit) - ->setName(pht('Preview / Publish'))); + ->setName(pht('Publish')) + ->setWorkflow(true)); + $actions->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-eye') + ->setHref($this->getApplicationURI('post/preview/'.$id.'/')) + ->setDisabled(!$can_edit) + ->setName(pht('Preview in Skin'))); } else { $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-eye-slash') ->setHref($this->getApplicationURI('post/unpublish/'.$id.'/')) ->setName(pht('Unpublish')) ->setDisabled(!$can_edit) ->setWorkflow(true)); } $blog = $post->getBlog(); $can_view_live = $blog && !$post->isDraft(); if ($can_view_live) { $live_uri = $blog->getLiveURI($post); } else { $live_uri = 'post/notlive/'.$post->getID().'/'; $live_uri = $this->getApplicationURI($live_uri); } $actions->addAction( id(new PhabricatorActionView()) ->setUser($viewer) ->setIcon('fa-globe') ->setHref($live_uri) ->setName(pht('View Live')) ->setDisabled(!$can_view_live) ->setWorkflow(!$can_view_live)); return $actions; } - private function renderProperties( - PhamePost $post, - PhabricatorUser $viewer) { - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($post); - - $properties->addProperty( - pht('Blog'), - $viewer->renderHandle($post->getBlogPHID())); - - $properties->addProperty( - pht('Blogger'), - $viewer->renderHandle($post->getBloggerPHID())); - - $properties->addProperty( - pht('Published'), - $post->isDraft() - ? pht('Draft') - : phabricator_datetime($post->getDatePublished(), $viewer)); - - $properties->invokeWillRenderEvent(); - - return $properties; - } - private function buildCommentForm(PhamePost $post) { $viewer = $this->getViewer(); - $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); - - $add_comment_header = $is_serious - ? pht('Add Comment') - : pht('Derp Text'); - $draft = PhabricatorDraft::newFromUserAndKey( $viewer, $post->getPHID()); $box = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setObjectPHID($post->getPHID()) ->setDraft($draft) - ->setHeaderText($add_comment_header) + ->setHeaderText(pht('Add Comment')) ->setAction($this->getApplicationURI('post/comment/'.$post->getID().'/')) ->setSubmitButtonName(pht('Add Comment')); return phutil_tag_div('phui-document-view-pro-box', $box); } } diff --git a/src/applications/phame/editor/PhamePostEditor.php b/src/applications/phame/editor/PhamePostEditor.php index 330ce86c19..e3cd1a4417 100644 --- a/src/applications/phame/editor/PhamePostEditor.php +++ b/src/applications/phame/editor/PhamePostEditor.php @@ -1,242 +1,255 @@ getTransactionType()) { case PhamePostTransaction::TYPE_TITLE: return $object->getTitle(); case PhamePostTransaction::TYPE_PHAME_TITLE: return $object->getPhameTitle(); case PhamePostTransaction::TYPE_BODY: return $object->getBody(); case PhamePostTransaction::TYPE_VISIBILITY: return $object->getVisibility(); } } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhamePostTransaction::TYPE_TITLE: case PhamePostTransaction::TYPE_PHAME_TITLE: case PhamePostTransaction::TYPE_BODY: case PhamePostTransaction::TYPE_VISIBILITY: return $xaction->getNewValue(); } } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhamePostTransaction::TYPE_TITLE: return $object->setTitle($xaction->getNewValue()); case PhamePostTransaction::TYPE_PHAME_TITLE: return $object->setPhameTitle($xaction->getNewValue()); case PhamePostTransaction::TYPE_BODY: return $object->setBody($xaction->getNewValue()); case PhamePostTransaction::TYPE_VISIBILITY: if ($xaction->getNewValue() == PhameConstants::VISIBILITY_DRAFT) { $object->setDatePublished(0); } else { - $object->setDatePublished(time()); + $object->setDatePublished(PhabricatorTime::getNow()); } return $object->setVisibility($xaction->getNewValue()); } return parent::applyCustomInternalTransaction($object, $xaction); } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhamePostTransaction::TYPE_TITLE: case PhamePostTransaction::TYPE_PHAME_TITLE: case PhamePostTransaction::TYPE_BODY: case PhamePostTransaction::TYPE_VISIBILITY: return; } return parent::applyCustomExternalTransaction($object, $xaction); } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhamePostTransaction::TYPE_TITLE: $missing = $this->validateIsEmptyTextField( $object->getTitle(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('Title is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } break; case PhamePostTransaction::TYPE_PHAME_TITLE: if (!$xactions) { continue; } $missing = $this->validateIsEmptyTextField( $object->getPhameTitle(), $xactions); $phame_title = last($xactions)->getNewValue(); if ($missing || $phame_title == '/') { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('Phame title is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } $duplicate_post = id(new PhamePostQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPhameTitles(array($phame_title)) ->executeOne(); if ($duplicate_post && $duplicate_post->getID() != $object->getID()) { $error_text = pht( 'Phame title must be unique; another post already has this phame '. 'title.'); $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Not Unique'), $error_text, nonempty(last($xactions), null)); $errors[] = $error; } break; } return $errors; } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { if ($object->isDraft()) { return false; } return true; } protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { if ($object->isDraft()) { return false; } return true; } protected function getMailTo(PhabricatorLiskDAO $object) { $phids = array(); $phids[] = $object->getBloggerPHID(); $phids[] = $this->requireActor()->getPHID(); $blog_phid = $object->getBlogPHID(); if ($blog_phid) { $cc_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( $blog_phid); foreach ($cc_phids as $cc) { $phids[] = $cc; } } return $phids; } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $phid = $object->getPHID(); $title = $object->getTitle(); return id(new PhabricatorMetaMTAMail()) ->setSubject($title) ->addHeader('Thread-Topic', $phid); } protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new PhamePostReplyHandler()) ->setMailReceiver($object); } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $body = parent::buildMailBody($object, $xactions); + // We don't send mail if the object is a draft, and we only want + // to include the full body of the post on the either the + // first creation or if it was created as a draft, once it goes live. if ($this->getIsNewObject()) { $body->addRemarkupSection(null, $object->getBody()); + } else { + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PhamePostTransaction::TYPE_VISIBILITY: + if (!$object->isDraft()) { + $body->addRemarkupSection(null, $object->getBody()); + } + break; + } + } } $body->addLinkSection( pht('POST DETAIL'), PhabricatorEnv::getProductionURI($object->getViewURI())); return $body; } public function getMailTagsMap() { return array( PhamePostTransaction::MAILTAG_CONTENT => pht("A post's content changes."), PhamePostTransaction::MAILTAG_COMMENT => pht('Someone comments on a post.'), PhamePostTransaction::MAILTAG_OTHER => pht('Other post activity not listed above occurs.'), ); } protected function getMailSubjectPrefix() { return '[Phame]'; } protected function supportsSearch() { return false; } } diff --git a/src/applications/phame/query/PhameBlogQuery.php b/src/applications/phame/query/PhameBlogQuery.php index 279ff91724..36ae306345 100644 --- a/src/applications/phame/query/PhameBlogQuery.php +++ b/src/applications/phame/query/PhameBlogQuery.php @@ -1,78 +1,118 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withDomain($domain) { $this->domain = $domain; return $this; } public function withStatuses(array $status) { $this->statuses = $status; return $this; } + public function needProfileImage($need) { + $this->needProfileImage = $need; + return $this; + } + public function newResultObject() { return new PhameBlog(); } protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->statuses !== null) { $where[] = qsprintf( $conn, 'status IN (%Ls)', $this->statuses); } if ($this->ids !== null) { $where[] = qsprintf( $conn, 'id IN (%Ls)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, 'phid IN (%Ls)', $this->phids); } if ($this->domain !== null) { $where[] = qsprintf( $conn, 'domain = %s', $this->domain); } return $where; } + protected function didFilterPage(array $blogs) { + if ($this->needProfileImage) { + $default = null; + + $file_phids = mpull($blogs, 'getProfileImagePHID'); + $file_phids = array_filter($file_phids); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + } else { + $files = array(); + } + + foreach ($blogs as $blog) { + $file = idx($files, $blog->getProfileImagePHID()); + if (!$file) { + if (!$default) { + $default = PhabricatorFile::loadBuiltin( + $this->getViewer(), + 'blog.png'); + } + $file = $default; + } + $blog->attachProfileImageFile($file); + } + } + return $blogs; + } + public function getQueryApplicationClass() { // TODO: Can we set this without breaking public blogs? return null; } } diff --git a/src/applications/phame/query/PhameBlogSearchEngine.php b/src/applications/phame/query/PhameBlogSearchEngine.php index 58dba3b810..d006745780 100644 --- a/src/applications/phame/query/PhameBlogSearchEngine.php +++ b/src/applications/phame/query/PhameBlogSearchEngine.php @@ -1,107 +1,114 @@ needProfileImage(true); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); if ($map['statuses']) { $query->withStatuses(array($map['statuses'])); } return $query; } protected function buildCustomSearchFields() { return array( id(new PhabricatorSearchSelectField()) ->setKey('statuses') ->setLabel(pht('Status')) ->setOptions(array( '' => pht('All'), PhameBlog::STATUS_ACTIVE => pht('Active'), PhameBlog::STATUS_ARCHIVED => pht('Archived'), )), ); } protected function getURI($path) { return '/phame/blog/'.$path; } protected function getBuiltinQueryNames() { $names = array( 'active' => pht('Active Blogs'), 'archived' => pht('Archived Blogs'), 'all' => pht('All Blogs'), ); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; case 'active': return $query->setParameter( 'statuses', PhameBlog::STATUS_ACTIVE); case 'archived': return $query->setParameter( 'statuses', PhameBlog::STATUS_ARCHIVED); } return parent::buildSavedQueryFromBuiltin($query_key); } protected function renderResultList( array $blogs, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($blogs, 'PhameBlog'); $viewer = $this->requireViewer(); $list = new PHUIObjectItemListView(); $list->setUser($viewer); foreach ($blogs as $blog) { - $archived = false; - $icon = 'fa-star'; - if ($blog->isArchived()) { - $archived = true; - $icon = 'fa-ban'; - } $id = $blog->getID(); + if ($blog->getDomain()) { + $domain = $blog->getDomain(); + } else { + $domain = pht('Local Blog'); + } $item = id(new PHUIObjectItemView()) ->setUser($viewer) ->setObject($blog) ->setHeader($blog->getName()) - ->setStatusIcon($icon) - ->setDisabled($archived) + ->setImageURI($blog->getProfileImageURI()) + ->setDisabled($blog->isArchived()) ->setHref($this->getApplicationURI("/blog/view/{$id}/")) - ->addAttribute($blog->getSkin()) - ->addAttribute($blog->getDomain()); + ->addAttribute($domain); + if (!$blog->isArchived()) { + $button = id(new PHUIButtonView()) + ->setTag('a') + ->setText('New Post') + ->setHref($this->getApplicationURI('/post/edit/?blog='.$id)); + $item->setLaunchButton($button); + } + $list->addItem($item); } $result = new PhabricatorApplicationSearchResultView(); $result->setObjectList($list); $result->setNoDataString(pht('No blogs found.')); return $result; } } diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index 3fc708f72a..9cec0f0a1b 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -1,362 +1,395 @@ true, self::CONFIG_SERIALIZATION => array( 'configData' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text64', 'description' => 'text', 'domain' => 'text128?', 'status' => 'text32', 'mailKey' => 'bytes20', + 'profileImagePHID' => 'phid?', // T6203/NULLABILITY // These policies should always be non-null. 'editPolicy' => 'policy?', 'viewPolicy' => 'policy?', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'domain' => array( 'columns' => array('domain'), 'unique' => true, ), ), ) + parent::getConfiguration(); } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPhameBlogPHIDType::TYPECONST); } public static function initializeNewBlog(PhabricatorUser $actor) { $blog = id(new PhameBlog()) ->setCreatorPHID($actor->getPHID()) ->setStatus(self::STATUS_ACTIVE) ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) ->setEditPolicy(PhabricatorPolicies::POLICY_USER); return $blog; } public function getSkinRenderer(AphrontRequest $request) { $spec = PhameSkinSpecification::loadOneSkinSpecification( $this->getSkin()); if (!$spec) { $spec = PhameSkinSpecification::loadOneSkinSpecification( self::SKIN_DEFAULT); } if (!$spec) { throw new Exception( pht( 'This blog has an invalid skin, and the default skin failed to '. 'load.')); } $skin = newv($spec->getSkinClass(), array()); $skin->setRequest($request); $skin->setSpecification($spec); return $skin; } public function isArchived() { return ($this->getStatus() == self::STATUS_ARCHIVED); } public static function getStatusNameMap() { return array( self::STATUS_ACTIVE => pht('Active'), self::STATUS_ARCHIVED => pht('Archived'), ); } /** * Makes sure a given custom blog uri is properly configured in DNS * to point at this Phabricator instance. If there is an error in * the configuration, return a string describing the error and how * to fix it. If there is no error, return an empty string. * * @return string */ public function validateCustomDomain($custom_domain) { $example_domain = 'blog.example.com'; $label = pht('Invalid'); // note this "uri" should be pretty busted given the desired input // so just use it to test if there's a protocol specified $uri = new PhutilURI($custom_domain); if ($uri->getProtocol()) { return array( $label, pht( 'The custom domain should not include a protocol. Just provide '. 'the bare domain name (for example, "%s").', $example_domain), ); } if ($uri->getPort()) { return array( $label, pht( 'The custom domain should not include a port number. Just provide '. 'the bare domain name (for example, "%s").', $example_domain), ); } if (strpos($custom_domain, '/') !== false) { return array( $label, pht( 'The custom domain should not specify a path (hosting a Phame '. 'blog at a path is currently not supported). Instead, just provide '. 'the bare domain name (for example, "%s").', $example_domain), ); } if (strpos($custom_domain, '.') === false) { return array( $label, pht( 'The custom domain should contain at least one dot (.) because '. 'some browsers fail to set cookies on domains without a dot. '. 'Instead, use a normal looking domain name like "%s".', $example_domain), ); } if (!PhabricatorEnv::getEnvConfig('policy.allow-public')) { $href = PhabricatorEnv::getProductionURI( '/config/edit/policy.allow-public/'); return array( pht('Fix Configuration'), pht( 'For custom domains to work, this Phabricator instance must be '. 'configured to allow the public access policy. Configure this '. 'setting %s, or ask an administrator to configure this setting. '. 'The domain can be specified later once this setting has been '. 'changed.', phutil_tag( 'a', array('href' => $href), pht('here'))), ); } return null; } public function getSkin() { $config = coalesce($this->getConfigData(), array()); return idx($config, 'skin', self::SKIN_DEFAULT); } public function setSkin($skin) { $config = coalesce($this->getConfigData(), array()); $config['skin'] = $skin; return $this->setConfigData($config); } public static function getSkinOptionsForSelect() { $classes = id(new PhutilSymbolLoader()) ->setAncestorClass('PhameBlogSkin') ->setType('class') ->setConcreteOnly(true) ->selectSymbolsWithoutLoading(); return ipull($classes, 'name', 'name'); } public static function setRequestBlog(PhameBlog $blog) { self::$requestBlog = $blog; } public static function getRequestBlog() { return self::$requestBlog; } public function getLiveURI(PhamePost $post = null) { if ($this->getDomain()) { $base = new PhutilURI('http://'.$this->getDomain().'/'); } else { $base = '/phame/live/'.$this->getID().'/'; $base = PhabricatorEnv::getURI($base); } if ($post) { $base .= '/post/'.$post->getPhameTitle(); } return $base; } public function getViewURI() { $uri = '/phame/blog/view/'.$this->getID().'/'; return PhabricatorEnv::getProductionURI($uri); } + public function getProfileImageURI() { + return $this->getProfileImageFile()->getBestURI(); + } + + public function attachProfileImageFile(PhabricatorFile $file) { + $this->profileImageFile = $file; + return $this; + } + + public function getProfileImageFile() { + return $this->assertAttached($this->profileImageFile); + } + /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $user) { $can_edit = PhabricatorPolicyCapability::CAN_EDIT; switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: // Users who can edit or post to a blog can always view it. if (PhabricatorPolicyFilter::hasCapability($user, $this, $can_edit)) { return true; } break; } return false; } public function describeAutomaticCapability($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return pht( 'Users who can edit a blog can always view it.'); } return null; } /* -( PhabricatorMarkupInterface Implementation )-------------------------- */ public function getMarkupFieldKey($field) { $hash = PhabricatorHash::digest($this->getMarkupText($field)); return $this->getPHID().':'.$field.':'.$hash; } public function newMarkupEngine($field) { return PhabricatorMarkupEngine::newPhameMarkupEngine(); } public function getMarkupText($field) { return $this->getDescription(); } public function didMarkupText( $field, $output, PhutilMarkupEngine $engine) { return $output; } public function shouldUseMarkupCache($field) { return (bool)$this->getPHID(); } +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + + $this->openTransaction(); + + $posts = id(new PhamePost()) + ->loadAllWhere('blogPHID = %s', $this->getPHID()); + foreach ($posts as $post) { + $post->delete(); + } + $this->delete(); + + $this->saveTransaction(); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhameBlogEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PhameBlogTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorSubscribableInterface Implementation )-------------------- */ public function isAutomaticallySubscribed($phid) { return ($this->creatorPHID == $phid); } public function shouldShowSubscribersProperty() { return true; } public function shouldAllowSubscription($phid) { return true; } } diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index eb04679e1f..3212f128b2 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -1,281 +1,294 @@ setBloggerPHID($blogger->getPHID()) ->setBlogPHID($blog->getPHID()) ->setBlog($blog) - ->setDatePublished(0) + ->setDatePublished(PhabricatorTime::getNow()) ->setVisibility(PhameConstants::VISIBILITY_PUBLISHED); return $post; } public function setBlog(PhameBlog $blog) { $this->blog = $blog; return $this; } public function getBlog() { return $this->blog; } public function getViewURI() { // go for the pretty uri if we can $domain = ($this->blog ? $this->blog->getDomain() : ''); if ($domain) { $phame_title = PhabricatorSlug::normalize($this->getPhameTitle()); return 'http://'.$domain.'/post/'.$phame_title; } $uri = '/phame/post/view/'.$this->getID().'/'; return PhabricatorEnv::getProductionURI($uri); } public function getEditURI() { return '/phame/post/edit/'.$this->getID().'/'; } public function isDraft() { return $this->getVisibility() == PhameConstants::VISIBILITY_DRAFT; } public function getHumanName() { if ($this->isDraft()) { $name = 'draft'; } else { $name = 'post'; } return $name; } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'configData' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'title' => 'text255', 'phameTitle' => 'sort64', 'visibility' => 'uint32', 'mailKey' => 'bytes20', // T6203/NULLABILITY // These seem like they should always be non-null? 'blogPHID' => 'phid?', 'body' => 'text?', 'configData' => 'text?', // T6203/NULLABILITY // This one probably should be nullable? 'datePublished' => 'epoch', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'phameTitle' => array( 'columns' => array('bloggerPHID', 'phameTitle'), 'unique' => true, ), 'bloggerPosts' => array( 'columns' => array( 'bloggerPHID', 'visibility', 'datePublished', 'id', ), ), ), ) + parent::getConfiguration(); } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPhamePostPHIDType::TYPECONST); } public function toDictionary() { return array( 'id' => $this->getID(), 'phid' => $this->getPHID(), 'blogPHID' => $this->getBlogPHID(), 'bloggerPHID' => $this->getBloggerPHID(), 'viewURI' => $this->getViewURI(), 'title' => $this->getTitle(), 'phameTitle' => $this->getPhameTitle(), 'body' => $this->getBody(), 'summary' => PhabricatorMarkupEngine::summarize($this->getBody()), 'datePublished' => $this->getDatePublished(), 'published' => !$this->isDraft(), ); } /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { // Draft posts are visible only to the author. Published posts are visible // to whoever the blog is visible to. switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: if (!$this->isDraft() && $this->getBlog()) { return $this->getBlog()->getViewPolicy(); } else if ($this->getBlog()) { return $this->getBlog()->getEditPolicy(); } else { return PhabricatorPolicies::POLICY_NOONE; } break; case PhabricatorPolicyCapability::CAN_EDIT: if ($this->getBlog()) { return $this->getBlog()->getEditPolicy(); } else { return PhabricatorPolicies::POLICY_NOONE; } } } public function hasAutomaticCapability($capability, PhabricatorUser $user) { // A blog post's author can always view it. switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: case PhabricatorPolicyCapability::CAN_EDIT: return ($user->getPHID() == $this->getBloggerPHID()); } } public function describeAutomaticCapability($capability) { return pht('The author of a blog post can always view and edit it.'); } /* -( PhabricatorMarkupInterface Implementation )-------------------------- */ public function getMarkupFieldKey($field) { $hash = PhabricatorHash::digest($this->getMarkupText($field)); return $this->getPHID().':'.$field.':'.$hash; } public function newMarkupEngine($field) { return PhabricatorMarkupEngine::newPhameMarkupEngine(); } public function getMarkupText($field) { switch ($field) { case self::MARKUP_FIELD_BODY: return $this->getBody(); case self::MARKUP_FIELD_SUMMARY: return PhabricatorMarkupEngine::summarize($this->getBody()); } } public function didMarkupText( $field, $output, PhutilMarkupEngine $engine) { return $output; } public function shouldUseMarkupCache($field) { return (bool)$this->getPHID(); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhamePostEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PhamePostTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } +/* -( PhabricatorDestructibleInterface )----------------------------------- */ + + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + + $this->openTransaction(); + + $this->delete(); + + $this->saveTransaction(); + } + /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { return array( $this->getBloggerPHID(), ); } /* -( PhabricatorSubscribableInterface Implementation )-------------------- */ public function isAutomaticallySubscribed($phid) { return ($this->bloggerPHID == $phid); } public function shouldShowSubscribersProperty() { return true; } public function shouldAllowSubscription($phid) { return true; } } diff --git a/src/applications/phame/storage/PhamePostTransaction.php b/src/applications/phame/storage/PhamePostTransaction.php index c985d41c25..5e28b87198 100644 --- a/src/applications/phame/storage/PhamePostTransaction.php +++ b/src/applications/phame/storage/PhamePostTransaction.php @@ -1,256 +1,249 @@ getTransactionType()) { case self::TYPE_BODY: $blocks[] = $this->getNewValue(); break; } return $blocks; } public function shouldHide() { $old = $this->getOldValue(); switch ($this->getTransactionType()) { case self::TYPE_PHAME_TITLE: case self::TYPE_BODY: return ($old === null); } return parent::shouldHide(); } public function getIcon() { $old = $this->getOldValue(); switch ($this->getTransactionType()) { case self::TYPE_TITLE: if ($old === null) { return 'fa-plus'; } else { return 'fa-pencil'; } break; case self::TYPE_PHAME_TITLE: case self::TYPE_BODY: case self::TYPE_VISIBILITY: return 'fa-pencil'; break; } return parent::getIcon(); } public function getMailTags() { $tags = parent::getMailTags(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $tags[] = self::MAILTAG_COMMENT; break; case self::TYPE_TITLE: case self::TYPE_PHAME_TITLE: case self::TYPE_BODY: $tags[] = self::MAILTAG_CONTENT; break; default: $tags[] = self::MAILTAG_OTHER; break; } return $tags; } public function getTitle() { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); $type = $this->getTransactionType(); switch ($type) { case self::TYPE_TITLE: if ($old === null) { return pht( '%s authored this post.', $this->renderHandleLink($author_phid)); } else { return pht( '%s updated the post\'s name to "%s".', $this->renderHandleLink($author_phid), $new); } break; case self::TYPE_BODY: return pht( '%s updated the blog post.', $this->renderHandleLink($author_phid)); break; case self::TYPE_VISIBILITY: if ($new == PhameConstants::VISIBILITY_DRAFT) { return pht( '%s marked this post as a draft.', $this->renderHandleLink($author_phid)); } else { return pht( '%s published this post.', $this->renderHandleLink($author_phid)); } break; case self::TYPE_PHAME_TITLE: return pht( '%s updated the post\'s Phame title to "%s".', $this->renderHandleLink($author_phid), rtrim($new, '/')); break; } return parent::getTitle(); } public function getTitleForFeed() { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); $type = $this->getTransactionType(); switch ($type) { case self::TYPE_TITLE: if ($old === null) { return pht( '%s authored %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else { return pht( '%s updated the name for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } break; case self::TYPE_BODY: return pht( '%s updated the blog post %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; case self::TYPE_VISIBILITY: if ($new == PhameConstants::VISIBILITY_DRAFT) { return pht( '%s marked %s as a draft.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else { return pht( '%s published %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } break; case self::TYPE_PHAME_TITLE: return pht( '%s updated the Phame title for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; } return parent::getTitleForFeed(); } - public function getBodyForFeed(PhabricatorFeedStory $story) { + public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { $text = null; switch ($this->getTransactionType()) { case self::TYPE_TITLE: if ($this->getOldValue() === null) { $post = $story->getPrimaryObject(); $text = $post->getBody(); } break; case self::TYPE_VISIBILITY: if ($this->getNewValue() == PhameConstants::VISIBILITY_PUBLISHED) { $post = $story->getPrimaryObject(); $text = $post->getBody(); } break; case self::TYPE_BODY: $text = $this->getNewValue(); break; } - if (strlen($text)) { - return phutil_escape_html_newlines( - id(new PhutilUTF8StringTruncator()) - ->setMaximumGlyphs(128) - ->truncateString($text)); - } - - return parent::getBodyForFeed($story); + return $text; } public function getColor() { $old = $this->getOldValue(); switch ($this->getTransactionType()) { case self::TYPE_TITLE: if ($old === null) { return PhabricatorTransactions::COLOR_GREEN; } break; } return parent::getColor(); } public function hasChangeDetails() { switch ($this->getTransactionType()) { case self::TYPE_BODY: return ($this->getOldValue() !== null); } return parent::hasChangeDetails(); } public function renderChangeDetails(PhabricatorUser $viewer) { switch ($this->getTransactionType()) { case self::TYPE_BODY: $old = $this->getOldValue(); $new = $this->getNewValue(); return $this->renderTextCorpusChangeDetails( $viewer, $old, $new); } return parent::renderChangeDetails($viewer); } } diff --git a/src/applications/phame/view/PhameDescriptionView.php b/src/applications/phame/view/PhameDescriptionView.php new file mode 100644 index 0000000000..346b22640f --- /dev/null +++ b/src/applications/phame/view/PhameDescriptionView.php @@ -0,0 +1,60 @@ +title = $title; + return $this; + } + + public function setDescription($description) { + $this->description = $description; + return $this; + } + + public function setImage($image) { + $this->image = $image; + return $this; + } + + public function setImageHref($href) { + $this->imageHref = $href; + return $this; + } + + protected function getTagAttributes() { + $classes = array(); + $classes[] = 'phame-blog-description'; + return array('class' => implode(' ', $classes)); + } + + protected function getTagContent() { + require_celerity_resource('phame-css'); + + $description = phutil_tag_div( + 'phame-blog-description-content', $this->description); + + $image = phutil_tag( + ($this->imageHref) ? 'a' : 'div', + array( + 'class' => 'phame-blog-description-image', + 'style' => 'background-image: url('.$this->image.');', + 'href' => $this->imageHref, + )); + + $header = phutil_tag( + 'div', + array( + 'class' => 'phame-blog-description-name', + ), + $this->title); + + return array($image, $header, $description); + } + +} diff --git a/src/applications/phame/view/PhamePostListView.php b/src/applications/phame/view/PhamePostListView.php new file mode 100644 index 0000000000..e8272d81bf --- /dev/null +++ b/src/applications/phame/view/PhamePostListView.php @@ -0,0 +1,111 @@ +posts = $posts; + return $this; + } + + public function setNodata($nodata) { + $this->nodata = $nodata; + return $this; + } + + public function showBlog($show) { + $this->showBlog = $show; + return $this; + } + + public function setViewer($viewer) { + $this->viewer = $viewer; + return $this; + } + + protected function getTagAttributes() { + return array(); + } + + protected function getTagContent() { + $viewer = $this->viewer; + $posts = $this->posts; + $nodata = $this->nodata; + + $handle_phids = array(); + foreach ($posts as $post) { + $handle_phids[] = $post->getBloggerPHID(); + if ($post->getBlog()) { + $handle_phids[] = $post->getBlog()->getPHID(); + } + } + $handles = $viewer->loadHandles($handle_phids); + + $list = array(); + foreach ($posts as $post) { + $blogger = $handles[$post->getBloggerPHID()]->renderLink(); + $blogger_uri = $handles[$post->getBloggerPHID()]->getURI(); + $blogger_image = $handles[$post->getBloggerPHID()]->getImageURI(); + + $phame_post = null; + if ($post->getBody()) { + $phame_post = PhabricatorMarkupEngine::summarize($post->getBody()); + $phame_post = new PHUIRemarkupView($viewer, $phame_post); + } else { + $phame_post = phutil_tag('em', array(), pht('(Empty Post)')); + } + + $blogger = phutil_tag('strong', array(), $blogger); + $date = phabricator_datetime($post->getDatePublished(), $viewer); + + $blog = null; + if ($post->getBlog()) { + $blog = phutil_tag( + 'a', + array( + 'href' => '/phame/blog/view/'.$post->getBlog()->getID().'/', + ), + $post->getBlog()->getName()); + } + + if ($this->showBlog && $blog) { + if ($post->isDraft()) { + $subtitle = pht('Unpublished draft by %s in %s.', $blogger, $blog); + } else { + $subtitle = pht('By %s on %s in %s.', $blogger, $date, $blog); + } + } else { + if ($post->isDraft()) { + $subtitle = pht('Unpublished draft by %s.', $blogger); + } else { + $subtitle = pht('Written by %s on %s.', $blogger, $date); + } + } + + $item = id(new PHUIDocumentSummaryView()) + ->setTitle($post->getTitle()) + ->setHref('/phame/post/view/'.$post->getID().'/') + ->setSubtitle($subtitle) + ->setImage($blogger_image) + ->setImageHref($blogger_uri) + ->setSummary($phame_post) + ->setDraft($post->isDraft()); + + $list[] = $item; + } + + if (empty($list)) { + $list = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NODATA) + ->appendChild($nodata); + } + + return $list; + } + +} diff --git a/src/applications/pholio/controller/PholioController.php b/src/applications/pholio/controller/PholioController.php index ad402eb4e3..533945c7c3 100644 --- a/src/applications/pholio/controller/PholioController.php +++ b/src/applications/pholio/controller/PholioController.php @@ -1,40 +1,22 @@ getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new PholioMockSearchEngine()) - ->setViewer($user) - ->addNavigationItems($nav->getMenu()); - - if ($for_app) { - $nav->addFilter('new/', pht('Create Mock')); - } - - $nav->selectFilter(null); - - return $nav; + public function buildApplicationMenu() { + return $this->newApplicationMenu() + ->setSearchEngine(new PholioMockSearchEngine()); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Mock')) ->setHref($this->getApplicationURI('new/')) ->setIcon('fa-plus-square')); return $crumbs; } - public function buildApplicationMenu() { - return $this->buildSideNavView(true)->getMenu(); - } - } diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php index 18423e0050..1eff4f8716 100644 --- a/src/applications/pholio/controller/PholioMockEditController.php +++ b/src/applications/pholio/controller/PholioMockEditController.php @@ -1,394 +1,396 @@ getViewer(); $id = $request->getURIData('id'); if ($id) { $mock = id(new PholioMockQuery()) ->setViewer($viewer) ->needImages(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($id)) ->executeOne(); if (!$mock) { return new Aphront404Response(); } $title = pht('Edit Mock'); $is_new = false; $mock_images = $mock->getImages(); $files = mpull($mock_images, 'getFile'); $mock_images = mpull($mock_images, null, 'getFilePHID'); } else { $mock = PholioMock::initializeNewMock($viewer); $title = pht('Create Mock'); $is_new = true; $files = array(); $mock_images = array(); } if ($is_new) { $v_projects = array(); } else { $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( $mock->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $v_projects = array_reverse($v_projects); } $e_name = true; $e_images = count($mock_images) ? null : true; $errors = array(); $posted_mock_images = array(); $v_name = $mock->getName(); $v_desc = $mock->getDescription(); $v_status = $mock->getStatus(); $v_view = $mock->getViewPolicy(); $v_edit = $mock->getEditPolicy(); $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID( $mock->getPHID()); $v_space = $mock->getSpacePHID(); if ($request->isFormPost()) { $xactions = array(); $type_name = PholioTransaction::TYPE_NAME; $type_desc = PholioTransaction::TYPE_DESCRIPTION; $type_status = PholioTransaction::TYPE_STATUS; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; $type_cc = PhabricatorTransactions::TYPE_SUBSCRIBERS; $type_space = PhabricatorTransactions::TYPE_SPACE; $v_name = $request->getStr('name'); $v_desc = $request->getStr('description'); $v_status = $request->getStr('status'); $v_view = $request->getStr('can_view'); $v_edit = $request->getStr('can_edit'); $v_cc = $request->getArr('cc'); $v_projects = $request->getArr('projects'); $v_space = $request->getStr('spacePHID'); $mock_xactions = array(); $mock_xactions[$type_name] = $v_name; $mock_xactions[$type_desc] = $v_desc; $mock_xactions[$type_status] = $v_status; $mock_xactions[$type_view] = $v_view; $mock_xactions[$type_edit] = $v_edit; $mock_xactions[$type_cc] = array('=' => $v_cc); $mock_xactions[$type_space] = $v_space; if (!strlen($request->getStr('name'))) { $e_name = pht('Required'); $errors[] = pht('You must give the mock a name.'); } $file_phids = $request->getArr('file_phids'); if ($file_phids) { $files = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); $files = array_select_keys($files, $file_phids); } else { $files = array(); } if (!$files) { $e_images = pht('Required'); $errors[] = pht('You must add at least one image to the mock.'); } else { $mock->setCoverPHID(head($files)->getPHID()); } foreach ($mock_xactions as $type => $value) { $xactions[$type] = id(new PholioTransaction()) ->setTransactionType($type) ->setNewValue($value); } $order = $request->getStrList('imageOrder'); $sequence_map = array_flip($order); $replaces = $request->getArr('replaces'); $replaces_map = array_flip($replaces); /** * Foreach file posted, check to see whether we are replacing an image, * adding an image, or simply updating image metadata. Create * transactions for these cases as appropos. */ foreach ($files as $file_phid => $file) { $replaces_image_phid = null; if (isset($replaces_map[$file_phid])) { $old_file_phid = $replaces_map[$file_phid]; if ($old_file_phid != $file_phid) { $old_image = idx($mock_images, $old_file_phid); if ($old_image) { $replaces_image_phid = $old_image->getPHID(); } } } $existing_image = idx($mock_images, $file_phid); $title = (string)$request->getStr('title_'.$file_phid); $description = (string)$request->getStr('description_'.$file_phid); $sequence = $sequence_map[$file_phid]; if ($replaces_image_phid) { $replace_image = id(new PholioImage()) ->setReplacesImagePHID($replaces_image_phid) ->setFilePhid($file_phid) ->attachFile($file) ->setName(strlen($title) ? $title : $file->getName()) ->setDescription($description) ->setSequence($sequence); $xactions[] = id(new PholioTransaction()) ->setTransactionType( PholioTransaction::TYPE_IMAGE_REPLACE) ->setNewValue($replace_image); $posted_mock_images[] = $replace_image; } else if (!$existing_image) { // this is an add $add_image = id(new PholioImage()) ->setFilePhid($file_phid) ->attachFile($file) ->setName(strlen($title) ? $title : $file->getName()) ->setDescription($description) ->setSequence($sequence); $xactions[] = id(new PholioTransaction()) ->setTransactionType(PholioTransaction::TYPE_IMAGE_FILE) ->setNewValue( array('+' => array($add_image))); $posted_mock_images[] = $add_image; } else { $xactions[] = id(new PholioTransaction()) ->setTransactionType(PholioTransaction::TYPE_IMAGE_NAME) ->setNewValue( array($existing_image->getPHID() => $title)); $xactions[] = id(new PholioTransaction()) ->setTransactionType( PholioTransaction::TYPE_IMAGE_DESCRIPTION) ->setNewValue( array($existing_image->getPHID() => $description)); $xactions[] = id(new PholioTransaction()) ->setTransactionType( PholioTransaction::TYPE_IMAGE_SEQUENCE) ->setNewValue( array($existing_image->getPHID() => $sequence)); $posted_mock_images[] = $existing_image; } } foreach ($mock_images as $file_phid => $mock_image) { if (!isset($files[$file_phid]) && !isset($replaces[$file_phid])) { // this is an outright delete $xactions[] = id(new PholioTransaction()) ->setTransactionType(PholioTransaction::TYPE_IMAGE_FILE) ->setNewValue( array('-' => array($mock_image))); } } if (!$errors) { $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new PholioTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $proj_edge_type) ->setNewValue(array('=' => array_fuse($v_projects))); $mock->openTransaction(); $editor = id(new PholioMockEditor()) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setActor($viewer); $xactions = $editor->applyTransactions($mock, $xactions); $mock->saveTransaction(); return id(new AphrontRedirectResponse()) ->setURI('/M'.$mock->getID()); } } if ($id) { $submit = id(new AphrontFormSubmitControl()) ->addCancelButton('/M'.$id) ->setValue(pht('Save')); } else { $submit = id(new AphrontFormSubmitControl()) ->addCancelButton($this->getApplicationURI()) ->setValue(pht('Create')); } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($mock) ->execute(); // NOTE: Make this show up correctly on the rendered form. $mock->setViewPolicy($v_view); $mock->setEditPolicy($v_edit); $image_elements = array(); if ($posted_mock_images) { $display_mock_images = $posted_mock_images; } else { $display_mock_images = $mock_images; } foreach ($display_mock_images as $mock_image) { $image_elements[] = id(new PholioUploadedImageView()) ->setUser($viewer) ->setImage($mock_image) ->setReplacesPHID($mock_image->getFilePHID()); } $list_id = celerity_generate_unique_node_id(); $drop_id = celerity_generate_unique_node_id(); $order_id = celerity_generate_unique_node_id(); $list_control = phutil_tag( 'div', array( 'id' => $list_id, 'class' => 'pholio-edit-list', ), $image_elements); $drop_control = phutil_tag( 'div', array( 'id' => $drop_id, 'class' => 'pholio-edit-drop', ), pht('Drag and drop images here to add them to the mock.')); $order_control = phutil_tag( 'input', array( 'type' => 'hidden', 'name' => 'imageOrder', 'id' => $order_id, )); Javelin::initBehavior( 'pholio-mock-edit', array( 'listID' => $list_id, 'dropID' => $drop_id, 'orderID' => $order_id, 'uploadURI' => '/file/dropupload/', 'renderURI' => $this->getApplicationURI('image/upload/'), 'pht' => array( 'uploading' => pht('Uploading Image...'), 'uploaded' => pht('Upload Complete...'), 'undo' => pht('Undo'), 'removed' => pht('This image will be removed from the mock.'), ), )); require_celerity_resource('pholio-edit-css'); $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild($order_control) ->appendChild( id(new AphrontFormTextControl()) ->setName('name') ->setValue($v_name) ->setLabel(pht('Name')) ->setError($e_name)) ->appendChild( id(new PhabricatorRemarkupControl()) ->setName('description') ->setValue($v_desc) ->setLabel(pht('Description')) ->setUser($viewer)); if ($id) { $form->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Status')) ->setName('status') ->setValue($mock->getStatus()) ->setOptions($mock->getStatuses())); } else { $form->addHiddenInput('status', 'open'); } $form ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setValue($v_projects) ->setDatasource(new PhabricatorProjectDatasource())) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Subscribers')) ->setName('cc') ->setValue($v_cc) ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource())) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($mock) ->setPolicies($policies) ->setSpacePHID($v_space) ->setName('can_view')) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($mock) ->setPolicies($policies) ->setName('can_edit')) ->appendChild( id(new AphrontFormMarkupControl()) ->setValue($list_control)) ->appendChild( id(new AphrontFormMarkupControl()) ->setValue($drop_control) ->setError($e_images)) ->appendChild($submit); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormErrors($errors) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); if (!$is_new) { $crumbs->addTextCrumb($mock->getMonogram(), '/'.$mock->getMonogram()); } $crumbs->addTextCrumb($title); $content = array( $crumbs, $form_box, ); - $this->addExtraQuicksandConfig( - array('mockEditConfig' => true)); - return $this->buildApplicationPage( - $content, - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->addQuicksandConfig( + array('mockEditConfig' => true)) + ->appendChild( + array( + $form_box, )); } } diff --git a/src/applications/pholio/controller/PholioMockListController.php b/src/applications/pholio/controller/PholioMockListController.php index b4fa17fc8a..56d513d9ea 100644 --- a/src/applications/pholio/controller/PholioMockListController.php +++ b/src/applications/pholio/controller/PholioMockListController.php @@ -1,20 +1,15 @@ getURIData('queryKey'); - - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine(new PholioMockSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); + return id(new PholioMockSearchEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/pholio/controller/PholioMockViewController.php b/src/applications/pholio/controller/PholioMockViewController.php index c5c6fed16c..2bf237ed0e 100644 --- a/src/applications/pholio/controller/PholioMockViewController.php +++ b/src/applications/pholio/controller/PholioMockViewController.php @@ -1,211 +1,208 @@ maniphestTaskPHIDs = $maniphest_task_phids; return $this; } private function getManiphestTaskPHIDs() { return $this->maniphestTaskPHIDs; } public function shouldAllowPublic() { return true; } public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); $image_id = $request->getURIData('imageID'); $mock = id(new PholioMockQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needImages(true) ->needInlineComments(true) ->executeOne(); if (!$mock) { return new Aphront404Response(); } $phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $mock->getPHID(), PholioMockHasTaskEdgeType::EDGECONST); $this->setManiphestTaskPHIDs($phids); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($viewer); $engine->addObject($mock, PholioMock::MARKUP_FIELD_DESCRIPTION); $title = $mock->getName(); if ($mock->isClosed()) { $header_icon = 'fa-ban'; $header_name = pht('Closed'); $header_color = 'dark'; } else { $header_icon = 'fa-square-o'; $header_name = pht('Open'); $header_color = 'bluegrey'; } $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) ->setStatus($header_icon, $header_color, $header_name) ->setPolicyObject($mock); $timeline = $this->buildTransactionTimeline( $mock, new PholioTransactionQuery(), $engine); $timeline->setMock($mock); $actions = $this->buildActionView($mock); $properties = $this->buildPropertyView($mock, $engine, $actions); require_celerity_resource('pholio-css'); require_celerity_resource('pholio-inline-comments-css'); $comment_form_id = celerity_generate_unique_node_id(); $mock_view = id(new PholioMockImagesView()) ->setRequestURI($request->getRequestURI()) ->setCommentFormID($comment_form_id) ->setUser($viewer) ->setMock($mock) ->setImageID($image_id); - $this->addExtraQuicksandConfig( - array('mockViewConfig' => $mock_view->getBehaviorConfig())); $output = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Image')) ->appendChild($mock_view); $add_comment = $this->buildAddCommentView($mock, $comment_form_id); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb('M'.$mock->getID(), '/M'.$mock->getID()); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); $thumb_grid = id(new PholioMockThumbGridView()) ->setUser($viewer) ->setMock($mock); - $content = array( - $crumbs, - $object_box, - $output, - $thumb_grid, - $timeline, - $add_comment, - ); - - return $this->buildApplicationPage( - $content, - array( - 'title' => 'M'.$mock->getID().' '.$title, - 'pageObjects' => array($mock->getPHID()), + return $this->newPage() + ->setTitle('M'.$mock->getID().' '.$title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($mock->getPHID())) + ->addQuicksandConfig( + array('mockViewConfig' => $mock_view->getBehaviorConfig())) + ->appendChild( + array( + $object_box, + $output, + $thumb_grid, + $timeline, + $add_comment, )); } private function buildActionView(PholioMock $mock) { $viewer = $this->getViewer(); $actions = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($mock); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $mock, PhabricatorPolicyCapability::CAN_EDIT); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Mock')) ->setHref($this->getApplicationURI('/edit/'.$mock->getID().'/')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $actions->addAction( id(new PhabricatorActionView()) ->setIcon('fa-anchor') ->setName(pht('Edit Maniphest Tasks')) ->setHref("/search/attach/{$mock->getPHID()}/TASK/edge/") ->setDisabled(!$viewer->isLoggedIn()) ->setWorkflow(true)); return $actions; } private function buildPropertyView( PholioMock $mock, PhabricatorMarkupEngine $engine, PhabricatorActionListView $actions) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($mock) ->setActionList($actions); $properties->addProperty( pht('Author'), $viewer->renderHandle($mock->getAuthorPHID())); $properties->addProperty( pht('Created'), phabricator_datetime($mock->getDateCreated(), $viewer)); if ($this->getManiphestTaskPHIDs()) { $properties->addProperty( pht('Maniphest Tasks'), $viewer->renderHandleList($this->getManiphestTaskPHIDs())); } $properties->invokeWillRenderEvent(); $properties->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); $properties->addImageContent( $engine->getOutput($mock, PholioMock::MARKUP_FIELD_DESCRIPTION)); return $properties; } private function buildAddCommentView(PholioMock $mock, $comment_form_id) { $viewer = $this->getViewer(); $draft = PhabricatorDraft::newFromUserAndKey($viewer, $mock->getPHID()); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $title = $is_serious ? pht('Add Comment') : pht('History Beckons'); $form = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setObjectPHID($mock->getPHID()) ->setFormID($comment_form_id) ->setDraft($draft) ->setHeaderText($title) ->setSubmitButtonName(pht('Add Comment')) ->setAction($this->getApplicationURI('/comment/'.$mock->getID().'/')) ->setRequestURI($this->getRequest()->getRequestURI()); return $form; } } diff --git a/src/applications/pholio/storage/PholioTransaction.php b/src/applications/pholio/storage/PholioTransaction.php index c4a0cf90f8..17b0730dcb 100644 --- a/src/applications/pholio/storage/PholioTransaction.php +++ b/src/applications/pholio/storage/PholioTransaction.php @@ -1,396 +1,389 @@ getObjectPHID(); $new = $this->getNewValue(); $old = $this->getOldValue(); switch ($this->getTransactionType()) { case self::TYPE_IMAGE_FILE: $phids = array_merge($phids, $new, $old); break; case self::TYPE_IMAGE_REPLACE: $phids[] = $new; $phids[] = $old; break; case self::TYPE_IMAGE_DESCRIPTION: case self::TYPE_IMAGE_NAME: case self::TYPE_IMAGE_SEQUENCE: $phids[] = key($new); break; } return $phids; } public function shouldHide() { $old = $this->getOldValue(); switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: return ($old === null); case self::TYPE_IMAGE_NAME: case self::TYPE_IMAGE_DESCRIPTION: return ($old === array(null => null)); // this is boring / silly to surface; changing sequence is NBD case self::TYPE_IMAGE_SEQUENCE: return true; } return parent::shouldHide(); } public function getIcon() { switch ($this->getTransactionType()) { case self::TYPE_INLINE: return 'fa-comment'; case self::TYPE_NAME: case self::TYPE_DESCRIPTION: case self::TYPE_STATUS: case self::TYPE_IMAGE_NAME: case self::TYPE_IMAGE_DESCRIPTION: case self::TYPE_IMAGE_SEQUENCE: return 'fa-pencil'; case self::TYPE_IMAGE_FILE: case self::TYPE_IMAGE_REPLACE: return 'fa-picture-o'; } return parent::getIcon(); } public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { case self::TYPE_INLINE: case PhabricatorTransactions::TYPE_COMMENT: $tags[] = self::MAILTAG_COMMENT; break; case self::TYPE_STATUS: $tags[] = self::MAILTAG_STATUS; break; case self::TYPE_NAME: case self::TYPE_DESCRIPTION: case self::TYPE_IMAGE_NAME: case self::TYPE_IMAGE_DESCRIPTION: case self::TYPE_IMAGE_SEQUENCE: case self::TYPE_IMAGE_FILE: case self::TYPE_IMAGE_REPLACE: $tags[] = self::MAILTAG_UPDATED; break; default: $tags[] = self::MAILTAG_OTHER; break; } return $tags; } public function getTitle() { $author_phid = $this->getAuthorPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); $type = $this->getTransactionType(); switch ($type) { case self::TYPE_NAME: if ($old === null) { return pht( '%s created "%s".', $this->renderHandleLink($author_phid), $new); } else { return pht( '%s renamed this mock from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); } break; case self::TYPE_DESCRIPTION: return pht( "%s updated the mock's description.", $this->renderHandleLink($author_phid)); break; case self::TYPE_STATUS: return pht( "%s updated the mock's status.", $this->renderHandleLink($author_phid)); break; case self::TYPE_INLINE: $count = 1; foreach ($this->getTransactionGroup() as $xaction) { if ($xaction->getTransactionType() == $type) { $count++; } } return pht( '%s added %d inline comment(s).', $this->renderHandleLink($author_phid), $count); break; case self::TYPE_IMAGE_REPLACE: return pht( '%s replaced %s with %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($old), $this->renderHandleLink($new)); break; case self::TYPE_IMAGE_FILE: $add = array_diff($new, $old); $rem = array_diff($old, $new); if ($add && $rem) { return pht( '%s edited image(s), added %d: %s; removed %d: %s.', $this->renderHandleLink($author_phid), count($add), $this->renderHandleList($add), count($rem), $this->renderHandleList($rem)); } else if ($add) { return pht( '%s added %d image(s): %s.', $this->renderHandleLink($author_phid), count($add), $this->renderHandleList($add)); } else { return pht( '%s removed %d image(s): %s.', $this->renderHandleLink($author_phid), count($rem), $this->renderHandleList($rem)); } break; case self::TYPE_IMAGE_NAME: return pht( '%s renamed an image (%s) from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink(key($new)), reset($old), reset($new)); break; case self::TYPE_IMAGE_DESCRIPTION: return pht( '%s updated an image\'s (%s) description.', $this->renderHandleLink($author_phid), $this->renderHandleLink(key($new))); break; case self::TYPE_IMAGE_SEQUENCE: return pht( '%s updated an image\'s (%s) sequence.', $this->renderHandleLink($author_phid), $this->renderHandleLink(key($new))); break; } return parent::getTitle(); } public function getTitleForFeed() { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); $type = $this->getTransactionType(); switch ($type) { case self::TYPE_NAME: if ($old === null) { return pht( '%s created %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else { return pht( '%s renamed %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old, $new); } break; case self::TYPE_DESCRIPTION: return pht( '%s updated the description for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; case self::TYPE_STATUS: return pht( '%s updated the status for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; case self::TYPE_INLINE: return pht( '%s added an inline comment to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; case self::TYPE_IMAGE_REPLACE: case self::TYPE_IMAGE_FILE: return pht( '%s updated images of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; case self::TYPE_IMAGE_NAME: return pht( '%s updated the image names of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; case self::TYPE_IMAGE_DESCRIPTION: return pht( '%s updated image descriptions of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; case self::TYPE_IMAGE_SEQUENCE: return pht( '%s updated image sequence of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; } return parent::getTitleForFeed(); } - public function getBodyForFeed(PhabricatorFeedStory $story) { + public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { $text = null; switch ($this->getTransactionType()) { case self::TYPE_NAME: if ($this->getOldValue() === null) { $mock = $story->getPrimaryObject(); $text = $mock->getDescription(); } break; case self::TYPE_INLINE: $text = $this->getComment()->getContent(); break; } - if ($text) { - return phutil_escape_html_newlines( - id(new PhutilUTF8StringTruncator()) - ->setMaximumGlyphs(128) - ->truncateString($text)); - } - - return parent::getBodyForFeed($story); + return $text; } public function hasChangeDetails() { switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: case self::TYPE_IMAGE_DESCRIPTION: return true; } return parent::hasChangeDetails(); } public function renderChangeDetails(PhabricatorUser $viewer) { $old = $this->getOldValue(); $new = $this->getNewValue(); if ($this->getTransactionType() == self::TYPE_IMAGE_DESCRIPTION) { $old = reset($old); $new = reset($new); } return $this->renderTextCorpusChangeDetails( $viewer, $old, $new); } public function getColor() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_NAME: if ($old === null) { return PhabricatorTransactions::COLOR_GREEN; } case self::TYPE_DESCRIPTION: case self::TYPE_STATUS: case self::TYPE_IMAGE_NAME: case self::TYPE_IMAGE_DESCRIPTION: case self::TYPE_IMAGE_SEQUENCE: return PhabricatorTransactions::COLOR_BLUE; case self::TYPE_IMAGE_REPLACE: return PhabricatorTransactions::COLOR_YELLOW; case self::TYPE_IMAGE_FILE: $add = array_diff($new, $old); $rem = array_diff($old, $new); if ($add && $rem) { return PhabricatorTransactions::COLOR_YELLOW; } else if ($add) { return PhabricatorTransactions::COLOR_GREEN; } else { return PhabricatorTransactions::COLOR_RED; } } return parent::getColor(); } public function getNoEffectDescription() { switch ($this->getTransactionType()) { case self::TYPE_IMAGE_NAME: return pht('The image title was not updated.'); case self::TYPE_IMAGE_DESCRIPTION: return pht('The image description was not updated.'); case self::TYPE_IMAGE_SEQUENCE: return pht('The image sequence was not updated.'); } return parent::getNoEffectDescription(); } } diff --git a/src/applications/phrequent/application/PhabricatorPhrequentApplication.php b/src/applications/phrequent/application/PhabricatorPhrequentApplication.php index bc0c39994f..bded663ed9 100644 --- a/src/applications/phrequent/application/PhabricatorPhrequentApplication.php +++ b/src/applications/phrequent/application/PhabricatorPhrequentApplication.php @@ -1,71 +1,72 @@ array( '(?:query/(?P[^/]+)/)?' => 'PhrequentListController', 'track/(?P[a-z]+)/(?P[^/]+)/' => 'PhrequentTrackController', ), ); } public function loadStatus(PhabricatorUser $user) { $status = array(); + $limit = self::MAX_STATUS_ITEMS; // Show number of objects that are currently // being tracked for a user. - $count = PhrequentUserTimeQuery::getUserTotalObjectsTracked( - $user, - self::MAX_STATUS_ITEMS); - $count_str = self::formatStatusCount( - $count, - '%s Objects Tracked', - '%d Object(s) Tracked'); + $count = PhrequentUserTimeQuery::getUserTotalObjectsTracked($user, $limit); + if ($count >= $limit) { + $count_str = pht('%s+ Object(s) Tracked', new PhutilNumber($limit - 1)); + } else { + $count_str = pht('%s Object(s) Tracked', new PhutilNumber($count)); + } + $type = PhabricatorApplicationStatusView::TYPE_NEEDS_ATTENTION; $status[] = id(new PhabricatorApplicationStatusView()) ->setType($type) ->setText($count_str) ->setCount($count); return $status; } } diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php index 14a929573a..a6d88df341 100644 --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -1,477 +1,476 @@ getViewer(); $this->slug = $request->getURIData('slug'); $slug = PhabricatorSlug::normalize($this->slug); if ($slug != $this->slug) { $uri = PhrictionDocument::getSlugURI($slug); // Canonicalize pages to their one true URI. return id(new AphrontRedirectResponse())->setURI($uri); } require_celerity_resource('phriction-document-css'); $document = id(new PhrictionDocumentQuery()) ->setViewer($viewer) ->withSlugs(array($slug)) ->executeOne(); $version_note = null; $core_content = ''; $move_notice = ''; $properties = null; $content = null; $toc = null; if (!$document) { $document = PhrictionDocument::initializeNewDocument($viewer, $slug); $create_uri = '/phriction/edit/?slug='.$slug; $notice = new PHUIInfoView(); $notice->setSeverity(PHUIInfoView::SEVERITY_WARNING); $notice->setTitle(pht('No content here!')); $notice->appendChild( pht( 'No document found at %s. You can '. 'create a new document here.', phutil_tag('tt', array(), $slug), $create_uri)); $core_content = $notice; $page_title = pht('Page Not Found'); } else { $version = $request->getInt('v'); if ($version) { $content = id(new PhrictionContent())->loadOneWhere( 'documentID = %d AND version = %d', $document->getID(), $version); if (!$content) { return new Aphront404Response(); } if ($content->getID() != $document->getContentID()) { $vdate = phabricator_datetime($content->getDateCreated(), $viewer); $version_note = new PHUIInfoView(); $version_note->setSeverity(PHUIInfoView::SEVERITY_NOTICE); $version_note->appendChild( pht('You are viewing an older version of this document, as it '. 'appeared on %s.', $vdate)); } } else { $content = id(new PhrictionContent())->load($document->getContentID()); } $page_title = $content->getTitle(); $properties = $this ->buildPropertyListView($document, $content, $slug); $doc_status = $document->getStatus(); $current_status = $content->getChangeType(); if ($current_status == PhrictionChangeType::CHANGE_EDIT || $current_status == PhrictionChangeType::CHANGE_MOVE_HERE) { $core_content = $content->renderContent($viewer); $toc = $this->getToc($content); } else if ($current_status == PhrictionChangeType::CHANGE_DELETE) { $notice = new PHUIInfoView(); $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); $notice->setTitle(pht('Document Deleted')); $notice->appendChild( pht('This document has been deleted. You can edit it to put new '. 'content here, or use history to revert to an earlier version.')); $core_content = $notice->render(); } else if ($current_status == PhrictionChangeType::CHANGE_STUB) { $notice = new PHUIInfoView(); $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); $notice->setTitle(pht('Empty Document')); $notice->appendChild( pht('This document is empty. You can edit it to put some proper '. 'content here.')); $core_content = $notice->render(); } else if ($current_status == PhrictionChangeType::CHANGE_MOVE_AWAY) { $new_doc_id = $content->getChangeRef(); $slug_uri = null; // If the new document exists and the viewer can see it, provide a link // to it. Otherwise, render a generic message. $new_docs = id(new PhrictionDocumentQuery()) ->setViewer($viewer) ->withIDs(array($new_doc_id)) ->execute(); if ($new_docs) { $new_doc = head($new_docs); $slug_uri = PhrictionDocument::getSlugURI($new_doc->getSlug()); } $notice = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); if ($slug_uri) { $notice->appendChild( phutil_tag( 'p', array(), pht( 'This document has been moved to %s. You can edit it to put '. 'new content here, or use history to revert to an earlier '. 'version.', phutil_tag('a', array('href' => $slug_uri), $slug_uri)))); } else { $notice->appendChild( phutil_tag( 'p', array(), pht( 'This document has been moved. You can edit it to put new '. 'contne here, or use history to revert to an earlier '. 'version.'))); } $core_content = $notice->render(); } else { throw new Exception(pht("Unknown document status '%s'!", $doc_status)); } $move_notice = null; if ($current_status == PhrictionChangeType::CHANGE_MOVE_HERE) { $from_doc_id = $content->getChangeRef(); $slug_uri = null; // If the old document exists and is visible, provide a link to it. $from_docs = id(new PhrictionDocumentQuery()) ->setViewer($viewer) ->withIDs(array($from_doc_id)) ->execute(); if ($from_docs) { $from_doc = head($from_docs); $slug_uri = PhrictionDocument::getSlugURI($from_doc->getSlug()); } $move_notice = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE); if ($slug_uri) { $move_notice->appendChild( pht( 'This document was moved from %s.', phutil_tag('a', array('href' => $slug_uri), $slug_uri))); } else { // Render this for consistency, even though it's a bit silly. $move_notice->appendChild( pht('This document was moved from elsewhere.')); } } } $children = $this->renderDocumentChildren($slug); $actions = $this->buildActionView($viewer, $document); $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); $crumb_views = $this->renderBreadcrumbs($slug); foreach ($crumb_views as $view) { $crumbs->addCrumb($view); } $action_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Actions')) ->setHref('#') ->setIconFont('fa-bars') ->addClass('phui-mobile-menu') ->setDropdownMenu($actions); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($document) ->setHeader($page_title) ->addActionLink($action_button); if ($content) { $header->setEpoch($content->getDateCreated()); } $prop_list = null; if ($properties) { $prop_list = new PHUIPropertyGroupView(); $prop_list->addPropertyList($properties); } $page_content = id(new PHUIDocumentViewPro()) ->setHeader($header) - ->setPropertyList($prop_list) ->setToc($toc) ->appendChild( array( $version_note, $move_notice, $core_content, )); return $this->buildApplicationPage( array( $crumbs->render(), $page_content, + $prop_list, $children, ), array( 'pageObjects' => array($document->getPHID()), 'title' => $page_title, - 'class' => 'pro-white-background', )); } private function buildPropertyListView( PhrictionDocument $document, PhrictionContent $content, $slug) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($document); $view->addProperty( pht('Last Author'), $viewer->renderHandle($content->getAuthorPHID())); return $view; } private function buildActionView( PhabricatorUser $viewer, PhrictionDocument $document) { $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $document, PhabricatorPolicyCapability::CAN_EDIT); $slug = PhabricatorSlug::normalize($this->slug); $action_view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObjectURI($this->getRequest()->getRequestURI()) ->setObject($document); if (!$document->getID()) { return $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('Create This Document')) ->setIcon('fa-plus-square') ->setHref('/phriction/edit/?slug='.$slug)); } $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Document')) ->setDisabled(!$can_edit) ->setIcon('fa-pencil') ->setHref('/phriction/edit/'.$document->getID().'/')); if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) { $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('Move Document')) ->setDisabled(!$can_edit) ->setIcon('fa-arrows') ->setHref('/phriction/move/'.$document->getID().'/') ->setWorkflow(true)); $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Document')) ->setDisabled(!$can_edit) ->setIcon('fa-times') ->setHref('/phriction/delete/'.$document->getID().'/') ->setWorkflow(true)); } return $action_view->addAction( id(new PhabricatorActionView()) ->setName(pht('View History')) ->setIcon('fa-list') ->setHref(PhrictionDocument::getSlugURI($slug, 'history'))); } private function renderDocumentChildren($slug) { $d_child = PhabricatorSlug::getDepth($slug) + 1; $d_grandchild = PhabricatorSlug::getDepth($slug) + 2; $limit = 250; $query = id(new PhrictionDocumentQuery()) ->setViewer($this->getRequest()->getUser()) ->withDepths(array($d_child, $d_grandchild)) ->withSlugPrefix($slug == '/' ? '' : $slug) ->withStatuses(array( PhrictionDocumentStatus::STATUS_EXISTS, PhrictionDocumentStatus::STATUS_STUB, )) ->setLimit($limit) ->setOrder(PhrictionDocumentQuery::ORDER_HIERARCHY) ->needContent(true); $children = $query->execute(); if (!$children) { return; } // We're going to render in one of three modes to try to accommodate // different information scales: // // - If we found fewer than $limit rows, we know we have all the children // and grandchildren and there aren't all that many. We can just render // everything. // - If we found $limit rows but the results included some grandchildren, // we just throw them out and render only the children, as we know we // have them all. // - If we found $limit rows and the results have no grandchildren, we // have a ton of children. Render them and then let the user know that // this is not an exhaustive list. if (count($children) == $limit) { $more_children = true; foreach ($children as $child) { if ($child->getDepth() == $d_grandchild) { $more_children = false; } } $show_grandchildren = false; } else { $show_grandchildren = true; $more_children = false; } $children_dicts = array(); $grandchildren_dicts = array(); foreach ($children as $key => $child) { $child_dict = array( 'slug' => $child->getSlug(), 'depth' => $child->getDepth(), 'title' => $child->getContent()->getTitle(), ); if ($child->getDepth() == $d_child) { $children_dicts[] = $child_dict; continue; } else { unset($children[$key]); if ($show_grandchildren) { $ancestors = PhabricatorSlug::getAncestry($child->getSlug()); $grandchildren_dicts[end($ancestors)][] = $child_dict; } } } // Fill in any missing children. $known_slugs = mpull($children, null, 'getSlug'); foreach ($grandchildren_dicts as $slug => $ignored) { if (empty($known_slugs[$slug])) { $children_dicts[] = array( 'slug' => $slug, 'depth' => $d_child, 'title' => PhabricatorSlug::getDefaultTitle($slug), 'empty' => true, ); } } $children_dicts = isort($children_dicts, 'title'); $list = array(); foreach ($children_dicts as $child) { $list[] = hsprintf('
  • '); $list[] = $this->renderChildDocumentLink($child); $grand = idx($grandchildren_dicts, $child['slug'], array()); if ($grand) { $list[] = hsprintf('
      '); foreach ($grand as $grandchild) { $list[] = hsprintf('
    • '); $list[] = $this->renderChildDocumentLink($grandchild); $list[] = hsprintf('
    • '); } $list[] = hsprintf('
    '); } $list[] = hsprintf('
  • '); } if ($more_children) { $list[] = phutil_tag( 'li', array( 'class' => 'remarkup-list-item', ), pht('More...')); } $header = id(new PHUIHeaderView()) ->setHeader(pht('Document Hierarchy')); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild(phutil_tag( 'div', array( 'class' => 'phabricator-remarkup mlt mlb', ), phutil_tag( 'ul', array( 'class' => 'remarkup-list', ), $list))); return phutil_tag_div('phui-document-view-pro-box', $box); } private function renderChildDocumentLink(array $info) { $title = nonempty($info['title'], pht('(Untitled Document)')); $item = phutil_tag( 'a', array( 'href' => PhrictionDocument::getSlugURI($info['slug']), ), $title); if (isset($info['empty'])) { $item = phutil_tag('em', array(), $item); } return $item; } protected function getDocumentSlug() { return $this->slug; } protected function getToc(PhrictionContent $content) { $toc = $content->getRenderedTableOfContents(); if ($toc) { $toc = phutil_tag_div('phui-document-toc-content', array( phutil_tag_div( 'phui-document-toc-header', pht('Contents')), $toc, )); } return $toc; } } diff --git a/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php b/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php index 712478c839..c36100f12b 100644 --- a/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php +++ b/src/applications/phurl/editor/PhabricatorPhurlURLEditor.php @@ -1,267 +1,265 @@ getTransactionType()) { case PhabricatorPhurlURLTransaction::TYPE_NAME: return $object->getName(); case PhabricatorPhurlURLTransaction::TYPE_URL: return $object->getLongURL(); case PhabricatorPhurlURLTransaction::TYPE_ALIAS: return $object->getAlias(); case PhabricatorPhurlURLTransaction::TYPE_DESCRIPTION: return $object->getDescription(); } return parent::getCustomTransactionOldValue($object, $xaction); } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorPhurlURLTransaction::TYPE_NAME: case PhabricatorPhurlURLTransaction::TYPE_URL: case PhabricatorPhurlURLTransaction::TYPE_DESCRIPTION: return $xaction->getNewValue(); case PhabricatorPhurlURLTransaction::TYPE_ALIAS: if (!strlen($xaction->getNewValue())) { return null; } return $xaction->getNewValue(); } return parent::getCustomTransactionNewValue($object, $xaction); } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorPhurlURLTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); return; case PhabricatorPhurlURLTransaction::TYPE_URL: $object->setLongURL($xaction->getNewValue()); return; case PhabricatorPhurlURLTransaction::TYPE_ALIAS: $object->setAlias($xaction->getNewValue()); return; case PhabricatorPhurlURLTransaction::TYPE_DESCRIPTION: $object->setDescription($xaction->getNewValue()); return; } return parent::applyCustomInternalTransaction($object, $xaction); } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorPhurlURLTransaction::TYPE_NAME: case PhabricatorPhurlURLTransaction::TYPE_URL: case PhabricatorPhurlURLTransaction::TYPE_ALIAS: case PhabricatorPhurlURLTransaction::TYPE_DESCRIPTION: return; } return parent::applyCustomExternalTransaction($object, $xaction); } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhabricatorPhurlURLTransaction::TYPE_ALIAS: $overdrawn = $this->validateIsTextFieldTooLong( $object->getName(), $xactions, 64); if ($overdrawn) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Alias Too Long'), pht('The alias can be no longer than 64 characters.'), nonempty(last($xactions), null)); } foreach ($xactions as $xaction) { if ($xaction->getOldValue() != $xaction->getNewValue()) { $new_alias = $xaction->getNewValue(); if (!preg_match('/[a-zA-Z]/', $new_alias)) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid Alias'), pht('The alias must contain at least one letter.'), $xaction); } if (preg_match('/[^a-z0-9]/i', $new_alias)) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid Alias'), pht('The alias may only contain letters and numbers.'), $xaction); } } } break; case PhabricatorPhurlURLTransaction::TYPE_URL: $missing = $this->validateIsEmptyTextField( $object->getLongURL(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('URL path is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } foreach ($xactions as $xaction) { if ($xaction->getOldValue() != $xaction->getNewValue()) { $protocols = PhabricatorEnv::getEnvConfig('uri.allowed-protocols'); $uri = new PhutilURI($xaction->getNewValue()); if (!isset($protocols[$uri->getProtocol()])) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid URL'), pht('The protocol of the URL is invalid.'), null); } } } break; } return $errors; } protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function supportsSearch() { return true; } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function getMailSubjectPrefix() { return pht('[Phurl]'); } protected function getMailTo(PhabricatorLiskDAO $object) { $phids = array(); if ($object->getPHID()) { $phids[] = $object->getPHID(); } $phids[] = $this->getActingAsPHID(); $phids = array_unique($phids); return $phids; } public function getMailTagsMap() { return array( - PhabricatorPhurlURLTransaction::MAILTAG_CONTENT => + PhabricatorPhurlURLTransaction::MAILTAG_DETAILS => pht( - "A URL's name or path changes."), - PhabricatorPhurlURLTransaction::MAILTAG_OTHER => - pht('Other event activity not listed above occurs.'), + "A URL's details change."), ); } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $id = $object->getID(); $name = $object->getName(); return id(new PhabricatorMetaMTAMail()) ->setSubject("U{$id}: {$name}") ->addHeader('Thread-Topic', "U{$id}: ".$object->getName()); } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $description = $object->getDescription(); $body = parent::buildMailBody($object, $xactions); if (strlen($description)) { $body->addRemarkupSection( pht('URL DESCRIPTION'), $object->getDescription()); } $body->addLinkSection( pht('URL DETAIL'), PhabricatorEnv::getProductionURI('/U'.$object->getID())); return $body; } protected function didCatchDuplicateKeyException( PhabricatorLiskDAO $object, array $xactions, Exception $ex) { $errors = array(); $errors[] = new PhabricatorApplicationTransactionValidationError( PhabricatorPhurlURLTransaction::TYPE_ALIAS, pht('Duplicate'), pht('This alias is already in use.'), null); throw new PhabricatorApplicationTransactionValidationException($errors); } } diff --git a/src/applications/phurl/storage/PhabricatorPhurlURL.php b/src/applications/phurl/storage/PhabricatorPhurlURL.php index 34a143bc90..7df1d241a7 100644 --- a/src/applications/phurl/storage/PhabricatorPhurlURL.php +++ b/src/applications/phurl/storage/PhabricatorPhurlURL.php @@ -1,194 +1,204 @@ setViewer($actor) ->withClasses(array('PhabricatorPhurlApplication')) ->executeOne(); return id(new PhabricatorPhurlURL()) ->setAuthorPHID($actor->getPHID()) ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) ->setEditPolicy($actor->getPHID()) ->setSpacePHID($actor->getDefaultSpacePHID()); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text', 'alias' => 'sort64?', 'longURL' => 'text', 'description' => 'text', + 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( 'key_instance' => array( 'columns' => array('alias'), 'unique' => true, ), 'key_author' => array( 'columns' => array('authorPHID'), ), ), ) + parent::getConfiguration(); } + public function save() { + if (!$this->getMailKey()) { + $this->setMailKey(Filesystem::readRandomCharacters(20)); + } + return parent::save(); + } + public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorPhurlURLPHIDType::TYPECONST); } public function getMonogram() { return 'U'.$this->getID(); } public function getURI() { $uri = '/'.$this->getMonogram(); return $uri; } public function isValid() { $allowed_protocols = PhabricatorEnv::getEnvConfig('uri.allowed-protocols'); $uri = new PhutilURI($this->getLongURL()); return isset($allowed_protocols[$uri->getProtocol()]); } public function getDisplayName() { if ($this->getName()) { return $this->getName(); } else { return $this->getLongURL(); } } public function getRedirectURI() { if (strlen($this->getAlias())) { return '/u/'.$this->getAlias(); } else { return '/u/'.$this->getID(); } } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { $user_phid = $this->getAuthorPHID(); if ($user_phid) { $viewer_phid = $viewer->getPHID(); if ($viewer_phid == $user_phid) { return true; } } return false; } public function describeAutomaticCapability($capability) { return pht('The owner of a URL can always view and edit it.'); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhabricatorPhurlURLEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PhabricatorPhurlURLTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorSubscribableInterface )----------------------------------- */ public function isAutomaticallySubscribed($phid) { return ($phid == $this->getAuthorPHID()); } public function shouldShowSubscribersProperty() { return true; } public function shouldAllowSubscription($phid) { return true; } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { return array($this->getAuthorPHID()); } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $this->delete(); $this->saveTransaction(); } /* -( PhabricatorSpacesInterface )----------------------------------------- */ public function getSpacePHID() { return $this->spacePHID; } } diff --git a/src/applications/phurl/storage/PhabricatorPhurlURLTransaction.php b/src/applications/phurl/storage/PhabricatorPhurlURLTransaction.php index 6d275bcefb..d520b30518 100644 --- a/src/applications/phurl/storage/PhabricatorPhurlURLTransaction.php +++ b/src/applications/phurl/storage/PhabricatorPhurlURLTransaction.php @@ -1,244 +1,243 @@ getTransactionType()) { case self::TYPE_NAME: case self::TYPE_URL: case self::TYPE_ALIAS: case self::TYPE_DESCRIPTION: $phids[] = $this->getObjectPHID(); break; } return $phids; } public function shouldHide() { $old = $this->getOldValue(); switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: return ($old === null); } return parent::shouldHide(); } public function getIcon() { switch ($this->getTransactionType()) { case self::TYPE_NAME: case self::TYPE_URL: case self::TYPE_ALIAS: case self::TYPE_DESCRIPTION: return 'fa-pencil'; break; } return parent::getIcon(); } public function getTitle() { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); $type = $this->getTransactionType(); switch ($type) { case self::TYPE_NAME: if ($old === null) { return pht( '%s created this URL.', $this->renderHandleLink($author_phid)); } else { return pht( '%s changed the name of the URL from %s to %s.', $this->renderHandleLink($author_phid), $old, $new); } case self::TYPE_URL: if ($old === null) { return pht( '%s set the destination of the URL to %s.', $this->renderHandleLink($author_phid), $new); } else { return pht( '%s changed the destination of the URL from %s to %s.', $this->renderHandleLink($author_phid), $old, $new); } case self::TYPE_ALIAS: if ($old === null) { return pht( '%s set the alias of the URL to %s.', $this->renderHandleLink($author_phid), $new); } else if ($new === null) { return pht( '%s removed the alias of the URL.', $this->renderHandleLink($author_phid)); } else { return pht( '%s changed the alias of the URL from %s to %s.', $this->renderHandleLink($author_phid), $old, $new); } case self::TYPE_DESCRIPTION: return pht( "%s updated the URL's description.", $this->renderHandleLink($author_phid)); } return parent::getTitle(); } public function getTitleForFeed() { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); $viewer = $this->getViewer(); $type = $this->getTransactionType(); switch ($type) { case self::TYPE_NAME: if ($old === null) { return pht( '%s created %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else { return pht( '%s changed the name of %s from %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old, $new); } case self::TYPE_URL: if ($old === null) { return pht( '%s set the destination of %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $new); } else { return pht( '%s changed the destination of %s from %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old, $new); } case self::TYPE_ALIAS: if ($old === null) { return pht( '%s set the alias of %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $new); } else if ($new === null) { return pht( '%s removed the alias of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } else { return pht( '%s changed the alias of %s from %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $old, $new); } case self::TYPE_DESCRIPTION: return pht( '%s updated the description of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } return parent::getTitleForFeed(); } public function getColor() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_NAME: case self::TYPE_URL: case self::TYPE_ALIAS: case self::TYPE_DESCRIPTION: return PhabricatorTransactions::COLOR_GREEN; } return parent::getColor(); } public function hasChangeDetails() { switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: return ($this->getOldValue() !== null); } return parent::hasChangeDetails(); } public function renderChangeDetails(PhabricatorUser $viewer) { switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: $old = $this->getOldValue(); $new = $this->getNewValue(); return $this->renderTextCorpusChangeDetails( $viewer, $old, $new); } return parent::renderChangeDetails($viewer); } public function getMailTags() { $tags = array(); switch ($this->getTransactionType()) { case self::TYPE_NAME: case self::TYPE_DESCRIPTION: case self::TYPE_URL: case self::TYPE_ALIAS: - $tags[] = self::MAILTAG_CONTENT; + $tags[] = self::MAILTAG_DETAILS; break; } return $tags; } } diff --git a/src/applications/policy/editor/PhabricatorPolicyEditEngineExtension.php b/src/applications/policy/editor/PhabricatorPolicyEditEngineExtension.php new file mode 100644 index 0000000000..21ea34ca28 --- /dev/null +++ b/src/applications/policy/editor/PhabricatorPolicyEditEngineExtension.php @@ -0,0 +1,118 @@ +getViewer(); + + $editor = $object->getApplicationTransactionEditor(); + $types = $editor->getTransactionTypesForObject($object); + $types = array_fuse($types); + + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->setObject($object) + ->execute(); + + $map = array( + PhabricatorTransactions::TYPE_VIEW_POLICY => array( + 'key' => 'policy.view', + 'aliases' => array('view'), + 'capability' => PhabricatorPolicyCapability::CAN_VIEW, + 'label' => pht('View Policy'), + 'description' => pht('Controls who can view the object.'), + 'edit' => 'view', + ), + PhabricatorTransactions::TYPE_EDIT_POLICY => array( + 'key' => 'policy.edit', + 'aliases' => array('edit'), + 'capability' => PhabricatorPolicyCapability::CAN_EDIT, + 'label' => pht('Edit Policy'), + 'description' => pht('Controls who can edit the object.'), + 'edit' => 'edit', + ), + PhabricatorTransactions::TYPE_JOIN_POLICY => array( + 'key' => 'policy.join', + 'aliases' => array('join'), + 'capability' => PhabricatorPolicyCapability::CAN_JOIN, + 'label' => pht('Join Policy'), + 'description' => pht('Controls who can join the object.'), + 'edit' => 'join', + ), + ); + + $fields = array(); + foreach ($map as $type => $spec) { + if (empty($types[$type])) { + continue; + } + + $capability = $spec['capability']; + $key = $spec['key']; + $aliases = $spec['aliases']; + $label = $spec['label']; + $description = $spec['description']; + $edit = $spec['edit']; + + $policy_field = id(new PhabricatorPolicyEditField()) + ->setKey($key) + ->setLabel($label) + ->setDescription($description) + ->setAliases($aliases) + ->setCapability($capability) + ->setPolicies($policies) + ->setTransactionType($type) + ->setEditTypeKey($edit) + ->setValue($object->getPolicy($capability)); + $fields[] = $policy_field; + + if (!($object instanceof PhabricatorSpacesInterface)) { + if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { + $type_space = PhabricatorTransactions::TYPE_SPACE; + if (isset($types[$type_space])) { + $space_field = id(new PhabricatorSpaceEditField()) + ->setKey('spacePHID') + ->setLabel(pht('Space')) + ->setEditTypeKey('space') + ->setDescription( + pht('Shifts the object in the Spaces application.')) + ->setIsReorderable(false) + ->setAliases(array('space', 'policy.space')) + ->setTransactionType($type_space) + ->setValue($object->getSpacePHID()); + $fields[] = $space_field; + + $policy_field->setSpaceField($space_field); + } + } + } + } + + return $fields; + } + +} diff --git a/src/applications/ponder/application/PhabricatorPonderApplication.php b/src/applications/ponder/application/PhabricatorPonderApplication.php index 38546a8a7d..886fddcc05 100644 --- a/src/applications/ponder/application/PhabricatorPonderApplication.php +++ b/src/applications/ponder/application/PhabricatorPonderApplication.php @@ -1,111 +1,103 @@ [1-9]\d*)' => 'PonderQuestionViewController', '/ponder/' => array( '(?:query/(?P[^/]+)/)?' => 'PonderQuestionListController', 'answer/add/' => 'PonderAnswerSaveController', 'answer/edit/(?P\d+)/' => 'PonderAnswerEditController', 'answer/comment/(?P\d+)/' => 'PonderAnswerCommentController', 'answer/history/(?P\d+)/' => 'PonderAnswerHistoryController', 'answer/helpful/(?Padd|remove)/(?P[1-9]\d*)/' => 'PonderHelpfulSaveController', 'question/edit/(?:(?P\d+)/)?' => 'PonderQuestionEditController', 'question/create/' => 'PonderQuestionEditController', 'question/comment/(?P\d+)/' => 'PonderQuestionCommentController', 'question/history/(?P\d+)/' => 'PonderQuestionHistoryController', 'preview/' => 'PhabricatorMarkupPreviewController', 'question/status/(?P[1-9]\d*)/' => 'PonderQuestionStatusController', ), ); } public function getMailCommandObjects() { return array( 'question' => array( 'name' => pht('Email Commands: Questions'), 'header' => pht('Interacting with Ponder Questions'), 'object' => new PonderQuestion(), 'summary' => pht( 'This page documents the commands you can use to interact with '. 'questions in Ponder.'), ), ); } protected function getCustomCapabilities() { return array( PonderDefaultViewCapability::CAPABILITY => array( 'template' => PonderQuestionPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_VIEW, ), PonderModerateCapability::CAPABILITY => array( 'default' => PhabricatorPolicies::POLICY_ADMIN, 'template' => PonderQuestionPHIDType::TYPECONST, 'capability' => PhabricatorPolicyCapability::CAN_EDIT, ), ); } public function getApplicationSearchDocumentTypes() { return array( PonderQuestionPHIDType::TYPECONST, ); } } diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 12699c8cbc..2d47031942 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -1,767 +1,771 @@ getUser(); $id = $request->getURIData('id'); $show_hidden = $request->getBool('hidden'); $this->showHidden = $show_hidden; $project = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->needImages(true); $id = $request->getURIData('id'); $slug = $request->getURIData('slug'); if ($slug) { $project->withSlugs(array($slug)); } else { $project->withIDs(array($id)); } $project = $project->executeOne(); if (!$project) { return new Aphront404Response(); } $this->setProject($project); $this->id = $project->getID(); $sort_key = $request->getStr('order'); switch ($sort_key) { case PhabricatorProjectColumn::ORDER_NATURAL: case PhabricatorProjectColumn::ORDER_PRIORITY: break; default: $sort_key = PhabricatorProjectColumn::DEFAULT_ORDER; break; } $this->sortKey = $sort_key; $column_query = id(new PhabricatorProjectColumnQuery()) ->setViewer($viewer) ->withProjectPHIDs(array($project->getPHID())); if (!$show_hidden) { $column_query->withStatuses( array(PhabricatorProjectColumn::STATUS_ACTIVE)); } $columns = $column_query->execute(); $columns = mpull($columns, null, 'getSequence'); // TODO: Expand the checks here if we add the ability // to hide the Backlog column if (!$columns) { $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); if (!$can_edit) { return $this->noAccessDialog($project); } switch ($request->getStr('initialize-type')) { case 'backlog-only': $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $column = PhabricatorProjectColumn::initializeNewColumn($viewer) ->setSequence(0) ->setProperty('isDefault', true) ->setProjectPHID($project->getPHID()) ->save(); $column->attachProject($project); $columns[0] = $column; unset($unguarded); break; case 'import': return id(new AphrontRedirectResponse()) ->setURI( $this->getApplicationURI('board/'.$project->getID().'/import/')); break; default: return $this->initializeWorkboardDialog($project); break; } } ksort($columns); $board_uri = $this->getApplicationURI('board/'.$project->getID().'/'); $engine = id(new ManiphestTaskSearchEngine()) ->setViewer($viewer) ->setBaseURI($board_uri) ->setIsBoardView(true); if ($request->isFormPost()) { $saved = $engine->buildSavedQueryFromRequest($request); $engine->saveQuery($saved); $filter_form = id(new AphrontFormView()) ->setUser($viewer); $engine->buildSearchForm($filter_form, $saved); if ($engine->getErrors()) { return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FULL) ->setTitle(pht('Advanced Filter')) ->appendChild($filter_form->buildLayoutView()) ->setErrors($engine->getErrors()) ->setSubmitURI($board_uri) ->addSubmitButton(pht('Apply Filter')) ->addCancelButton($board_uri); } return id(new AphrontRedirectResponse())->setURI( $this->getURIWithState( $engine->getQueryResultsPageURI($saved->getQueryKey()))); } $query_key = $request->getURIData('queryKey'); if (!$query_key) { $query_key = 'open'; } $this->queryKey = $query_key; $custom_query = null; if ($engine->isBuiltinQuery($query_key)) { $saved = $engine->buildSavedQueryFromBuiltin($query_key); } else { $saved = id(new PhabricatorSavedQueryQuery()) ->setViewer($viewer) ->withQueryKeys(array($query_key)) ->executeOne(); if (!$saved) { return new Aphront404Response(); } $custom_query = $saved; } if ($request->getURIData('filter')) { $filter_form = id(new AphrontFormView()) ->setUser($viewer); $engine->buildSearchForm($filter_form, $saved); return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FULL) ->setTitle(pht('Advanced Filter')) ->appendChild($filter_form->buildLayoutView()) ->setSubmitURI($board_uri) ->addSubmitButton(pht('Apply Filter')) ->addCancelButton($board_uri); } $task_query = $engine->buildQueryFromSavedQuery($saved); $tasks = $task_query ->withEdgeLogicPHIDs( PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, PhabricatorQueryConstraint::OPERATOR_AND, array($project->getPHID())) ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) ->setViewer($viewer) ->execute(); $tasks = mpull($tasks, null, 'getPHID'); if ($tasks) { $positions = id(new PhabricatorProjectColumnPositionQuery()) ->setViewer($viewer) ->withObjectPHIDs(mpull($tasks, 'getPHID')) ->withColumns($columns) ->execute(); $positions = mpull($positions, null, 'getObjectPHID'); } else { $positions = array(); } $task_map = array(); foreach ($tasks as $task) { $task_phid = $task->getPHID(); if (empty($positions[$task_phid])) { // This shouldn't normally be possible because we create positions on // demand, but we might have raced as an object was removed from the // board. Just drop the task if we don't have a position for it. continue; } $position = $positions[$task_phid]; $task_map[$position->getColumnPHID()][] = $task_phid; } // If we're showing the board in "natural" order, sort columns by their // column positions. if ($this->sortKey == PhabricatorProjectColumn::ORDER_NATURAL) { foreach ($task_map as $column_phid => $task_phids) { $order = array(); foreach ($task_phids as $task_phid) { if (isset($positions[$task_phid])) { $order[$task_phid] = $positions[$task_phid]->getOrderingKey(); } else { $order[$task_phid] = 0; } } asort($order); $task_map[$column_phid] = array_keys($order); } } $task_can_edit_map = id(new PhabricatorPolicyFilter()) ->setViewer($viewer) ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)) ->apply($tasks); // If this is a batch edit, select the editable tasks in the chosen column // and ship the user into the batch editor. $batch_edit = $request->getStr('batch'); if ($batch_edit) { if ($batch_edit !== self::BATCH_EDIT_ALL) { $column_id_map = mpull($columns, null, 'getID'); $batch_column = idx($column_id_map, $batch_edit); if (!$batch_column) { return new Aphront404Response(); } $batch_task_phids = idx($task_map, $batch_column->getPHID(), array()); foreach ($batch_task_phids as $key => $batch_task_phid) { if (empty($task_can_edit_map[$batch_task_phid])) { unset($batch_task_phids[$key]); } } $batch_tasks = array_select_keys($tasks, $batch_task_phids); } else { $batch_tasks = $task_can_edit_map; } if (!$batch_tasks) { $cancel_uri = $this->getURIWithState($board_uri); return $this->newDialog() ->setTitle(pht('No Editable Tasks')) ->appendParagraph( pht( 'The selected column contains no visible tasks which you '. 'have permission to edit.')) ->addCancelButton($board_uri); } $batch_ids = mpull($batch_tasks, 'getID'); $batch_ids = implode(',', $batch_ids); $batch_uri = new PhutilURI('/maniphest/batch/'); $batch_uri->setQueryParam('board', $this->id); $batch_uri->setQueryParam('batch', $batch_ids); return id(new AphrontRedirectResponse()) ->setURI($batch_uri); } $board_id = celerity_generate_unique_node_id(); $board = id(new PHUIWorkboardView()) ->setUser($viewer) ->setID($board_id); $behavior_config = array( 'boardID' => $board_id, 'projectPHID' => $project->getPHID(), 'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'), 'createURI' => '/maniphest/task/create/', 'order' => $this->sortKey, ); $this->initBehavior( 'project-boards', $behavior_config); - $this->addExtraQuickSandConfig(array('boardConfig' => $behavior_config)); $this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); foreach ($columns as $column) { $task_phids = idx($task_map, $column->getPHID(), array()); $column_tasks = array_select_keys($tasks, $task_phids); $panel = id(new PHUIWorkpanelView()) ->setHeader($column->getDisplayName()) ->setSubHeader($column->getDisplayType()) ->addSigil('workpanel'); $header_icon = $column->getHeaderIcon(); if ($header_icon) { $panel->setHeaderIcon($header_icon); } if ($column->isHidden()) { $panel->addClass('project-panel-hidden'); } $column_menu = $this->buildColumnMenu($project, $column); $panel->addHeaderAction($column_menu); $tag_id = celerity_generate_unique_node_id(); $tag_content_id = celerity_generate_unique_node_id(); $count_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) ->setShade(PHUITagView::COLOR_BLUE) ->setID($tag_id) ->setName(phutil_tag('span', array('id' => $tag_content_id), '-')) ->setStyle('display: none'); $panel->setHeaderTag($count_tag); $cards = id(new PHUIObjectItemListView()) ->setUser($viewer) ->setFlush(true) ->setAllowEmptyList(true) ->addSigil('project-column') ->setMetadata( array( 'columnPHID' => $column->getPHID(), 'countTagID' => $tag_id, 'countTagContentID' => $tag_content_id, 'pointLimit' => $column->getPointLimit(), )); foreach ($column_tasks as $task) { $owner = null; if ($task->getOwnerPHID()) { $owner = $this->handles[$task->getOwnerPHID()]; } $can_edit = idx($task_can_edit_map, $task->getPHID(), false); $cards->addItem(id(new ProjectBoardTaskCard()) ->setViewer($viewer) ->setTask($task) ->setOwner($owner) ->setCanEdit($can_edit) ->getItem()); } $panel->setCards($cards); $board->addPanel($panel); } $sort_menu = $this->buildSortMenu( $viewer, $sort_key); $filter_menu = $this->buildFilterMenu( $viewer, $custom_query, $engine, $query_key); $manage_menu = $this->buildManageMenu($project, $show_hidden); $header_link = phutil_tag( 'a', array( 'href' => $this->getApplicationURI('profile/'.$project->getID().'/'), ), $project->getName()); $header = id(new PHUIHeaderView()) ->setHeader($header_link) ->setUser($viewer) ->setNoBackground(true) ->addActionLink($sort_menu) ->addActionLink($filter_menu) ->addActionLink($manage_menu) ->setPolicyObject($project); $header_box = id(new PHUIBoxView()) ->appendChild($header) ->addClass('project-board-header'); $board_box = id(new PHUIBoxView()) ->appendChild($board) ->addClass('project-board-wrapper'); $nav = $this->buildIconNavView($project); - $nav->appendChild($header_box); - $nav->appendChild($board_box); - return $this->buildApplicationPage( - $nav, - array( - 'title' => pht('%s Board', $project->getName()), - 'showFooter' => false, - 'pageObjects' => array($project->getPHID()), - )); + return $this->newPage() + ->setTitle(pht('%s Board', $project->getName())) + ->setPageObjectPHIDs(array($project->getPHID())) + ->setShowFooter(false) + ->setNavigation($nav) + ->addQuicksandConfig( + array( + 'boardConfig' => $behavior_config, + )) + ->appendChild( + array( + $header_box, + $board_box, + )); } private function buildSortMenu( PhabricatorUser $viewer, $sort_key) { $sort_icon = id(new PHUIIconView()) ->setIconFont('fa-sort-amount-asc bluegrey'); $named = array( PhabricatorProjectColumn::ORDER_NATURAL => pht('Natural'), PhabricatorProjectColumn::ORDER_PRIORITY => pht('Sort by Priority'), ); $base_uri = $this->getURIWithState(); $items = array(); foreach ($named as $key => $name) { $is_selected = ($key == $sort_key); if ($is_selected) { $active_order = $name; } $item = id(new PhabricatorActionView()) ->setIcon('fa-sort-amount-asc') ->setSelected($is_selected) ->setName($name); $uri = $base_uri->alter('order', $key); $item->setHref($uri); $items[] = $item; } $sort_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($items as $item) { $sort_menu->addAction($item); } $sort_button = id(new PHUIButtonView()) ->setText(pht('Sort: %s', $active_order)) ->setIcon($sort_icon) ->setTag('a') ->setHref('#') ->addSigil('boards-dropdown-menu') ->setMetadata( array( 'items' => hsprintf('%s', $sort_menu), )); return $sort_button; } private function buildFilterMenu( PhabricatorUser $viewer, $custom_query, PhabricatorApplicationSearchEngine $engine, $query_key) { $filter_icon = id(new PHUIIconView()) ->setIconFont('fa-search-plus bluegrey'); $named = array( 'open' => pht('Open Tasks'), 'all' => pht('All Tasks'), ); if ($viewer->isLoggedIn()) { $named['assigned'] = pht('Assigned to Me'); } if ($custom_query) { $named[$custom_query->getQueryKey()] = pht('Custom Filter'); } $items = array(); foreach ($named as $key => $name) { $is_selected = ($key == $query_key); if ($is_selected) { $active_filter = $name; } $is_custom = false; if ($custom_query) { $is_custom = ($key == $custom_query->getQueryKey()); } $item = id(new PhabricatorActionView()) ->setIcon('fa-search') ->setSelected($is_selected) ->setName($name); if ($is_custom) { $uri = $this->getApplicationURI( 'board/'.$this->id.'/filter/query/'.$key.'/'); $item->setWorkflow(true); } else { $uri = $engine->getQueryResultsPageURI($key); } $uri = $this->getURIWithState($uri); $item->setHref($uri); $items[] = $item; } $items[] = id(new PhabricatorActionView()) ->setIcon('fa-cog') ->setHref($this->getApplicationURI('board/'.$this->id.'/filter/')) ->setWorkflow(true) ->setName(pht('Advanced Filter...')); $filter_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($items as $item) { $filter_menu->addAction($item); } $filter_button = id(new PHUIButtonView()) ->setText(pht('Filter: %s', $active_filter)) ->setIcon($filter_icon) ->setTag('a') ->setHref('#') ->addSigil('boards-dropdown-menu') ->setMetadata( array( 'items' => hsprintf('%s', $filter_menu), )); return $filter_button; } private function buildManageMenu( PhabricatorProject $project, $show_hidden) { $request = $this->getRequest(); $viewer = $request->getUser(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); $manage_icon = id(new PHUIIconView()) ->setIconFont('fa-cog bluegrey'); $manage_items = array(); $manage_items[] = id(new PhabricatorActionView()) ->setIcon('fa-plus') ->setName(pht('Add Column')) ->setHref($this->getApplicationURI('board/'.$this->id.'/edit/')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit); $manage_items[] = id(new PhabricatorActionView()) ->setIcon('fa-exchange') ->setName(pht('Reorder Columns')) ->setHref($this->getApplicationURI('board/'.$this->id.'/reorder/')) ->setDisabled(!$can_edit) ->setWorkflow(true); if ($show_hidden) { $hidden_uri = $this->getURIWithState() ->setQueryParam('hidden', null); $hidden_icon = 'fa-eye-slash'; $hidden_text = pht('Hide Hidden Columns'); } else { $hidden_uri = $this->getURIWithState() ->setQueryParam('hidden', 'true'); $hidden_icon = 'fa-eye'; $hidden_text = pht('Show Hidden Columns'); } $manage_items[] = id(new PhabricatorActionView()) ->setIcon($hidden_icon) ->setName($hidden_text) ->setHref($hidden_uri); $batch_edit_uri = $request->getRequestURI(); $batch_edit_uri->setQueryParam('batch', self::BATCH_EDIT_ALL); $can_batch_edit = PhabricatorPolicyFilter::hasCapability( $viewer, PhabricatorApplication::getByClass('PhabricatorManiphestApplication'), ManiphestBulkEditCapability::CAPABILITY); $manage_items[] = id(new PhabricatorActionView()) ->setIcon('fa-list-ul') ->setName(pht('Batch Edit Visible Tasks...')) ->setHref($batch_edit_uri) ->setDisabled(!$can_batch_edit); $manage_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($manage_items as $item) { $manage_menu->addAction($item); } $manage_button = id(new PHUIButtonView()) ->setText(pht('Manage Board')) ->setIcon($manage_icon) ->setTag('a') ->setHref('#') ->addSigil('boards-dropdown-menu') ->setMetadata( array( 'items' => hsprintf('%s', $manage_menu), )); return $manage_button; } private function buildColumnMenu( PhabricatorProject $project, PhabricatorProjectColumn $column) { $request = $this->getRequest(); $viewer = $request->getUser(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); $column_items = array(); $column_items[] = id(new PhabricatorActionView()) ->setIcon('fa-plus') ->setName(pht('Create Task...')) ->setHref('/maniphest/task/create/') ->addSigil('column-add-task') ->setMetadata( array( 'columnPHID' => $column->getPHID(), )); $batch_edit_uri = $request->getRequestURI(); $batch_edit_uri->setQueryParam('batch', $column->getID()); $can_batch_edit = PhabricatorPolicyFilter::hasCapability( $viewer, PhabricatorApplication::getByClass('PhabricatorManiphestApplication'), ManiphestBulkEditCapability::CAPABILITY); $column_items[] = id(new PhabricatorActionView()) ->setIcon('fa-list-ul') ->setName(pht('Batch Edit Tasks...')) ->setHref($batch_edit_uri) ->setDisabled(!$can_batch_edit); $detail_uri = $this->getApplicationURI( 'board/'.$this->id.'/column/'.$column->getID().'/'); $column_items[] = id(new PhabricatorActionView()) ->setIcon('fa-columns') ->setName(pht('Column Details')) ->setHref($detail_uri); $can_hide = ($can_edit && !$column->isDefaultColumn()); $hide_uri = 'board/'.$this->id.'/hide/'.$column->getID().'/'; $hide_uri = $this->getApplicationURI($hide_uri); $hide_uri = $this->getURIWithState($hide_uri); if (!$column->isHidden()) { $column_items[] = id(new PhabricatorActionView()) ->setName(pht('Hide Column')) ->setIcon('fa-eye-slash') ->setHref($hide_uri) ->setDisabled(!$can_hide) ->setWorkflow(true); } else { $column_items[] = id(new PhabricatorActionView()) ->setName(pht('Show Column')) ->setIcon('fa-eye') ->setHref($hide_uri) ->setDisabled(!$can_hide) ->setWorkflow(true); } $column_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($column_items as $item) { $column_menu->addAction($item); } $column_button = id(new PHUIIconView()) ->setIconFont('fa-caret-down') ->setHref('#') ->addSigil('boards-dropdown-menu') ->setMetadata( array( 'items' => hsprintf('%s', $column_menu), )); return $column_button; } private function initializeWorkboardDialog(PhabricatorProject $project) { $instructions = pht('This workboard has not been setup yet.'); $new_selector = id(new AphrontFormRadioButtonControl()) ->setName('initialize-type') ->setValue('backlog-only') ->addButton( 'backlog-only', pht('New Empty Board'), pht('Create a new board with just a backlog column.')) ->addButton( 'import', pht('Import Columns'), pht('Import board columns from another project.')); $dialog = id(new AphrontDialogView()) ->setUser($this->getRequest()->getUser()) ->setTitle(pht('New Workboard')) ->addSubmitButton('Continue') ->addCancelButton($this->getApplicationURI('view/'.$project->getID().'/')) ->appendParagraph($instructions) ->appendChild($new_selector); return id(new AphrontDialogResponse()) ->setDialog($dialog); } private function noAccessDialog(PhabricatorProject $project) { $instructions = pht('This workboard has not been setup yet.'); $dialog = id(new AphrontDialogView()) ->setUser($this->getRequest()->getUser()) ->setTitle(pht('No Workboard')) ->addCancelButton($this->getApplicationURI('view/'.$project->getID().'/')) ->appendParagraph($instructions); return id(new AphrontDialogResponse()) ->setDialog($dialog); } /** * Add current state parameters (like order and the visibility of hidden * columns) to a URI. * * This allows actions which toggle or adjust one piece of state to keep * the rest of the board state persistent. If no URI is provided, this method * starts with the request URI. * * @param string|null URI to add state parameters to. * @return PhutilURI URI with state parameters. */ private function getURIWithState($base = null) { if ($base === null) { $base = $this->getRequest()->getRequestURI(); } $base = new PhutilURI($base); if ($this->sortKey != PhabricatorProjectColumn::DEFAULT_ORDER) { $base->setQueryParam('order', $this->sortKey); } else { $base->setQueryParam('order', null); } $base->setQueryParam('hidden', $this->showHidden ? 'true' : null); return $base; } } diff --git a/src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php b/src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php new file mode 100644 index 0000000000..b8cd651a11 --- /dev/null +++ b/src/applications/project/editor/PhabricatorProjectsEditEngineExtension.php @@ -0,0 +1,66 @@ +getPHID(); + if ($object_phid) { + $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $object_phid, + $project_edge_type); + $project_phids = array_reverse($project_phids); + } else { + $project_phids = array(); + } + + $projects_field = id(new PhabricatorProjectsEditField()) + ->setKey('projectPHIDs') + ->setLabel(pht('Projects')) + ->setEditTypeKey('projects') + ->setDescription(pht('Add or remove associated projects.')) + ->setAliases(array('project', 'projects')) + ->setUseEdgeTransactions(true) + ->setEdgeTransactionDescriptions( + pht('Add projects.'), + pht('Remove projects.'), + pht('Set associated projects, overwriting current value.')) + ->setCommentActionLabel(pht('Add Projects')) + ->setTransactionType($edge_type) + ->setMetadataValue('edge:type', $project_edge_type) + ->setValue($project_phids); + + return array( + $projects_field, + ); + } + +} diff --git a/src/applications/releeph/storage/ReleephRequestTransaction.php b/src/applications/releeph/storage/ReleephRequestTransaction.php index b12ed6ad8c..6c7f98b3db 100644 --- a/src/applications/releeph/storage/ReleephRequestTransaction.php +++ b/src/applications/releeph/storage/ReleephRequestTransaction.php @@ -1,275 +1,275 @@ getTransactionType()) { default; break; } return parent::hasChangeDetails(); } public function getRequiredHandlePHIDs() { $phids = parent::getRequiredHandlePHIDs(); $phids[] = $this->getObjectPHID(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_REQUEST: case self::TYPE_DISCOVERY: $phids[] = $new; break; case self::TYPE_EDIT_FIELD: self::searchForPHIDs($this->getOldValue(), $phids); self::searchForPHIDs($this->getNewValue(), $phids); break; } return $phids; } public function getTitle() { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_REQUEST: return pht( '%s requested %s', $this->renderHandleLink($author_phid), $this->renderHandleLink($new)); break; case self::TYPE_USER_INTENT: return $this->getIntentTitle(); break; case self::TYPE_EDIT_FIELD: $field = newv($this->getMetadataValue('fieldClass'), array()); $name = $field->getName(); $markup = $name; if ($this->getRenderingTarget() === - PhabricatorApplicationTransaction::TARGET_HTML) { + parent::TARGET_HTML) { $markup = hsprintf('%s', $name); } return pht( '%s changed the %s to "%s"', $this->renderHandleLink($author_phid), $markup, $field->normalizeForTransactionView($this, $new)); break; case self::TYPE_PICK_STATUS: switch ($new) { case ReleephRequest::PICK_OK: return pht('%s found this request picks without error', $this->renderHandleLink($author_phid)); case ReleephRequest::REVERT_OK: return pht('%s found this request reverts without error', $this->renderHandleLink($author_phid)); case ReleephRequest::PICK_FAILED: return pht("%s couldn't pick this request", $this->renderHandleLink($author_phid)); case ReleephRequest::REVERT_FAILED: return pht("%s couldn't revert this request", $this->renderHandleLink($author_phid)); } break; case self::TYPE_COMMIT: $action_type = $this->getMetadataValue('action'); switch ($action_type) { case 'pick': return pht( '%s picked this request and committed the result upstream', $this->renderHandleLink($author_phid)); break; case 'revert': return pht( '%s reverted this request and committed the result upstream', $this->renderHandleLink($author_phid)); break; } break; case self::TYPE_MANUAL_IN_BRANCH: $action = $new ? pht('picked') : pht('reverted'); return pht( '%s marked this request as manually %s', $this->renderHandleLink($author_phid), $action); break; case self::TYPE_DISCOVERY: return pht('%s discovered this commit as %s', $this->renderHandleLink($author_phid), $this->renderHandleLink($new)); break; default: return parent::getTitle(); break; } } public function getActionName() { switch ($this->getTransactionType()) { case self::TYPE_REQUEST: return pht('Requested'); case self::TYPE_COMMIT: $action_type = $this->getMetadataValue('action'); switch ($action_type) { case 'pick': return pht('Picked'); case 'revert': return pht('Reverted'); } } return parent::getActionName(); } public function getColor() { $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_USER_INTENT: switch ($new) { case ReleephRequest::INTENT_WANT: return PhabricatorTransactions::COLOR_GREEN; case ReleephRequest::INTENT_PASS: return PhabricatorTransactions::COLOR_RED; } } return parent::getColor(); } private static function searchForPHIDs($thing, array &$phids) { /** * To implement something like getRequiredHandlePHIDs() in a * ReleephFieldSpecification, we'd have to provide the field with its * ReleephRequest (so that it could load the PHIDs from the * ReleephRequest's storage, and return them.) * * We don't have fields initialized with their ReleephRequests, but we can * make a good guess at what handles will be needed for rendering the field * in this transaction by inspecting the old and new values. */ if (!is_array($thing)) { $thing = array($thing); } foreach ($thing as $value) { if (phid_get_type($value) !== PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { $phids[] = $value; } } } private function getIntentTitle() { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $new = $this->getNewValue(); $is_pusher = $this->getMetadataValue('isPusher'); switch ($new) { case ReleephRequest::INTENT_WANT: if ($is_pusher) { return pht( '%s approved this request', $this->renderHandleLink($author_phid)); } else { return pht( '%s wanted this request', $this->renderHandleLink($author_phid)); } case ReleephRequest::INTENT_PASS: if ($is_pusher) { return pht( '%s rejected this request', $this->renderHandleLink($author_phid)); } else { return pht( '%s passed on this request', $this->renderHandleLink($author_phid)); } } } public function shouldHide() { $type = $this->getTransactionType(); if ($type === self::TYPE_USER_INTENT && $this->getMetadataValue('isRQCreate')) { return true; } if ($this->isBoringPickStatus()) { return true; } // ReleephSummaryFieldSpecification is usually blank when an RQ is created, // creating a transaction change from null to "". Hide these! if ($type === self::TYPE_EDIT_FIELD) { if ($this->getOldValue() === null && $this->getNewValue() === '') { return true; } } return parent::shouldHide(); } public function isBoringPickStatus() { $type = $this->getTransactionType(); if ($type === self::TYPE_PICK_STATUS) { $new = $this->getNewValue(); if ($new === ReleephRequest::PICK_OK || $new === ReleephRequest::REVERT_OK) { return true; } } return false; } } diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteController.php index 9925dae3d7..0c9d2a979d 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteController.php @@ -1,41 +1,10 @@ getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - if ($for_app) { - $nav->addFilter('', pht('Create Poll'), - $this->getApplicationURI('create/')); - } - - id(new PhabricatorSlowvoteSearchEngine()) - ->setViewer($user) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; - } - public function buildApplicationMenu() { - return $this->buildSideNavView(true)->getMenu(); - } - - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Poll')) - ->setHref($this->getApplicationURI('create/')) - ->setIcon('fa-plus-square')); - - return $crumbs; + return $this->newApplicationMenu() + ->setSearchEngine(new PhabricatorSlowvoteSearchEngine()); } } diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php index e497a50fbc..bd6dca4ce3 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteEditController.php @@ -1,278 +1,277 @@ getViewer(); $id = $request->getURIData('id'); if ($id) { $poll = id(new PhabricatorSlowvoteQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$poll) { return new Aphront404Response(); } $is_new = false; } else { $poll = PhabricatorSlowvotePoll::initializeNewPoll($viewer); $is_new = true; } if ($is_new) { $v_projects = array(); } else { $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( $poll->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $v_projects = array_reverse($v_projects); } $e_question = true; $e_response = true; $errors = array(); $v_question = $poll->getQuestion(); $v_description = $poll->getDescription(); $v_responses = $poll->getResponseVisibility(); $v_shuffle = $poll->getShuffle(); $v_space = $poll->getSpacePHID(); $responses = $request->getArr('response'); if ($request->isFormPost()) { $v_question = $request->getStr('question'); $v_description = $request->getStr('description'); $v_responses = (int)$request->getInt('responses'); $v_shuffle = (int)$request->getBool('shuffle'); $v_view_policy = $request->getStr('viewPolicy'); $v_projects = $request->getArr('projects'); $v_space = $request->getStr('spacePHID'); if ($is_new) { $poll->setMethod($request->getInt('method')); } if (!strlen($v_question)) { $e_question = pht('Required'); $errors[] = pht('You must ask a poll question.'); } else { $e_question = null; } if ($is_new) { $responses = array_filter($responses); if (empty($responses)) { $errors[] = pht('You must offer at least one response.'); $e_response = pht('Required'); } else { $e_response = null; } } $xactions = array(); $template = id(new PhabricatorSlowvoteTransaction()); $xactions[] = id(clone $template) ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_QUESTION) ->setNewValue($v_question); $xactions[] = id(clone $template) ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_DESCRIPTION) ->setNewValue($v_description); $xactions[] = id(clone $template) ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_RESPONSES) ->setNewValue($v_responses); $xactions[] = id(clone $template) ->setTransactionType(PhabricatorSlowvoteTransaction::TYPE_SHUFFLE) ->setNewValue($v_shuffle); $xactions[] = id(clone $template) ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) ->setNewValue($v_view_policy); $xactions[] = id(clone $template) ->setTransactionType(PhabricatorTransactions::TYPE_SPACE) ->setNewValue($v_space); if (empty($errors)) { $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $xactions[] = id(new PhabricatorSlowvoteTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $proj_edge_type) ->setNewValue(array('=' => array_fuse($v_projects))); $editor = id(new PhabricatorSlowvoteEditor()) ->setActor($viewer) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request); $xactions = $editor->applyTransactions($poll, $xactions); if ($is_new) { $poll->save(); foreach ($responses as $response) { $option = new PhabricatorSlowvoteOption(); $option->setName($response); $option->setPollID($poll->getID()); $option->save(); } } return id(new AphrontRedirectResponse()) ->setURI('/V'.$poll->getID()); } else { $poll->setViewPolicy($v_view_policy); } } $instructions = phutil_tag( 'p', array( 'class' => 'aphront-form-instructions', ), pht('Resolve issues and build consensus through '. 'protracted deliberation.')); $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild($instructions) ->appendChild( id(new AphrontFormTextAreaControl()) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) ->setLabel(pht('Question')) ->setName('question') ->setValue($v_question) ->setError($e_question)) ->appendChild( id(new PhabricatorRemarkupControl()) ->setUser($viewer) ->setLabel(pht('Description')) ->setName('description') ->setValue($v_description)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setValue($v_projects) ->setDatasource(new PhabricatorProjectDatasource())); if ($is_new) { for ($ii = 0; $ii < 10; $ii++) { $n = ($ii + 1); $response = id(new AphrontFormTextControl()) ->setLabel(pht('Response %d', $n)) ->setName('response[]') ->setValue(idx($responses, $ii, '')); if ($ii == 0) { $response->setError($e_response); } $form->appendChild($response); } } $poll_type_options = array( PhabricatorSlowvotePoll::METHOD_PLURALITY => pht('Plurality (Single Choice)'), PhabricatorSlowvotePoll::METHOD_APPROVAL => pht('Approval (Multiple Choice)'), ); $response_type_options = array( PhabricatorSlowvotePoll::RESPONSES_VISIBLE => pht('Allow anyone to see the responses'), PhabricatorSlowvotePoll::RESPONSES_VOTERS => pht('Require a vote to see the responses'), PhabricatorSlowvotePoll::RESPONSES_OWNER => pht('Only I can see the responses'), ); if ($is_new) { $form->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Vote Type')) ->setName('method') ->setValue($poll->getMethod()) ->setOptions($poll_type_options)); } else { $form->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Vote Type')) ->setValue(idx($poll_type_options, $poll->getMethod()))); } if ($is_new) { $title = pht('Create Slowvote'); $button = pht('Create'); $cancel_uri = $this->getApplicationURI(); } else { $title = pht('Edit %s', 'V'.$poll->getID()); $button = pht('Save Changes'); $cancel_uri = '/V'.$poll->getID(); } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->setObject($poll) ->execute(); $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Responses')) ->setName('responses') ->setValue($v_responses) ->setOptions($response_type_options)) ->appendChild( id(new AphrontFormCheckboxControl()) ->setLabel(pht('Shuffle')) ->addCheckbox( 'shuffle', 1, pht('Show choices in random order.'), $v_shuffle)) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($viewer) ->setName('viewPolicy') ->setPolicyObject($poll) ->setPolicies($policies) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setSpacePHID($v_space)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue($button) ->addCancelButton($cancel_uri)); - $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormErrors($errors) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $form_box, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $form_box, )); } } diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteListController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteListController.php index bbb96d4dc4..97157bfb06 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteListController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteListController.php @@ -1,21 +1,28 @@ getURIData('queryKey'); + return id(new PhabricatorSlowvoteSearchEngine()) + ->setController($this) + ->buildResponse(); + } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine(new PhabricatorSlowvoteSearchEngine()) - ->setNavigation($this->buildSideNavView()); + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Poll')) + ->setHref($this->getApplicationURI('create/')) + ->setIcon('fa-plus-square')); - return $this->delegateToController($controller); + return $crumbs; } } diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index 97a6a1f84f..87de1ff660 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -1,158 +1,157 @@ getViewer(); $id = $request->getURIData('id'); $poll = id(new PhabricatorSlowvoteQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->needOptions(true) ->needChoices(true) ->needViewerChoices(true) ->executeOne(); if (!$poll) { return new Aphront404Response(); } $poll_view = id(new SlowvoteEmbedView()) ->setHeadless(true) ->setUser($viewer) ->setPoll($poll); if ($request->isAjax()) { return id(new AphrontAjaxResponse()) ->setContent( array( 'pollID' => $poll->getID(), 'contentHTML' => $poll_view->render(), )); } $header_icon = $poll->getIsClosed() ? 'fa-ban' : 'fa-circle-o'; $header_name = $poll->getIsClosed() ? pht('Closed') : pht('Open'); $header_color = $poll->getIsClosed() ? 'dark' : 'bluegrey'; $header = id(new PHUIHeaderView()) ->setHeader($poll->getQuestion()) ->setUser($viewer) ->setStatus($header_icon, $header_color, $header_name) ->setPolicyObject($poll); $actions = $this->buildActionView($poll); $properties = $this->buildPropertyView($poll, $actions); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb('V'.$poll->getID()); $timeline = $this->buildTransactionTimeline( $poll, new PhabricatorSlowvoteTransactionQuery()); $add_comment = $this->buildCommentForm($poll); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $poll_view, - $timeline, - $add_comment, - ), - array( - 'title' => 'V'.$poll->getID().' '.$poll->getQuestion(), - 'pageObjects' => array($poll->getPHID()), + return $this->newPage() + ->setTitle('V'.$poll->getID().' '.$poll->getQuestion()) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($poll->getPHID())) + ->appendChild( + array( + $object_box, + $poll_view, + $timeline, + $add_comment, )); } private function buildActionView(PhabricatorSlowvotePoll $poll) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($poll); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $poll, PhabricatorPolicyCapability::CAN_EDIT); $is_closed = $poll->getIsClosed(); $close_poll_text = $is_closed ? pht('Reopen Poll') : pht('Close Poll'); $close_poll_icon = $is_closed ? 'fa-play-circle-o' : 'fa-ban'; $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Poll')) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI('edit/'.$poll->getID().'/')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); $view->addAction( id(new PhabricatorActionView()) ->setName($close_poll_text) ->setIcon($close_poll_icon) ->setHref($this->getApplicationURI('close/'.$poll->getID().'/')) ->setDisabled(!$can_edit) ->setWorkflow(true)); return $view; } private function buildPropertyView( PhabricatorSlowvotePoll $poll, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($poll) ->setActionList($actions); $view->invokeWillRenderEvent(); if (strlen($poll->getDescription())) { $view->addTextContent( $output = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent( $poll->getDescription()), 'default', $viewer)); } return $view; } private function buildCommentForm(PhabricatorSlowvotePoll $poll) { $viewer = $this->getRequest()->getUser(); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $add_comment_header = $is_serious ? pht('Add Comment') : pht('Enter Deliberations'); $draft = PhabricatorDraft::newFromUserAndKey($viewer, $poll->getPHID()); return id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setObjectPHID($poll->getPHID()) ->setDraft($draft) ->setHeaderText($add_comment_header) ->setAction($this->getApplicationURI('/comment/'.$poll->getID().'/')) ->setSubmitButtonName(pht('Add Comment')); } } diff --git a/src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php b/src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php index 4519a1c902..3723a755be 100644 --- a/src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php +++ b/src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php @@ -1,140 +1,131 @@ phid = idx($data, 'phid'); - $this->action = idx($data, 'action'); - } - - public function processRequest() { - $request = $this->getRequest(); + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $phid = $request->getURIData('phid'); + $action = $request->getURIData('action'); if (!$request->isFormPost()) { return new Aphront400Response(); } - switch ($this->action) { + switch ($action) { case 'add': $is_add = true; break; case 'delete': $is_add = false; break; default: return new Aphront400Response(); } - $user = $request->getUser(); - $phid = $this->phid; - $handle = id(new PhabricatorHandleQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($phid)) ->executeOne(); if (phid_get_type($phid) == PhabricatorProjectProjectPHIDType::TYPECONST) { // TODO: This is a big hack, but a weak argument for adding some kind // of "load for role" feature to ObjectQuery, and also not a really great // argument for adding some kind of "load extra stuff" feature to // SubscriberInterface. Do this for now and wait for the best way forward // to become more clear? $object = id(new PhabricatorProjectQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($phid)) ->needWatchers(true) ->executeOne(); } else { $object = id(new PhabricatorObjectQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($phid)) ->executeOne(); } if (!($object instanceof PhabricatorSubscribableInterface)) { return $this->buildErrorResponse( pht('Bad Object'), pht('This object is not subscribable.'), $handle->getURI()); } - if ($object->isAutomaticallySubscribed($user->getPHID())) { + if ($object->isAutomaticallySubscribed($viewer->getPHID())) { return $this->buildErrorResponse( pht('Automatically Subscribed'), pht('You are automatically subscribed to this object.'), $handle->getURI()); } - if (!$object->shouldAllowSubscription($user->getPHID())) { + if (!$object->shouldAllowSubscription($viewer->getPHID())) { return $this->buildErrorResponse( pht('You Can Not Subscribe'), pht('You can not subscribe to this object.'), $handle->getURI()); } if ($object instanceof PhabricatorApplicationTransactionInterface) { if ($is_add) { $xaction_value = array( - '+' => array($user->getPHID()), + '+' => array($viewer->getPHID()), ); } else { $xaction_value = array( - '-' => array($user->getPHID()), + '-' => array($viewer->getPHID()), ); } $xaction = id($object->getApplicationTransactionTemplate()) ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) ->setNewValue($xaction_value); $editor = id($object->getApplicationTransactionEditor()) - ->setActor($user) + ->setActor($viewer) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->setContentSourceFromRequest($request); $editor->applyTransactions( $object->getApplicationTransactionObject(), array($xaction)); } else { // TODO: Eventually, get rid of this once everything implements // PhabriatorApplicationTransactionInterface. $editor = id(new PhabricatorSubscriptionsEditor()) - ->setActor($user) + ->setActor($viewer) ->setObject($object); if ($is_add) { - $editor->subscribeExplicit(array($user->getPHID()), $explicit = true); + $editor->subscribeExplicit(array($viewer->getPHID()), $explicit = true); } else { - $editor->unsubscribe(array($user->getPHID())); + $editor->unsubscribe(array($viewer->getPHID())); } $editor->save(); } // TODO: We should just render the "Unsubscribe" action and swap it out // in the document for Ajax requests. return id(new AphrontReloadResponse())->setURI($handle->getURI()); } private function buildErrorResponse($title, $message, $uri) { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setTitle($title) ->appendChild($message) ->addCancelButton($uri); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/subscriptions/controller/PhabricatorSubscriptionsListController.php b/src/applications/subscriptions/controller/PhabricatorSubscriptionsListController.php index 2497a7ed24..0b4e962e64 100644 --- a/src/applications/subscriptions/controller/PhabricatorSubscriptionsListController.php +++ b/src/applications/subscriptions/controller/PhabricatorSubscriptionsListController.php @@ -1,45 +1,46 @@ getUser(); + $viewer = $request->getViewer(); + $object = id(new PhabricatorObjectQuery()) ->setViewer($viewer) ->withPHIDs(array($request->getURIData('phid'))) ->executeOne(); if (!$object) { return new Aphront404Response(); } if (!($object instanceof PhabricatorSubscribableInterface)) { return new Aphront404Response(); } $phid = $object->getPHID(); $handle_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID($phid); $handle_phids[] = $phid; $handles = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs($handle_phids) ->execute(); $object_handle = $handles[$phid]; $dialog = id(new SubscriptionListDialogBuilder()) ->setViewer($viewer) ->setTitle(pht('Subscribers')) ->setObjectPHID($phid) ->setHandles($handles) ->buildDialog(); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/subscriptions/controller/PhabricatorSubscriptionsTransactionController.php b/src/applications/subscriptions/controller/PhabricatorSubscriptionsTransactionController.php index 70760f3e1e..777a834830 100644 --- a/src/applications/subscriptions/controller/PhabricatorSubscriptionsTransactionController.php +++ b/src/applications/subscriptions/controller/PhabricatorSubscriptionsTransactionController.php @@ -1,81 +1,72 @@ phid = idx($data, 'phid'); - $this->changeType = idx($data, 'type'); - } - - public function processRequest() { - $request = $this->getRequest(); - - $viewer = $request->getUser(); - $xaction_phid = $this->phid; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getViewer(); + $phid = $request->getURIData('phid'); + $type = $request->getURIData('type'); $xaction = id(new PhabricatorObjectQuery()) - ->withPHIDs(array($xaction_phid)) + ->withPHIDs(array($phid)) ->setViewer($viewer) ->executeOne(); if (!$xaction) { return new Aphront404Response(); } $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); - switch ($this->changeType) { + switch ($type) { case 'add': $subscriber_phids = array_diff($new, $old); break; case 'rem': $subscriber_phids = array_diff($old, $new); break; default: return id(new Aphront404Response()); } $object_phid = $xaction->getObjectPHID(); $author_phid = $xaction->getAuthorPHID(); $handle_phids = $subscriber_phids; $handle_phids[] = $object_phid; $handle_phids[] = $author_phid; $handles = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs($handle_phids) ->execute(); $author_handle = $handles[$author_phid]; if (!in_array($author_phid, $subscriber_phids)) { unset($handles[$author_phid]); } - switch ($this->changeType) { + switch ($type) { case 'add': $title = pht( 'All %d subscribers added by %s', count($subscriber_phids), $author_handle->renderLink()); break; case 'rem': $title = pht( 'All %d subscribers removed by %s', count($subscriber_phids), $author_handle->renderLink()); break; } $dialog = id(new SubscriptionListDialogBuilder()) ->setViewer($viewer) ->setTitle($title) ->setObjectPHID($object_phid) ->setHandles($handles) ->buildDialog(); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php b/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php new file mode 100644 index 0000000000..2023fbee09 --- /dev/null +++ b/src/applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php @@ -0,0 +1,62 @@ +getPHID(); + if ($object_phid) { + $sub_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( + $object_phid); + } else { + // TODO: Allow applications to provide default subscribers? Maniphest + // does this at a minimum. + $sub_phids = array(); + } + + $subscribers_field = id(new PhabricatorSubscribersEditField()) + ->setKey('subscriberPHIDs') + ->setLabel(pht('Subscribers')) + ->setEditTypeKey('subscribers') + ->setDescription(pht('Manage subscribers.')) + ->setAliases(array('subscriber', 'subscribers')) + ->setUseEdgeTransactions(true) + ->setEdgeTransactionDescriptions( + pht('Add subscribers.'), + pht('Remove subscribers.'), + pht('Set subscribers, overwriting current value.')) + ->setCommentActionLabel(pht('Add Subscribers')) + ->setTransactionType($subscribers_type) + ->setValue($sub_phids); + + return array( + $subscribers_field, + ); + } + +} diff --git a/src/applications/transactions/application/PhabricatorTransactionsApplication.php b/src/applications/transactions/application/PhabricatorTransactionsApplication.php index c0a300e327..098b99a71b 100644 --- a/src/applications/transactions/application/PhabricatorTransactionsApplication.php +++ b/src/applications/transactions/application/PhabricatorTransactionsApplication.php @@ -1,60 +1,64 @@ array( 'edit/(?[^/]+)/' => 'PhabricatorApplicationTransactionCommentEditController', 'remove/(?[^/]+)/' => 'PhabricatorApplicationTransactionCommentRemoveController', 'history/(?[^/]+)/' => 'PhabricatorApplicationTransactionCommentHistoryController', 'quote/(?[^/]+)/' => 'PhabricatorApplicationTransactionCommentQuoteController', 'raw/(?[^/]+)/' => 'PhabricatorApplicationTransactionCommentRawController', 'detail/(?[^/]+)/' => 'PhabricatorApplicationTransactionDetailController', 'showolder/(?[^/]+)/' => 'PhabricatorApplicationTransactionShowOlderController', '(?Pold|new)/(?[^/]+)/' => 'PhabricatorApplicationTransactionValueController', 'editengine/' => array( $this->getQueryRoutePattern() => 'PhabricatorEditEngineListController', '(?P[^/]+)/' => array( $this->getQueryRoutePattern() => 'PhabricatorEditEngineConfigurationListController', $this->getEditRoutePattern('edit/') => 'PhabricatorEditEngineConfigurationEditController', 'view/(?P[^/]+)/' => 'PhabricatorEditEngineConfigurationViewController', 'save/(?P[^/]+)/' => 'PhabricatorEditEngineConfigurationSaveController', 'reorder/(?P[^/]+)/' => 'PhabricatorEditEngineConfigurationReorderController', 'defaults/(?P[^/]+)/' => 'PhabricatorEditEngineConfigurationDefaultsController', 'lock/(?P[^/]+)/' => 'PhabricatorEditEngineConfigurationLockController', + 'defaultcreate/(?P[^/]+)/' => + 'PhabricatorEditEngineConfigurationDefaultCreateController', + 'disable/(?P[^/]+)/' => + 'PhabricatorEditEngineConfigurationDisableController', ), ), ), ); } } diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentHistoryController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentHistoryController.php index dcff0d76e2..f292c0d9d4 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentHistoryController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentHistoryController.php @@ -1,88 +1,82 @@ phid = $data['phid']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $phid = $request->getURIData('phid'); $xaction = id(new PhabricatorObjectQuery()) - ->withPHIDs(array($this->phid)) - ->setViewer($user) + ->withPHIDs(array($phid)) + ->setViewer($viewer) ->executeOne(); if (!$xaction) { return new Aphront404Response(); } if (!$xaction->getComment()) { // You can't view history of a transaction with no comments. return new Aphront404Response(); } if ($xaction->getComment()->getIsRemoved()) { // You can't view history of a transaction with a removed comment. return new Aphront400Response(); } $comments = id(new PhabricatorApplicationTransactionTemplatedCommentQuery()) - ->setViewer($user) + ->setViewer($viewer) ->setTemplate($xaction->getApplicationTransactionCommentObject()) ->withTransactionPHIDs(array($xaction->getPHID())) ->execute(); if (!$comments) { return new Aphront404Response(); } $comments = msort($comments, 'getCommentVersion'); $xactions = array(); foreach ($comments as $comment) { $xactions[] = id(clone $xaction) ->makeEphemeral() ->setCommentVersion($comment->getCommentVersion()) ->setContentSource($comment->getContentSource()) ->setDateCreated($comment->getDateCreated()) ->attachComment($comment); } $obj_phid = $xaction->getObjectPHID(); $obj_handle = id(new PhabricatorHandleQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($obj_phid)) ->executeOne(); $view = id(new PhabricatorApplicationTransactionView()) - ->setUser($user) + ->setUser($viewer) ->setObjectPHID($obj_phid) ->setTransactions($xactions) ->setShowEditActions(false) ->setHideCommentOptions(true); $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->setWidth(AphrontDialogView::WIDTH_FULL) ->setFlush(true) ->setTitle(pht('Comment History')); $dialog->appendChild($view); $dialog ->addCancelButton($obj_handle->getURI()); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentQuoteController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentQuoteController.php index 01a40c14a0..d4bb7ccddd 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentQuoteController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentQuoteController.php @@ -1,68 +1,62 @@ phid = $data['phid']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $phid = $request->getURIData('phid'); $xaction = id(new PhabricatorObjectQuery()) - ->withPHIDs(array($this->phid)) + ->withPHIDs(array($phid)) ->setViewer($viewer) ->executeOne(); if (!$xaction) { return new Aphront404Response(); } if (!$xaction->getComment()) { return new Aphront404Response(); } if ($xaction->getComment()->getIsRemoved()) { return new Aphront400Response(); } if (!$xaction->hasComment()) { return new Aphront404Response(); } $content = $xaction->getComment()->getContent(); $content = rtrim($content, "\r\n"); $content = phutil_split_lines($content, true); foreach ($content as $key => $line) { if (strlen($line) && ($line[0] != '>')) { $content[$key] = '> '.$line; } else { $content[$key] = '>'.$line; } } $content = implode('', $content); $author = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs(array($xaction->getComment()->getAuthorPHID())) ->executeOne(); $ref = $request->getStr('ref'); if (strlen($ref)) { $quote = pht('In %s, %s wrote:', $ref, '@'.$author->getName()); } else { $quote = pht('%s wrote:', '@'.$author->getName()); } $content = ">>! {$quote}\n{$content}"; return id(new AphrontAjaxResponse())->setContent( array( 'quoteText' => $content, )); } } diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRawController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRawController.php index a4a68259fa..6670c147fe 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRawController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRawController.php @@ -1,92 +1,86 @@ phid = $data['phid']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $phid = $request->getURIData('phid'); $xaction = id(new PhabricatorObjectQuery()) - ->withPHIDs(array($this->phid)) - ->setViewer($user) + ->withPHIDs(array($phid)) + ->setViewer($viewer) ->executeOne(); if (!$xaction) { return new Aphront404Response(); } if (!$xaction->getComment()) { // You can't view a raw comment if there is no comment. return new Aphront404Response(); } if ($xaction->getComment()->getIsRemoved()) { // You can't view a raw comment if the comment is deleted. return new Aphront400Response(); } $obj_phid = $xaction->getObjectPHID(); $obj_handle = id(new PhabricatorHandleQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array($obj_phid)) ->executeOne(); $title = pht('Raw Comment'); $body = $xaction->getComment()->getContent(); $addendum = null; if ($request->getExists('email')) { $content_source = $xaction->getContentSource(); $source_email = PhabricatorContentSource::SOURCE_EMAIL; if ($content_source->getSource() == $source_email) { $source_id = $content_source->getParam('id'); if ($source_id) { $message = id(new PhabricatorMetaMTAReceivedMail())->loadOneWhere( 'id = %d', $source_id); if ($message) { $title = pht('Email Body Text'); $body = $message->getRawTextBody(); $details_text = pht( 'For full details, run `/bin/mail show-outbound --id %d`', $source_id); $addendum = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($details_text), 'default', - $user); + $viewer); } } } } $dialog = id(new AphrontDialogView()) - ->setUser($user) + ->setUser($viewer) ->addCancelButton($obj_handle->getURI()) ->setTitle($title); $dialog ->addHiddenInput('anchor', $request->getStr('anchor')) ->appendChild( id(new PHUIFormLayoutView()) ->setFullWidth(true) ->appendChild( id(new AphrontFormTextAreaControl()) ->setReadOnly(true) ->setValue($body))); if ($addendum) { $dialog->appendParagraph($addendum); } return id(new AphrontDialogResponse())->setDialog($dialog); } public function shouldAllowPublic() { return true; } } diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php index a4feff433d..c52b087273 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php @@ -1,79 +1,73 @@ phid = $data['phid']; - } - - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $phid = $request->getURIData('phid'); $xaction = id(new PhabricatorObjectQuery()) - ->withPHIDs(array($this->phid)) + ->withPHIDs(array($phid)) ->setViewer($viewer) ->executeOne(); if (!$xaction) { return new Aphront404Response(); } if (!$xaction->getComment()) { return new Aphront404Response(); } if ($xaction->getComment()->getIsRemoved()) { // You can't remove an already-removed comment. return new Aphront400Response(); } $obj_phid = $xaction->getObjectPHID(); $obj_handle = id(new PhabricatorHandleQuery()) ->setViewer($viewer) ->withPHIDs(array($obj_phid)) ->executeOne(); if ($request->isDialogFormPost()) { $comment = $xaction->getApplicationTransactionCommentObject() ->setContent('') ->setIsRemoved(true); $editor = id(new PhabricatorApplicationTransactionCommentEditor()) ->setActor($viewer) ->setContentSource(PhabricatorContentSource::newFromRequest($request)) ->applyEdit($xaction, $comment); if ($request->isAjax()) { return id(new AphrontAjaxResponse())->setContent(array()); } else { return id(new AphrontReloadResponse())->setURI($obj_handle->getURI()); } } $form = id(new AphrontFormView()) ->setUser($viewer); $dialog = $this->newDialog() ->setTitle(pht('Remove Comment')); $dialog ->addHiddenInput('anchor', $request->getStr('anchor')) ->appendParagraph( pht( "Removing a comment prevents anyone (including you) from reading ". "it. Removing a comment also hides the comment's edit history ". "and prevents it from being edited.")) ->appendParagraph( pht('Really remove this comment?')); $dialog ->addSubmitButton(pht('Remove Comment')) ->addCancelButton($obj_handle->getURI()); return $dialog; } } diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php index e5388184db..67cc3c63aa 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php @@ -1,56 +1,55 @@ getRequest(); - $viewer = $request->getUser(); + $viewer = $this->getViewer(); $object = id(new PhabricatorObjectQuery()) ->withPHIDs(array($request->getURIData('phid'))) ->setViewer($viewer) ->executeOne(); if (!$object) { return new Aphront404Response(); } if (!$object instanceof PhabricatorApplicationTransactionInterface) { return new Aphront404Response(); } $template = $object->getApplicationTransactionTemplate(); $queries = id(new PhutilClassMapQuery()) ->setAncestorClass('PhabricatorApplicationTransactionQuery') ->execute(); $object_query = null; foreach ($queries as $query) { if ($query->getTemplateApplicationTransaction() == $template) { $object_query = $query; break; } } if (!$object_query) { return new Aphront404Response(); } $timeline = $this->buildTransactionTimeline( $object, $query); $phui_timeline = $timeline->buildPHUITimelineView($with_hiding = false); $phui_timeline->setShouldAddSpacers(false); $events = $phui_timeline->buildEvents(); return id(new AphrontAjaxResponse()) ->setContent(array( 'timeline' => hsprintf('%s', $events), )); } } diff --git a/src/applications/transactions/controller/PhabricatorApplicationTransactionValueController.php b/src/applications/transactions/controller/PhabricatorApplicationTransactionValueController.php index 37008db6d3..6808ed2af5 100644 --- a/src/applications/transactions/controller/PhabricatorApplicationTransactionValueController.php +++ b/src/applications/transactions/controller/PhabricatorApplicationTransactionValueController.php @@ -1,149 +1,145 @@ phid = $data['phid']; - $this->value = $data['value']; + public function shouldAllowPublic() { + return true; } - public function processRequest() { - $request = $this->getRequest(); - $viewer = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + $phid = $request->getURIData('phid'); + $type = $request->getURIData('value'); $xaction = id(new PhabricatorObjectQuery()) ->setViewer($viewer) - ->withPHIDs(array($this->phid)) + ->withPHIDs(array($phid)) ->executeOne(); if (!$xaction) { return new Aphront404Response(); } // For now, this pathway only supports policy transactions // to show the details of custom policies. If / when this pathway // supports more transaction types, rendering coding should be moved // into PhabricatorTransactions e.g. feed rendering code. // TODO: This should be some kind of "hey do you support this?" thing on // the transactions themselves. switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: break; default: return new Aphront404Response(); break; } - if ($this->value == 'old') { + if ($type == 'old') { $value = $xaction->getOldValue(); } else { $value = $xaction->getNewValue(); } + $policy = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) ->withPHIDs(array($value)) ->executeOne(); if (!$policy) { return new Aphront404Response(); } + if ($policy->getType() != PhabricatorPolicyType::TYPE_CUSTOM) { return new Aphront404Response(); } $rule_objects = array(); foreach ($policy->getCustomRuleClasses() as $class) { $rule_objects[$class] = newv($class, array()); } $policy->attachRuleObjects($rule_objects); $this->requireResource('policy-transaction-detail-css'); $cancel_uri = $this->guessCancelURI($viewer, $xaction); - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + + return $this->newDialog() ->setTitle($policy->getFullName()) ->setWidth(AphrontDialogView::WIDTH_FORM) - ->appendChild( - $this->renderPolicyDetails($policy, $rule_objects)) + ->appendChild($this->renderPolicyDetails($policy, $rule_objects)) ->addCancelButton($cancel_uri, pht('Close')); - - return id(new AphrontDialogResponse())->setDialog($dialog); } private function extractPHIDs( PhabricatorPolicy $policy, array $rule_objects) { $phids = array(); foreach ($policy->getRules() as $rule) { $rule_object = $rule_objects[$rule['rule']]; $phids[] = $rule_object->getRequiredHandlePHIDsForSummary($rule['value']); } return array_filter(array_mergev($phids)); } private function renderPolicyDetails( PhabricatorPolicy $policy, array $rule_objects) { $details = array(); $details[] = phutil_tag( 'p', array( 'class' => 'policy-transaction-detail-intro', ), pht('These rules are processed in order:')); foreach ($policy->getRules() as $index => $rule) { $rule_object = $rule_objects[$rule['rule']]; if ($rule['action'] == 'allow') { $icon = 'fa-check-circle green'; } else { $icon = 'fa-minus-circle red'; } $icon = id(new PHUIIconView()) ->setIconFont($icon) ->setText( ucfirst($rule['action']).' '.$rule_object->getRuleDescription()); $handle_phids = $rule_object->getRequiredHandlePHIDsForSummary( $rule['value']); if ($handle_phids) { $value = $this->getViewer() ->renderHandleList($handle_phids) ->setAsInline(true); } else { $value = $rule['value']; } $details[] = phutil_tag('div', array( 'class' => 'policy-transaction-detail-row', ), array( $icon, $value, )); } $details[] = phutil_tag( 'p', array( 'class' => 'policy-transaction-detail-end', ), pht( 'If no rules match, %s all other users.', phutil_tag('b', array(), $policy->getDefaultAction()))); return $details; } } diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultCreateController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultCreateController.php new file mode 100644 index 0000000000..f99745d1a3 --- /dev/null +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultCreateController.php @@ -0,0 +1,61 @@ +getViewer(); + + $config = $this->loadConfigForEdit(); + if (!$config) { + return id(new Aphront404Response()); + } + + $engine_key = $config->getEngineKey(); + $key = $config->getIdentifier(); + $cancel_uri = "/transactions/editengine/{$engine_key}/view/{$key}/"; + + $type = PhabricatorEditEngineConfigurationTransaction::TYPE_DEFAULTCREATE; + + if ($request->isFormPost()) { + $xactions = array(); + + $xactions[] = id(new PhabricatorEditEngineConfigurationTransaction()) + ->setTransactionType($type) + ->setNewValue(!$config->getIsDefault()); + + $editor = id(new PhabricatorEditEngineConfigurationEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($config, $xactions); + + return id(new AphrontRedirectResponse()) + ->setURI($cancel_uri); + } + + if ($config->getIsDefault()) { + $title = pht('Remove From "Create" Menu'); + $body = pht( + 'Remove this form from the application "Create" menu? It will still '. + 'function properly, but no longer be reachable directly from the '. + 'application.'); + $button = pht('Remove From Menu'); + } else { + $title = pht('Add To "Create" Menu'); + $body = pht( + 'Add this form to the application "Create" menu? Users will '. + 'be able to choose it when creating new objects.'); + $button = pht('Add To Menu'); + } + + return $this->newDialog() + ->setTitle($title) + ->appendParagraph($body) + ->addSubmitButton($button) + ->addCancelbutton($cancel_uri); + } + +} diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDisableController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDisableController.php new file mode 100644 index 0000000000..24a32c5598 --- /dev/null +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationDisableController.php @@ -0,0 +1,59 @@ +getViewer(); + + $config = $this->loadConfigForEdit(); + if (!$config) { + return id(new Aphront404Response()); + } + + $engine_key = $config->getEngineKey(); + $key = $config->getIdentifier(); + $cancel_uri = "/transactions/editengine/{$engine_key}/view/{$key}/"; + + $type = PhabricatorEditEngineConfigurationTransaction::TYPE_DISABLE; + + if ($request->isFormPost()) { + $xactions = array(); + + $xactions[] = id(new PhabricatorEditEngineConfigurationTransaction()) + ->setTransactionType($type) + ->setNewValue(!$config->getIsDisabled()); + + $editor = id(new PhabricatorEditEngineConfigurationEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($config, $xactions); + + return id(new AphrontRedirectResponse()) + ->setURI($cancel_uri); + } + + if ($config->getIsDisabled()) { + $title = pht('Enable Form'); + $body = pht( + 'Enable this form? Users who can see it will be able to use it to '. + 'create objects.'); + $button = pht('Enable Form'); + } else { + $title = pht('Disable Form'); + $body = pht( + 'Disable this form? Users will no longer be able to use it.'); + $button = pht('Disable Form'); + } + + return $this->newDialog() + ->setTitle($title) + ->appendParagraph($body) + ->addSubmitButton($button) + ->addCancelbutton($cancel_uri); + } + +} diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php index b2c18f13e5..e4f15ddbc7 100644 --- a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationListController.php @@ -1,34 +1,34 @@ setEngineKey($request->getURIData('engineKey')); return id(new PhabricatorEditEngineConfigurationSearchEngine()) ->setController($this) ->setEngineKey($this->getEngineKey()) ->buildResponse(); } protected function buildApplicationCrumbs() { + $viewer = $this->getViewer(); $crumbs = parent::buildApplicationCrumbs(); - $engine_key = $this->getEngineKey(); - $edit_uri = "/transactions/editengine/{$engine_key}/edit/"; + $target_key = $this->getEngineKey(); + $target_engine = PhabricatorEditEngine::getByKey($viewer, $target_key); - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('New Form')) - ->setHref($edit_uri) - ->setIcon('fa-plus-square')); + id(new PhabricatorEditEngineConfigurationEditEngine()) + ->setTargetEngine($target_engine) + ->setViewer($viewer) + ->addActionToCrumbs($crumbs); return $crumbs; } } diff --git a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php index 5a60b16f0d..a1e37f96c3 100644 --- a/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php +++ b/src/applications/transactions/controller/PhabricatorEditEngineConfigurationViewController.php @@ -1,197 +1,225 @@ getURIData('engineKey'); - $this->setEngineKey($engine_key); - - $key = $request->getURIData('key'); $viewer = $this->getViewer(); - $config = id(new PhabricatorEditEngineConfigurationQuery()) - ->setViewer($viewer) - ->withEngineKeys(array($engine_key)) - ->withIdentifiers(array($key)) - ->executeOne(); + $config = $this->loadConfigForEdit(); if (!$config) { return id(new Aphront404Response()); } $is_concrete = (bool)$config->getID(); $actions = $this->buildActionView($config); $properties = $this->buildPropertyView($config) ->setActionList($actions); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($config) ->setHeader(pht('Edit Form: %s', $config->getDisplayName())); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); $field_list = $this->buildFieldList($config); $crumbs = $this->buildApplicationCrumbs(); if ($is_concrete) { $crumbs->addTextCrumb(pht('Form %d', $config->getID())); } else { $crumbs->addTextCrumb(pht('Builtin')); } if ($is_concrete) { $timeline = $this->buildTransactionTimeline( $config, new PhabricatorEditEngineConfigurationTransactionQuery()); $timeline->setShouldTerminate(true); } else { $timeline = null; } return $this->newPage() ->setCrumbs($crumbs) ->appendChild( array( $box, $field_list, $timeline, )); } private function buildActionView( PhabricatorEditEngineConfiguration $config) { $viewer = $this->getViewer(); $engine = $config->getEngine(); $engine_key = $engine->getEngineKey(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $config, PhabricatorPolicyCapability::CAN_EDIT); $view = id(new PhabricatorActionListView()) ->setUser($viewer); $form_key = $config->getIdentifier(); $base_uri = "/transactions/editengine/{$engine_key}"; $is_concrete = (bool)$config->getID(); if (!$is_concrete) { $save_uri = "{$base_uri}/save/{$form_key}/"; $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Make Editable')) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(true) ->setHref($save_uri)); $can_edit = false; } else { $edit_uri = "{$base_uri}/edit/{$form_key}/"; $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Form Configuration')) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setHref($edit_uri)); } $use_uri = $engine->getEditURI(null, "form/{$form_key}/"); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Use Form')) ->setIcon('fa-th-list') ->setHref($use_uri)); $defaults_uri = "{$base_uri}/defaults/{$form_key}/"; $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Change Default Values')) ->setIcon('fa-paint-brush') ->setHref($defaults_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); $reorder_uri = "{$base_uri}/reorder/{$form_key}/"; $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Change Field Order')) ->setIcon('fa-sort-alpha-asc') ->setHref($reorder_uri) ->setWorkflow(true) ->setDisabled(!$can_edit)); $lock_uri = "{$base_uri}/lock/{$form_key}/"; $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Lock / Hide Fields')) ->setIcon('fa-lock') ->setHref($lock_uri) ->setWorkflow(true) ->setDisabled(!$can_edit)); + $disable_uri = "{$base_uri}/disable/{$form_key}/"; + + if ($config->getIsDisabled()) { + $disable_name = pht('Enable Form'); + $disable_icon = 'fa-check'; + } else { + $disable_name = pht('Disable Form'); + $disable_icon = 'fa-ban'; + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setName($disable_name) + ->setIcon($disable_icon) + ->setHref($disable_uri) + ->setWorkflow(true) + ->setDisabled(!$can_edit)); + + $defaultcreate_uri = "{$base_uri}/defaultcreate/{$form_key}/"; + + if ($config->getIsDefault()) { + $defaultcreate_name = pht('Remove from "Create" Menu'); + $defaultcreate_icon = 'fa-minus'; + } else { + $defaultcreate_name = pht('Add to "Create" Menu'); + $defaultcreate_icon = 'fa-plus'; + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setName($defaultcreate_name) + ->setIcon($defaultcreate_icon) + ->setHref($defaultcreate_uri) + ->setWorkflow(true) + ->setDisabled(!$can_edit)); + return $view; } private function buildPropertyView( PhabricatorEditEngineConfiguration $config) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($config); return $properties; } private function buildFieldList(PhabricatorEditEngineConfiguration $config) { $viewer = $this->getViewer(); $engine = $config->getEngine(); $fields = $engine->getFieldsForConfig($config); $form = id(new AphrontFormView()) ->setUser($viewer) ->setAction(null); foreach ($fields as $field) { $field->setIsPreview(true); $field->appendToForm($form); } $info = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setErrors( array( pht('This is a preview of the current form configuration.'), )); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Form Preview')) ->setInfoView($info) ->setForm($form); return $box; } } diff --git a/src/applications/transactions/controller/PhabricatorEditEngineController.php b/src/applications/transactions/controller/PhabricatorEditEngineController.php index e920260f37..b7a7d39483 100644 --- a/src/applications/transactions/controller/PhabricatorEditEngineController.php +++ b/src/applications/transactions/controller/PhabricatorEditEngineController.php @@ -1,37 +1,72 @@ engineKey = $engine_key; return $this; } public function getEngineKey() { return $this->engineKey; } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Engines'), '/transactions/editengine/'); $engine_key = $this->getEngineKey(); if ($engine_key !== null) { $engine = PhabricatorEditEngine::getByKey( $this->getViewer(), $engine_key); if ($engine) { $crumbs->addTextCrumb( $engine->getEngineName(), "/transactions/editengine/{$engine_key}/"); } } return $crumbs; } + protected function loadConfigForEdit() { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $engine_key = $request->getURIData('engineKey'); + $this->setEngineKey($engine_key); + + $key = $request->getURIData('key'); + + $config = id(new PhabricatorEditEngineConfigurationQuery()) + ->setViewer($viewer) + ->withEngineKeys(array($engine_key)) + ->withIdentifiers(array($key)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + if ($config) { + $engine = $config->getEngine(); + + // TODO: When we're editing the meta-engine, we need to set the engine + // itself as its own target. This is hacky and it would be nice to find + // a cleaner approach later. + if ($engine instanceof PhabricatorEditEngineConfigurationEditEngine) { + $engine->setTargetEngine($engine); + } + } + + return $config; + } + + } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index b398f226eb..2f067bc504 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1,1074 +1,1332 @@ viewer = $viewer; return $this; } final public function getViewer() { return $this->viewer; } final public function setController(PhabricatorController $controller) { $this->controller = $controller; $this->setViewer($controller->getViewer()); return $this; } final public function getController() { return $this->controller; } final public function getEngineKey() { return $this->getPhobjectClassConstant('ENGINECONST', 64); } final public function getApplication() { $app_class = $this->getEngineApplicationClass(); return PhabricatorApplication::getByClass($app_class); } /* -( Managing Fields )---------------------------------------------------- */ abstract public function getEngineApplicationClass(); abstract protected function buildCustomEditFields($object); public function getFieldsForConfig( PhabricatorEditEngineConfiguration $config) { $object = $this->newEditableObject(); $this->editEngineConfiguration = $config; // This is mostly making sure that we fill in default values. $this->setIsCreate(true); return $this->buildEditFields($object); } final protected function buildEditFields($object) { $viewer = $this->getViewer(); - $editor = $object->getApplicationTransactionEditor(); - - $types = $editor->getTransactionTypesForObject($object); - $types = array_fuse($types); $fields = $this->buildCustomEditFields($object); - if ($object instanceof PhabricatorPolicyInterface) { - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($object) - ->execute(); - - $map = array( - PhabricatorTransactions::TYPE_VIEW_POLICY => array( - 'key' => 'policy.view', - 'aliases' => array('view'), - 'capability' => PhabricatorPolicyCapability::CAN_VIEW, - 'label' => pht('View Policy'), - 'description' => pht('Controls who can view the object.'), - 'edit' => 'view', - ), - PhabricatorTransactions::TYPE_EDIT_POLICY => array( - 'key' => 'policy.edit', - 'aliases' => array('edit'), - 'capability' => PhabricatorPolicyCapability::CAN_EDIT, - 'label' => pht('Edit Policy'), - 'description' => pht('Controls who can edit the object.'), - 'edit' => 'edit', - ), - PhabricatorTransactions::TYPE_JOIN_POLICY => array( - 'key' => 'policy.join', - 'aliases' => array('join'), - 'capability' => PhabricatorPolicyCapability::CAN_JOIN, - 'label' => pht('Join Policy'), - 'description' => pht('Controls who can join the object.'), - 'edit' => 'join', - ), - ); + $extensions = PhabricatorEditEngineExtension::getAllEnabledExtensions(); + foreach ($extensions as $extension) { + $extension->setViewer($viewer); - foreach ($map as $type => $spec) { - if (empty($types[$type])) { - continue; - } - - $capability = $spec['capability']; - $key = $spec['key']; - $aliases = $spec['aliases']; - $label = $spec['label']; - $description = $spec['description']; - $edit = $spec['edit']; - - $policy_field = id(new PhabricatorPolicyEditField()) - ->setKey($key) - ->setLabel($label) - ->setDescription($description) - ->setAliases($aliases) - ->setCapability($capability) - ->setPolicies($policies) - ->setTransactionType($type) - ->setEditTypeKey($edit) - ->setValue($object->getPolicy($capability)); - $fields[] = $policy_field; - - if ($object instanceof PhabricatorSpacesInterface) { - if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { - $type_space = PhabricatorTransactions::TYPE_SPACE; - if (isset($types[$type_space])) { - $space_field = id(new PhabricatorSpaceEditField()) - ->setKey('spacePHID') - ->setLabel(pht('Space')) - ->setEditTypeKey('space') - ->setDescription( - pht('Shifts the object in the Spaces application.')) - ->setIsReorderable(false) - ->setAliases(array('space', 'policy.space')) - ->setTransactionType($type_space) - ->setValue($object->getSpacePHID()); - $fields[] = $space_field; - - $policy_field->setSpaceField($space_field); - } - } - } + if (!$extension->supportsObject($this, $object)) { + continue; } - } - - $edge_type = PhabricatorTransactions::TYPE_EDGE; - $object_phid = $object->getPHID(); - - $project_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - - if ($object instanceof PhabricatorProjectInterface) { - if (isset($types[$edge_type])) { - if ($object_phid) { - $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( - $object_phid, - $project_edge_type); - $project_phids = array_reverse($project_phids); - } else { - $project_phids = array(); - } - $edge_field = id(new PhabricatorProjectsEditField()) - ->setKey('projectPHIDs') - ->setLabel(pht('Projects')) - ->setEditTypeKey('projects') - ->setDescription(pht('Add or remove associated projects.')) - ->setAliases(array('project', 'projects')) - ->setTransactionType($edge_type) - ->setMetadataValue('edge:type', $project_edge_type) - ->setValue($project_phids); - $fields[] = $edge_field; - } - } + $extension_fields = $extension->buildCustomEditFields($this, $object); - $subscribers_type = PhabricatorTransactions::TYPE_SUBSCRIBERS; - - if ($object instanceof PhabricatorSubscribableInterface) { - if (isset($types[$subscribers_type])) { - if ($object_phid) { - $sub_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( - $object_phid); - } else { - // TODO: Allow applications to provide default subscribers; Maniphest - // does this at a minimum. - $sub_phids = array(); - } + // TODO: Validate this in more detail with a more tailored error. + assert_instances_of($extension_fields, 'PhabricatorEditField'); - $subscribers_field = id(new PhabricatorSubscribersEditField()) - ->setKey('subscriberPHIDs') - ->setLabel(pht('Subscribers')) - ->setEditTypeKey('subscribers') - ->setDescription(pht('Manage subscribers.')) - ->setAliases(array('subscriber', 'subscribers')) - ->setTransactionType($subscribers_type) - ->setValue($sub_phids); - $fields[] = $subscribers_field; + foreach ($extension_fields as $field) { + $fields[] = $field; } } - $xaction = $object->getApplicationTransactionTemplate(); - $comment = $xaction->getApplicationTransactionCommentObject(); - if ($comment) { - $comment_type = PhabricatorTransactions::TYPE_COMMENT; - - $comment_field = id(new PhabricatorCommentEditField()) - ->setKey('comment') - ->setLabel(pht('Comments')) - ->setDescription(pht('Add comments.')) - ->setAliases(array('comments')) - ->setIsHidden(true) - ->setTransactionType($comment_type) - ->setValue(null); - $fields[] = $comment_field; - } - $config = $this->getEditEngineConfiguration(); $fields = $config->applyConfigurationToFields($this, $fields); foreach ($fields as $field) { $field ->setViewer($viewer) ->setObject($object); } return $fields; } /* -( Display Text )------------------------------------------------------- */ /** * @task text */ abstract public function getEngineName(); /** * @task text */ abstract protected function getObjectCreateTitleText($object); /** * @task text */ protected function getFormHeaderText($object) { $config = $this->getEditEngineConfiguration(); return $config->getName(); } /** * @task text */ abstract protected function getObjectEditTitleText($object); /** * @task text */ abstract protected function getObjectCreateShortText(); /** * @task text */ abstract protected function getObjectEditShortText($object); /** * @task text */ protected function getObjectCreateButtonText($object) { return $this->getObjectCreateTitleText($object); } /** * @task text */ protected function getObjectEditButtonText($object) { return pht('Save Changes'); } + /** + * @task text + */ + protected function getCommentViewHeaderText($object) { + return pht('Add Comment'); + } + + + /** + * @task text + */ + protected function getCommentViewButtonText($object) { + return pht('Add Comment'); + } + + /* -( Edit Engine Configuration )------------------------------------------ */ protected function supportsEditEngineConfiguration() { return true; } final protected function getEditEngineConfiguration() { return $this->editEngineConfiguration; } private function loadEditEngineConfiguration($key) { if ($key === null) { $key = self::EDITENGINECONFIG_DEFAULT; } $config = id(new PhabricatorEditEngineConfigurationQuery()) ->setViewer($this->getViewer()) ->withEngineKeys(array($this->getEngineKey())) ->withIdentifiers(array($key)) ->executeOne(); if (!$config) { return null; } $this->editEngineConfiguration = $config; return $config; } final public function getBuiltinEngineConfigurations() { $configurations = $this->newBuiltinEngineConfigurations(); if (!$configurations) { throw new Exception( pht( 'EditEngine ("%s") returned no builtin engine configurations, but '. 'an edit engine must have at least one configuration.', get_class($this))); } assert_instances_of($configurations, 'PhabricatorEditEngineConfiguration'); $has_default = false; foreach ($configurations as $config) { if ($config->getBuiltinKey() == self::EDITENGINECONFIG_DEFAULT) { $has_default = true; } } if (!$has_default) { $first = head($configurations); if (!$first->getBuiltinKey()) { - $first->setBuiltinKey(self::EDITENGINECONFIG_DEFAULT); + $first + ->setBuiltinKey(self::EDITENGINECONFIG_DEFAULT) + ->setIsDefault(true); if (!strlen($first->getName())) { $first->setName($this->getObjectCreateShortText()); } } else { throw new Exception( pht( 'EditEngine ("%s") returned builtin engine configurations, '. 'but none are marked as default and the first configuration has '. 'a different builtin key already. Mark a builtin as default or '. 'omit the key from the first configuration', get_class($this))); } } $builtins = array(); foreach ($configurations as $key => $config) { $builtin_key = $config->getBuiltinKey(); if ($builtin_key === null) { throw new Exception( pht( 'EditEngine ("%s") returned builtin engine configurations, '. 'but one (with key "%s") is missing a builtin key. Provide a '. 'builtin key for each configuration (you can omit it from the '. 'first configuration in the list to automatically assign the '. 'default key).', get_class($this), $key)); } if (isset($builtins[$builtin_key])) { throw new Exception( pht( 'EditEngine ("%s") returned builtin engine configurations, '. 'but at least two specify the same builtin key ("%s"). Engines '. 'must have unique builtin keys.', get_class($this), $builtin_key)); } $builtins[$builtin_key] = $config; } return $builtins; } protected function newBuiltinEngineConfigurations() { return array( $this->newConfiguration(), ); } final protected function newConfiguration() { return PhabricatorEditEngineConfiguration::initializeNewConfiguration( $this->getViewer(), $this); } /* -( Managing URIs )------------------------------------------------------ */ /** * @task uri */ abstract protected function getObjectViewURI($object); /** * @task uri */ protected function getObjectCreateCancelURI($object) { return $this->getApplication()->getApplicationURI(); } /** * @task uri */ protected function getEditorURI() { return $this->getApplication()->getApplicationURI('edit/'); } /** * @task uri */ protected function getObjectEditCancelURI($object) { return $this->getObjectViewURI($object); } /** * @task uri */ public function getEditURI($object = null, $path = null) { $parts = array(); $parts[] = $this->getEditorURI(); if ($object && $object->getID()) { $parts[] = $object->getID().'/'; } if ($path !== null) { $parts[] = $path; } return implode('', $parts); } /* -( Creating and Loading Objects )--------------------------------------- */ /** * Initialize a new object for creation. * * @return object Newly initialized object. * @task load */ abstract protected function newEditableObject(); /** * Build an empty query for objects. * * @return PhabricatorPolicyAwareQuery Query. * @task load */ abstract protected function newObjectQuery(); /** * Test if this workflow is creating a new object or editing an existing one. * * @return bool True if a new object is being created. * @task load */ final public function getIsCreate() { return $this->isCreate; } /** * Flag this workflow as a create or edit. * * @param bool True if this is a create workflow. * @return this * @task load */ private function setIsCreate($is_create) { $this->isCreate = $is_create; return $this; } + /** + * Try to load an object by ID, PHID, or monogram. This is done primarily + * to make Conduit a little easier to use. + * + * @param wild ID, PHID, or monogram. + * @return object Corresponding editable object. + * @task load + */ + private function newObjectFromIdentifier($identifier) { + if (is_int($identifier) || ctype_digit($identifier)) { + $object = $this->newObjectFromID($identifier); + + if (!$object) { + throw new Exception( + pht( + 'No object exists with ID "%s".', + $identifier)); + } + + return $object; + } + + $type_unknown = PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN; + if (phid_get_type($identifier) != $type_unknown) { + $object = $this->newObjectFromPHID($identifier); + + if (!$object) { + throw new Exception( + pht( + 'No object exists with PHID "%s".', + $identifier)); + } + + return $object; + } + + $target = id(new PhabricatorObjectQuery()) + ->setViewer($this->getViewer()) + ->withNames(array($identifier)) + ->executeOne(); + if (!$target) { + throw new Exception( + pht( + 'Monogram "%s" does not identify a valid object.', + $identifier)); + } + + $expect = $this->newEditableObject(); + $expect_class = get_class($expect); + $target_class = get_class($target); + if ($expect_class !== $target_class) { + throw new Exception( + pht( + 'Monogram "%s" identifies an object of the wrong type. Loaded '. + 'object has class "%s", but this editor operates on objects of '. + 'type "%s".', + $identifier, + $target_class, + $expect_class)); + } + + // Load the object by PHID using this engine's standard query. This makes + // sure it's really valid, goes through standard policy check logic, and + // picks up any `need...()` clauses we want it to load with. + + $object = $this->newObjectFromPHID($target->getPHID()); + if (!$object) { + throw new Exception( + pht( + 'Failed to reload object identified by monogram "%s" when '. + 'querying by PHID.', + $identifier)); + } + + return $object; + } + /** * Load an object by ID. * * @param int Object ID. * @return object|null Object, or null if no such object exists. * @task load */ private function newObjectFromID($id) { $query = $this->newObjectQuery() ->withIDs(array($id)); return $this->newObjectFromQuery($query); } /** * Load an object by PHID. * * @param phid Object PHID. * @return object|null Object, or null if no such object exists. * @task load */ private function newObjectFromPHID($phid) { $query = $this->newObjectQuery() ->withPHIDs(array($phid)); return $this->newObjectFromQuery($query); } /** * Load an object given a configured query. * * @param PhabricatorPolicyAwareQuery Configured query. * @return object|null Object, or null if no such object exists. * @task load */ private function newObjectFromQuery(PhabricatorPolicyAwareQuery $query) { $viewer = $this->getViewer(); $object = $query ->setViewer($viewer) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$object) { return null; } return $object; } /** * Verify that an object is appropriate for editing. * * @param wild Loaded value. * @return void * @task load */ private function validateObject($object) { if (!$object || !is_object($object)) { throw new Exception( pht( 'EditEngine "%s" created or loaded an invalid object: object must '. 'actually be an object, but is of some other type ("%s").', get_class($this), gettype($object))); } if (!($object instanceof PhabricatorApplicationTransactionInterface)) { throw new Exception( pht( 'EditEngine "%s" created or loaded an invalid object: object (of '. 'class "%s") must implement "%s", but does not.', get_class($this), get_class($object), 'PhabricatorApplicationTransactionInterface')); } } /* -( Responding to Web Requests )----------------------------------------- */ final public function buildResponse() { $viewer = $this->getViewer(); $controller = $this->getController(); $request = $controller->getRequest(); $form_key = $request->getURIData('formKey'); $config = $this->loadEditEngineConfiguration($form_key); if (!$config) { return new Aphront404Response(); } $id = $request->getURIData('id'); if ($id) { $this->setIsCreate(false); $object = $this->newObjectFromID($id); if (!$object) { return new Aphront404Response(); } } else { $this->setIsCreate(true); $object = $this->newEditableObject(); } $this->validateObject($object); $action = $request->getURIData('editAction'); switch ($action) { case 'parameters': return $this->buildParametersResponse($object); + case 'nodefault': + return $this->buildNoDefaultResponse($object); + case 'comment': + return $this->buildCommentResponse($object); default: return $this->buildEditResponse($object); } } private function buildCrumbs($object, $final = false) { $controller = $this->getcontroller(); $crumbs = $controller->buildApplicationCrumbsForEditEngine(); if ($this->getIsCreate()) { $create_text = $this->getObjectCreateShortText(); if ($final) { $crumbs->addTextCrumb($create_text); } else { $edit_uri = $this->getEditURI($object); $crumbs->addTextCrumb($create_text, $edit_uri); } } else { $crumbs->addTextCrumb( $this->getObjectEditShortText($object), $this->getObjectViewURI($object)); $edit_text = pht('Edit'); if ($final) { $crumbs->addTextCrumb($edit_text); } else { $edit_uri = $this->getEditURI($object); $crumbs->addTextCrumb($edit_text, $edit_uri); } } return $crumbs; } private function buildEditResponse($object) { $viewer = $this->getViewer(); $controller = $this->getController(); $request = $controller->getRequest(); $fields = $this->buildEditFields($object); $template = $object->getApplicationTransactionTemplate(); $validation_exception = null; if ($request->isFormPost()) { foreach ($fields as $field) { + $field->setIsSubmittedForm(true); + if ($field->getIsLocked() || $field->getIsHidden()) { continue; } $field->readValueFromSubmit($request); } $xactions = array(); foreach ($fields as $field) { - $xaction = $field->generateTransaction(clone $template); + $types = $field->getWebEditTypes(); + foreach ($types as $type) { + $type_xactions = $type->generateTransactions( + clone $template, + array( + 'value' => $field->getValueForTransaction(), + )); + + if (!$type_xactions) { + continue; + } - if (!$xaction) { - continue; + foreach ($type_xactions as $type_xaction) { + $xactions[] = $type_xaction; + } } - - $xactions[] = $xaction; } $editor = $object->getApplicationTransactionEditor() ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($object, $xactions); return id(new AphrontRedirectResponse()) ->setURI($this->getObjectViewURI($object)); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; + + foreach ($fields as $field) { + $xaction_type = $field->getTransactionType(); + if ($xaction_type === null) { + continue; + } + + $message = $ex->getShortMessage($xaction_type); + if ($message === null) { + continue; + } + + $field->setControlError($message); + } } } else { if ($this->getIsCreate()) { foreach ($fields as $field) { if ($field->getIsLocked() || $field->getIsHidden()) { continue; } $field->readValueFromRequest($request); } - } else { - foreach ($fields as $field) { - $field->readValueFromObject($object); - } } } $action_button = $this->buildEditFormActionButton($object); if ($this->getIsCreate()) { $header_text = $this->getFormHeaderText($object); } else { $header_text = $this->getObjectEditTitleText($object); } $header = id(new PHUIHeaderView()) ->setHeader($header_text) ->addActionLink($action_button); $crumbs = $this->buildCrumbs($object, $final = true); $form = $this->buildEditForm($object, $fields); $box = id(new PHUIObjectBoxView()) ->setUser($viewer) ->setHeader($header) ->setValidationException($validation_exception) ->appendChild($form); return $controller->newPage() ->setTitle($header_text) ->setCrumbs($crumbs) ->appendChild($box); } private function buildEditForm($object, array $fields) { $viewer = $this->getViewer(); $form = id(new AphrontFormView()) ->setUser($viewer); foreach ($fields as $field) { $field->appendToForm($form); } if ($this->getIsCreate()) { $cancel_uri = $this->getObjectCreateCancelURI($object); $submit_button = $this->getObjectCreateButtonText($object); } else { $cancel_uri = $this->getObjectEditCancelURI($object); $submit_button = $this->getObjectEditButtonText($object); } $form->appendControl( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($submit_button)); return $form; } private function buildEditFormActionButton($object) { $viewer = $this->getViewer(); $action_view = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($this->buildEditFormActions($object) as $action) { $action_view->addAction($action); } $action_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Actions')) ->setHref('#') ->setIconFont('fa-bars') ->setDropdownMenu($action_view); return $action_button; } private function buildEditFormActions($object) { $actions = array(); if ($this->supportsEditEngineConfiguration()) { $engine_key = $this->getEngineKey(); $config = $this->getEditEngineConfiguration(); $actions[] = id(new PhabricatorActionView()) ->setName(pht('Manage Form Configurations')) ->setIcon('fa-list-ul') ->setHref("/transactions/editengine/{$engine_key}/"); $actions[] = id(new PhabricatorActionView()) ->setName(pht('Edit Form Configuration')) ->setIcon('fa-pencil') ->setHref($config->getURI()); } $actions[] = id(new PhabricatorActionView()) ->setName(pht('Show HTTP Parameters')) ->setIcon('fa-crosshairs') ->setHref($this->getEditURI($object, 'parameters/')); return $actions; } + final public function addActionToCrumbs(PHUICrumbsView $crumbs) { + $viewer = $this->getViewer(); + + $configs = id(new PhabricatorEditEngineConfigurationQuery()) + ->setViewer($viewer) + ->withEngineKeys(array($this->getEngineKey())) + ->withIsDefault(true) + ->withIsDisabled(false) + ->execute(); + + $dropdown = null; + $disabled = false; + $workflow = false; + + $menu_icon = 'fa-plus-square'; + + if (!$configs) { + if ($viewer->isLoggedIn()) { + $disabled = true; + } else { + // If the viewer isn't logged in, assume they'll get hit with a login + // dialog and are likely able to create objects after they log in. + $disabled = false; + } + $workflow = true; + $create_uri = $this->getEditURI(null, 'nodefault/'); + } else { + $config = head($configs); + $form_key = $config->getIdentifier(); + $create_uri = $this->getEditURI(null, "form/{$form_key}/"); + + if (count($configs) > 1) { + $configs = msort($configs, 'getDisplayName'); + + $menu_icon = 'fa-caret-square-o-down'; + + $dropdown = id(new PhabricatorActionListView()) + ->setUser($viewer); + + foreach ($configs as $config) { + $form_key = $config->getIdentifier(); + $config_uri = $this->getEditURI(null, "form/{$form_key}/"); + + $item_icon = 'fa-plus'; + + $dropdown->addAction( + id(new PhabricatorActionView()) + ->setName($config->getDisplayName()) + ->setIcon($item_icon) + ->setHref($config_uri)); + } + } + } + + $action = id(new PHUIListItemView()) + ->setName($this->getObjectCreateShortText()) + ->setHref($create_uri) + ->setIcon($menu_icon) + ->setWorkflow($workflow) + ->setDisabled($disabled); + + if ($dropdown) { + $action->setDropdownMenu($dropdown); + } + + $crumbs->addAction($action); + } + + final public function buildEditEngineCommentView($object) { + $config = $this->loadEditEngineConfiguration(null); + + $viewer = $this->getViewer(); + $object_phid = $object->getPHID(); + + $header_text = $this->getCommentViewHeaderText($object); + $button_text = $this->getCommentViewButtonText($object); + + $comment_uri = $this->getEditURI($object, 'comment/'); + + $view = id(new PhabricatorApplicationTransactionCommentView()) + ->setUser($viewer) + ->setObjectPHID($object_phid) + ->setHeaderText($header_text) + ->setAction($comment_uri) + ->setSubmitButtonName($button_text); + + $draft = PhabricatorVersionedDraft::loadDraft( + $object_phid, + $viewer->getPHID()); + if ($draft) { + $view->setVersionedDraft($draft); + } + + $view->setCurrentVersion($this->loadDraftVersion($object)); + + $fields = $this->buildEditFields($object); + + $all_types = array(); + foreach ($fields as $field) { + // TODO: Load draft stuff. + $types = $field->getCommentEditTypes(); + foreach ($types as $type) { + $all_types[] = $type; + } + } + + $view->setEditTypes($all_types); + + return $view; + } + + protected function loadDraftVersion($object) { + $viewer = $this->getViewer(); + + if (!$viewer->isLoggedIn()) { + return null; + } + + $template = $object->getApplicationTransactionTemplate(); + $conn_r = $template->establishConnection('r'); + + // Find the most recent transaction the user has written. We'll use this + // as a version number to make sure that out-of-date drafts get discarded. + $result = queryfx_one( + $conn_r, + 'SELECT id AS version FROM %T + WHERE objectPHID = %s AND authorPHID = %s + ORDER BY id DESC LIMIT 1', + $template->getTableName(), + $object->getPHID(), + $viewer->getPHID()); + + if ($result) { + return (int)$result['version']; + } else { + return null; + } + } + /* -( Responding to HTTP Parameter Requests )------------------------------ */ /** * Respond to a request for documentation on HTTP parameters. * * @param object Editable object. * @return AphrontResponse Response object. * @task http */ private function buildParametersResponse($object) { $controller = $this->getController(); $viewer = $this->getViewer(); $request = $controller->getRequest(); $fields = $this->buildEditFields($object); $crumbs = $this->buildCrumbs($object); $crumbs->addTextCrumb(pht('HTTP Parameters')); $crumbs->setBorder(true); $header_text = pht( 'HTTP Parameters: %s', $this->getObjectCreateShortText()); $header = id(new PHUIHeaderView()) ->setHeader($header_text); $help_view = id(new PhabricatorApplicationEditHTTPParameterHelpView()) ->setUser($viewer) ->setFields($fields); $document = id(new PHUIDocumentViewPro()) ->setUser($viewer) ->setHeader($header) ->appendChild($help_view); return $controller->newPage() ->setTitle(pht('HTTP Parameters')) ->setCrumbs($crumbs) - ->addClass('pro-white-background') ->appendChild($document); } + private function buildNoDefaultResponse($object) { + $cancel_uri = $this->getObjectCreateCancelURI($object); + + return $this->getController() + ->newDialog() + ->setTitle(pht('No Default Create Forms')) + ->appendParagraph( + pht( + 'This application is not configured with any visible, enabled '. + 'forms for creating objects.')) + ->addCancelButton($cancel_uri); + } + + private function buildCommentResponse($object) { + $viewer = $this->getViewer(); + + if ($this->getIsCreate()) { + return new Aphront404Response(); + } + + $controller = $this->getController(); + $request = $controller->getRequest(); + + if (!$request->isFormPost()) { + return new Aphront400Response(); + } + + $config = $this->loadEditEngineConfiguration(null); + $fields = $this->buildEditFields($object); + + $is_preview = $request->isPreviewRequest(); + $view_uri = $this->getObjectViewURI($object); + + $template = $object->getApplicationTransactionTemplate(); + $comment_template = $template->getApplicationTransactionCommentObject(); + + $comment_text = $request->getStr('comment'); + + if ($is_preview) { + $version_key = PhabricatorVersionedDraft::KEY_VERSION; + $request_version = $request->getInt($version_key); + $current_version = $this->loadDraftVersion($object); + if ($request_version >= $current_version) { + $draft = PhabricatorVersionedDraft::loadOrCreateDraft( + $object->getPHID(), + $viewer->getPHID(), + $current_version); + + // TODO: This is just a proof of concept. + $draft->setProperty('temporary.comment', $comment_text); + $draft->save(); + } + } + + $xactions = array(); + + $actions = $request->getStr('editengine.actions'); + if ($actions) { + $type_map = array(); + foreach ($fields as $field) { + $types = $field->getCommentEditTypes(); + foreach ($types as $type) { + $type_map[$type->getEditType()] = $type; + } + } + + $actions = phutil_json_decode($actions); + foreach ($actions as $action) { + $type = idx($action, 'type'); + if (!$type) { + continue; + } + + $edit_type = idx($type_map, $type); + if (!$edit_type) { + continue; + } + + $type_xactions = $edit_type->generateTransactions( + $template, + array( + 'value' => idx($action, 'value'), + )); + foreach ($type_xactions as $type_xaction) { + $xactions[] = $type_xaction; + } + } + } + + if (strlen($comment_text) || !$xactions) { + $xactions[] = id(clone $template) + ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) + ->attachComment( + id(clone $comment_template) + ->setContent($comment_text)); + } + + $editor = $object->getApplicationTransactionEditor() + ->setActor($viewer) + ->setContinueOnNoEffect($request->isContinueRequest()) + ->setContentSourceFromRequest($request) + ->setIsPreview($is_preview); + + try { + $xactions = $editor->applyTransactions($object, $xactions); + } catch (PhabricatorApplicationTransactionNoEffectException $ex) { + return id(new PhabricatorApplicationTransactionNoEffectResponse()) + ->setCancelURI($view_uri) + ->setException($ex); + } + + if (!$is_preview) { + PhabricatorVersionedDraft::purgeDrafts( + $object->getPHID(), + $viewer->getPHID(), + $this->loadDraftVersion($object)); + } + + if ($request->isAjax() && $is_preview) { + return id(new PhabricatorApplicationTransactionResponse()) + ->setViewer($viewer) + ->setTransactions($xactions) + ->setIsPreview($is_preview); + } else { + return id(new AphrontRedirectResponse()) + ->setURI($view_uri); + } + } + + /* -( Conduit )------------------------------------------------------------ */ /** * Respond to a Conduit edit request. * * This method accepts a list of transactions to apply to an object, and * either edits an existing object or creates a new one. * * @task conduit */ final public function buildConduitResponse(ConduitAPIRequest $request) { $viewer = $this->getViewer(); $config = $this->loadEditEngineConfiguration(null); if (!$config) { throw new Exception( pht( 'Unable to load configuration for this EditEngine ("%s").', get_class($this))); } - $phid = $request->getValue('objectPHID'); - if ($phid) { + $identifier = $request->getValue('objectIdentifier'); + if ($identifier) { $this->setIsCreate(false); - $object = $this->newObjectFromPHID($phid); - if (!$object) { - throw new Exception(pht('No such object with PHID "%s".', $phid)); - } + $object = $this->newObjectFromIdentifier($identifier); } else { $this->setIsCreate(true); $object = $this->newEditableObject(); } $this->validateObject($object); $fields = $this->buildEditFields($object); - $types = $this->getAllEditTypesFromFields($fields); + $types = $this->getConduitEditTypesFromFields($fields); $template = $object->getApplicationTransactionTemplate(); $xactions = $this->getConduitTransactions($request, $types, $template); $editor = $object->getApplicationTransactionEditor() ->setActor($viewer) ->setContentSourceFromConduitRequest($request) ->setContinueOnNoEffect(true); $xactions = $editor->applyTransactions($object, $xactions); $xactions_struct = array(); foreach ($xactions as $xaction) { $xactions_struct[] = array( 'phid' => $xaction->getPHID(), ); } return array( 'object' => array( 'id' => $object->getID(), 'phid' => $object->getPHID(), ), 'transactions' => $xactions_struct, ); } /** * Generate transactions which can be applied from edit actions in a Conduit * request. * * @param ConduitAPIRequest The request. * @param list Supported edit types. * @param PhabricatorApplicationTransaction Template transaction. * @return list Generated transactions. * @task conduit */ private function getConduitTransactions( ConduitAPIRequest $request, array $types, PhabricatorApplicationTransaction $template) { $transactions_key = 'transactions'; $xactions = $request->getValue($transactions_key); if (!is_array($xactions)) { throw new Exception( pht( 'Parameter "%s" is not a list of transactions.', $transactions_key)); } foreach ($xactions as $key => $xaction) { if (!is_array($xaction)) { throw new Exception( pht( 'Parameter "%s" must contain a list of transaction descriptions, '. 'but item with key "%s" is not a dictionary.', $transactions_key, $key)); } if (!array_key_exists('type', $xaction)) { throw new Exception( pht( 'Parameter "%s" must contain a list of transaction descriptions, '. 'but item with key "%s" is missing a "type" field. Each '. 'transaction must have a type field.', $transactions_key, $key)); } $type = $xaction['type']; if (empty($types[$type])) { throw new Exception( pht( 'Transaction with key "%s" has invalid type "%s". This type is '. 'not recognized. Valid types are: %s.', $key, $type, implode(', ', array_keys($types)))); } } $results = array(); foreach ($xactions as $xaction) { $type = $types[$xaction['type']]; - $results[] = $type->generateTransaction( + $type_xactions = $type->generateTransactions( clone $template, $xaction); + + foreach ($type_xactions as $type_xaction) { + $results[] = $type_xaction; + } } return $results; } /** * @return map * @task conduit */ - private function getAllEditTypesFromFields(array $fields) { + private function getConduitEditTypesFromFields(array $fields) { $types = array(); foreach ($fields as $field) { - $field_types = $field->getEditTransactionTypes(); + $field_types = $field->getConduitEditTypes(); + + if ($field_types === null) { + continue; + } + foreach ($field_types as $field_type) { $field_type->setField($field); $types[$field_type->getEditType()] = $field_type; } } return $types; } - public function getAllEditTypes() { + public function getConduitEditTypes() { $config = $this->loadEditEngineConfiguration(null); if (!$config) { return array(); } $object = $this->newEditableObject(); $fields = $this->buildEditFields($object); - return $this->getAllEditTypesFromFields($fields); + return $this->getConduitEditTypesFromFields($fields); } final public static function getAllEditEngines() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) ->setUniqueMethod('getEngineKey') ->execute(); } final public static function getByKey(PhabricatorUser $viewer, $key) { return id(new PhabricatorEditEngineQuery()) ->setViewer($viewer) ->withEngineKeys(array($key)) ->executeOne(); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getPHID() { return get_class($this); } public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return PhabricatorPolicies::getMostOpenPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } public function describeAutomaticCapability($capability) { return null; } } diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php index ca6b9fe370..8c7fafdc2a 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php @@ -1,194 +1,202 @@ newEditEngine(); $class = $engine->getEngineApplicationClass(); return PhabricatorApplication::getByClass($class); } public function getMethodStatus() { return self::METHOD_STATUS_UNSTABLE; } public function getMethodStatusDescription() { return pht('ApplicationEditor methods are highly unstable.'); } final protected function defineParamTypes() { return array( 'transactions' => 'list>', - 'objectPHID' => 'optional phid', + 'objectIdentifier' => 'optional id|phid|string', ); } final protected function defineReturnType() { return 'map'; } final protected function execute(ConduitAPIRequest $request) { $engine = $this->newEditEngine() ->setViewer($request->getUser()); return $engine->buildConduitResponse($request); } final public function getMethodDescription() { // TODO: We don't currently have a real viewer in this method. $viewer = PhabricatorUser::getOmnipotentUser(); $engine = $this->newEditEngine() ->setViewer($viewer); - $types = $engine->getAllEditTypes(); + $types = $engine->getConduitEditTypes(); $out = array(); $out[] = pht(<<getEditType(); $edit_summary = $type->getSummary(); $table[] = "| `{$edit_type}` | {$edit_summary} |"; } $out[] = implode("\n", $table); foreach ($types as $type) { $section = array(); $section[] = pht('Edit Type: %s', $type->getEditType()); $section[] = '---------'; $section[] = null; $section[] = $type->getDescription(); $section[] = null; $section[] = pht( 'This edit generates transactions of type `%s` internally.', $type->getTransactionType()); $section[] = null; $type_description = pht( 'Use `%s` to select this edit type.', $type->getEditType()); $value_type = $type->getValueType(); + if (!strlen($value_type)) { + $value_type = '?'; + } + $value_description = $type->getValueDescription(); $table = array(); $table[] = "| {$key} | {$head_type} | {$description} |"; $table[] = '|--------|--------------|----------------|'; $table[] = "| `type` | `const` | {$type_description} |"; $table[] = "| `value` | `{$value_type}` | {$value_description} |"; $section[] = implode("\n", $table); $out[] = implode("\n", $section); } $out = implode("\n\n", $out); return $out; } } diff --git a/src/applications/transactions/editengineextension/PhabricatorCommentEditEngineExtension.php b/src/applications/transactions/editengineextension/PhabricatorCommentEditEngineExtension.php new file mode 100644 index 0000000000..6837d8e933 --- /dev/null +++ b/src/applications/transactions/editengineextension/PhabricatorCommentEditEngineExtension.php @@ -0,0 +1,55 @@ +getApplicationTransactionTemplate(); + + try { + $comment = $xaction->getApplicationTransactionCommentObject(); + } catch (PhutilMethodNotImplementedException $ex) { + $comment = null; + } + + return (bool)$comment; + } + + public function buildCustomEditFields( + PhabricatorEditEngine $engine, + PhabricatorApplicationTransactionInterface $object) { + + $comment_type = PhabricatorTransactions::TYPE_COMMENT; + + $comment_field = id(new PhabricatorCommentEditField()) + ->setKey('comment') + ->setLabel(pht('Comments')) + ->setDescription(pht('Add comments.')) + ->setAliases(array('comments')) + ->setIsHidden(true) + ->setTransactionType($comment_type) + ->setValue(null); + + return array( + $comment_field, + ); + } + +} diff --git a/src/applications/transactions/editengineextension/PhabricatorEditEngineExtension.php b/src/applications/transactions/editengineextension/PhabricatorEditEngineExtension.php new file mode 100644 index 0000000000..9a799d4e5b --- /dev/null +++ b/src/applications/transactions/editengineextension/PhabricatorEditEngineExtension.php @@ -0,0 +1,55 @@ +getPhobjectClassConstant('EXTENSIONKEY'); + } + + final public function setViewer($viewer) { + $this->viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + public function getExtensionPriority() { + return 1000; + } + + abstract public function isExtensionEnabled(); + abstract public function getExtensionName(); + + abstract public function supportsObject( + PhabricatorEditEngine $engine, + PhabricatorApplicationTransactionInterface $object); + + abstract public function buildCustomEditFields( + PhabricatorEditEngine $engine, + PhabricatorApplicationTransactionInterface $object); + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getExtensionKey') + ->setSortMethod('getExtensionPriority') + ->execute(); + } + + final public static function getAllEnabledExtensions() { + $extensions = self::getAllExtensions(); + + foreach ($extensions as $key => $extension) { + if (!$extension->isExtensionEnabled()) { + unset($extensions[$key]); + } + } + + return $extensions; + } + +} diff --git a/src/applications/transactions/editengineextension/PhabricatorEditEngineExtensionModule.php b/src/applications/transactions/editengineextension/PhabricatorEditEngineExtensionModule.php new file mode 100644 index 0000000000..98a4e0cb3b --- /dev/null +++ b/src/applications/transactions/editengineextension/PhabricatorEditEngineExtensionModule.php @@ -0,0 +1,52 @@ +getViewer(); + + $extensions = PhabricatorEditEngineExtension::getAllExtensions(); + + $rows = array(); + foreach ($extensions as $extension) { + $rows[] = array( + $extension->getExtensionPriority(), + get_class($extension), + $extension->getExtensionName(), + $extension->isExtensionEnabled() + ? pht('Yes') + : pht('No'), + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Priority'), + pht('Class'), + pht('Name'), + pht('Enabled'), + )) + ->setColumnClasses( + array( + null, + null, + 'wide pri', + null, + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('EditEngine Extensions')) + ->setTable($table); + } + +} diff --git a/src/applications/transactions/editfield/PhabricatorCommentEditField.php b/src/applications/transactions/editfield/PhabricatorCommentEditField.php index 6080b9fa73..7a7e671e8c 100644 --- a/src/applications/transactions/editfield/PhabricatorCommentEditField.php +++ b/src/applications/transactions/editfield/PhabricatorCommentEditField.php @@ -1,25 +1,14 @@ $this->getValueForTransaction(), - ); - - return head($this->getEditTransactionTypes()) - ->generateTransaction($xaction, $spec); - } - } diff --git a/src/applications/transactions/editfield/PhabricatorDatasourceEditField.php b/src/applications/transactions/editfield/PhabricatorDatasourceEditField.php index f3cf5fe079..d32716bfa5 100644 --- a/src/applications/transactions/editfield/PhabricatorDatasourceEditField.php +++ b/src/applications/transactions/editfield/PhabricatorDatasourceEditField.php @@ -1,21 +1,24 @@ datasource = $datasource; return $this; } public function getDatasource() { + if (!$this->datasource) { + throw new PhutilInvalidStateException('setDatasource'); + } return $this->datasource; } protected function newDatasource() { return id(clone $this->getDatasource()); } } diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php index fcf66c3883..f2acbe056b 100644 --- a/src/applications/transactions/editfield/PhabricatorEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEditField.php @@ -1,439 +1,501 @@ key = $key; return $this; } public function getKey() { return $this->key; } public function setLabel($label) { $this->label = $label; return $this; } public function getLabel() { return $this->label; } public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; } public function getViewer() { return $this->viewer; } public function setAliases(array $aliases) { $this->aliases = $aliases; return $this; } public function getAliases() { return $this->aliases; } public function setObject($object) { $this->object = $object; return $this; } public function getObject() { return $this->object; } public function setDescription($description) { $this->description = $description; return $this; } public function getDescription() { return $this->description; } public function setIsLocked($is_locked) { $this->isLocked = $is_locked; return $this; } public function getIsLocked() { return $this->isLocked; } public function setIsPreview($preview) { $this->isPreview = $preview; return $this; } public function getIsPreview() { return $this->isPreview; } public function setIsReorderable($is_reorderable) { $this->isReorderable = $is_reorderable; return $this; } public function getIsReorderable() { return $this->isReorderable; } public function setIsEditDefaults($is_edit_defaults) { $this->isEditDefaults = $is_edit_defaults; return $this; } public function getIsEditDefaults() { return $this->isEditDefaults; } public function setIsDefaultable($is_defaultable) { $this->isDefaultable = $is_defaultable; return $this; } public function getIsDefaultable() { return $this->isDefaultable; } public function setIsLockable($is_lockable) { $this->isLockable = $is_lockable; return $this; } public function getIsLockable() { return $this->isLockable; } public function setIsHidden($is_hidden) { $this->isHidden = $is_hidden; return $this; } public function getIsHidden() { return $this->isHidden; } + public function setIsSubmittedForm($is_submitted) { + $this->isSubmittedForm = $is_submitted; + return $this; + } + + public function getIsSubmittedForm() { + return $this->isSubmittedForm; + } + + public function setIsRequired($is_required) { + $this->isRequired = $is_required; + return $this; + } + + public function getIsRequired() { + return $this->isRequired; + } + + public function setControlError($control_error) { + $this->controlError = $control_error; + return $this; + } + + public function getControlError() { + return $this->controlError; + } + protected function newControl() { throw new PhutilMethodNotImplementedException(); } - protected function renderControl() { + protected function buildControl() { $control = $this->newControl(); if ($control === null) { return null; } $control ->setValue($this->getValueForControl()) ->setName($this->getKey()); if (!$control->getLabel()) { $control->setLabel($this->getLabel()); } + if ($this->getIsSubmittedForm()) { + $error = $this->getControlError(); + if ($error !== null) { + $control->setError($error); + } + } else if ($this->getIsRequired()) { + $control->setError(true); + } + + return $control; + } + + protected function renderControl() { + $control = $this->buildControl(); + if ($control === null) { + return null; + } + if ($this->getIsPreview()) { $disabled = true; $hidden = false; } else if ($this->getIsEditDefaults()) { $disabled = false; $hidden = false; } else { $disabled = $this->getIsLocked(); $hidden = $this->getIsHidden(); } if ($hidden) { return null; } $control->setDisabled($disabled); return $control; } public function appendToForm(AphrontFormView $form) { $control = $this->renderControl(); if ($control !== null) { if ($this->getIsPreview()) { if ($this->getIsHidden()) { $control ->addClass('aphront-form-preview-hidden') ->setError(pht('Hidden')); } else if ($this->getIsLocked()) { $control ->setError(pht('Locked')); } } $form->appendControl($control); } return $this; } protected function getValueForControl() { return $this->getValue(); } public function getValueForDefaults() { $value = $this->getValue(); // By default, just treat the empty string like `null` since they're // equivalent for almost all fields and this reduces the number of // meaningless transactions we generate when adjusting defaults. if ($value === '') { return null; } return $value; } protected function getValue() { return $this->value; } public function setValue($value) { $this->hasValue = true; + $this->initialValue = $value; $this->value = $value; return $this; } - public function generateTransaction( - PhabricatorApplicationTransaction $xaction) { - - if (!$this->getTransactionType()) { - return null; - } - - $xaction - ->setTransactionType($this->getTransactionType()) - ->setNewValue($this->getValueForTransaction()); - - foreach ($this->metadata as $key => $value) { - $xaction->setMetadataValue($key, $value); - } - - return $xaction; - } - public function setMetadataValue($key, $value) { $this->metadata[$key] = $value; return $this; } - protected function getValueForTransaction() { + public function getMetadata() { + return $this->metadata; + } + + public function getValueForTransaction() { return $this->getValue(); } public function getTransactionType() { return $this->transactionType; } public function setTransactionType($type) { $this->transactionType = $type; return $this; } public function readValueFromRequest(AphrontRequest $request) { - $check = array_merge(array($this->getKey()), $this->getAliases()); + $check = $this->getAllReadValueFromRequestKeys(); foreach ($check as $key) { if (!$this->getValueExistsInRequest($request, $key)) { continue; } $this->value = $this->getValueFromRequest($request, $key); - return; + break; } - - $this->readValueFromObject($this->getObject()); - return $this; } - public function readValueFromObject($object) { - $this->value = $this->getValueFromObject($object); - return $this; + public function getAllReadValueFromRequestKeys() { + $keys = array(); + + $keys[] = $this->getKey(); + foreach ($this->getAliases() as $alias) { + $keys[] = $alias; + } + + return $keys; } public function readDefaultValueFromConfiguration($value) { $this->value = $this->getDefaultValueFromConfiguration($value); return $this; } protected function getDefaultValueFromConfiguration($value) { return $value; } protected function getValueFromObject($object) { if ($this->hasValue) { return $this->value; } else { return $this->getDefaultValue(); } } protected function getValueExistsInRequest(AphrontRequest $request, $key) { - return $this->getValueExistsInSubmit($request, $key); + return $this->getHTTPParameterValueExists($request, $key); } protected function getValueFromRequest(AphrontRequest $request, $key) { - return $this->getValueFromSubmit($request, $key); + return $this->getHTTPParameterValue($request, $key); + } + + + /** + * Read and return the value the object had when the user first loaded the + * form. + * + * This is the initial value from the user's point of view when they started + * the edit process, and used primarily to prevent race conditions for fields + * like "Projects" and "Subscribers" that use tokenizers and support edge + * transactions. + * + * Most fields do not need to store these values or deal with initial value + * handling. + * + * @param AphrontRequest Request to read from. + * @param string Key to read. + * @return wild Value read from request. + */ + protected function getInitialValueFromSubmit(AphrontRequest $request, $key) { + return null; + } + + public function getInitialValue() { + return $this->initialValue; } public function readValueFromSubmit(AphrontRequest $request) { $key = $this->getKey(); if ($this->getValueExistsInSubmit($request, $key)) { $value = $this->getValueFromSubmit($request, $key); } else { $value = $this->getDefaultValue(); } $this->value = $value; + + $initial_value = $this->getInitialValueFromSubmit($request, $key); + $this->initialValue = $initial_value; + return $this; } protected function getValueExistsInSubmit(AphrontRequest $request, $key) { + return $this->getHTTPParameterValueExists($request, $key); + } + + protected function getValueFromSubmit(AphrontRequest $request, $key) { + return $this->getHTTPParameterValue($request, $key); + } + + protected function getHTTPParameterValueExists( + AphrontRequest $request, + $key) { $type = $this->getHTTPParameterType(); if ($type) { return $type->getExists($request, $key); } return false; } - protected function getValueFromSubmit(AphrontRequest $request, $key) { - return $this->getHTTPParameterType()->getValue($request, $key); + protected function getHTTPParameterValue($request, $key) { + $type = $this->getHTTPParameterType(); + + if ($type) { + return $type->getValue($request, $key); + } + + return null; } protected function getDefaultValue() { $type = $this->getHTTPParameterType(); if ($type) { return $type->getDefaultValue(); } return null; } final public function getHTTPParameterType() { $type = $this->newHTTPParameterType(); if ($type) { $type->setViewer($this->getViewer()); } return $type; } protected function newHTTPParameterType() { return new AphrontStringHTTPParameterType(); } public function setEditTypeKey($edit_type_key) { $this->editTypeKey = $edit_type_key; return $this; } public function getEditTypeKey() { if ($this->editTypeKey === null) { return $this->getKey(); } return $this->editTypeKey; } protected function newEditType() { return id(new PhabricatorSimpleEditType()) ->setValueType($this->getHTTPParameterType()->getTypeName()); } - public function getEditTransactionTypes() { + protected function getEditType() { $transaction_type = $this->getTransactionType(); + if ($transaction_type === null) { - return array(); + return null; } $type_key = $this->getEditTypeKey(); - // TODO: This is a pretty big pile of hard-coded hacks for now. - - $edge_types = array( - PhabricatorTransactions::TYPE_EDGE => array( - '+' => pht('Add projects.'), - '-' => pht('Remove projects.'), - '=' => pht('Set associated projects, overwriting current value.'), - ), - PhabricatorTransactions::TYPE_SUBSCRIBERS => array( - '+' => pht('Add subscribers.'), - '-' => pht('Remove subscribers.'), - '=' => pht('Set subscribers, overwriting current value.'), - ), - ); - - if (isset($edge_types[$transaction_type])) { - $base = id(new PhabricatorEdgeEditType()) - ->setTransactionType($transaction_type) - ->setMetadata($this->metadata); - - $strings = $edge_types[$transaction_type]; - - $add = id(clone $base) - ->setEditType($type_key.'.add') - ->setEdgeOperation('+') - ->setDescription($strings['+']) - ->setValueDescription(pht('List of PHIDs to add.')); - $rem = id(clone $base) - ->setEditType($type_key.'.remove') - ->setEdgeOperation('-') - ->setDescription($strings['-']) - ->setValueDescription(pht('List of PHIDs to remove.')); - $set = id(clone $base) - ->setEditType($type_key.'.set') - ->setEdgeOperation('=') - ->setDescription($strings['=']) - ->setValueDescription(pht('List of PHIDs to set.')); - - return array( - $add, - $rem, - $set, - ); + return $this->newEditType() + ->setEditType($type_key) + ->setTransactionType($transaction_type) + ->setDescription($this->getDescription()) + ->setMetadata($this->getMetadata()); + } + + public function getConduitEditTypes() { + $edit_type = $this->getEditType(); + + if ($edit_type === null) { + return null; } - return array( - $this->newEditType() - ->setEditType($type_key) - ->setTransactionType($transaction_type) - ->setDescription($this->getDescription()) - ->setMetadata($this->metadata), - ); + return array($edit_type); + } + + public function getWebEditTypes() { + $edit_type = $this->getEditType(); + + if ($edit_type === null) { + return null; + } + + return array($edit_type); + } + + public function getCommentEditTypes() { + return array(); } } diff --git a/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php b/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php new file mode 100644 index 0000000000..b1ce19a1e0 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php @@ -0,0 +1,114 @@ +useEdgeTransactions = $use_edge_transactions; + return $this; + } + + public function getUseEdgeTransactions() { + return $this->useEdgeTransactions; + } + + public function setEdgeTransactionDescriptions($add, $rem, $set) { + $this->transactionDescriptions = array( + '+' => $add, + '-' => $rem, + '=' => $set, + ); + return $this; + } + + protected function newHTTPParameterType() { + return new AphrontPHIDListHTTPParameterType(); + } + + public function getValueForTransaction() { + $new = parent::getValueForTransaction(); + + if (!$this->getUseEdgeTransactions()) { + return $new; + } + + $old = $this->getInitialValue(); + if ($old === null) { + return array( + '=' => array_fuse($new), + ); + } + + // If we're building an edge transaction and the request has data about the + // original value the user saw when they loaded the form, interpret the + // edit as a mixture of "+" and "-" operations instead of a single "=" + // operation. This limits our exposure to race conditions by making most + // concurrent edits merge correctly. + + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + $value = array(); + + if ($add) { + $value['+'] = array_fuse($add); + } + if ($rem) { + $value['-'] = array_fuse($rem); + } + + return $value; + } + + protected function newEditType() { + if ($this->getUseEdgeTransactions()) { + return new PhabricatorEdgeEditType(); + } + + return parent::newEditType(); + } + + public function getConduitEditTypes() { + if (!$this->getUseEdgeTransactions()) { + return parent::getConduitEditTypes(); + } + + $transaction_type = $this->getTransactionType(); + if ($transaction_type === null) { + return array(); + } + + $type_key = $this->getEditTypeKey(); + $strings = $this->transactionDescriptions; + + $base = $this->getEditType(); + + $add = id(clone $base) + ->setEditType($type_key.'.add') + ->setEdgeOperation('+') + ->setDescription(idx($strings, '+')) + ->setValueDescription(pht('List of PHIDs to add.')); + + $rem = id(clone $base) + ->setEditType($type_key.'.remove') + ->setEdgeOperation('-') + ->setDescription(idx($strings, '-')) + ->setValueDescription(pht('List of PHIDs to remove.')); + + $set = id(clone $base) + ->setEditType($type_key.'.set') + ->setEdgeOperation('=') + ->setDescription(idx($strings, '=')) + ->setValueDescription(pht('List of PHIDs to set.')); + + return array( + $add, + $rem, + $set, + ); + } + +} diff --git a/src/applications/transactions/editfield/PhabricatorTokenizerEditField.php b/src/applications/transactions/editfield/PhabricatorTokenizerEditField.php index cced1236a1..3fe45fc5b6 100644 --- a/src/applications/transactions/editfield/PhabricatorTokenizerEditField.php +++ b/src/applications/transactions/editfield/PhabricatorTokenizerEditField.php @@ -1,85 +1,73 @@ commentActionLabel = $label; + return $this; + } + + public function getCommentActionLabel() { + return $this->commentActionLabel; + } + protected function newControl() { $control = id(new AphrontFormTokenizerControl()) ->setDatasource($this->newDatasource()); - if ($this->originalValue !== null) { - $control->setOriginalValue($this->originalValue); + $initial_value = $this->getInitialValue(); + if ($initial_value !== null) { + $control->setOriginalValue($initial_value); } return $control; } - public function setValue($value) { - $this->originalValue = $value; - return parent::setValue($value); + protected function getInitialValueFromSubmit(AphrontRequest $request, $key) { + return $request->getArr($key.'.original'); } - protected function getValueFromSubmit(AphrontRequest $request, $key) { - // TODO: Maybe move this unusual read somewhere else so subclassing this - // correctly is easier? - $this->originalValue = $request->getArr($key.'.original'); - - return parent::getValueFromSubmit($request, $key); - } - - protected function getValueForTransaction() { - $new = parent::getValueForTransaction(); - - $edge_types = array( - PhabricatorTransactions::TYPE_EDGE => true, - PhabricatorTransactions::TYPE_SUBSCRIBERS => true, - ); - - if (isset($edge_types[$this->getTransactionType()])) { - if ($this->originalValue !== null) { - // If we're building an edge transaction and the request has data - // about the original value the user saw when they loaded the form, - // interpret the edit as a mixture of "+" and "-" operations instead - // of a single "=" operation. This limits our exposure to race - // conditions by making most concurrent edits merge correctly. + protected function newEditType() { + $type = parent::newEditType(); - $new = parent::getValueForTransaction(); - $old = $this->originalValue; - - $add = array_diff($new, $old); - $rem = array_diff($old, $new); - - $value = array(); + if ($this->getUseEdgeTransactions()) { + $datasource = $this->newDatasource() + ->setViewer($this->getViewer()); + $type->setDatasource($datasource); + } - if ($add) { - $value['+'] = array_fuse($add); - } - if ($rem) { - $value['-'] = array_fuse($rem); - } + return $type; + } - return $value; - } else { + public function getCommentEditTypes() { + if (!$this->getUseEdgeTransactions()) { + return parent::getCommentEditTypes(); + } - if (!is_array($new)) { - throw new Exception(print_r($new, true)); - } + $transaction_type = $this->getTransactionType(); + if ($transaction_type === null) { + return array(); + } - return array( - '=' => array_fuse($new), - ); - } + $label = $this->getCommentActionLabel(); + if ($label === null) { + return array(); } - return $new; - } + $type_key = $this->getEditTypeKey(); + $base = $this->getEditType(); + + $add = id(clone $base) + ->setEditType($type_key.'.add') + ->setEdgeOperation('+') + ->setLabel($label); - protected function newHTTPParameterType() { - return new AphrontPHIDListHTTPParameterType(); + return array($add); } } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 8fdcb8c925..4e4b9e751e 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -1,3314 +1,3324 @@ actingAsPHID = $acting_as_phid; return $this; } public function getActingAsPHID() { if ($this->actingAsPHID) { return $this->actingAsPHID; } return $this->getActor()->getPHID(); } /** * When the editor tries to apply transactions that have no effect, should * it raise an exception (default) or drop them and continue? * * Generally, you will set this flag for edits coming from "Edit" interfaces, * and leave it cleared for edits coming from "Comment" interfaces, so the * user will get a useful error if they try to submit a comment that does * nothing (e.g., empty comment with a status change that has already been * performed by another user). * * @param bool True to drop transactions without effect and continue. * @return this */ public function setContinueOnNoEffect($continue) { $this->continueOnNoEffect = $continue; return $this; } public function getContinueOnNoEffect() { return $this->continueOnNoEffect; } /** * When the editor tries to apply transactions which don't populate all of * an object's required fields, should it raise an exception (default) or * drop them and continue? * * For example, if a user adds a new required custom field (like "Severity") * to a task, all existing tasks won't have it populated. When users * manually edit existing tasks, it's usually desirable to have them provide * a severity. However, other operations (like batch editing just the * owner of a task) will fail by default. * * By setting this flag for edit operations which apply to specific fields * (like the priority, batch, and merge editors in Maniphest), these * operations can continue to function even if an object is outdated. * * @param bool True to continue when transactions don't completely satisfy * all required fields. * @return this */ public function setContinueOnMissingFields($continue_on_missing_fields) { $this->continueOnMissingFields = $continue_on_missing_fields; return $this; } public function getContinueOnMissingFields() { return $this->continueOnMissingFields; } /** * Not strictly necessary, but reply handlers ideally set this value to * make email threading work better. */ public function setParentMessageID($parent_message_id) { $this->parentMessageID = $parent_message_id; return $this; } public function getParentMessageID() { return $this->parentMessageID; } public function getIsNewObject() { return $this->isNewObject; } protected function getMentionedPHIDs() { return $this->mentionedPHIDs; } public function setIsPreview($is_preview) { $this->isPreview = $is_preview; return $this; } public function getIsPreview() { return $this->isPreview; } public function setIsInverseEdgeEditor($is_inverse_edge_editor) { $this->isInverseEdgeEditor = $is_inverse_edge_editor; return $this; } public function getIsInverseEdgeEditor() { return $this->isInverseEdgeEditor; } public function setIsHeraldEditor($is_herald_editor) { $this->isHeraldEditor = $is_herald_editor; return $this; } public function getIsHeraldEditor() { return $this->isHeraldEditor; } /** * Prevent this editor from generating email when applying transactions. * * @param bool True to disable email. * @return this */ public function setDisableEmail($disable_email) { $this->disableEmail = $disable_email; return $this; } public function getDisableEmail() { return $this->disableEmail; } public function setUnmentionablePHIDMap(array $map) { $this->unmentionablePHIDMap = $map; return $this; } public function getUnmentionablePHIDMap() { return $this->unmentionablePHIDMap; } protected function shouldEnableMentions( PhabricatorLiskDAO $object, array $xactions) { return true; } public function setApplicationEmail( PhabricatorMetaMTAApplicationEmail $email) { $this->applicationEmail = $email; return $this; } public function getApplicationEmail() { return $this->applicationEmail; } public function getTransactionTypesForObject($object) { $old = $this->object; try { $this->object = $object; $result = $this->getTransactionTypes(); $this->object = $old; } catch (Exception $ex) { $this->object = $old; throw $ex; } return $result; } public function getTransactionTypes() { $types = array(); if ($this->object instanceof PhabricatorSubscribableInterface) { $types[] = PhabricatorTransactions::TYPE_SUBSCRIBERS; } if ($this->object instanceof PhabricatorCustomFieldInterface) { $types[] = PhabricatorTransactions::TYPE_CUSTOMFIELD; } if ($this->object instanceof HarbormasterBuildableInterface) { $types[] = PhabricatorTransactions::TYPE_BUILDABLE; } if ($this->object instanceof PhabricatorTokenReceiverInterface) { $types[] = PhabricatorTransactions::TYPE_TOKEN; } if ($this->object instanceof PhabricatorProjectInterface || $this->object instanceof PhabricatorMentionableInterface) { $types[] = PhabricatorTransactions::TYPE_EDGE; } if ($this->object instanceof PhabricatorSpacesInterface) { $types[] = PhabricatorTransactions::TYPE_SPACE; } return $types; } private function adjustTransactionValues( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { if ($xaction->shouldGenerateOldValue()) { $old = $this->getTransactionOldValue($object, $xaction); $xaction->setOldValue($old); } $new = $this->getTransactionNewValue($object, $xaction); $xaction->setNewValue($new); } private function getTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_SUBSCRIBERS: return array_values($this->subscribers); case PhabricatorTransactions::TYPE_VIEW_POLICY: return $object->getViewPolicy(); case PhabricatorTransactions::TYPE_EDIT_POLICY: return $object->getEditPolicy(); case PhabricatorTransactions::TYPE_JOIN_POLICY: return $object->getJoinPolicy(); case PhabricatorTransactions::TYPE_SPACE: $space_phid = $object->getSpacePHID(); if ($space_phid === null) { if ($this->getIsNewObject()) { // In this case, just return `null` so we know this is the initial // transaction and it should be hidden. return null; } $default_space = PhabricatorSpacesNamespaceQuery::getDefaultSpace(); if ($default_space) { $space_phid = $default_space->getPHID(); } } return $space_phid; case PhabricatorTransactions::TYPE_EDGE: $edge_type = $xaction->getMetadataValue('edge:type'); if (!$edge_type) { throw new Exception( pht( "Edge transaction has no '%s'!", 'edge:type')); } $old_edges = array(); if ($object->getPHID()) { $edge_src = $object->getPHID(); $old_edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array($edge_src)) ->withEdgeTypes(array($edge_type)) ->needEdgeData(true) ->execute(); $old_edges = $old_edges[$edge_src][$edge_type]; } return $old_edges; case PhabricatorTransactions::TYPE_CUSTOMFIELD: // NOTE: Custom fields have their old value pre-populated when they are // built by PhabricatorCustomFieldList. return $xaction->getOldValue(); case PhabricatorTransactions::TYPE_COMMENT: return null; default: return $this->getCustomTransactionOldValue($object, $xaction); } } private function getTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_SUBSCRIBERS: return $this->getPHIDTransactionNewValue($xaction); case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: case PhabricatorTransactions::TYPE_BUILDABLE: case PhabricatorTransactions::TYPE_TOKEN: case PhabricatorTransactions::TYPE_INLINESTATE: return $xaction->getNewValue(); case PhabricatorTransactions::TYPE_SPACE: $space_phid = $xaction->getNewValue(); if (!strlen($space_phid)) { // If an install has no Spaces or the Spaces controls are not visible // to the viewer, we might end up with the empty string here instead // of a strict `null`, because some controller just used `getStr()` // to read the space PHID from the request. // Just make this work like callers might reasonably expect so we // don't need to handle this specially in every EditController. return $this->getActor()->getDefaultSpacePHID(); } else { return $space_phid; } case PhabricatorTransactions::TYPE_EDGE: return $this->getEdgeTransactionNewValue($xaction); case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getCustomFieldForTransaction($object, $xaction); return $field->getNewValueFromApplicationTransactions($xaction); case PhabricatorTransactions::TYPE_COMMENT: return null; default: return $this->getCustomTransactionNewValue($object, $xaction); } } protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { throw new Exception(pht('Capability not supported!')); } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { throw new Exception(pht('Capability not supported!')); } protected function transactionHasEffect( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return $xaction->hasComment(); case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getCustomFieldForTransaction($object, $xaction); return $field->getApplicationTransactionHasEffect($xaction); case PhabricatorTransactions::TYPE_EDGE: // A straight value comparison here doesn't always get the right // result, because newly added edges aren't fully populated. Instead, // compare the changes in a more granular way. $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $old_dst = array_keys($old); $new_dst = array_keys($new); // NOTE: For now, we don't consider edge reordering to be a change. // We have very few order-dependent edges and effectively no order // oriented UI. This might change in the future. sort($old_dst); sort($new_dst); if ($old_dst !== $new_dst) { // We've added or removed edges, so this transaction definitely // has an effect. return true; } // We haven't added or removed edges, but we might have changed // edge data. foreach ($old as $key => $old_value) { $new_value = $new[$key]; if ($old_value['data'] !== $new_value['data']) { return true; } } return false; } return ($xaction->getOldValue() !== $xaction->getNewValue()); } protected function shouldApplyInitialEffects( PhabricatorLiskDAO $object, array $xactions) { return false; } protected function applyInitialEffects( PhabricatorLiskDAO $object, array $xactions) { throw new PhutilMethodNotImplementedException(); } private function applyInternalEffects( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getCustomFieldForTransaction($object, $xaction); return $field->applyApplicationTransactionInternalEffects($xaction); case PhabricatorTransactions::TYPE_BUILDABLE: case PhabricatorTransactions::TYPE_TOKEN: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: case PhabricatorTransactions::TYPE_SUBSCRIBERS: case PhabricatorTransactions::TYPE_INLINESTATE: case PhabricatorTransactions::TYPE_EDGE: case PhabricatorTransactions::TYPE_SPACE: case PhabricatorTransactions::TYPE_COMMENT: return $this->applyBuiltinInternalTransaction($object, $xaction); } return $this->applyCustomInternalTransaction($object, $xaction); } private function applyExternalEffects( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_SUBSCRIBERS: $subeditor = id(new PhabricatorSubscriptionsEditor()) ->setObject($object) ->setActor($this->requireActor()); $old_map = array_fuse($xaction->getOldValue()); $new_map = array_fuse($xaction->getNewValue()); $subeditor->unsubscribe( array_keys( array_diff_key($old_map, $new_map))); $subeditor->subscribeExplicit( array_keys( array_diff_key($new_map, $old_map))); $subeditor->save(); // for the rest of these edits, subscribers should include those just // added as well as those just removed. $subscribers = array_unique(array_merge( $this->subscribers, $xaction->getOldValue(), $xaction->getNewValue())); $this->subscribers = $subscribers; return $this->applyBuiltinExternalTransaction($object, $xaction); case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getCustomFieldForTransaction($object, $xaction); return $field->applyApplicationTransactionExternalEffects($xaction); case PhabricatorTransactions::TYPE_EDGE: case PhabricatorTransactions::TYPE_BUILDABLE: case PhabricatorTransactions::TYPE_TOKEN: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: case PhabricatorTransactions::TYPE_INLINESTATE: case PhabricatorTransactions::TYPE_SPACE: case PhabricatorTransactions::TYPE_COMMENT: return $this->applyBuiltinExternalTransaction($object, $xaction); } return $this->applyCustomExternalTransaction($object, $xaction); } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $type = $xaction->getTransactionType(); throw new Exception( pht( "Transaction type '%s' is missing an internal apply implementation!", $type)); } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $type = $xaction->getTransactionType(); throw new Exception( pht( "Transaction type '%s' is missing an external apply implementation!", $type)); } /** * @{class:PhabricatorTransactions} provides many built-in transactions * which should not require much - if any - code in specific applications. * * This method is a hook for the exceedingly-rare cases where you may need * to do **additional** work for built-in transactions. Developers should * extend this method, making sure to return the parent implementation * regardless of handling any transactions. * * See also @{method:applyBuiltinExternalTransaction}. */ protected function applyBuiltinInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_VIEW_POLICY: $object->setViewPolicy($xaction->getNewValue()); break; case PhabricatorTransactions::TYPE_EDIT_POLICY: $object->setEditPolicy($xaction->getNewValue()); break; case PhabricatorTransactions::TYPE_JOIN_POLICY: $object->setJoinPolicy($xaction->getNewValue()); break; case PhabricatorTransactions::TYPE_SPACE: $object->setSpacePHID($xaction->getNewValue()); break; } } /** * See @{method::applyBuiltinInternalTransaction}. */ protected function applyBuiltinExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_EDGE: if ($this->getIsInverseEdgeEditor()) { // If we're writing an inverse edge transaction, don't actually // do anything. The initiating editor on the other side of the // transaction will take care of the edge writes. break; } $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $src = $object->getPHID(); $const = $xaction->getMetadataValue('edge:type'); $type = PhabricatorEdgeType::getByConstant($const); if ($type->shouldWriteInverseTransactions()) { $this->applyInverseEdgeTransactions( $object, $xaction, $type->getInverseEdgeConstant()); } foreach ($new as $dst_phid => $edge) { $new[$dst_phid]['src'] = $src; } $editor = new PhabricatorEdgeEditor(); foreach ($old as $dst_phid => $edge) { if (!empty($new[$dst_phid])) { if ($old[$dst_phid]['data'] === $new[$dst_phid]['data']) { continue; } } $editor->removeEdge($src, $const, $dst_phid); } foreach ($new as $dst_phid => $edge) { if (!empty($old[$dst_phid])) { if ($old[$dst_phid]['data'] === $new[$dst_phid]['data']) { continue; } } $data = array( 'data' => $edge['data'], ); $editor->addEdge($src, $const, $dst_phid, $data); } $editor->save(); break; } } /** * Fill in a transaction's common values, like author and content source. */ protected function populateTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $actor = $this->getActor(); // TODO: This needs to be more sophisticated once we have meta-policies. $xaction->setViewPolicy(PhabricatorPolicies::POLICY_PUBLIC); if ($actor->isOmnipotent()) { $xaction->setEditPolicy(PhabricatorPolicies::POLICY_NOONE); } else { $xaction->setEditPolicy($this->getActingAsPHID()); } $xaction->setAuthorPHID($this->getActingAsPHID()); $xaction->setContentSource($this->getContentSource()); $xaction->attachViewer($actor); $xaction->attachObject($object); if ($object->getPHID()) { $xaction->setObjectPHID($object->getPHID()); } return $xaction; } protected function didApplyInternalEffects( PhabricatorLiskDAO $object, array $xactions) { return $xactions; } protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { return $xactions; } public function setContentSource(PhabricatorContentSource $content_source) { $this->contentSource = $content_source; return $this; } public function setContentSourceFromRequest(AphrontRequest $request) { return $this->setContentSource( PhabricatorContentSource::newFromRequest($request)); } public function setContentSourceFromConduitRequest( ConduitAPIRequest $request) { $content_source = PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_CONDUIT, array()); return $this->setContentSource($content_source); } public function getContentSource() { return $this->contentSource; } final public function applyTransactions( PhabricatorLiskDAO $object, array $xactions) { $this->object = $object; $this->xactions = $xactions; $this->isNewObject = ($object->getPHID() === null); $this->validateEditParameters($object, $xactions); $actor = $this->requireActor(); // NOTE: Some transaction expansion requires that the edited object be // attached. foreach ($xactions as $xaction) { $xaction->attachObject($object); $xaction->attachViewer($actor); } $xactions = $this->expandTransactions($object, $xactions); $xactions = $this->expandSupportTransactions($object, $xactions); $xactions = $this->combineTransactions($xactions); foreach ($xactions as $xaction) { $xaction = $this->populateTransaction($object, $xaction); } $is_preview = $this->getIsPreview(); $read_locking = false; $transaction_open = false; if (!$is_preview) { $errors = array(); $type_map = mgroup($xactions, 'getTransactionType'); foreach ($this->getTransactionTypes() as $type) { $type_xactions = idx($type_map, $type, array()); $errors[] = $this->validateTransaction($object, $type, $type_xactions); } $errors[] = $this->validateAllTransactions($object, $xactions); $errors = array_mergev($errors); $continue_on_missing = $this->getContinueOnMissingFields(); foreach ($errors as $key => $error) { if ($continue_on_missing && $error->getIsMissingFieldError()) { unset($errors[$key]); } } if ($errors) { throw new PhabricatorApplicationTransactionValidationException($errors); } if ($object->getID()) { foreach ($xactions as $xaction) { // If any of the transactions require a read lock, hold one and // reload the object. We need to do this fairly early so that the // call to `adjustTransactionValues()` (which populates old values) // is based on the synchronized state of the object, which may differ // from the state when it was originally loaded. if ($this->shouldReadLock($object, $xaction)) { $object->openTransaction(); $object->beginReadLocking(); $transaction_open = true; $read_locking = true; $object->reload(); break; } } } if ($this->shouldApplyInitialEffects($object, $xactions)) { if (!$transaction_open) { $object->openTransaction(); $transaction_open = true; } } } if ($this->shouldApplyInitialEffects($object, $xactions)) { $this->applyInitialEffects($object, $xactions); } foreach ($xactions as $xaction) { $this->adjustTransactionValues($object, $xaction); } - $xactions = $this->filterTransactions($object, $xactions); + try { + $xactions = $this->filterTransactions($object, $xactions); + } catch (Exception $ex) { + if ($read_locking) { + $object->endReadLocking(); + } + if ($transaction_open) { + $object->killTransaction(); + } + throw $ex; + } // Now that we've merged, filtered, and combined transactions, check for // required capabilities. foreach ($xactions as $xaction) { $this->requireCapabilities($object, $xaction); } $xactions = $this->sortTransactions($xactions); $file_phids = $this->extractFilePHIDs($object, $xactions); if ($is_preview) { $this->loadHandles($xactions); return $xactions; } $comment_editor = id(new PhabricatorApplicationTransactionCommentEditor()) ->setActor($actor) ->setActingAsPHID($this->getActingAsPHID()) ->setContentSource($this->getContentSource()); if (!$transaction_open) { $object->openTransaction(); } foreach ($xactions as $xaction) { $this->applyInternalEffects($object, $xaction); } $xactions = $this->didApplyInternalEffects($object, $xactions); try { $object->save(); } catch (AphrontDuplicateKeyQueryException $ex) { $object->killTransaction(); // This callback has an opportunity to throw a better exception, // so execution may end here. $this->didCatchDuplicateKeyException($object, $xactions, $ex); throw $ex; } foreach ($xactions as $xaction) { $xaction->setObjectPHID($object->getPHID()); if ($xaction->getComment()) { $xaction->setPHID($xaction->generatePHID()); $comment_editor->applyEdit($xaction, $xaction->getComment()); } else { $xaction->save(); } } if ($file_phids) { $this->attachFiles($object, $file_phids); } foreach ($xactions as $xaction) { $this->applyExternalEffects($object, $xaction); } $xactions = $this->applyFinalEffects($object, $xactions); if ($read_locking) { $object->endReadLocking(); $read_locking = false; } $object->saveTransaction(); // Now that we've completely applied the core transaction set, try to apply // Herald rules. Herald rules are allowed to either take direct actions on // the database (like writing flags), or take indirect actions (like saving // some targets for CC when we generate mail a little later), or return // transactions which we'll apply normally using another Editor. // First, check if *this* is a sub-editor which is itself applying Herald // rules: if it is, stop working and return so we don't descend into // madness. // Otherwise, we're not a Herald editor, so process Herald rules (possibly // using a Herald editor to apply resulting transactions) and then send out // mail, notifications, and feed updates about everything. if ($this->getIsHeraldEditor()) { // We are the Herald editor, so stop work here and return the updated // transactions. return $xactions; } else if ($this->getIsInverseEdgeEditor()) { // If we're applying inverse edge transactions, don't trigger Herald. // From a product perspective, the current set of inverse edges (most // often, mentions) aren't things users would expect to trigger Herald. // From a technical perspective, objects loaded by the inverse editor may // not have enough data to execute rules. At least for now, just stop // Herald from executing when applying inverse edges. } else if ($this->shouldApplyHeraldRules($object, $xactions)) { // We are not the Herald editor, so try to apply Herald rules. $herald_xactions = $this->applyHeraldRules($object, $xactions); if ($herald_xactions) { $xscript_id = $this->getHeraldTranscript()->getID(); foreach ($herald_xactions as $herald_xaction) { $herald_xaction->setMetadataValue('herald:transcriptID', $xscript_id); } // NOTE: We're acting as the omnipotent user because rules deal with // their own policy issues. We use a synthetic author PHID (the // Herald application) as the author of record, so that transactions // will render in a reasonable way ("Herald assigned this task ..."). $herald_actor = PhabricatorUser::getOmnipotentUser(); $herald_phid = id(new PhabricatorHeraldApplication())->getPHID(); // TODO: It would be nice to give transactions a more specific source // which points at the rule which generated them. You can figure this // out from transcripts, but it would be cleaner if you didn't have to. $herald_source = PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_HERALD, array()); $herald_editor = newv(get_class($this), array()) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->setParentMessageID($this->getParentMessageID()) ->setIsHeraldEditor(true) ->setActor($herald_actor) ->setActingAsPHID($herald_phid) ->setContentSource($herald_source); $herald_xactions = $herald_editor->applyTransactions( $object, $herald_xactions); // Merge the new transactions into the transaction list: we want to // send email and publish feed stories about them, too. $xactions = array_merge($xactions, $herald_xactions); } // If Herald did not generate transactions, we may still need to handle // "Send an Email" rules. $adapter = $this->getHeraldAdapter(); $this->heraldEmailPHIDs = $adapter->getEmailPHIDs(); $this->heraldForcedEmailPHIDs = $adapter->getForcedEmailPHIDs(); } $this->didApplyTransactions($xactions); if ($object instanceof PhabricatorCustomFieldInterface) { // Maybe this makes more sense to move into the search index itself? For // now I'm putting it here since I think we might end up with things that // need it to be up to date once the next page loads, but if we don't go // there we could move it into search once search moves to the daemons. // It now happens in the search indexer as well, but the search indexer is // always daemonized, so the logic above still potentially holds. We could // possibly get rid of this. The major motivation for putting it in the // indexer was to enable reindexing to work. $fields = PhabricatorCustomField::getObjectFields( $object, PhabricatorCustomField::ROLE_APPLICATIONSEARCH); $fields->readFieldsFromStorage($object); $fields->rebuildIndexes($object); } $herald_xscript = $this->getHeraldTranscript(); if ($herald_xscript) { $herald_header = $herald_xscript->getXHeraldRulesHeader(); $herald_header = HeraldTranscript::saveXHeraldRulesHeader( $object->getPHID(), $herald_header); } else { $herald_header = HeraldTranscript::loadXHeraldRulesHeader( $object->getPHID()); } $this->heraldHeader = $herald_header; // We're going to compute some of the data we'll use to publish these // transactions here, before queueing a worker. // // Primarily, this is more correct: we want to publish the object as it // exists right now. The worker may not execute for some time, and we want // to use the current To/CC list, not respect any changes which may occur // between now and when the worker executes. // // As a secondary benefit, this tends to reduce the amount of state that // Editors need to pass into workers. $object = $this->willPublish($object, $xactions); if (!$this->getDisableEmail()) { if ($this->shouldSendMail($object, $xactions)) { $this->mailToPHIDs = $this->getMailTo($object); $this->mailCCPHIDs = $this->getMailCC($object); } } if ($this->shouldPublishFeedStory($object, $xactions)) { $this->feedRelatedPHIDs = $this->getFeedRelatedPHIDs($object, $xactions); $this->feedNotifyPHIDs = $this->getFeedNotifyPHIDs($object, $xactions); } PhabricatorWorker::scheduleTask( 'PhabricatorApplicationTransactionPublishWorker', array( 'objectPHID' => $object->getPHID(), 'actorPHID' => $this->getActingAsPHID(), 'xactionPHIDs' => mpull($xactions, 'getPHID'), 'state' => $this->getWorkerState(), ), array( 'objectPHID' => $object->getPHID(), 'priority' => PhabricatorWorker::PRIORITY_ALERTS, )); return $xactions; } protected function didCatchDuplicateKeyException( PhabricatorLiskDAO $object, array $xactions, Exception $ex) { return; } public function publishTransactions( PhabricatorLiskDAO $object, array $xactions) { // Hook for edges or other properties that may need (re-)loading $object = $this->willPublish($object, $xactions); $messages = array(); if (!$this->getDisableEmail()) { if ($this->shouldSendMail($object, $xactions)) { $messages = $this->buildMail($object, $xactions); } } if ($this->supportsSearch()) { id(new PhabricatorSearchIndexer()) ->queueDocumentForIndexing( $object->getPHID(), $this->getSearchContextParameter($object, $xactions)); } if ($this->shouldPublishFeedStory($object, $xactions)) { $mailed = array(); foreach ($messages as $mail) { foreach ($mail->buildRecipientList() as $phid) { $mailed[$phid] = $phid; } } $this->publishFeedStory($object, $xactions, $mailed); } // NOTE: This actually sends the mail. We do this last to reduce the chance // that we send some mail, hit an exception, then send the mail again when // retrying. foreach ($messages as $mail) { $mail->save(); } return $xactions; } protected function didApplyTransactions(array $xactions) { // Hook for subclasses. return; } /** * Determine if the editor should hold a read lock on the object while * applying a transaction. * * If the editor does not hold a lock, two editors may read an object at the * same time, then apply their changes without any synchronization. For most * transactions, this does not matter much. However, it is important for some * transactions. For example, if an object has a transaction count on it, both * editors may read the object with `count = 23`, then independently update it * and save the object with `count = 24` twice. This will produce the wrong * state: the object really has 25 transactions, but the count is only 24. * * Generally, transactions fall into one of four buckets: * * - Append operations: Actions like adding a comment to an object purely * add information to its state, and do not depend on the current object * state in any way. These transactions never need to hold locks. * - Overwrite operations: Actions like changing the title or description * of an object replace the current value with a new value, so the end * state is consistent without a lock. We currently do not lock these * transactions, although we may in the future. * - Edge operations: Edge and subscription operations have internal * synchronization which limits the damage race conditions can cause. * We do not currently lock these transactions, although we may in the * future. * - Update operations: Actions like incrementing a count on an object. * These operations generally should use locks, unless it is not * important that the state remain consistent in the presence of races. * * @param PhabricatorLiskDAO Object being updated. * @param PhabricatorApplicationTransaction Transaction being applied. * @return bool True to synchronize the edit with a lock. */ protected function shouldReadLock( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { return false; } private function loadHandles(array $xactions) { $phids = array(); foreach ($xactions as $key => $xaction) { $phids[$key] = $xaction->getRequiredHandlePHIDs(); } $handles = array(); $merged = array_mergev($phids); if ($merged) { $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireActor()) ->withPHIDs($merged) ->execute(); } foreach ($xactions as $key => $xaction) { $xaction->setHandles(array_select_keys($handles, $phids[$key])); } } private function loadSubscribers(PhabricatorLiskDAO $object) { if ($object->getPHID() && ($object instanceof PhabricatorSubscribableInterface)) { $subs = PhabricatorSubscribersQuery::loadSubscribersForPHID( $object->getPHID()); $this->subscribers = array_fuse($subs); } else { $this->subscribers = array(); } } private function validateEditParameters( PhabricatorLiskDAO $object, array $xactions) { if (!$this->getContentSource()) { throw new PhutilInvalidStateException('setContentSource'); } // Do a bunch of sanity checks that the incoming transactions are fresh. // They should be unsaved and have only "transactionType" and "newValue" // set. $types = array_fill_keys($this->getTransactionTypes(), true); assert_instances_of($xactions, 'PhabricatorApplicationTransaction'); foreach ($xactions as $xaction) { if ($xaction->getPHID() || $xaction->getID()) { throw new PhabricatorApplicationTransactionStructureException( $xaction, pht('You can not apply transactions which already have IDs/PHIDs!')); } if ($xaction->getObjectPHID()) { throw new PhabricatorApplicationTransactionStructureException( $xaction, pht( 'You can not apply transactions which already have %s!', 'objectPHIDs')); } if ($xaction->getAuthorPHID()) { throw new PhabricatorApplicationTransactionStructureException( $xaction, pht( 'You can not apply transactions which already have %s!', 'authorPHIDs')); } if ($xaction->getCommentPHID()) { throw new PhabricatorApplicationTransactionStructureException( $xaction, pht( 'You can not apply transactions which already have %s!', 'commentPHIDs')); } if ($xaction->getCommentVersion() !== 0) { throw new PhabricatorApplicationTransactionStructureException( $xaction, pht( 'You can not apply transactions which already have '. 'commentVersions!')); } $expect_value = !$xaction->shouldGenerateOldValue(); $has_value = $xaction->hasOldValue(); if ($expect_value && !$has_value) { throw new PhabricatorApplicationTransactionStructureException( $xaction, pht( 'This transaction is supposed to have an %s set, but it does not!', 'oldValue')); } if ($has_value && !$expect_value) { throw new PhabricatorApplicationTransactionStructureException( $xaction, pht( 'This transaction should generate its %s automatically, '. 'but has already had one set!', 'oldValue')); } $type = $xaction->getTransactionType(); if (empty($types[$type])) { throw new PhabricatorApplicationTransactionStructureException( $xaction, pht( 'Transaction has type "%s", but that transaction type is not '. 'supported by this editor (%s).', $type, get_class($this))); } } } protected function requireCapabilities( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { if ($this->getIsNewObject()) { return; } $actor = $this->requireActor(); switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: PhabricatorPolicyFilter::requireCapability( $actor, $object, PhabricatorPolicyCapability::CAN_VIEW); break; case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: case PhabricatorTransactions::TYPE_SPACE: PhabricatorPolicyFilter::requireCapability( $actor, $object, PhabricatorPolicyCapability::CAN_EDIT); break; } } private function buildSubscribeTransaction( PhabricatorLiskDAO $object, array $xactions, array $blocks) { if (!($object instanceof PhabricatorSubscribableInterface)) { return null; } if ($this->shouldEnableMentions($object, $xactions)) { $texts = array_mergev($blocks); $phids = PhabricatorMarkupEngine::extractPHIDsFromMentions( $this->getActor(), $texts); } else { $phids = array(); } $this->mentionedPHIDs = $phids; if ($object->getPHID()) { // Don't try to subscribe already-subscribed mentions: we want to generate // a dialog about an action having no effect if the user explicitly adds // existing CCs, but not if they merely mention existing subscribers. $phids = array_diff($phids, $this->subscribers); } if ($phids) { $users = id(new PhabricatorPeopleQuery()) ->setViewer($this->getActor()) ->withPHIDs($phids) ->execute(); $users = mpull($users, null, 'getPHID'); foreach ($phids as $key => $phid) { // Do not subscribe mentioned users // who do not have VIEW Permissions if ($object instanceof PhabricatorPolicyInterface && !PhabricatorPolicyFilter::hasCapability( $users[$phid], $object, PhabricatorPolicyCapability::CAN_VIEW) ) { unset($phids[$key]); } else { if ($object->isAutomaticallySubscribed($phid)) { unset($phids[$key]); } } } $phids = array_values($phids); } // No else here to properly return null should we unset all subscriber if (!$phids) { return null; } $xaction = newv(get_class(head($xactions)), array()); $xaction->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS); $xaction->setNewValue(array('+' => $phids)); return $xaction; } protected function getRemarkupBlocksFromTransaction( PhabricatorApplicationTransaction $transaction) { return $transaction->getRemarkupBlocks(); } protected function mergeTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { $type = $u->getTransactionType(); switch ($type) { case PhabricatorTransactions::TYPE_SUBSCRIBERS: return $this->mergePHIDOrEdgeTransactions($u, $v); case PhabricatorTransactions::TYPE_EDGE: $u_type = $u->getMetadataValue('edge:type'); $v_type = $v->getMetadataValue('edge:type'); if ($u_type == $v_type) { return $this->mergePHIDOrEdgeTransactions($u, $v); } return null; } // By default, do not merge the transactions. return null; } /** * Optionally expand transactions which imply other effects. For example, * resigning from a revision in Differential implies removing yourself as * a reviewer. */ private function expandTransactions( PhabricatorLiskDAO $object, array $xactions) { $results = array(); foreach ($xactions as $xaction) { foreach ($this->expandTransaction($object, $xaction) as $expanded) { $results[] = $expanded; } } return $results; } protected function expandTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { return array($xaction); } public function getExpandedSupportTransactions( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $xactions = array($xaction); $xactions = $this->expandSupportTransactions( $object, $xactions); if (count($xactions) == 1) { return array(); } foreach ($xactions as $index => $cxaction) { if ($cxaction === $xaction) { unset($xactions[$index]); break; } } return $xactions; } private function expandSupportTransactions( PhabricatorLiskDAO $object, array $xactions) { $this->loadSubscribers($object); $xactions = $this->applyImplicitCC($object, $xactions); $blocks = array(); foreach ($xactions as $key => $xaction) { $blocks[$key] = $this->getRemarkupBlocksFromTransaction($xaction); } $subscribe_xaction = $this->buildSubscribeTransaction( $object, $xactions, $blocks); if ($subscribe_xaction) { $xactions[] = $subscribe_xaction; } // TODO: For now, this is just a placeholder. $engine = PhabricatorMarkupEngine::getEngine('extract'); $engine->setConfig('viewer', $this->requireActor()); $block_xactions = $this->expandRemarkupBlockTransactions( $object, $xactions, $blocks, $engine); foreach ($block_xactions as $xaction) { $xactions[] = $xaction; } return $xactions; } private function expandRemarkupBlockTransactions( PhabricatorLiskDAO $object, array $xactions, $blocks, PhutilMarkupEngine $engine) { $block_xactions = $this->expandCustomRemarkupBlockTransactions( $object, $xactions, $blocks, $engine); $mentioned_phids = array(); if ($this->shouldEnableMentions($object, $xactions)) { foreach ($blocks as $key => $xaction_blocks) { foreach ($xaction_blocks as $block) { $engine->markupText($block); $mentioned_phids += $engine->getTextMetadata( PhabricatorObjectRemarkupRule::KEY_MENTIONED_OBJECTS, array()); } } } if (!$mentioned_phids) { return $block_xactions; } $mentioned_objects = id(new PhabricatorObjectQuery()) ->setViewer($this->getActor()) ->withPHIDs($mentioned_phids) ->execute(); $mentionable_phids = array(); if ($this->shouldEnableMentions($object, $xactions)) { foreach ($mentioned_objects as $mentioned_object) { if ($mentioned_object instanceof PhabricatorMentionableInterface) { $mentioned_phid = $mentioned_object->getPHID(); if (idx($this->getUnmentionablePHIDMap(), $mentioned_phid)) { continue; } // don't let objects mention themselves if ($object->getPHID() && $mentioned_phid == $object->getPHID()) { continue; } $mentionable_phids[$mentioned_phid] = $mentioned_phid; } } } if ($mentionable_phids) { $edge_type = PhabricatorObjectMentionsObjectEdgeType::EDGECONST; $block_xactions[] = newv(get_class(head($xactions)), array()) ->setIgnoreOnNoEffect(true) ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) ->setMetadataValue('edge:type', $edge_type) ->setNewValue(array('+' => $mentionable_phids)); } return $block_xactions; } protected function expandCustomRemarkupBlockTransactions( PhabricatorLiskDAO $object, array $xactions, $blocks, PhutilMarkupEngine $engine) { return array(); } /** * Attempt to combine similar transactions into a smaller number of total * transactions. For example, two transactions which edit the title of an * object can be merged into a single edit. */ private function combineTransactions(array $xactions) { $stray_comments = array(); $result = array(); $types = array(); foreach ($xactions as $key => $xaction) { $type = $xaction->getTransactionType(); if (isset($types[$type])) { foreach ($types[$type] as $other_key) { $merged = $this->mergeTransactions($result[$other_key], $xaction); if ($merged) { $result[$other_key] = $merged; if ($xaction->getComment() && ($xaction->getComment() !== $merged->getComment())) { $stray_comments[] = $xaction->getComment(); } if ($result[$other_key]->getComment() && ($result[$other_key]->getComment() !== $merged->getComment())) { $stray_comments[] = $result[$other_key]->getComment(); } // Move on to the next transaction. continue 2; } } } $result[$key] = $xaction; $types[$type][] = $key; } // If we merged any comments away, restore them. foreach ($stray_comments as $comment) { $xaction = newv(get_class(head($result)), array()); $xaction->setTransactionType(PhabricatorTransactions::TYPE_COMMENT); $xaction->setComment($comment); $result[] = $xaction; } return array_values($result); } protected function mergePHIDOrEdgeTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { $result = $u->getNewValue(); foreach ($v->getNewValue() as $key => $value) { if ($u->getTransactionType() == PhabricatorTransactions::TYPE_EDGE) { if (empty($result[$key])) { $result[$key] = $value; } else { // We're merging two lists of edge adds, sets, or removes. Merge // them by merging individual PHIDs within them. $merged = $result[$key]; foreach ($value as $dst => $v_spec) { if (empty($merged[$dst])) { $merged[$dst] = $v_spec; } else { // Two transactions are trying to perform the same operation on // the same edge. Normalize the edge data and then merge it. This // allows transactions to specify how data merges execute in a // precise way. $u_spec = $merged[$dst]; if (!is_array($u_spec)) { $u_spec = array('dst' => $u_spec); } if (!is_array($v_spec)) { $v_spec = array('dst' => $v_spec); } $ux_data = idx($u_spec, 'data', array()); $vx_data = idx($v_spec, 'data', array()); $merged_data = $this->mergeEdgeData( $u->getMetadataValue('edge:type'), $ux_data, $vx_data); $u_spec['data'] = $merged_data; $merged[$dst] = $u_spec; } } $result[$key] = $merged; } } else { $result[$key] = array_merge($value, idx($result, $key, array())); } } $u->setNewValue($result); // When combining an "ignore" transaction with a normal transaction, make // sure we don't propagate the "ignore" flag. if (!$v->getIgnoreOnNoEffect()) { $u->setIgnoreOnNoEffect(false); } return $u; } protected function mergeEdgeData($type, array $u, array $v) { return $v + $u; } protected function getPHIDTransactionNewValue( PhabricatorApplicationTransaction $xaction, $old = null) { if ($old !== null) { $old = array_fuse($old); } else { $old = array_fuse($xaction->getOldValue()); } $new = $xaction->getNewValue(); $new_add = idx($new, '+', array()); unset($new['+']); $new_rem = idx($new, '-', array()); unset($new['-']); $new_set = idx($new, '=', null); if ($new_set !== null) { $new_set = array_fuse($new_set); } unset($new['=']); if ($new) { throw new Exception( pht( "Invalid '%s' value for PHID transaction. Value should contain only ". "keys '%s' (add PHIDs), '%s' (remove PHIDs) and '%s' (set PHIDS).", 'new', '+', '-', '=')); } $result = array(); foreach ($old as $phid) { if ($new_set !== null && empty($new_set[$phid])) { continue; } $result[$phid] = $phid; } if ($new_set !== null) { foreach ($new_set as $phid) { $result[$phid] = $phid; } } foreach ($new_add as $phid) { $result[$phid] = $phid; } foreach ($new_rem as $phid) { unset($result[$phid]); } return array_values($result); } protected function getEdgeTransactionNewValue( PhabricatorApplicationTransaction $xaction) { $new = $xaction->getNewValue(); $new_add = idx($new, '+', array()); unset($new['+']); $new_rem = idx($new, '-', array()); unset($new['-']); $new_set = idx($new, '=', null); unset($new['=']); if ($new) { throw new Exception( pht( "Invalid '%s' value for Edge transaction. Value should contain only ". "keys '%s' (add edges), '%s' (remove edges) and '%s' (set edges).", 'new', '+', '-', '=')); } $old = $xaction->getOldValue(); $lists = array($new_set, $new_add, $new_rem); foreach ($lists as $list) { $this->checkEdgeList($list, $xaction->getMetadataValue('edge:type')); } $result = array(); foreach ($old as $dst_phid => $edge) { if ($new_set !== null && empty($new_set[$dst_phid])) { continue; } $result[$dst_phid] = $this->normalizeEdgeTransactionValue( $xaction, $edge, $dst_phid); } if ($new_set !== null) { foreach ($new_set as $dst_phid => $edge) { $result[$dst_phid] = $this->normalizeEdgeTransactionValue( $xaction, $edge, $dst_phid); } } foreach ($new_add as $dst_phid => $edge) { $result[$dst_phid] = $this->normalizeEdgeTransactionValue( $xaction, $edge, $dst_phid); } foreach ($new_rem as $dst_phid => $edge) { unset($result[$dst_phid]); } return $result; } private function checkEdgeList($list, $edge_type) { if (!$list) { return; } foreach ($list as $key => $item) { if (phid_get_type($key) === PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { throw new Exception( pht( 'Edge transactions must have destination PHIDs as in edge '. 'lists (found key "%s" on transaction of type "%s").', $key, $edge_type)); } if (!is_array($item) && $item !== $key) { throw new Exception( pht( 'Edge transactions must have PHIDs or edge specs as values '. '(found value "%s" on transaction of type "%s").', $item, $edge_type)); } } } private function normalizeEdgeTransactionValue( PhabricatorApplicationTransaction $xaction, $edge, $dst_phid) { if (!is_array($edge)) { if ($edge != $dst_phid) { throw new Exception( pht( 'Transaction edge data must either be the edge PHID or an edge '. 'specification dictionary.')); } $edge = array(); } else { foreach ($edge as $key => $value) { switch ($key) { case 'src': case 'dst': case 'type': case 'data': case 'dateCreated': case 'dateModified': case 'seq': case 'dataID': break; default: throw new Exception( pht( 'Transaction edge specification contains unexpected key "%s".', $key)); } } } $edge['dst'] = $dst_phid; $edge_type = $xaction->getMetadataValue('edge:type'); if (empty($edge['type'])) { $edge['type'] = $edge_type; } else { if ($edge['type'] != $edge_type) { $this_type = $edge['type']; throw new Exception( pht( "Edge transaction includes edge of type '%s', but ". "transaction is of type '%s'. Each edge transaction ". "must alter edges of only one type.", $this_type, $edge_type)); } } if (!isset($edge['data'])) { $edge['data'] = array(); } return $edge; } protected function sortTransactions(array $xactions) { $head = array(); $tail = array(); // Move bare comments to the end, so the actions precede them. foreach ($xactions as $xaction) { $type = $xaction->getTransactionType(); if ($type == PhabricatorTransactions::TYPE_COMMENT) { $tail[] = $xaction; } else { $head[] = $xaction; } } return array_values(array_merge($head, $tail)); } protected function filterTransactions( PhabricatorLiskDAO $object, array $xactions) { $type_comment = PhabricatorTransactions::TYPE_COMMENT; $no_effect = array(); $has_comment = false; $any_effect = false; foreach ($xactions as $key => $xaction) { if ($this->transactionHasEffect($object, $xaction)) { if ($xaction->getTransactionType() != $type_comment) { $any_effect = true; } } else if ($xaction->getIgnoreOnNoEffect()) { unset($xactions[$key]); } else { $no_effect[$key] = $xaction; } if ($xaction->hasComment()) { $has_comment = true; } } if (!$no_effect) { return $xactions; } if (!$this->getContinueOnNoEffect() && !$this->getIsPreview()) { throw new PhabricatorApplicationTransactionNoEffectException( $no_effect, $any_effect, $has_comment); } if (!$any_effect && !$has_comment) { // If we only have empty comment transactions, just drop them all. return array(); } foreach ($no_effect as $key => $xaction) { if ($xaction->hasComment()) { $xaction->setTransactionType($type_comment); $xaction->setOldValue(null); $xaction->setNewValue(null); } else { unset($xactions[$key]); } } return $xactions; } /** * Hook for validating transactions. This callback will be invoked for each * available transaction type, even if an edit does not apply any transactions * of that type. This allows you to raise exceptions when required fields are * missing, by detecting that the object has no field value and there is no * transaction which sets one. * * @param PhabricatorLiskDAO Object being edited. * @param string Transaction type to validate. * @param list Transactions of given type, * which may be empty if the edit does not apply any transactions of the * given type. * @return list List of * validation errors. */ protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = array(); switch ($type) { case PhabricatorTransactions::TYPE_VIEW_POLICY: $errors[] = $this->validatePolicyTransaction( $object, $xactions, $type, PhabricatorPolicyCapability::CAN_VIEW); break; case PhabricatorTransactions::TYPE_EDIT_POLICY: $errors[] = $this->validatePolicyTransaction( $object, $xactions, $type, PhabricatorPolicyCapability::CAN_EDIT); break; case PhabricatorTransactions::TYPE_SPACE: $errors[] = $this->validateSpaceTransactions( $object, $xactions, $type); break; case PhabricatorTransactions::TYPE_CUSTOMFIELD: $groups = array(); foreach ($xactions as $xaction) { $groups[$xaction->getMetadataValue('customfield:key')][] = $xaction; } $field_list = PhabricatorCustomField::getObjectFields( $object, PhabricatorCustomField::ROLE_EDIT); $field_list->setViewer($this->getActor()); $role_xactions = PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS; foreach ($field_list->getFields() as $field) { if (!$field->shouldEnableForRole($role_xactions)) { continue; } $errors[] = $field->validateApplicationTransactions( $this, $type, idx($groups, $field->getFieldKey(), array())); } break; } return array_mergev($errors); } private function validatePolicyTransaction( PhabricatorLiskDAO $object, array $xactions, $transaction_type, $capability) { $actor = $this->requireActor(); $errors = array(); // Note $this->xactions is necessary; $xactions is $this->xactions of // $transaction_type $policy_object = $this->adjustObjectForPolicyChecks( $object, $this->xactions); // Make sure the user isn't editing away their ability to $capability this // object. foreach ($xactions as $xaction) { try { PhabricatorPolicyFilter::requireCapabilityWithForcedPolicy( $actor, $policy_object, $capability, $xaction->getNewValue()); } catch (PhabricatorPolicyException $ex) { $errors[] = new PhabricatorApplicationTransactionValidationError( $transaction_type, pht('Invalid'), pht( 'You can not select this %s policy, because you would no longer '. 'be able to %s the object.', $capability, $capability), $xaction); } } if ($this->getIsNewObject()) { if (!$xactions) { $has_capability = PhabricatorPolicyFilter::hasCapability( $actor, $policy_object, $capability); if (!$has_capability) { $errors[] = new PhabricatorApplicationTransactionValidationError( $transaction_type, pht('Invalid'), pht( 'The selected %s policy excludes you. Choose a %s policy '. 'which allows you to %s the object.', $capability, $capability, $capability)); } } } return $errors; } private function validateSpaceTransactions( PhabricatorLiskDAO $object, array $xactions, $transaction_type) { $errors = array(); $actor = $this->getActor(); $has_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($actor); $actor_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($actor); $active_spaces = PhabricatorSpacesNamespaceQuery::getViewerActiveSpaces( $actor); foreach ($xactions as $xaction) { $space_phid = $xaction->getNewValue(); if ($space_phid === null) { if (!$has_spaces) { // The install doesn't have any spaces, so this is fine. continue; } // The install has some spaces, so every object needs to be put // in a valid space. $errors[] = new PhabricatorApplicationTransactionValidationError( $transaction_type, pht('Invalid'), pht('You must choose a space for this object.'), $xaction); continue; } // If the PHID isn't `null`, it needs to be a valid space that the // viewer can see. if (empty($actor_spaces[$space_phid])) { $errors[] = new PhabricatorApplicationTransactionValidationError( $transaction_type, pht('Invalid'), pht( 'You can not shift this object in the selected space, because '. 'the space does not exist or you do not have access to it.'), $xaction); } else if (empty($active_spaces[$space_phid])) { // It's OK to edit objects in an archived space, so just move on if // we aren't adjusting the value. $old_space_phid = $this->getTransactionOldValue($object, $xaction); if ($space_phid == $old_space_phid) { continue; } $errors[] = new PhabricatorApplicationTransactionValidationError( $transaction_type, pht('Archived'), pht( 'You can not shift this object into the selected space, because '. 'the space is archived. Objects can not be created inside (or '. 'moved into) archived spaces.'), $xaction); } } return $errors; } protected function adjustObjectForPolicyChecks( PhabricatorLiskDAO $object, array $xactions) { $copy = clone $object; foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorTransactions::TYPE_SUBSCRIBERS: $clone_xaction = clone $xaction; $clone_xaction->setOldValue(array_values($this->subscribers)); $clone_xaction->setNewValue( $this->getPHIDTransactionNewValue( $clone_xaction)); PhabricatorPolicyRule::passTransactionHintToRule( $copy, new PhabricatorSubscriptionsSubscribersPolicyRule(), array_fuse($clone_xaction->getNewValue())); break; case PhabricatorTransactions::TYPE_SPACE: $space_phid = $this->getTransactionNewValue($object, $xaction); $copy->setSpacePHID($space_phid); break; } } return $copy; } protected function validateAllTransactions( PhabricatorLiskDAO $object, array $xactions) { return array(); } /** * Check for a missing text field. * * A text field is missing if the object has no value and there are no * transactions which set a value, or if the transactions remove the value. * This method is intended to make implementing @{method:validateTransaction} * more convenient: * * $missing = $this->validateIsEmptyTextField( * $object->getName(), * $xactions); * * This will return `true` if the net effect of the object and transactions * is an empty field. * * @param wild Current field value. * @param list Transactions editing the * field. * @return bool True if the field will be an empty text field after edits. */ protected function validateIsEmptyTextField($field_value, array $xactions) { if (strlen($field_value) && empty($xactions)) { return false; } if ($xactions && strlen(last($xactions)->getNewValue())) { return false; } return true; } /** * Check that text field input isn't longer than a specified length. * * A text field input is invalid if the length of the input is longer than a * specified length. This length can be determined by the space allotted in * the database, or given arbitrarily. * This method is intended to make implementing @{method:validateTransaction} * more convenient: * * $overdrawn = $this->validateIsTextFieldTooLong( * $object->getName(), * $xactions, * $field_length); * * This will return `true` if the net effect of the object and transactions * is a field that is too long. * * @param wild Current field value. * @param list Transactions editing the * field. * @param integer for maximum field length. * @return bool True if the field will be too long after edits. */ protected function validateIsTextFieldTooLong( $field_value, array $xactions, $length) { if ($xactions) { $new_value_length = phutil_utf8_strlen(last($xactions)->getNewValue()); if ($new_value_length <= $length) { return false; } else { return true; } } $old_value_length = phutil_utf8_strlen($field_value); if ($old_value_length <= $length) { return false; } return true; } /* -( Implicit CCs )------------------------------------------------------- */ /** * When a user interacts with an object, we might want to add them to CC. */ final public function applyImplicitCC( PhabricatorLiskDAO $object, array $xactions) { if (!($object instanceof PhabricatorSubscribableInterface)) { // If the object isn't subscribable, we can't CC them. return $xactions; } $actor_phid = $this->getActingAsPHID(); $type_user = PhabricatorPeopleUserPHIDType::TYPECONST; if (phid_get_type($actor_phid) != $type_user) { // Transactions by application actors like Herald, Harbormaster and // Diffusion should not CC the applications. return $xactions; } if ($object->isAutomaticallySubscribed($actor_phid)) { // If they're auto-subscribed, don't CC them. return $xactions; } $should_cc = false; foreach ($xactions as $xaction) { if ($this->shouldImplyCC($object, $xaction)) { $should_cc = true; break; } } if (!$should_cc) { // Only some types of actions imply a CC (like adding a comment). return $xactions; } if ($object->getPHID()) { if (isset($this->subscribers[$actor_phid])) { // If the user is already subscribed, don't implicitly CC them. return $xactions; } $unsub = PhabricatorEdgeQuery::loadDestinationPHIDs( $object->getPHID(), PhabricatorObjectHasUnsubscriberEdgeType::EDGECONST); $unsub = array_fuse($unsub); if (isset($unsub[$actor_phid])) { // If the user has previously unsubscribed from this object explicitly, // don't implicitly CC them. return $xactions; } } $xaction = newv(get_class(head($xactions)), array()); $xaction->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS); $xaction->setNewValue(array('+' => array($actor_phid))); array_unshift($xactions, $xaction); return $xactions; } protected function shouldImplyCC( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { return $xaction->isCommentTransaction(); } /* -( Sending Mail )------------------------------------------------------- */ /** * @task mail */ protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { return false; } /** * @task mail */ private function buildMail( PhabricatorLiskDAO $object, array $xactions) { $email_to = $this->mailToPHIDs; $email_cc = $this->mailCCPHIDs; $email_cc = array_merge($email_cc, $this->heraldEmailPHIDs); $targets = $this->buildReplyHandler($object) ->getMailTargets($email_to, $email_cc); // Set this explicitly before we start swapping out the effective actor. $this->setActingAsPHID($this->getActingAsPHID()); $messages = array(); foreach ($targets as $target) { $original_actor = $this->getActor(); $viewer = $target->getViewer(); $this->setActor($viewer); $locale = PhabricatorEnv::beginScopedLocale($viewer->getTranslation()); $caught = null; $mail = null; try { // Reload handles for the new viewer. $this->loadHandles($xactions); $mail = $this->buildMailForTarget($object, $xactions, $target); } catch (Exception $ex) { $caught = $ex; } $this->setActor($original_actor); unset($locale); if ($caught) { throw $ex; } if ($mail) { $messages[] = $mail; } } $this->runHeraldMailRules($messages); return $messages; } private function buildMailForTarget( PhabricatorLiskDAO $object, array $xactions, PhabricatorMailTarget $target) { // Check if any of the transactions are visible for this viewer. If we // don't have any visible transactions, don't send the mail. $any_visible = false; foreach ($xactions as $xaction) { if (!$xaction->shouldHideForMail($xactions)) { $any_visible = true; break; } } if (!$any_visible) { return null; } $mail = $this->buildMailTemplate($object); $body = $this->buildMailBody($object, $xactions); $mail_tags = $this->getMailTags($object, $xactions); $action = $this->getMailAction($object, $xactions); if (PhabricatorEnv::getEnvConfig('metamta.email-preferences')) { $this->addEmailPreferenceSectionToMailBody( $body, $object, $xactions); } $mail ->setSensitiveContent(false) ->setFrom($this->getActingAsPHID()) ->setSubjectPrefix($this->getMailSubjectPrefix()) ->setVarySubjectPrefix('['.$action.']') ->setThreadID($this->getMailThreadID($object), $this->getIsNewObject()) ->setRelatedPHID($object->getPHID()) ->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs()) ->setForceHeraldMailRecipientPHIDs($this->heraldForcedEmailPHIDs) ->setMailTags($mail_tags) ->setIsBulk(true) ->setBody($body->render()) ->setHTMLBody($body->renderHTML()); foreach ($body->getAttachments() as $attachment) { $mail->addAttachment($attachment); } if ($this->heraldHeader) { $mail->addHeader('X-Herald-Rules', $this->heraldHeader); } if ($object instanceof PhabricatorProjectInterface) { $this->addMailProjectMetadata($object, $mail); } if ($this->getParentMessageID()) { $mail->setParentMessageID($this->getParentMessageID()); } return $target->willSendMail($mail); } private function addMailProjectMetadata( PhabricatorLiskDAO $object, PhabricatorMetaMTAMail $template) { $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $object->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); if (!$project_phids) { return; } // TODO: This viewer isn't quite right. It would be slightly better to use // the mail recipient, but that's not very easy given the way rendering // works today. $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireActor()) ->withPHIDs($project_phids) ->execute(); $project_tags = array(); foreach ($handles as $handle) { if (!$handle->isComplete()) { continue; } $project_tags[] = '<'.$handle->getObjectName().'>'; } if (!$project_tags) { return; } $project_tags = implode(', ', $project_tags); $template->addHeader('X-Phabricator-Projects', $project_tags); } protected function getMailThreadID(PhabricatorLiskDAO $object) { return $object->getPHID(); } /** * @task mail */ protected function getStrongestAction( PhabricatorLiskDAO $object, array $xactions) { return last(msort($xactions, 'getActionStrength')); } /** * @task mail */ protected function buildReplyHandler(PhabricatorLiskDAO $object) { throw new Exception(pht('Capability not supported.')); } /** * @task mail */ protected function getMailSubjectPrefix() { throw new Exception(pht('Capability not supported.')); } /** * @task mail */ protected function getMailTags( PhabricatorLiskDAO $object, array $xactions) { $tags = array(); foreach ($xactions as $xaction) { $tags[] = $xaction->getMailTags(); } return array_mergev($tags); } /** * @task mail */ public function getMailTagsMap() { // TODO: We should move shared mail tags, like "comment", here. return array(); } /** * @task mail */ protected function getMailAction( PhabricatorLiskDAO $object, array $xactions) { return $this->getStrongestAction($object, $xactions)->getActionName(); } /** * @task mail */ protected function buildMailTemplate(PhabricatorLiskDAO $object) { throw new Exception(pht('Capability not supported.')); } /** * @task mail */ protected function getMailTo(PhabricatorLiskDAO $object) { throw new Exception(pht('Capability not supported.')); } /** * @task mail */ protected function getMailCC(PhabricatorLiskDAO $object) { $phids = array(); $has_support = false; if ($object instanceof PhabricatorSubscribableInterface) { $phid = $object->getPHID(); $phids[] = PhabricatorSubscribersQuery::loadSubscribersForPHID($phid); $has_support = true; } if ($object instanceof PhabricatorProjectInterface) { $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $object->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); if ($project_phids) { $watcher_type = PhabricatorObjectHasWatcherEdgeType::EDGECONST; $query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs($project_phids) ->withEdgeTypes(array($watcher_type)); $query->execute(); $watcher_phids = $query->getDestinationPHIDs(); if ($watcher_phids) { // We need to do a visibility check for all the watchers, as // watching a project is not a guarantee that you can see objects // associated with it. $users = id(new PhabricatorPeopleQuery()) ->setViewer($this->requireActor()) ->withPHIDs($watcher_phids) ->execute(); $watchers = array(); foreach ($users as $user) { $can_see = PhabricatorPolicyFilter::hasCapability( $user, $object, PhabricatorPolicyCapability::CAN_VIEW); if ($can_see) { $watchers[] = $user->getPHID(); } } $phids[] = $watchers; } } $has_support = true; } if (!$has_support) { throw new Exception(pht('Capability not supported.')); } return array_mergev($phids); } /** * @task mail */ protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $body = new PhabricatorMetaMTAMailBody(); $body->setViewer($this->requireActor()); $this->addHeadersAndCommentsToMailBody($body, $xactions); $this->addCustomFieldsToMailBody($body, $object, $xactions); return $body; } /** * @task mail */ protected function addEmailPreferenceSectionToMailBody( PhabricatorMetaMTAMailBody $body, PhabricatorLiskDAO $object, array $xactions) { $href = PhabricatorEnv::getProductionURI( '/settings/panel/emailpreferences/'); $body->addLinkSection(pht('EMAIL PREFERENCES'), $href); } /** * @task mail */ protected function addHeadersAndCommentsToMailBody( PhabricatorMetaMTAMailBody $body, array $xactions) { $headers = array(); $comments = array(); foreach ($xactions as $xaction) { if ($xaction->shouldHideForMail($xactions)) { continue; } $header = $xaction->getTitleForMail(); if ($header !== null) { $headers[] = $header; } $comment = $xaction->getBodyForMail(); if ($comment !== null) { $comments[] = $comment; } } $body->addRawSection(implode("\n", $headers)); foreach ($comments as $comment) { $body->addRemarkupSection(null, $comment); } } /** * @task mail */ protected function addCustomFieldsToMailBody( PhabricatorMetaMTAMailBody $body, PhabricatorLiskDAO $object, array $xactions) { if ($object instanceof PhabricatorCustomFieldInterface) { $field_list = PhabricatorCustomField::getObjectFields( $object, PhabricatorCustomField::ROLE_TRANSACTIONMAIL); $field_list->setViewer($this->getActor()); $field_list->readFieldsFromStorage($object); foreach ($field_list->getFields() as $field) { $field->updateTransactionMailBody( $body, $this, $xactions); } } } /** * @task mail */ private function runHeraldMailRules(array $messages) { foreach ($messages as $message) { $engine = new HeraldEngine(); $adapter = id(new PhabricatorMailOutboundMailHeraldAdapter()) ->setObject($message); $rules = $engine->loadRulesForAdapter($adapter); $effects = $engine->applyRules($rules, $adapter); $engine->applyEffects($effects, $adapter, $rules); } } /* -( Publishing Feed Stories )-------------------------------------------- */ /** * @task feed */ protected function shouldPublishFeedStory( PhabricatorLiskDAO $object, array $xactions) { return false; } /** * @task feed */ protected function getFeedStoryType() { return 'PhabricatorApplicationTransactionFeedStory'; } /** * @task feed */ protected function getFeedRelatedPHIDs( PhabricatorLiskDAO $object, array $xactions) { $phids = array( $object->getPHID(), $this->getActingAsPHID(), ); if ($object instanceof PhabricatorProjectInterface) { $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $object->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); foreach ($project_phids as $project_phid) { $phids[] = $project_phid; } } return $phids; } /** * @task feed */ protected function getFeedNotifyPHIDs( PhabricatorLiskDAO $object, array $xactions) { return array_unique(array_merge( $this->getMailTo($object), $this->getMailCC($object))); } /** * @task feed */ protected function getFeedStoryData( PhabricatorLiskDAO $object, array $xactions) { $xactions = msort($xactions, 'getActionStrength'); $xactions = array_reverse($xactions); return array( 'objectPHID' => $object->getPHID(), 'transactionPHIDs' => mpull($xactions, 'getPHID'), ); } /** * @task feed */ protected function publishFeedStory( PhabricatorLiskDAO $object, array $xactions, array $mailed_phids) { $xactions = mfilter($xactions, 'shouldHideForFeed', true); if (!$xactions) { return; } $related_phids = $this->feedRelatedPHIDs; $subscribed_phids = $this->feedNotifyPHIDs; $story_type = $this->getFeedStoryType(); $story_data = $this->getFeedStoryData($object, $xactions); id(new PhabricatorFeedStoryPublisher()) ->setStoryType($story_type) ->setStoryData($story_data) ->setStoryTime(time()) ->setStoryAuthorPHID($this->getActingAsPHID()) ->setRelatedPHIDs($related_phids) ->setPrimaryObjectPHID($object->getPHID()) ->setSubscribedPHIDs($subscribed_phids) ->setMailRecipientPHIDs($mailed_phids) ->setMailTags($this->getMailTags($object, $xactions)) ->publish(); } /* -( Search Index )------------------------------------------------------- */ /** * @task search */ protected function supportsSearch() { return false; } /** * @task search */ protected function getSearchContextParameter( PhabricatorLiskDAO $object, array $xactions) { return null; } /* -( Herald Integration )-------------------------------------------------- */ protected function shouldApplyHeraldRules( PhabricatorLiskDAO $object, array $xactions) { return false; } protected function buildHeraldAdapter( PhabricatorLiskDAO $object, array $xactions) { throw new Exception(pht('No herald adapter specified.')); } private function setHeraldAdapter(HeraldAdapter $adapter) { $this->heraldAdapter = $adapter; return $this; } protected function getHeraldAdapter() { return $this->heraldAdapter; } private function setHeraldTranscript(HeraldTranscript $transcript) { $this->heraldTranscript = $transcript; return $this; } protected function getHeraldTranscript() { return $this->heraldTranscript; } private function applyHeraldRules( PhabricatorLiskDAO $object, array $xactions) { $adapter = $this->buildHeraldAdapter($object, $xactions) ->setContentSource($this->getContentSource()) ->setIsNewObject($this->getIsNewObject()) ->setAppliedTransactions($xactions); if ($this->getApplicationEmail()) { $adapter->setApplicationEmail($this->getApplicationEmail()); } $xscript = HeraldEngine::loadAndApplyRules($adapter); $this->setHeraldAdapter($adapter); $this->setHeraldTranscript($xscript); if ($adapter instanceof HarbormasterBuildableAdapterInterface) { HarbormasterBuildable::applyBuildPlans( $adapter->getHarbormasterBuildablePHID(), $adapter->getHarbormasterContainerPHID(), $adapter->getQueuedHarbormasterBuildRequests()); } return array_merge( $this->didApplyHeraldRules($object, $adapter, $xscript), $adapter->getQueuedTransactions()); } protected function didApplyHeraldRules( PhabricatorLiskDAO $object, HeraldAdapter $adapter, HeraldTranscript $transcript) { return array(); } /* -( Custom Fields )------------------------------------------------------ */ /** * @task customfield */ private function getCustomFieldForTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $field_key = $xaction->getMetadataValue('customfield:key'); if (!$field_key) { throw new Exception( pht( "Custom field transaction has no '%s'!", 'customfield:key')); } $field = PhabricatorCustomField::getObjectField( $object, PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS, $field_key); if (!$field) { throw new Exception( pht( "Custom field transaction has invalid '%s'; field '%s' ". "is disabled or does not exist.", 'customfield:key', $field_key)); } if (!$field->shouldAppearInApplicationTransactions()) { throw new Exception( pht( "Custom field transaction '%s' does not implement ". "integration for %s.", $field_key, 'ApplicationTransactions')); } $field->setViewer($this->getActor()); return $field; } /* -( Files )-------------------------------------------------------------- */ /** * Extract the PHIDs of any files which these transactions attach. * * @task files */ private function extractFilePHIDs( PhabricatorLiskDAO $object, array $xactions) { $blocks = array(); foreach ($xactions as $xaction) { $blocks[] = $this->getRemarkupBlocksFromTransaction($xaction); } $blocks = array_mergev($blocks); $phids = array(); if ($blocks) { $phids[] = PhabricatorMarkupEngine::extractFilePHIDsFromEmbeddedFiles( $this->getActor(), $blocks); } foreach ($xactions as $xaction) { $phids[] = $this->extractFilePHIDsFromCustomTransaction( $object, $xaction); } $phids = array_unique(array_filter(array_mergev($phids))); if (!$phids) { return array(); } // Only let a user attach files they can actually see, since this would // otherwise let you access any file by attaching it to an object you have // view permission on. $files = id(new PhabricatorFileQuery()) ->setViewer($this->getActor()) ->withPHIDs($phids) ->execute(); return mpull($files, 'getPHID'); } /** * @task files */ protected function extractFilePHIDsFromCustomTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { return array(); } /** * @task files */ private function attachFiles( PhabricatorLiskDAO $object, array $file_phids) { if (!$file_phids) { return; } $editor = new PhabricatorEdgeEditor(); $src = $object->getPHID(); $type = PhabricatorObjectHasFileEdgeType::EDGECONST; foreach ($file_phids as $dst) { $editor->addEdge($src, $type, $dst); } $editor->save(); } private function applyInverseEdgeTransactions( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction, $inverse_type) { $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $add = array_keys(array_diff_key($new, $old)); $rem = array_keys(array_diff_key($old, $new)); $add = array_fuse($add); $rem = array_fuse($rem); $all = $add + $rem; $nodes = id(new PhabricatorObjectQuery()) ->setViewer($this->requireActor()) ->withPHIDs($all) ->execute(); foreach ($nodes as $node) { if (!($node instanceof PhabricatorApplicationTransactionInterface)) { continue; } if ($node instanceof PhabricatorUser) { // TODO: At least for now, don't record inverse edge transactions // for users (for example, "alincoln joined project X"): Feed fills // this role instead. continue; } $editor = $node->getApplicationTransactionEditor(); $template = $node->getApplicationTransactionTemplate(); $target = $node->getApplicationTransactionObject(); if (isset($add[$node->getPHID()])) { $edge_edit_type = '+'; } else { $edge_edit_type = '-'; } $template ->setTransactionType($xaction->getTransactionType()) ->setMetadataValue('edge:type', $inverse_type) ->setNewValue( array( $edge_edit_type => array($object->getPHID() => $object->getPHID()), )); $editor ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->setParentMessageID($this->getParentMessageID()) ->setIsInverseEdgeEditor(true) ->setActor($this->requireActor()) ->setActingAsPHID($this->getActingAsPHID()) ->setContentSource($this->getContentSource()); $editor->applyTransactions($target, array($template)); } } /* -( Workers )------------------------------------------------------------ */ /** * Load any object state which is required to publish transactions. * * This hook is invoked in the main process before we compute data related * to publishing transactions (like email "To" and "CC" lists), and again in * the worker before publishing occurs. * * @return object Publishable object. * @task workers */ protected function willPublish(PhabricatorLiskDAO $object, array $xactions) { return $object; } /** * Convert the editor state to a serializable dictionary which can be passed * to a worker. * * This data will be loaded with @{method:loadWorkerState} in the worker. * * @return dict Serializable editor state. * @task workers */ final private function getWorkerState() { $state = array(); foreach ($this->getAutomaticStateProperties() as $property) { $state[$property] = $this->$property; } $custom_state = $this->getCustomWorkerState(); $custom_encoding = $this->getCustomWorkerStateEncoding(); $state += array( 'excludeMailRecipientPHIDs' => $this->getExcludeMailRecipientPHIDs(), 'custom' => $this->encodeStateForStorage($custom_state, $custom_encoding), 'custom.encoding' => $custom_encoding, ); return $state; } /** * Hook; return custom properties which need to be passed to workers. * * @return dict Custom properties. * @task workers */ protected function getCustomWorkerState() { return array(); } /** * Hook; return storage encoding for custom properties which need to be * passed to workers. * * This primarily allows binary data to be passed to workers and survive * JSON encoding. * * @return dict Property encodings. * @task workers */ protected function getCustomWorkerStateEncoding() { return array(); } /** * Load editor state using a dictionary emitted by @{method:getWorkerState}. * * This method is used to load state when running worker operations. * * @param dict Editor state, from @{method:getWorkerState}. * @return this * @task workers */ final public function loadWorkerState(array $state) { foreach ($this->getAutomaticStateProperties() as $property) { $this->$property = idx($state, $property); } $exclude = idx($state, 'excludeMailRecipientPHIDs', array()); $this->setExcludeMailRecipientPHIDs($exclude); $custom_state = idx($state, 'custom', array()); $custom_encodings = idx($state, 'custom.encoding', array()); $custom = $this->decodeStateFromStorage($custom_state, $custom_encodings); $this->loadCustomWorkerState($custom); return $this; } /** * Hook; set custom properties on the editor from data emitted by * @{method:getCustomWorkerState}. * * @param dict Custom state, * from @{method:getCustomWorkerState}. * @return this * @task workers */ protected function loadCustomWorkerState(array $state) { return $this; } /** * Get a list of object properties which should be automatically sent to * workers in the state data. * * These properties will be automatically stored and loaded by the editor in * the worker. * * @return list List of properties. * @task workers */ private function getAutomaticStateProperties() { return array( 'parentMessageID', 'disableEmail', 'isNewObject', 'heraldEmailPHIDs', 'heraldForcedEmailPHIDs', 'heraldHeader', 'mailToPHIDs', 'mailCCPHIDs', 'feedNotifyPHIDs', 'feedRelatedPHIDs', ); } /** * Apply encodings prior to storage. * * See @{method:getCustomWorkerStateEncoding}. * * @param map Map of values to encode. * @param map Map of encodings to apply. * @return map Map of encoded values. * @task workers */ final private function encodeStateForStorage( array $state, array $encodings) { foreach ($state as $key => $value) { $encoding = idx($encodings, $key); switch ($encoding) { case self::STORAGE_ENCODING_BINARY: // The mechanics of this encoding (serialize + base64) are a little // awkward, but it allows us encode arrays and still be JSON-safe // with binary data. $value = @serialize($value); if ($value === false) { throw new Exception( pht( 'Failed to serialize() value for key "%s".', $key)); } $value = base64_encode($value); if ($value === false) { throw new Exception( pht( 'Failed to base64 encode value for key "%s".', $key)); } break; } $state[$key] = $value; } return $state; } /** * Undo storage encoding applied when storing state. * * See @{method:getCustomWorkerStateEncoding}. * * @param map Map of encoded values. * @param map Map of encodings. * @return map Map of decoded values. * @task workers */ final private function decodeStateFromStorage( array $state, array $encodings) { foreach ($state as $key => $value) { $encoding = idx($encodings, $key); switch ($encoding) { case self::STORAGE_ENCODING_BINARY: $value = base64_decode($value); if ($value === false) { throw new Exception( pht( 'Failed to base64_decode() value for key "%s".', $key)); } $value = unserialize($value); break; } $state[$key] = $value; } return $state; } } diff --git a/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php index d0bbe118be..2c340e8649 100644 --- a/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php +++ b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditEngine.php @@ -1,90 +1,93 @@ targetEngine = $target_engine; return $this; } public function getTargetEngine() { + if (!$this->targetEngine) { + throw new PhutilInvalidStateException('setTargetEngine'); + } return $this->targetEngine; } public function getEngineName() { return pht('Edit Configurations'); } public function getEngineApplicationClass() { return 'PhabricatorTransactionsApplication'; } protected function newEditableObject() { return PhabricatorEditEngineConfiguration::initializeNewConfiguration( $this->getViewer(), $this->getTargetEngine()); } protected function newObjectQuery() { return id(new PhabricatorEditEngineConfigurationQuery()); } protected function getObjectCreateTitleText($object) { return pht('Create New Form'); } protected function getObjectEditTitleText($object) { return pht('Edit Form %d: %s', $object->getID(), $object->getDisplayName()); } protected function getObjectEditShortText($object) { return pht('Form %d', $object->getID()); } protected function getObjectCreateShortText() { return pht('Create Form'); } protected function getObjectViewURI($object) { $id = $object->getID(); return $this->getURI("view/{$id}/"); } protected function getEditorURI() { return $this->getURI('edit/'); } protected function getObjectCreateCancelURI($object) { return $this->getURI(); } private function getURI($path = null) { $engine_key = $this->getTargetEngine()->getEngineKey(); return "/transactions/editengine/{$engine_key}/{$path}"; } protected function buildCustomEditFields($object) { return array( id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the form.')) ->setTransactionType( PhabricatorEditEngineConfigurationTransaction::TYPE_NAME) ->setValue($object->getName()), id(new PhabricatorRemarkupEditField()) ->setKey('preamble') ->setLabel(pht('Preamble')) ->setDescription(pht('Optional instructions, shown above the form.')) ->setTransactionType( PhabricatorEditEngineConfigurationTransaction::TYPE_PREAMBLE) ->setValue($object->getPreamble()), ); } } diff --git a/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php index 9c115254fe..8b3f446c90 100644 --- a/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php +++ b/src/applications/transactions/editor/PhabricatorEditEngineConfigurationEditor.php @@ -1,132 +1,151 @@ validateIsEmptyTextField( $object->getName(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('Form name is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } break; } return $errors; } protected function getCustomTransactionOldValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorEditEngineConfigurationTransaction::TYPE_NAME: return $object->getName(); case PhabricatorEditEngineConfigurationTransaction::TYPE_PREAMBLE; return $object->getPreamble(); case PhabricatorEditEngineConfigurationTransaction::TYPE_ORDER: return $object->getFieldOrder(); case PhabricatorEditEngineConfigurationTransaction::TYPE_DEFAULT: $field_key = $xaction->getMetadataValue('field.key'); return $object->getFieldDefault($field_key); case PhabricatorEditEngineConfigurationTransaction::TYPE_LOCKS: return $object->getFieldLocks(); + case PhabricatorEditEngineConfigurationTransaction::TYPE_DEFAULTCREATE: + return (int)$object->getIsDefault(); + case PhabricatorEditEngineConfigurationTransaction::TYPE_DISABLE: + return (int)$object->getIsDisabled(); } } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorEditEngineConfigurationTransaction::TYPE_NAME: case PhabricatorEditEngineConfigurationTransaction::TYPE_PREAMBLE; case PhabricatorEditEngineConfigurationTransaction::TYPE_ORDER: case PhabricatorEditEngineConfigurationTransaction::TYPE_DEFAULT: case PhabricatorEditEngineConfigurationTransaction::TYPE_LOCKS: return $xaction->getNewValue(); + case PhabricatorEditEngineConfigurationTransaction::TYPE_DEFAULTCREATE: + case PhabricatorEditEngineConfigurationTransaction::TYPE_DISABLE: + return (int)$xaction->getNewValue(); } } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorEditEngineConfigurationTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); return; case PhabricatorEditEngineConfigurationTransaction::TYPE_PREAMBLE; $object->setPreamble($xaction->getNewValue()); return; case PhabricatorEditEngineConfigurationTransaction::TYPE_ORDER: $object->setFieldOrder($xaction->getNewValue()); return; case PhabricatorEditEngineConfigurationTransaction::TYPE_DEFAULT: $field_key = $xaction->getMetadataValue('field.key'); $object->setFieldDefault($field_key, $xaction->getNewValue()); return; case PhabricatorEditEngineConfigurationTransaction::TYPE_LOCKS: $object->setFieldLocks($xaction->getNewValue()); return; + case PhabricatorEditEngineConfigurationTransaction::TYPE_DEFAULTCREATE: + $object->setIsDefault($xaction->getNewValue()); + return; + case PhabricatorEditEngineConfigurationTransaction::TYPE_DISABLE: + $object->setIsDisabled($xaction->getNewValue()); + return; } return parent::applyCustomInternalTransaction($object, $xaction); } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorEditEngineConfigurationTransaction::TYPE_NAME: case PhabricatorEditEngineConfigurationTransaction::TYPE_PREAMBLE; case PhabricatorEditEngineConfigurationTransaction::TYPE_ORDER; case PhabricatorEditEngineConfigurationTransaction::TYPE_DEFAULT: case PhabricatorEditEngineConfigurationTransaction::TYPE_LOCKS: + case PhabricatorEditEngineConfigurationTransaction::TYPE_DEFAULTCREATE: + case PhabricatorEditEngineConfigurationTransaction::TYPE_DISABLE: return; } return parent::applyCustomExternalTransaction($object, $xaction); } } diff --git a/src/applications/transactions/edittype/PhabricatorCommentEditType.php b/src/applications/transactions/edittype/PhabricatorCommentEditType.php index 5e4df3942f..ee3d840015 100644 --- a/src/applications/transactions/edittype/PhabricatorCommentEditType.php +++ b/src/applications/transactions/edittype/PhabricatorCommentEditType.php @@ -1,31 +1,26 @@ getTypeName(); } - public function generateTransaction( + public function generateTransactions( PhabricatorApplicationTransaction $template, array $spec) { $comment = $template->getApplicationTransactionCommentObject() ->setContent(idx($spec, 'value')); - $template - ->setTransactionType($this->getTransactionType()) + $xaction = $this->newTransaction($template) ->attachComment($comment); - foreach ($this->getMetadata() as $key => $value) { - $template->setMetadataValue($key, $value); - } - - return $template; + return array($xaction); } public function getValueDescription() { return pht('Comment to add, formated as remarkup.'); } } diff --git a/src/applications/transactions/edittype/PhabricatorEdgeEditType.php b/src/applications/transactions/edittype/PhabricatorEdgeEditType.php index 17d1441a80..ebb8a311ad 100644 --- a/src/applications/transactions/edittype/PhabricatorEdgeEditType.php +++ b/src/applications/transactions/edittype/PhabricatorEdgeEditType.php @@ -1,51 +1,88 @@ edgeOperation = $edge_operation; return $this; } public function getEdgeOperation() { return $this->edgeOperation; } + public function setDatasource($datasource) { + $this->datasource = $datasource; + return $this; + } + + public function getDatasource() { + return $this->datasource; + } + public function getValueType() { return 'list'; } - public function generateTransaction( + public function generateTransactions( PhabricatorApplicationTransaction $template, array $spec) { $value = idx($spec, 'value'); - $value = array_fuse($value); - $value = array( - $this->getEdgeOperation() => $value, - ); - $template - ->setTransactionType($this->getTransactionType()) - ->setNewValue($value); - - foreach ($this->getMetadata() as $key => $value) { - $template->setMetadataValue($key, $value); + if ($this->getEdgeOperation() !== null) { + $value = array_fuse($value); + $value = array( + $this->getEdgeOperation() => $value, + ); } - return $template; + $xaction = $this->newTransaction($template) + ->setNewValue($value); + + return array($xaction); } public function setValueDescription($value_description) { $this->valueDescription = $value_description; return $this; } public function getValueDescription() { return $this->valueDescription; } + public function getPHUIXControlType() { + $datasource = $this->getDatasource(); + + if (!$datasource) { + return null; + } + + return 'tokenizer'; + } + + public function getPHUIXControlSpecification() { + $datasource = $this->getDatasource(); + + if (!$datasource) { + return null; + } + + $template = new AphrontTokenizerTemplateView(); + + return array( + 'markup' => $template->render(), + 'config' => array( + 'src' => $datasource->getDatasourceURI(), + 'browseURI' => $datasource->getBrowseURI(), + 'placeholder' => $datasource->getPlaceholderText(), + ), + ); + } + } diff --git a/src/applications/transactions/edittype/PhabricatorEditType.php b/src/applications/transactions/edittype/PhabricatorEditType.php index a1e580f6fd..bb58213594 100644 --- a/src/applications/transactions/edittype/PhabricatorEditType.php +++ b/src/applications/transactions/edittype/PhabricatorEditType.php @@ -1,76 +1,107 @@ description = $description; return $this; } public function getDescription() { return $this->description; } public function setSummary($summary) { $this->summary = $summary; return $this; } public function getSummary() { if ($this->summary === null) { return $this->getDescription(); } return $this->summary; } + public function setLabel($label) { + $this->label = $label; + return $this; + } + + public function getLabel() { + return $this->label; + } + public function setField(PhabricatorEditField $field) { $this->field = $field; return $this; } public function getField() { return $this->field; } public function setEditType($edit_type) { $this->editType = $edit_type; return $this; } public function getEditType() { return $this->editType; } public function setMetadata($metadata) { $this->metadata = $metadata; return $this; } public function getMetadata() { return $this->metadata; } public function setTransactionType($transaction_type) { $this->transactionType = $transaction_type; return $this; } public function getTransactionType() { return $this->transactionType; } - abstract public function generateTransaction( + abstract public function generateTransactions( PhabricatorApplicationTransaction $template, array $spec); abstract public function getValueType(); abstract public function getValueDescription(); + protected function newTransaction( + PhabricatorApplicationTransaction $template) { + + $xaction = id(clone $template) + ->setTransactionType($this->getTransactionType()); + + foreach ($this->getMetadata() as $key => $value) { + $xaction->setMetadataValue($key, $value); + } + + return $xaction; + } + + public function getPHUIXControlType() { + return null; + } + + public function getPHUIXControlSpecification() { + return null; + } + } diff --git a/src/applications/transactions/edittype/PhabricatorSimpleEditType.php b/src/applications/transactions/edittype/PhabricatorSimpleEditType.php index 3aa0575579..c1a1a98730 100644 --- a/src/applications/transactions/edittype/PhabricatorSimpleEditType.php +++ b/src/applications/transactions/edittype/PhabricatorSimpleEditType.php @@ -1,41 +1,36 @@ valueType = $value_type; return $this; } public function getValueType() { return $this->valueType; } - public function generateTransaction( + public function generateTransactions( PhabricatorApplicationTransaction $template, array $spec) { - $template - ->setTransactionType($this->getTransactionType()) + $edit = $this->newTransaction($template) ->setNewValue(idx($spec, 'value')); - foreach ($this->getMetadata() as $key => $value) { - $template->setMetadataValue($key, $value); - } - - return $template; + return array($edit); } public function setValueDescription($value_description) { $this->valueDescription = $value_description; return $this; } public function getValueDescription() { return $this->valueDescription; } } diff --git a/src/applications/transactions/query/PhabricatorEditEngineConfigurationQuery.php b/src/applications/transactions/query/PhabricatorEditEngineConfigurationQuery.php index 6fc91dffbc..9b37909d76 100644 --- a/src/applications/transactions/query/PhabricatorEditEngineConfigurationQuery.php +++ b/src/applications/transactions/query/PhabricatorEditEngineConfigurationQuery.php @@ -1,208 +1,236 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withEngineKeys(array $engine_keys) { $this->engineKeys = $engine_keys; return $this; } public function withBuiltinKeys(array $builtin_keys) { $this->builtinKeys = $builtin_keys; return $this; } public function withIdentifiers(array $identifiers) { $this->identifiers = $identifiers; return $this; } + public function withIsDefault($default) { + $this->default = $default; + return $this; + } + + public function withIsDisabled($disabled) { + $this->disabled = $disabled; + return $this; + } + public function newResultObject() { return new PhabricatorEditEngineConfiguration(); } protected function loadPage() { // TODO: The logic here is a little flimsy and won't survive pagination. // For now, I'm just not bothering with pagination since I believe it will // take some time before any install manages to produce a large enough // number of edit forms for any particular engine for the lack of UI // pagination to become a problem. $page = $this->loadStandardPage($this->newResultObject()); // Now that we've loaded the real results from the database, we're going // to load builtins from the edit engines and add them to the list. $engines = PhabricatorEditEngine::getAllEditEngines(); if ($this->engineKeys) { $engines = array_select_keys($engines, $this->engineKeys); } foreach ($engines as $engine) { $engine->setViewer($this->getViewer()); } // List all the builtins which have already been saved to the database as // real objects. $concrete = array(); foreach ($page as $config) { $builtin_key = $config->getBuiltinKey(); if ($builtin_key !== null) { $engine_key = $config->getEngineKey(); $concrete[$engine_key][$builtin_key] = $config; } } $builtins = array(); foreach ($engines as $engine_key => $engine) { $engine_builtins = $engine->getBuiltinEngineConfigurations(); foreach ($engine_builtins as $engine_builtin) { $builtin_key = $engine_builtin->getBuiltinKey(); if (isset($concrete[$engine_key][$builtin_key])) { continue; } else { $builtins[] = $engine_builtin; } } } foreach ($builtins as $builtin) { $page[] = $builtin; } // Now we have to do some extra filtering to make sure everything we're // about to return really satisfies the query. if ($this->ids !== null) { $ids = array_fuse($this->ids); foreach ($page as $key => $config) { if (empty($ids[$config->getID()])) { unset($page[$key]); } } } if ($this->phids !== null) { $phids = array_fuse($this->phids); foreach ($page as $key => $config) { if (empty($phids[$config->getPHID()])) { unset($page[$key]); } } } if ($this->builtinKeys !== null) { $builtin_keys = array_fuse($this->builtinKeys); foreach ($page as $key => $config) { if (empty($builtin_keys[$config->getBuiltinKey()])) { unset($page[$key]); } } } + if ($this->default !== null) { + foreach ($page as $key => $config) { + if ($config->getIsDefault() != $this->default) { + unset($page[$key]); + } + } + } + + if ($this->disabled !== null) { + foreach ($page as $key => $config) { + if ($config->getIsDisabled() != $this->disabled) { + unset($page[$key]); + } + } + } + if ($this->identifiers !== null) { $identifiers = array_fuse($this->identifiers); foreach ($page as $key => $config) { if (isset($identifiers[$config->getBuiltinKey()])) { continue; } if (isset($identifiers[$config->getID()])) { continue; } unset($page[$key]); } } return $page; } protected function willFilterPage(array $configs) { $engine_keys = mpull($configs, 'getEngineKey'); $engines = id(new PhabricatorEditEngineQuery()) ->setParentQuery($this) ->setViewer($this->getViewer()) ->withEngineKeys($engine_keys) ->execute(); $engines = mpull($engines, null, 'getEngineKey'); foreach ($configs as $key => $config) { $engine = idx($engines, $config->getEngineKey()); if (!$engine) { $this->didRejectResult($config); unset($configs[$key]); continue; } $config->attachEngine($engine); } return $configs; } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, 'phid IN (%Ls)', $this->phids); } if ($this->engineKeys !== null) { $where[] = qsprintf( $conn, 'engineKey IN (%Ls)', $this->engineKeys); } if ($this->builtinKeys !== null) { $where[] = qsprintf( $conn, 'builtinKey IN (%Ls)', $this->builtinKeys); } if ($this->identifiers !== null) { $where[] = qsprintf( $conn, '(id IN (%Ls) OR builtinKey IN (%Ls))', $this->identifiers, $this->identifiers); } return $where; } public function getQueryApplicationClass() { return 'PhabricatorTransactionsApplication'; } } diff --git a/src/applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php b/src/applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php index 8d55aeb5ac..f411b17275 100644 --- a/src/applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php +++ b/src/applications/transactions/query/PhabricatorEditEngineConfigurationSearchEngine.php @@ -1,102 +1,109 @@ engineKey = $engine_key; return $this; } public function getEngineKey() { return $this->engineKey; } public function canUseInPanelContext() { return false; } public function getResultTypeDescription() { return pht('Forms'); } public function getApplicationClassName() { return 'PhabricatorTransactionsApplication'; } public function newQuery() { return id(new PhabricatorEditEngineConfigurationQuery()) ->withEngineKeys(array($this->getEngineKey())); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); return $query; } protected function buildCustomSearchFields() { return array(); } protected function getDefaultFieldOrder() { return array(); } protected function getURI($path) { return '/transactions/editengine/'.$this->getEngineKey().'/'.$path; } protected function getBuiltinQueryNames() { $names = array( 'all' => pht('All Forms'), ); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } protected function renderResultList( array $configs, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($configs, 'PhabricatorEditEngineConfiguration'); $viewer = $this->requireViewer(); $engine_key = $this->getEngineKey(); $list = id(new PHUIObjectItemListView()) ->setUser($viewer); foreach ($configs as $config) { $item = id(new PHUIObjectItemView()) ->setHeader($config->getDisplayName()); $id = $config->getID(); if ($id) { $item->setObjectName(pht('Form %d', $id)); $key = $id; } else { $item->setObjectName(pht('Builtin')); $key = $config->getBuiltinKey(); } $item->setHref("/transactions/editengine/{$engine_key}/view/{$key}/"); + if ($config->getIsDefault()) { + $item->addIcon('fa-plus', pht('Default')); + } + + if ($config->getIsDisabled()) { + $item->addIcon('fa-ban', pht('Disabled')); + } $list->addItem($item); } return id(new PhabricatorApplicationSearchResultView()) ->setObjectList($list); } } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 9eb1609931..df46649c08 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -1,1283 +1,1293 @@ ignoreOnNoEffect = $ignore; return $this; } public function getIgnoreOnNoEffect() { return $this->ignoreOnNoEffect; } public function shouldGenerateOldValue() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_BUILDABLE: case PhabricatorTransactions::TYPE_TOKEN: case PhabricatorTransactions::TYPE_CUSTOMFIELD: case PhabricatorTransactions::TYPE_INLINESTATE: return false; } return true; } abstract public function getApplicationTransactionType(); private function getApplicationObjectTypeName() { $types = PhabricatorPHIDType::getAllTypes(); $type = idx($types, $this->getApplicationTransactionType()); if ($type) { return $type->getTypeName(); } return pht('Object'); } public function getApplicationTransactionCommentObject() { throw new PhutilMethodNotImplementedException(); } public function getApplicationTransactionViewObject() { return new PhabricatorApplicationTransactionView(); } public function getMetadataValue($key, $default = null) { return idx($this->metadata, $key, $default); } public function setMetadataValue($key, $value) { $this->metadata[$key] = $value; return $this; } public function generatePHID() { $type = PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST; $subtype = $this->getApplicationTransactionType(); return PhabricatorPHID::generateNewPHID($type, $subtype); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'oldValue' => self::SERIALIZATION_JSON, 'newValue' => self::SERIALIZATION_JSON, 'metadata' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'commentPHID' => 'phid?', 'commentVersion' => 'uint32', 'contentSource' => 'text', 'transactionType' => 'text32', ), self::CONFIG_KEY_SCHEMA => array( 'key_object' => array( 'columns' => array('objectPHID'), ), ), ) + parent::getConfiguration(); } public function setContentSource(PhabricatorContentSource $content_source) { $this->contentSource = $content_source->serialize(); return $this; } public function getContentSource() { return PhabricatorContentSource::newFromSerialized($this->contentSource); } public function hasComment() { return $this->getComment() && strlen($this->getComment()->getContent()); } public function getComment() { if ($this->commentNotLoaded) { throw new Exception(pht('Comment for this transaction was not loaded.')); } return $this->comment; } public function attachComment( PhabricatorApplicationTransactionComment $comment) { $this->comment = $comment; $this->commentNotLoaded = false; return $this; } public function setCommentNotLoaded($not_loaded) { $this->commentNotLoaded = $not_loaded; return $this; } public function attachObject($object) { $this->object = $object; return $this; } public function getObject() { return $this->assertAttached($this->object); } public function getRemarkupBlocks() { $blocks = array(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { $custom_blocks = $field->getApplicationTransactionRemarkupBlocks( $this); foreach ($custom_blocks as $custom_block) { $blocks[] = $custom_block; } } break; } if ($this->getComment()) { $blocks[] = $this->getComment()->getContent(); } return $blocks; } public function setOldValue($value) { $this->oldValueHasBeenSet = true; $this->writeField('oldValue', $value); return $this; } public function hasOldValue() { return $this->oldValueHasBeenSet; } /* -( Rendering )---------------------------------------------------------- */ public function setRenderingTarget($rendering_target) { $this->renderingTarget = $rendering_target; return $this; } public function getRenderingTarget() { return $this->renderingTarget; } public function attachViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; } public function getViewer() { return $this->assertAttached($this->viewer); } public function getRequiredHandlePHIDs() { $phids = array(); $old = $this->getOldValue(); $new = $this->getNewValue(); $phids[] = array($this->getAuthorPHID()); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { $phids[] = $field->getApplicationTransactionRequiredHandlePHIDs( $this); } break; case PhabricatorTransactions::TYPE_SUBSCRIBERS: $phids[] = $old; $phids[] = $new; break; case PhabricatorTransactions::TYPE_EDGE: $phids[] = ipull($old, 'dst'); $phids[] = ipull($new, 'dst'); break; case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: if (!PhabricatorPolicyQuery::isSpecialPolicy($old)) { $phids[] = array($old); } if (!PhabricatorPolicyQuery::isSpecialPolicy($new)) { $phids[] = array($new); } break; case PhabricatorTransactions::TYPE_SPACE: if ($old) { $phids[] = array($old); } if ($new) { $phids[] = array($new); } break; case PhabricatorTransactions::TYPE_TOKEN: break; case PhabricatorTransactions::TYPE_BUILDABLE: $phid = $this->getMetadataValue('harbormaster:buildablePHID'); if ($phid) { $phids[] = array($phid); } break; } if ($this->getComment()) { $phids[] = array($this->getComment()->getAuthorPHID()); } return array_mergev($phids); } public function setHandles(array $handles) { $this->handles = $handles; return $this; } public function getHandle($phid) { if (empty($this->handles[$phid])) { throw new Exception( pht( 'Transaction ("%s", of type "%s") requires a handle ("%s") that it '. 'did not load.', $this->getPHID(), $this->getTransactionType(), $phid)); } return $this->handles[$phid]; } public function getHandleIfExists($phid) { return idx($this->handles, $phid); } public function getHandles() { if ($this->handles === null) { throw new Exception( pht('Transaction requires handles and it did not load them.')); } return $this->handles; } public function renderHandleLink($phid) { if ($this->renderingTarget == self::TARGET_HTML) { return $this->getHandle($phid)->renderLink(); } else { return $this->getHandle($phid)->getLinkName(); } } public function renderHandleList(array $phids) { $links = array(); foreach ($phids as $phid) { $links[] = $this->renderHandleLink($phid); } if ($this->renderingTarget == self::TARGET_HTML) { return phutil_implode_html(', ', $links); } else { return implode(', ', $links); } } private function renderSubscriberList(array $phids, $change_type) { if ($this->getRenderingTarget() == self::TARGET_TEXT) { return $this->renderHandleList($phids); } else { $handles = array_select_keys($this->getHandles(), $phids); return id(new SubscriptionListStringBuilder()) ->setHandles($handles) ->setObjectPHID($this->getPHID()) ->buildTransactionString($change_type); } } protected function renderPolicyName($phid, $state = 'old') { $policy = PhabricatorPolicy::newFromPolicyAndHandle( $phid, $this->getHandleIfExists($phid)); if ($this->renderingTarget == self::TARGET_HTML) { switch ($policy->getType()) { case PhabricatorPolicyType::TYPE_CUSTOM: $policy->setHref('/transactions/'.$state.'/'.$this->getPHID().'/'); $policy->setWorkflow(true); break; default: break; } $output = $policy->renderDescription(); } else { $output = hsprintf('%s', $policy->getFullName()); } return $output; } public function getIcon() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $comment = $this->getComment(); if ($comment && $comment->getIsRemoved()) { return 'fa-trash'; } return 'fa-comment'; case PhabricatorTransactions::TYPE_SUBSCRIBERS: $old = $this->getOldValue(); $new = $this->getNewValue(); $add = array_diff($new, $old); $rem = array_diff($old, $new); if ($add && $rem) { return 'fa-user'; } else if ($add) { return 'fa-user-plus'; } else if ($rem) { return 'fa-user-times'; } else { return 'fa-user'; } case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: return 'fa-lock'; case PhabricatorTransactions::TYPE_EDGE: return 'fa-link'; case PhabricatorTransactions::TYPE_BUILDABLE: return 'fa-wrench'; case PhabricatorTransactions::TYPE_TOKEN: return 'fa-trophy'; case PhabricatorTransactions::TYPE_SPACE: return 'fa-th-large'; } return 'fa-pencil'; } public function getToken() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_TOKEN: $old = $this->getOldValue(); $new = $this->getNewValue(); if ($new) { $icon = substr($new, 10); } else { $icon = substr($old, 10); } return array($icon, !$this->getNewValue()); } return array(null, null); } public function getColor() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT; $comment = $this->getComment(); if ($comment && $comment->getIsRemoved()) { return 'black'; } break; case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { case HarbormasterBuildable::STATUS_PASSED: return 'green'; case HarbormasterBuildable::STATUS_FAILED: return 'red'; } break; } return null; } protected function getTransactionCustomField() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_CUSTOMFIELD: $key = $this->getMetadataValue('customfield:key'); if (!$key) { return null; } $field = PhabricatorCustomField::getObjectField( $this->getObject(), PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS, $key); if (!$field) { return null; } $field->setViewer($this->getViewer()); return $field; } return null; } public function shouldHide() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: case PhabricatorTransactions::TYPE_SPACE: if ($this->getOldValue() === null) { return true; } else { return false; } break; case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { return $field->shouldHideInApplicationTransactions($this); } case PhabricatorTransactions::TYPE_EDGE: $edge_type = $this->getMetadataValue('edge:type'); switch ($edge_type) { case PhabricatorObjectMentionsObjectEdgeType::EDGECONST: return true; break; case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST: $new = ipull($this->getNewValue(), 'dst'); $old = ipull($this->getOldValue(), 'dst'); $add = array_diff($new, $old); $add_value = reset($add); $add_handle = $this->getHandle($add_value); if ($add_handle->getPolicyFiltered()) { return true; } return false; break; default: break; } break; } return false; } public function shouldHideForMail(array $xactions) { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_TOKEN: return true; case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { case HarbormasterBuildable::STATUS_FAILED: // For now, only ever send mail when builds fail. We might let // you customize this later, but in most cases this is probably // completely uninteresting. return false; } return true; case PhabricatorTransactions::TYPE_EDGE: $edge_type = $this->getMetadataValue('edge:type'); switch ($edge_type) { case PhabricatorObjectMentionsObjectEdgeType::EDGECONST: case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST: return true; break; default: break; } break; } // If a transaction publishes an inline comment: // // - Don't show it if there are other kinds of transactions. The // rationale here is that application mail will make the presence // of inline comments obvious enough by including them prominently // in the body. We could change this in the future if the obviousness // needs to be increased. // - If there are only inline transactions, only show the first // transaction. The rationale is that seeing multiple "added an inline // comment" transactions is not useful. if ($this->isInlineCommentTransaction()) { foreach ($xactions as $xaction) { if (!$xaction->isInlineCommentTransaction()) { return true; } } return ($this !== head($xactions)); } return $this->shouldHide(); } public function shouldHideForFeed() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_TOKEN: return true; case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { case HarbormasterBuildable::STATUS_FAILED: // For now, don't notify on build passes either. These are pretty // high volume and annoying, with very little present value. We // might want to turn them back on in the specific case of // build successes on the current document? return false; } return true; case PhabricatorTransactions::TYPE_EDGE: $edge_type = $this->getMetadataValue('edge:type'); switch ($edge_type) { case PhabricatorObjectMentionsObjectEdgeType::EDGECONST: case PhabricatorObjectMentionedByObjectEdgeType::EDGECONST: return true; break; default: break; } break; case PhabricatorTransactions::TYPE_INLINESTATE: return true; } return $this->shouldHide(); } public function getTitleForMail() { return id(clone $this)->setRenderingTarget('text')->getTitle(); } public function getBodyForMail() { if ($this->isInlineCommentTransaction()) { // We don't return inline comment content as mail body content, because // applications need to contextualize it (by adding line numbers, for // example) in order for it to make sense. return null; } $comment = $this->getComment(); if ($comment && strlen($comment->getContent())) { return $comment->getContent(); } return null; } public function getNoEffectDescription() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht('You can not post an empty comment.'); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( 'This %s already has that view policy.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( 'This %s already has that edit policy.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_JOIN_POLICY: return pht( 'This %s already has that join policy.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht( 'All users are already subscribed to this %s.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_SPACE: return pht('This object is already in that space.'); case PhabricatorTransactions::TYPE_EDGE: return pht('Edges already exist; transaction has no effect.'); } return pht( 'Transaction (of type "%s") has no effect.', $this->getTransactionType()); } public function getTitle() { $author_phid = $this->getAuthorPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht( '%s added a comment.', $this->renderHandleLink($author_phid)); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( '%s changed the visibility from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( '%s changed the edit policy from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case PhabricatorTransactions::TYPE_JOIN_POLICY: return pht( '%s changed the join policy from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case PhabricatorTransactions::TYPE_SPACE: return pht( '%s shifted this object from the %s space to the %s space.', $this->renderHandleLink($author_phid), $this->renderHandleLink($old), $this->renderHandleLink($new)); case PhabricatorTransactions::TYPE_SUBSCRIBERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); if ($add && $rem) { return pht( '%s edited subscriber(s), added %d: %s; removed %d: %s.', $this->renderHandleLink($author_phid), count($add), $this->renderSubscriberList($add, 'add'), count($rem), $this->renderSubscriberList($rem, 'rem')); } else if ($add) { return pht( '%s added %d subscriber(s): %s.', $this->renderHandleLink($author_phid), count($add), $this->renderSubscriberList($add, 'add')); } else if ($rem) { return pht( '%s removed %d subscriber(s): %s.', $this->renderHandleLink($author_phid), count($rem), $this->renderSubscriberList($rem, 'rem')); } else { // This is used when rendering previews, before the user actually // selects any CCs. return pht( '%s updated subscribers...', $this->renderHandleLink($author_phid)); } break; case PhabricatorTransactions::TYPE_EDGE: $new = ipull($new, 'dst'); $old = ipull($old, 'dst'); $add = array_diff($new, $old); $rem = array_diff($old, $new); $type = $this->getMetadata('edge:type'); $type = head($type); $type_obj = PhabricatorEdgeType::getByConstant($type); if ($add && $rem) { return $type_obj->getTransactionEditString( $this->renderHandleLink($author_phid), new PhutilNumber(count($add) + count($rem)), phutil_count($add), $this->renderHandleList($add), phutil_count($rem), $this->renderHandleList($rem)); } else if ($add) { return $type_obj->getTransactionAddString( $this->renderHandleLink($author_phid), phutil_count($add), $this->renderHandleList($add)); } else if ($rem) { return $type_obj->getTransactionRemoveString( $this->renderHandleLink($author_phid), phutil_count($rem), $this->renderHandleList($rem)); } else { return $type_obj->getTransactionPreviewString( $this->renderHandleLink($author_phid)); } case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { return $field->getApplicationTransactionTitle($this); } else { return pht( '%s edited a custom field.', $this->renderHandleLink($author_phid)); } case PhabricatorTransactions::TYPE_TOKEN: if ($old && $new) { return pht( '%s updated a token.', $this->renderHandleLink($author_phid)); } else if ($old) { return pht( '%s rescinded a token.', $this->renderHandleLink($author_phid)); } else { return pht( '%s awarded a token.', $this->renderHandleLink($author_phid)); } case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { case HarbormasterBuildable::STATUS_BUILDING: return pht( '%s started building %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID'))); case HarbormasterBuildable::STATUS_PASSED: return pht( '%s completed building %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID'))); case HarbormasterBuildable::STATUS_FAILED: return pht( '%s failed to build %s!', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID'))); default: return null; } case PhabricatorTransactions::TYPE_INLINESTATE: $done = 0; $undone = 0; foreach ($new as $phid => $state) { if ($state == PhabricatorInlineCommentInterface::STATE_DONE) { $done++; } else { $undone++; } } if ($done && $undone) { return pht( '%s marked %s inline comment(s) as done and %s inline comment(s) '. 'as not done.', $this->renderHandleLink($author_phid), new PhutilNumber($done), new PhutilNumber($undone)); } else if ($done) { return pht( '%s marked %s inline comment(s) as done.', $this->renderHandleLink($author_phid), new PhutilNumber($done)); } else { return pht( '%s marked %s inline comment(s) as not done.', $this->renderHandleLink($author_phid), new PhutilNumber($undone)); } break; default: return pht( '%s edited this %s.', $this->renderHandleLink($author_phid), $this->getApplicationObjectTypeName()); } } public function getTitleForFeed() { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht( '%s added a comment to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( '%s changed the visibility for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( '%s changed the edit policy for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_JOIN_POLICY: return pht( '%s changed the join policy for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht( '%s updated subscribers of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_SPACE: return pht( '%s shifted %s from the %s space to the %s space.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), $this->renderHandleLink($old), $this->renderHandleLink($new)); case PhabricatorTransactions::TYPE_EDGE: $new = ipull($new, 'dst'); $old = ipull($old, 'dst'); $add = array_diff($new, $old); $rem = array_diff($old, $new); $type = $this->getMetadata('edge:type'); $type = head($type); $type_obj = PhabricatorEdgeType::getByConstant($type); if ($add && $rem) { return $type_obj->getFeedEditString( $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), new PhutilNumber(count($add) + count($rem)), phutil_count($add), $this->renderHandleList($add), phutil_count($rem), $this->renderHandleList($rem)); } else if ($add) { return $type_obj->getFeedAddString( $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), phutil_count($add), $this->renderHandleList($add)); } else if ($rem) { return $type_obj->getFeedRemoveString( $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), phutil_count($rem), $this->renderHandleList($rem)); } else { return pht( '%s edited edge metadata for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { return $field->getApplicationTransactionTitleForFeed($this); } else { return pht( '%s edited a custom field on %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { case HarbormasterBuildable::STATUS_BUILDING: return pht( '%s started building %s for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID')), $this->renderHandleLink($object_phid)); case HarbormasterBuildable::STATUS_PASSED: return pht( '%s completed building %s for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID')), $this->renderHandleLink($object_phid)); case HarbormasterBuildable::STATUS_FAILED: return pht( '%s failed to build %s for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID')), $this->renderHandleLink($object_phid)); default: return null; } } return $this->getTitle(); } public function getMarkupFieldsForFeed(PhabricatorFeedStory $story) { $fields = array(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $text = $this->getComment()->getContent(); if (strlen($text)) { $fields[] = 'comment/'.$this->getID(); } break; } return $fields; } public function getMarkupTextForFeed(PhabricatorFeedStory $story, $field) { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $text = $this->getComment()->getContent(); return PhabricatorMarkupEngine::summarize($text); } return null; } public function getBodyForFeed(PhabricatorFeedStory $story) { + $remarkup = $this->getRemarkupBodyForFeed($story); + if ($remarkup !== null) { + $remarkup = PhabricatorMarkupEngine::summarize($remarkup); + return new PHUIRemarkupView($this->viewer, $remarkup); + } + $old = $this->getOldValue(); $new = $this->getNewValue(); $body = null; switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $text = $this->getComment()->getContent(); if (strlen($text)) { $body = $story->getMarkupFieldOutput('comment/'.$this->getID()); } break; } return $body; } + public function getRemarkupBodyForFeed(PhabricatorFeedStory $story) { + return null; + } + public function getActionStrength() { if ($this->isInlineCommentTransaction()) { return 0.25; } switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return 0.5; case PhabricatorTransactions::TYPE_SUBSCRIBERS: $old = $this->getOldValue(); $new = $this->getNewValue(); $add = array_diff($old, $new); $rem = array_diff($new, $old); // If this action is the actor subscribing or unsubscribing themselves, // it is less interesting. In particular, if someone makes a comment and // also implicitly subscribes themselves, we should treat the // transaction group as "comment", not "subscribe". In this specific // case (one affected user, and that affected user it the actor), // decrease the action strength. if ((count($add) + count($rem)) != 1) { // Not exactly one CC change. break; } $affected_phid = head(array_merge($add, $rem)); if ($affected_phid != $this->getAuthorPHID()) { // Affected user is someone else. break; } // Make this weaker than TYPE_COMMENT. return 0.25; } return 1.0; } public function isCommentTransaction() { if ($this->hasComment()) { return true; } switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return true; } return false; } public function isInlineCommentTransaction() { return false; } public function getActionName() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht('Commented On'); case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: return pht('Changed Policy'); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht('Changed Subscribers'); case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { case HarbormasterBuildable::STATUS_PASSED: return pht('Build Passed'); case HarbormasterBuildable::STATUS_FAILED: return pht('Build Failed'); default: return pht('Build Status'); } default: return pht('Updated'); } } public function getMailTags() { return array(); } public function hasChangeDetails() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { return $field->getApplicationTransactionHasChangeDetails($this); } break; } return false; } public function renderChangeDetails(PhabricatorUser $viewer) { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { return $field->getApplicationTransactionChangeDetails($this, $viewer); } break; } return $this->renderTextCorpusChangeDetails( $viewer, $this->getOldValue(), $this->getNewValue()); } public function renderTextCorpusChangeDetails( PhabricatorUser $viewer, $old, $new) { require_celerity_resource('differential-changeset-view-css'); $view = id(new PhabricatorApplicationTransactionTextDiffDetailView()) ->setUser($viewer) ->setOldText($old) ->setNewText($new); return $view->render(); } public function attachTransactionGroup(array $group) { assert_instances_of($group, __CLASS__); $this->transactionGroup = $group; return $this; } public function getTransactionGroup() { return $this->transactionGroup; } /** * Should this transaction be visually grouped with an existing transaction * group? * * @param list List of transactions. * @return bool True to display in a group with the other transactions. */ public function shouldDisplayGroupWith(array $group) { $this_source = null; if ($this->getContentSource()) { $this_source = $this->getContentSource()->getSource(); } foreach ($group as $xaction) { // Don't group transactions by different authors. if ($xaction->getAuthorPHID() != $this->getAuthorPHID()) { return false; } // Don't group transactions for different objects. if ($xaction->getObjectPHID() != $this->getObjectPHID()) { return false; } // Don't group anything into a group which already has a comment. if ($xaction->isCommentTransaction()) { return false; } // Don't group transactions from different content sources. $other_source = null; if ($xaction->getContentSource()) { $other_source = $xaction->getContentSource()->getSource(); } if ($other_source != $this_source) { return false; } // Don't group transactions which happened more than 2 minutes apart. $apart = abs($xaction->getDateCreated() - $this->getDateCreated()); if ($apart > (60 * 2)) { return false; } } return true; } public function renderExtraInformationLink() { $herald_xscript_id = $this->getMetadataValue('herald:transcriptID'); if ($herald_xscript_id) { return phutil_tag( 'a', array( 'href' => '/herald/transcript/'.$herald_xscript_id.'/', ), pht('View Herald Transcript')); } return null; } public function renderAsTextForDoorkeeper( DoorkeeperFeedStoryPublisher $publisher, PhabricatorFeedStory $story, array $xactions) { $text = array(); $body = array(); foreach ($xactions as $xaction) { $xaction_body = $xaction->getBodyForMail(); if ($xaction_body !== null) { $body[] = $xaction_body; } if ($xaction->shouldHideForMail($xactions)) { continue; } $old_target = $xaction->getRenderingTarget(); $new_target = self::TARGET_TEXT; $xaction->setRenderingTarget($new_target); if ($publisher->getRenderWithImpliedContext()) { $text[] = $xaction->getTitle(); } else { $text[] = $xaction->getTitleForFeed(); } $xaction->setRenderingTarget($old_target); } $text = implode("\n", $text); $body = implode("\n\n", $body); return rtrim($text."\n\n".$body); } /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return ($viewer->getPHID() == $this->getAuthorPHID()); } public function describeAutomaticCapability($capability) { return pht( 'Transactions are visible to users that can see the object which was '. 'acted upon. Some transactions - in particular, comments - are '. 'editable by the transaction author.'); } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $comment_template = null; try { $comment_template = $this->getApplicationTransactionCommentObject(); } catch (Exception $ex) { // Continue; no comments for these transactions. } if ($comment_template) { $comments = $comment_template->loadAllWhere( 'transactionPHID = %s', $this->getPHID()); foreach ($comments as $comment) { $engine->destroyObject($comment); } } $this->delete(); $this->saveTransaction(); } } diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php index 1c48ad8abd..2251e9f592 100644 --- a/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfiguration.php @@ -1,263 +1,263 @@ setEngineKey($engine->getEngineKey()) ->attachEngine($engine) ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) ->setEditPolicy($edit_policy); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorEditEngineConfigurationPHIDType::TYPECONST); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'properties' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'engineKey' => 'text64', 'builtinKey' => 'text64?', 'name' => 'text255', 'isDisabled' => 'bool', 'isDefault' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_engine' => array( 'columns' => array('engineKey', 'builtinKey'), 'unique' => true, ), 'key_default' => array( 'columns' => array('engineKey', 'isDefault', 'isDisabled'), ), ), ) + parent::getConfiguration(); } public function getProperty($key, $default = null) { return idx($this->properties, $key, $default); } public function setProperty($key, $value) { $this->properties[$key] = $value; return $this; } public function attachEngine(PhabricatorEditEngine $engine) { $this->engine = $engine; return $this; } public function getEngine() { return $this->assertAttached($this->engine); } public function applyConfigurationToFields( PhabricatorEditEngine $engine, array $fields) { $fields = mpull($fields, null, 'getKey'); $values = $this->getProperty('defaults', array()); foreach ($fields as $key => $field) { if ($engine->getIsCreate()) { if (array_key_exists($key, $values)) { $field->readDefaultValueFromConfiguration($values[$key]); } } } $locks = $this->getFieldLocks(); foreach ($fields as $field) { $key = $field->getKey(); switch (idx($locks, $key)) { case self::LOCK_LOCKED: $field->setIsHidden(false); $field->setIsLocked(true); break; case self::LOCK_HIDDEN: $field->setIsHidden(true); $field->setIsLocked(false); break; case self::LOCK_VISIBLE: $field->setIsHidden(false); $field->setIsLocked(false); break; default: // If we don't have an explicit value, don't make any adjustments. break; } } $fields = $this->reorderFields($fields); $preamble = $this->getPreamble(); if (strlen($preamble)) { $fields = array( 'config.preamble' => id(new PhabricatorInstructionsEditField()) ->setKey('config.preamble') ->setIsReorderable(false) ->setIsDefaultable(false) ->setIsLockable(false) ->setValue($preamble), ) + $fields; } return $fields; } private function reorderFields(array $fields) { $keys = $this->getFieldOrder(); $fields = array_select_keys($fields, $keys) + $fields; return $fields; } public function getURI() { $engine_key = $this->getEngineKey(); $key = $this->getIdentifier(); return "/transactions/editengine/{$engine_key}/view/{$key}/"; } public function getIdentifier() { $key = $this->getID(); if (!$key) { $key = $this->getBuiltinKey(); } return $key; } public function getDisplayName() { $name = $this->getName(); if (strlen($name)) { return $name; } $builtin = $this->getBuiltinKey(); if ($builtin !== null) { return pht('Builtin Form "%s"', $builtin); } return pht('Untitled Form'); } public function getPreamble() { return $this->getProperty('preamble'); } public function setPreamble($preamble) { return $this->setProperty('preamble', $preamble); } public function setFieldOrder(array $field_order) { return $this->setProperty('order', $field_order); } public function getFieldOrder() { return $this->getProperty('order', array()); } public function setFieldLocks(array $field_locks) { return $this->setProperty('locks', $field_locks); } public function getFieldLocks() { return $this->getProperty('locks', array()); } public function getFieldDefault($key) { $defaults = $this->getProperty('defaults', array()); return idx($defaults, $key); } public function setFieldDefault($key, $value) { $defaults = $this->getProperty('defaults', array()); $defaults[$key] = $value; return $this->setProperty('defaults', $defaults); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } public function describeAutomaticCapability($capability) { return null; } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhabricatorEditEngineConfigurationEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PhabricatorEditEngineConfigurationTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } } diff --git a/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php b/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php index b79cb2a648..7d0794c3dd 100644 --- a/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorEditEngineConfigurationTransaction.php @@ -1,68 +1,90 @@ getAuthorPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); $type = $this->getTransactionType(); switch ($type) { case self::TYPE_NAME: if (strlen($old)) { return pht( '%s renamed this form from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); } else { return pht( '%s named this form "%s".', $this->renderHandleLink($author_phid), $new); } case self::TYPE_PREAMBLE: return pht( '%s updated the preamble for this form.', $this->renderHandleLink($author_phid)); case self::TYPE_ORDER: return pht( '%s reordered the fields in this form.', $this->renderHandleLink($author_phid)); case self::TYPE_DEFAULT: $key = $this->getMetadataValue('field.key'); return pht( '%s changed the default value for field "%s".', $this->renderHandleLink($author_phid), $key); case self::TYPE_LOCKS: return pht( '%s changed locked and hidden fields.', $this->renderHandleLink($author_phid)); + case self::TYPE_DEFAULTCREATE: + if ($new) { + return pht( + '%s added this form to the "Create" menu.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s removed this form from the "Create" menu.', + $this->renderHandleLink($author_phid)); + } + case self::TYPE_DISABLE: + if ($new) { + return pht( + '%s disabled this form.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s enabled this form.', + $this->renderHandleLink($author_phid)); + } } return parent::getTitle(); } } diff --git a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php index e8397e6a65..9200bdcf1f 100644 --- a/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php +++ b/src/applications/transactions/view/PhabricatorApplicationEditHTTPParameterHelpView.php @@ -1,258 +1,258 @@ object = $object; return $this; } public function getObject() { return $this->object; } public function setFields(array $fields) { $this->fields = $fields; return $this; } public function getFields() { return $this->fields; } public function render() { $object = $this->getObject(); $fields = $this->getFields(); $uri = 'https://your.install.com/application/edit/'; // Remove fields which do not expose an HTTP parameter type. $types = array(); foreach ($fields as $key => $field) { $type = $field->getHTTPParameterType(); if ($type === null) { unset($fields[$key]); continue; } $types[$type->getTypeName()] = $type; } $intro = pht(<<getLabel(), - $field->getKey(), + head($field->getAllReadValueFromRequestKeys()), $field->getHTTPParameterType()->getTypeName(), $field->getDescription(), ); } $main_table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Label'), pht('Key'), pht('Type'), pht('Description'), )) ->setColumnClasses( array( 'pri', null, null, 'wide', )); $aliases_text = pht(<<getAliases(); + $aliases = array_slice($field->getAllReadValueFromRequestKeys(), 1); if (!$aliases) { continue; } $rows[] = array( $field->getLabel(), $field->getKey(), implode(', ', $aliases), ); } $alias_table = id(new AphrontTableView($rows)) ->setNoDataString(pht('This object has no fields with aliases.')) ->setHeaders( array( pht('Label'), pht('Key'), pht('Aliases'), )) ->setColumnClasses( array( 'pri', null, 'wide', )); $select_text = pht(<<getOptions(); $label = $field->getLabel(); foreach ($options as $option_key => $option_value) { if (strlen($option_key)) { $option_display = $option_key; } else { $option_display = phutil_tag('em', array(), pht('')); } $rows[] = array( $label, $option_display, $option_value, ); $label = null; } } $select_table = id(new AphrontTableView($rows)) ->setNoDataString(pht('This object has no select fields.')) ->setHeaders( array( pht('Field'), pht('Value'), pht('Label'), )) ->setColumnClasses( array( 'pri', null, 'wide', )); $types_text = pht(<<setHTTPParameterTypes($types); return array( $this->renderInstructions($intro), $main_table, $this->renderInstructions($aliases_text), $alias_table, $this->renderInstructions($select_text), $select_table, $this->renderInstructions($types_text), $types_table, ); } protected function renderInstructions($corpus) { $viewer = $this->getUser(); return new PHUIRemarkupView($viewer, $corpus); } } diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php index cbc3739cfb..ed903e6ab0 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php @@ -1,235 +1,328 @@ objectPHID = $object_phid; return $this; } public function getObjectPHID() { return $this->objectPHID; } public function setShowPreview($show_preview) { $this->showPreview = $show_preview; return $this; } public function getShowPreview() { return $this->showPreview; } public function setRequestURI(PhutilURI $request_uri) { $this->requestURI = $request_uri; return $this; } public function getRequestURI() { return $this->requestURI; } + public function setCurrentVersion($current_version) { + $this->currentVersion = $current_version; + return $this; + } + + public function getCurrentVersion() { + return $this->currentVersion; + } + + public function setVersionedDraft( + PhabricatorVersionedDraft $versioned_draft) { + $this->versionedDraft = $versioned_draft; + return $this; + } + + public function getVersionedDraft() { + return $this->versionedDraft; + } + public function setDraft(PhabricatorDraft $draft) { $this->draft = $draft; return $this; } public function getDraft() { return $this->draft; } public function setSubmitButtonName($submit_button_name) { $this->submitButtonName = $submit_button_name; return $this; } public function getSubmitButtonName() { return $this->submitButtonName; } public function setAction($action) { $this->action = $action; return $this; } public function getAction() { return $this->action; } public function setHeaderText($text) { $this->headerText = $text; return $this; } + public function setEditTypes($edit_types) { + $this->editTypes = $edit_types; + return $this; + } + + public function getEditTypes() { + return $this->editTypes; + } + public function render() { $user = $this->getUser(); if (!$user->isLoggedIn()) { $uri = id(new PhutilURI('/login/')) ->setQueryParam('next', (string)$this->getRequestURI()); return id(new PHUIObjectBoxView()) ->setFlush(true) ->setHeaderText(pht('Add Comment')) ->appendChild( javelin_tag( 'a', array( 'class' => 'login-to-comment button', 'href' => $uri, ), pht('Login to Comment'))); } $data = array(); $comment = $this->renderCommentPanel(); if ($this->getShowPreview()) { $preview = $this->renderPreviewPanel(); } else { $preview = null; } Javelin::initBehavior( 'phabricator-transaction-comment-form', array( 'formID' => $this->getFormID(), 'timelineID' => $this->getPreviewTimelineID(), 'panelID' => $this->getPreviewPanelID(), 'statusID' => $this->getStatusID(), 'commentID' => $this->getCommentID(), 'loadingString' => pht('Loading Preview...'), 'savingString' => pht('Saving Draft...'), 'draftString' => pht('Saved Draft'), 'showPreview' => $this->getShowPreview(), 'actionURI' => $this->getAction(), )); $comment_box = id(new PHUIObjectBoxView()) ->setFlush(true) ->setHeaderText($this->headerText) ->appendChild($comment); return array($comment_box, $preview); } private function renderCommentPanel() { $status = phutil_tag( 'div', array( 'id' => $this->getStatusID(), ), ''); $draft_comment = ''; $draft_key = null; if ($this->getDraft()) { $draft_comment = $this->getDraft()->getDraft(); $draft_key = $this->getDraft()->getDraftKey(); } + $versioned_draft = $this->getVersionedDraft(); + if ($versioned_draft) { + $draft_comment = $versioned_draft->getProperty('temporary.comment', ''); + } + if (!$this->getObjectPHID()) { throw new PhutilInvalidStateException('setObjectPHID', 'render'); } - return id(new AphrontFormView()) + $version_key = PhabricatorVersionedDraft::KEY_VERSION; + $version_value = $this->getCurrentVersion(); + + $form = id(new AphrontFormView()) ->setUser($this->getUser()) ->addSigil('transaction-append') ->setWorkflow(true) ->setMetadata( array( 'objectPHID' => $this->getObjectPHID(), )) ->setAction($this->getAction()) ->setID($this->getFormID()) ->addHiddenInput('__draft__', $draft_key) + ->addHiddenInput($version_key, $version_value); + + $edit_types = $this->getEditTypes(); + if ($edit_types) { + + $action_map = array(); + foreach ($edit_types as $edit_type) { + $key = $edit_type->getEditType(); + $action_map[$key] = array( + 'key' => $key, + 'label' => $edit_type->getLabel(), + 'type' => $edit_type->getPHUIXControlType(), + 'spec' => $edit_type->getPHUIXControlSpecification(), + ); + } + + $options = array(); + $options['+'] = pht('Add Action...'); + foreach ($action_map as $key => $item) { + $options[$key] = $item['label']; + } + + $action_id = celerity_generate_unique_node_id(); + $input_id = celerity_generate_unique_node_id(); + + $form->appendChild( + phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => 'editengine.actions', + 'id' => $input_id, + ))); + + $form->appendChild( + id(new AphrontFormSelectControl()) + ->setLabel(pht('Actions')) + ->setID($action_id) + ->setOptions($options)); + + Javelin::initBehavior( + 'comment-actions', + array( + 'actionID' => $action_id, + 'inputID' => $input_id, + 'formID' => $this->getFormID(), + 'actions' => $action_map, + )); + } + + $form ->appendChild( id(new PhabricatorRemarkupControl()) ->setID($this->getCommentID()) ->setName('comment') ->setLabel(pht('Comment')) ->setUser($this->getUser()) ->setValue($draft_comment)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue($this->getSubmitButtonName())) ->appendChild( id(new AphrontFormMarkupControl()) ->setValue($status)); + + return $form; } private function renderPreviewPanel() { $preview = id(new PHUITimelineView()) ->setID($this->getPreviewTimelineID()); return phutil_tag( 'div', array( 'id' => $this->getPreviewPanelID(), 'style' => 'display: none', ), $preview); } private function getPreviewPanelID() { if (!$this->previewPanelID) { $this->previewPanelID = celerity_generate_unique_node_id(); } return $this->previewPanelID; } private function getPreviewTimelineID() { if (!$this->previewTimelineID) { $this->previewTimelineID = celerity_generate_unique_node_id(); } return $this->previewTimelineID; } public function setFormID($id) { $this->formID = $id; return $this; } private function getFormID() { if (!$this->formID) { $this->formID = celerity_generate_unique_node_id(); } return $this->formID; } private function getStatusID() { if (!$this->statusID) { $this->statusID = celerity_generate_unique_node_id(); } return $this->statusID; } private function getCommentID() { if (!$this->commentID) { $this->commentID = celerity_generate_unique_node_id(); } return $this->commentID; } } diff --git a/src/docs/user/configuration/advanced_configuration.diviner b/src/docs/user/configuration/advanced_configuration.diviner index 70a78fc902..17212ad4ee 100644 --- a/src/docs/user/configuration/advanced_configuration.diviner +++ b/src/docs/user/configuration/advanced_configuration.diviner @@ -1,116 +1,117 @@ @title Configuration User Guide: Advanced Configuration @group config Configuring Phabricator for multiple environments. = Overview = Phabricator reads configuration from multiple sources. This document explains the configuration stack and how to set up advanced configuration sources, which may be useful for deployments with multiple environments (e.g., development and production). This is a complicated topic for advanced users. You do not need to understand this topic to install Phabricator. = Configuration Sources = Phabricator supports the following configuration sources, from highest priority to lowest priority: - **Database**: Values are stored in the database and edited from the web UI by administrators. They have the highest priority and override other settings. - **Local**: Values are stored in `conf/local/config.json` and edited by running `bin/config`. - **Config Files**: Values are stored in a config file in `conf/`. The file to use is selected by writing to `conf/local/ENVIRONMENT`, or setting the `PHABRICATOR_ENV` configuration variable. See below for more information. - **Defaults**: Defaults hard-coded in the Phabricator source, which can not be edited. They have the lowest priority, and all other settings override them. Normally, you install and configure Phabricator by writing enough configuration into the local config to get access to the database configuration (e.g., the MySQL username, password, and hostname), then use the web interface to further configure Phabricator. = Configuration Files = Configuration files provide an alternative to database configuration, and may be appropriate if you want to deploy in multiple environments or create dynamic configuration. Configuration files are more complicated than database configuration, which is why they are not used by default. == Creating a Configuration File == To create a configuration file, first choose a name for the config (like "devserver" or "live"). For the purposes of this section, we'll assume you chose `exampleconfig`. Replace "exampleconfig" with whatever you actually chose in the examples below. First, write an `exampleconfig.conf.php` file here (rename it according to the name you chose): phabricator/conf/custom/exampleconfig.conf.php Its contents should look like this: 'examplevalue', ); For example, to specify MySQL credentials in your config file, you might create a config like this: 'localhost', 'mysql.user' => 'root', 'mysql.pass' => 'hunter2trustno1', ); == Selecting a Configuration File == To select a configuration file, write the name of the file (relative to `phabricator/conf/`) to `phabricator/conf/local/ENVIRONMENT`. For example, to select `phabricator/conf/custom/exampleconfig.conf.php`, you would write "custom/exampleconfig" to `phabrictor/conf/local/ENVIRONMENT`: phabricator/ $ echo custom/exampleconfig > conf/local/ENVIRONMENT phabricator/ $ cat conf/local/ENVIRONMENT custom/exampleconfig phabricator/ $ You can also set the environmental variable `PHABRICATOR_ENV`. This is more involved but may be easier in some deployment environments. Note that this needs to be set in your webserver environment, and also in your shell whenever you run a script: ``` # Shell export PHABRICATOR_ENV=custom/exampleconfig # Apache SetEnv PHABRICATOR_ENV custom/exampleconfig # nginx fastcgi_param PHABRICATOR_ENV "custom/exampleconfig"; # lighttpd setenv.add-environment = ( "PHABRICATOR_ENV" => "custom/exampleconfig", ) ``` -After creating and selecting a configuration file, restart your webserver. Any -configuration you set should take effect immediately, and your file should be -visible in the Config application when examining configuration. +After creating and selecting a configuration file, restart Phabricator (for +help, see @{article:Restarting Phabricator}). Any configuration you set should +take effect immediately, and your file should be visible in the Config +application when examining configuration. = Next Steps = Return to the @{article:Configuration Guide}. diff --git a/src/docs/user/configuration/custom_fields.diviner b/src/docs/user/configuration/custom_fields.diviner index 7ecfa98723..9bd1bb0e28 100644 --- a/src/docs/user/configuration/custom_fields.diviner +++ b/src/docs/user/configuration/custom_fields.diviner @@ -1,217 +1,219 @@ @title Configuring Custom Fields @group config How to add custom fields to applications which support them. = Overview = Several Phabricator applications allow the configuration of custom fields. These fields allow you to add more information to objects, and in some cases reorder or remove builtin fields. For example, you could use custom fields to add an "Estimated Hours" field to tasks, a "Lead" field to projects, or a "T-Shirt Size" field to users. These applications currently support custom fields: | Application | Support | |-------------|---------| | Differential | Partial Support | | Diffusion | Limited Support | | Maniphest | Full Support | | Owners | Full Support | | People | Full Support | | Projects | Full Support | Custom fields can appear in many interfaces and support search, editing, and other features. = Basic Custom Fields = To get started with custom fields, you can use configuration to select and reorder fields and to add new simple fields. If you don't need complicated display controls or sophisticated validation, these simple fields should cover most use cases. They allow you to attach things like strings, numbers, and dropdown menus to objects. The relevant configuration settings are: | Application | Add Fields | Select Fields | |-------------|------------|---------------| | Differential | Planned | `differential.fields` | | Diffusion | Planned | Planned | | Maniphest | `maniphest.custom-field-definitions` | `maniphest.fields` | | Owners | `owners.custom-field-definitions` | `owners.fields` | | People | `user.custom-field-definitions` | `user.fields` | | Projects | `projects.custom-field-definitions` | `projects.fields` | When adding fields, you'll specify a JSON blob like this (for example, as the value of `maniphest.custom-field-definitions`): { "mycompany:estimated-hours": { "name": "Estimated Hours", "type": "int", "caption": "Estimated number of hours this will take.", "required": true }, "mycompany:actual-hours": { "name": "Actual Hours", "type": "int", "caption": "Actual number of hours this took." }, "mycompany:company-jobs": { "name": "Job Role", "type": "select", "options": { "mycompany:engineer": "Engineer", "mycompany:nonengineer": "Other" } }, "mycompany:favorite-dinosaur": { "name": "Favorite Dinosaur", "type": "text" } } The fields will then appear in the other config option for the application (for example, in `maniphest.fields`) and you can enable, disable, or reorder them. For details on how to define a field, see the next section. = Custom Field Configuration = When defining custom fields using a configuration option like `maniphest.custom-field-definitions`, these options are available: - **name**: Display label for the field on the edit and detail interfaces. - **description**: Optional text shown when managing the field. - **type**: Field type. The supported field types are: - **int**: An integer, rendered as a text field. - **text**: A string, rendered as a text field. - **bool**: A boolean value, rendered as a checkbox. - **select**: Allows the user to select from several options as defined by **options**, rendered as a dropdown. - **remarkup**: A text area which allows the user to enter markup. - **users**: A typeahead which allows multiple users to be input. - **date**: A date/time picker. - **header**: Renders a visual divider which you can use to group fields. - **link**: A text field which allows the user to enter a link. - **edit**: Show this field on the application's edit interface (this defaults to `true`). - **view**: Show this field on the application's view interface (this defaults to `true`). (Note: Empty fields are not shown.) - **search**: Show this field on the application's search interface, allowing users to filter objects by the field value. - **fulltext**: Index the text in this field as part of the object's global full-text index. This allows users to find the object by searching for the field's contents using global search. - **caption**: A caption to display underneath the field (optional). - **required**: True if the user should be required to provide a value. - **options**: If type is set to **select**, provide options for the dropdown as a dictionary. - **default**: Default field value. - **strings**: Allows you to override specific strings based on the field type. See below. - **instructions**: Optional block of remarkup text which will appear above the control when rendered on the edit view. - **placeholder**: A placeholder text that appears on text boxes. Only supported in text, int and remarkup fields (optional). The `strings` value supports different strings per control type. They are: - **bool** - **edit.checkbox** Text for the edit interface, no default. - **view.yes** Text for the view interface, defaults to "Yes". - **search.default** Text for the search interface, defaults to "(Any)". - **search.require** Text for the search interface, defaults to "Require". Some applications have specific options which only work in that application. In **Maniphest**: - **copy**: When a user creates a task, the UI gives them an option to "Create Another Similar Task". Some fields from the original task are copied into the new task, while others are not; by default, fields are not copied. If you want this field to be copied, specify `true` for the `copy` property. Internally, Phabricator implements some additional custom field types and options. These are not intended for general use and are subject to abrupt change, but are documented here for completeness: - **Credentials**: Controls with type `credential` allow selection of a Passphrase credential which provides `credential.provides`, and creation of credentials of `credential.type`. - **Datasource**: Controls with type `datasource` allow selection of tokens from an arbitrary datasource, controlled with `datasource.class` and `datasource.parameters`. = Advanced Custom Fields = If you want custom fields to have advanced behaviors (sophisticated rendering, advanced validation, complicated controls, interaction with other systems, etc), you can write a custom field as an extension and add it to Phabricator. NOTE: This API is somewhat new and fairly large. You should expect that there will be occasional changes to the API requiring minor updates in your code. To do this, extend the appropriate `CustomField` class for the application you want to add a field to: | Application | Extend | |-------------|---------| | Differential | @{class:DifferentialCustomField} | | Diffusion | @{class:PhabricatorCommitCustomField} | | Maniphest | @{class:ManiphestCustomField} | | Owners | @{class:PhabricatorOwnersCustomField} | | People | @{class:PhabricatorUserCustomField} | | Projects | @{class:PhabricatorProjectCustomField} | The easiest way to get started is to drop your subclass into -`phabricator/src/extensions/`, which should make it immediately available in the -UI (if you use APC, you may need to restart your webserver). For example, this -is a simple template which adds a custom field to Maniphest: +`phabricator/src/extensions/`. If Phabricator is configured in development +mode, the class should immediately be available in the UI. If not, you can +restart Phabricator (for help, see @{article:Restarting Phabricator}). + +For example, this is a simple template which adds a custom field to Maniphest: name=ExampleManiphestCustomField.php 'color: #ff00ff', ), pht('It worked!')); } } Broadly, you can then add features by overriding more methods and implementing them. Many of the native fields are implemented on the custom field architecture, and it may be useful to look at them. For details on available integrations, see the base class for your application and @{class:PhabricatorCustomField}. = Next Steps = Continue by: - learning more about extending Phabricator with custom code in @{article@phabcontrib:Adding New Classes}; - or returning to the @{article: Configuration Guide}. diff --git a/src/docs/user/field/restarting.diviner b/src/docs/user/field/restarting.diviner new file mode 100644 index 0000000000..5bdd92da78 --- /dev/null +++ b/src/docs/user/field/restarting.diviner @@ -0,0 +1,116 @@ +@title Restarting Phabricator +@group fieldmanual + +Instructions on how to restart HTTP and PHP servers to reload configuration +changes in Phabricator. + + +Overview +======== + +Phabricator's setup and configuration instructions sometimes require you to +restart your server processes, particularly after making configuration changes. +This document explains how to restart them properly. + +In general, you need to restart both whatever is serving HTTP requests and +whatever is serving PHP requests. In some cases, these will be the same process +and handled with one restart command. In other cases, they will be two +different processes and handled with two different restart commands. + +{icon exclamation-circle color=blue} If you have two different processes (for +example, nginx and PHP-FPM), you need to issue two different restart commands. + +It's important to restart both your HTTP server and PHP server because each +server caches different configuration and settings. Restarting both servers +after making changes ensures you're running up-to-date configuration. + +To restart properly: + + - Identify which HTTP server you are running (for example, Apache or nginx). + - Identify which PHP server you are running (for example, mod_php or PHP-FPM). + - For each server, follow the instructions below to restart it. + - If the instructions tell you to do so, make sure you restart **both** + servers! + + +Quick Start +=========== + +**Apache**: If you use Apache with `mod_php`, you can just restart Apache. You +do not need to restart `mod_php` separately. See below for instructions on how +to do this if you aren't sure. This is a very common configuration. + +**nginx**: If you use nginx with PHP-FPM, you need to restart both nginx and +PHP-FPM. See below for instructions on how to do this if you aren't sure. This +is also a very common configuration. + +It's possible to use Apache or nginx in other configurations, or a different +webserver. Consult the documentation for your system or webserver if you aren't +sure how things are set up. + + +Universal Restart +================= + +If you are having trouble properly restarting processes on your server, try +turning it off and on again. This is effective on every known system and +under all configurations. + + +HTTP Server: Apache +=================== + +If you are using Apache with `mod_php`, you only need to restart Apache. + +If you are using Apache in FastCGI mode, you need to restart both Apache and +the FCGI server (usually PHP-FPM). This is very unusual. + +The correct method for restarting Apache depends on what system you are +running. Consult your system documentation for details. You might use a command +like one of these on your system, or a different command: + +``` +$ sudo apachectl restart +$ sudo /etc/init.d/httpd restart +$ sudo service apache2 restart +``` + + +HTTP Server: Nginx +================== + +If you're using Nginx with PHP-FPM, you need to restart both of them. This is +the most common Nginx configuration. + +The correct method for restarting Nginx depends on what system you are running. +Consult your system documentation for details. You might use a command like +one of these on your system, or a different command: + +``` +$ sudo /etc/init.d/nginx restart +$ sudo service nginx restart +``` + + +PHP Server: mod_php +=================== + +This is a builtin PHP server that runs within Apache. Restarting Apache (see +above) is sufficient to restart it. There is no separate restart command for +`mod_php`, so you don't need to do anything else. + + +PHP Server: PHP-FPM +=================== + +If you're using FastCGI mode, PHP-FPM is the most common PHP FastCGI server. +You'll need to restart it if you're running it. + +The correct method for restarting PHP-FPM depends on what system you are +running. Consult your system documentation for details. You might use a command +like one of these on your system, or a different command: + +``` +$ sudo /etc/init.d/php-fpm restart +$ sudo service php5-fpm reload +``` diff --git a/src/docs/user/upgrading.diviner b/src/docs/user/upgrading.diviner index 4291ded636..d9eca240ec 100644 --- a/src/docs/user/upgrading.diviner +++ b/src/docs/user/upgrading.diviner @@ -1,133 +1,134 @@ @title Upgrading Phabricator @group intro This document contains instructions for keeping Phabricator up to date. Overview ======== Phabricator is under active development, and new features are released continuously. Staying up to date will keep your install secure. We recommend installs upgrade regularly (every 1-2 weeks). Upgrades usually go smoothly and complete in a few minutes. If you put off upgrades for a long time, it may take a lot more work to bring things up to date if you want access to a useful new feature or an important security change. Staying On Top of Changes ========================= We release a weekly [[https://secure.phabricator.com/w/changelog | Changelog]], which describes changes in the previous week. You can look at the changelogs for an idea of what new features are available, upcoming changes, security information, and warnings about compatibility issues or migrations. Stable Branch ============= You can either run the `master` or `stable` branch of Phabricator. The `stable` branch is run in the [[ https://phacility.com | Phacility Cluster ]], and lags about a week behind `master`. The `stable` branch is a little more stable than `master`, and may be helpful if you administrate a larger install. We promote `master` to `stable` about once a week, then publish the changelog and deploy the cluster. During the week, major bugfixes are cherry-picked to the `stable` branch. The changelog lists the `stable` hashes for that week, as well as any fixes which were cherry-picked. To switch to `stable`, check the branch out in each working copy: phabricator/ $ git checkout stable arcanist/ $ git checkout stable libphutil/ $ git checkout stable You can now follow the upgrade process normally. Upgrade Process =============== -IMPORTANT: You **MUST** restart Apache or PHP-FPM after upgrading. +IMPORTANT: You **MUST** restart Phabricator after upgrading. For help, see +@{article:Restarting Phabricator}. IMPORTANT: You **MUST** upgrade `libphutil`, `arcanist` and `phabricator` at the same time. Phabricator runs on many different systems, with many different webservers. Given this diversity, we don't currently maintain a comprehensive upgrade script which can work on any system. However, the general steps are the same on every system: - Stop the webserver (including `php-fpm`, if you use it). - Stop the daemons, with `phabricator/bin/phd stop`. - Run `git pull` in `libphutil/`, `arcanist/` and `phabricator/`. - Run `phabricator/bin/storage upgrade`. - Start the daemons, with `phabricator/bin/phd start`. - Restart the webserver (and `php-fpm`, if you stopped it earlier). For some more discussion details, see @{article:Configuration Guide}. This template script roughly outlines the steps required to upgrade Phabricator. You'll need to adjust paths and commands a bit for your particular system: ```lang=sh #!/bin/sh set -e set -x # This is an example script for updating Phabricator, similar to the one used to # update . It might not work perfectly on your # system, but hopefully it should be easy to adapt. This script is not intended # to work without modifications. # NOTE: This script assumes you are running it from a directory which contains # arcanist/, libphutil/, and phabricator/. ROOT=`pwd` # You can hard-code the path here instead. ### UPDATE WORKING COPIES ###################################################### cd $ROOT/libphutil git pull cd $ROOT/arcanist git pull cd $ROOT/phabricator git pull ### CYCLE WEB SERVER AND DAEMONS ############################################### # Stop daemons. $ROOT/phabricator/bin/phd stop # If running the notification server, stop it. # $ROOT/phabricator/bin/aphlict stop # Stop the webserver (apache, nginx, lighttpd, etc). This command will differ # depending on which system and webserver you are running: replace it with an # appropriate command for your system. # NOTE: If you're running php-fpm, you should stop it here too. sudo /etc/init.d/httpd stop # Upgrade the database schema. You may want to add the "--force" flag to allow # this script to run noninteractively. $ROOT/phabricator/bin/storage upgrade # Restart the webserver. As above, this depends on your system and webserver. # NOTE: If you're running php-fpm, restart it here too. sudo /etc/init.d/httpd start # Restart daemons. $ROOT/phabricator/bin/phd start # If running the notification server, start it. # $ROOT/phabricator/bin/aphlict start ``` diff --git a/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditEngineExtension.php b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditEngineExtension.php new file mode 100644 index 0000000000..07618aab67 --- /dev/null +++ b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditEngineExtension.php @@ -0,0 +1,53 @@ +getViewer(); + + $field_list = PhabricatorCustomField::getObjectFields( + $object, + PhabricatorCustomField::ROLE_EDIT); + + $field_list->setViewer($viewer); + + if (!$engine->getIsCreate()) { + $field_list->readFieldsFromStorage($object); + } + + $results = array(); + foreach ($field_list->getFields() as $field) { + $edit_fields = $field->getEditEngineFields($engine); + foreach ($edit_fields as $edit_field) { + $results[] = $edit_field; + } + } + + return $results; + } + +} diff --git a/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php new file mode 100644 index 0000000000..e585853a13 --- /dev/null +++ b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditField.php @@ -0,0 +1,112 @@ +customField = $custom_field; + return $this; + } + + public function getCustomField() { + return $this->customField; + } + + public function setCustomFieldHTTPParameterType( + AphrontHTTPParameterType $type) { + $this->httpParameterType = $type; + return $this; + } + + public function getCustomFieldHTTPParameterType() { + return $this->httpParameterType; + } + + protected function buildControl() { + $field = $this->getCustomField(); + $clone = clone $field; + + $value = $this->getValue(); + $clone->setValueFromApplicationTransactions($value); + + return $clone->renderEditControl(array()); + } + + protected function newEditType() { + $type = id(new PhabricatorCustomFieldEditType()) + ->setCustomField($this->getCustomField()); + + $http_type = $this->getHTTPParameterType(); + if ($http_type) { + $type->setValueType($http_type->getTypeName()); + } + + return $type; + } + + public function getValueForTransaction() { + $value = $this->getValue(); + $field = $this->getCustomField(); + + // Avoid changing the value of the field itself, since later calls would + // incorrectly reflect the new value. + $clone = clone $field; + $clone->setValueFromApplicationTransactions($value); + return $clone->getNewValueForApplicationTransactions(); + } + + protected function getValueExistsInSubmit(AphrontRequest $request, $key) { + return true; + } + + protected function getValueFromSubmit(AphrontRequest $request, $key) { + $field = $this->getCustomField(); + + $clone = clone $field; + + $clone->readValueFromRequest($request); + return $clone->getNewValueForApplicationTransactions(); + } + + public function getConduitEditTypes() { + $field = $this->getCustomField(); + + if (!$field->shouldAppearInConduitTransactions()) { + return array(); + } + + return parent::getConduitEditTypes(); + } + + protected function newHTTPParameterType() { + $type = $this->getCustomFieldHTTPParameterType(); + + if ($type) { + return clone $type; + } + + return null; + } + + public function getAllReadValueFromRequestKeys() { + $keys = array(); + + // NOTE: This piece of complexity is so we can expose a reasonable key in + // the UI ("custom.x") instead of a crufty internal key ("std:app:x"). + // Perhaps we can simplify this some day. + + // In the parent, this is just getKey(), but that returns a cumbersome + // key in EditFields. Use the simpler edit type key instead. + $keys[] = $this->getEditTypeKey(); + + foreach ($this->getAliases() as $alias) { + $keys[] = $alias; + } + + return $keys; + } + +} diff --git a/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php new file mode 100644 index 0000000000..a247f50d9b --- /dev/null +++ b/src/infrastructure/customfield/editor/PhabricatorCustomFieldEditType.php @@ -0,0 +1,58 @@ +customField = $custom_field; + return $this; + } + + public function getCustomField() { + return $this->customField; + } + + public function setValueType($value_type) { + $this->valueType = $value_type; + return $this; + } + + public function getValueType() { + return $this->valueType; + } + + public function getMetadata() { + $field = $this->getCustomField(); + return parent::getMetadata() + $field->getApplicationTransactionMetadata(); + } + + public function getValueDescription() { + $field = $this->getCustomField(); + return $field->getFieldDescription(); + } + + public function generateTransactions( + PhabricatorApplicationTransaction $template, + array $spec) { + + $value = idx($spec, 'value'); + + $xaction = $this->newTransaction($template) + ->setNewValue($value); + + $custom_type = PhabricatorTransactions::TYPE_CUSTOMFIELD; + if ($xaction->getTransactionType() == $custom_type) { + $field = $this->getCustomField(); + + $xaction + ->setOldValue($field->getOldValueForApplicationTransactions()) + ->setMetadataValue('customfield:key', $field->getFieldKey()); + } + + return array($xaction); + } + +} diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php index 64639ed34f..f3bf787e72 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -1,1360 +1,1409 @@ getCustomFields(); } catch (PhabricatorDataNotAttachedException $ex) { $attachment = new PhabricatorCustomFieldAttachment(); $object->attachCustomFields($attachment); } try { $field_list = $attachment->getCustomFieldList($role); } catch (PhabricatorCustomFieldNotAttachedException $ex) { $base_class = $object->getCustomFieldBaseClass(); $spec = $object->getCustomFieldSpecificationForRole($role); if (!is_array($spec)) { throw new Exception( pht( "Expected an array from %s for object of class '%s'.", 'getCustomFieldSpecificationForRole()', get_class($object))); } $fields = self::buildFieldList( $base_class, $spec, $object); foreach ($fields as $key => $field) { if (!$field->shouldEnableForRole($role)) { unset($fields[$key]); } } foreach ($fields as $field) { $field->setObject($object); } $field_list = new PhabricatorCustomFieldList($fields); $attachment->addCustomFieldList($role, $field_list); } return $field_list; } /** * @task apps */ public static function getObjectField( PhabricatorCustomFieldInterface $object, $role, $field_key) { $fields = self::getObjectFields($object, $role)->getFields(); return idx($fields, $field_key); } /** * @task apps */ public static function buildFieldList( $base_class, array $spec, $object, array $options = array()) { PhutilTypeSpec::checkMap( $options, array( 'withDisabled' => 'optional bool', )); $field_objects = id(new PhutilSymbolLoader()) ->setAncestorClass($base_class) ->loadObjects(); $fields = array(); $from_map = array(); foreach ($field_objects as $field_object) { $current_class = get_class($field_object); foreach ($field_object->createFields($object) as $field) { $key = $field->getFieldKey(); if (isset($fields[$key])) { throw new Exception( pht( "Both '%s' and '%s' define a custom field with ". "field key '%s'. Field keys must be unique.", $from_map[$key], $current_class, $key)); } $from_map[$key] = $current_class; $fields[$key] = $field; } } foreach ($fields as $key => $field) { if (!$field->isFieldEnabled()) { unset($fields[$key]); } } $fields = array_select_keys($fields, array_keys($spec)) + $fields; if (empty($options['withDisabled'])) { foreach ($fields as $key => $field) { $config = idx($spec, $key, array()) + array( 'disabled' => $field->shouldDisableByDefault(), ); if (!empty($config['disabled'])) { if ($field->canDisableField()) { unset($fields[$key]); } } } } return $fields; } /* -( Core Properties and Field Identity )--------------------------------- */ /** * Return a key which uniquely identifies this field, like * "mycompany:dinosaur:count". Normally you should provide some level of * namespacing to prevent collisions. * * @return string String which uniquely identifies this field. * @task core */ public function getFieldKey() { if ($this->proxy) { return $this->proxy->getFieldKey(); } throw new PhabricatorCustomFieldImplementationIncompleteException( $this, $field_key_is_incomplete = true); } /** * Return a human-readable field name. * * @return string Human readable field name. * @task core */ public function getFieldName() { if ($this->proxy) { return $this->proxy->getFieldName(); } return $this->getFieldKey(); } /** * Return a short, human-readable description of the field's behavior. This * provides more context to administrators when they are customizing fields. * * @return string|null Optional human-readable description. * @task core */ public function getFieldDescription() { if ($this->proxy) { return $this->proxy->getFieldDescription(); } return null; } /** * Most field implementations are unique, in that one class corresponds to * one field. However, some field implementations are general and a single * implementation may drive several fields. * * For general implementations, the general field implementation can return * multiple field instances here. * * @param object The object to create fields for. * @return list List of fields. * @task core */ public function createFields($object) { return array($this); } /** * You can return `false` here if the field should not be enabled for any * role. For example, it might depend on something (like an application or * library) which isn't installed, or might have some global configuration * which allows it to be disabled. * * @return bool False to completely disable this field for all roles. * @task core */ public function isFieldEnabled() { if ($this->proxy) { return $this->proxy->isFieldEnabled(); } return true; } /** * Low level selector for field availability. Fields can appear in different * roles (like an edit view, a list view, etc.), but not every field needs * to appear everywhere. Fields that are disabled in a role won't appear in * that context within applications. * * Normally, you do not need to override this method. Instead, override the * methods specific to roles you want to enable. For example, implement * @{method:shouldUseStorage()} to activate the `'storage'` role. * * @return bool True to enable the field for the given role. * @task core */ public function shouldEnableForRole($role) { // NOTE: All of these calls proxy individually, so we don't need to // proxy this call as a whole. switch ($role) { case self::ROLE_APPLICATIONTRANSACTIONS: return $this->shouldAppearInApplicationTransactions(); case self::ROLE_APPLICATIONSEARCH: return $this->shouldAppearInApplicationSearch(); case self::ROLE_STORAGE: return $this->shouldUseStorage(); case self::ROLE_EDIT: return $this->shouldAppearInEditView(); case self::ROLE_VIEW: return $this->shouldAppearInPropertyView(); case self::ROLE_LIST: return $this->shouldAppearInListView(); case self::ROLE_GLOBALSEARCH: return $this->shouldAppearInGlobalSearch(); case self::ROLE_CONDUIT: return $this->shouldAppearInConduitDictionary(); case self::ROLE_TRANSACTIONMAIL: return $this->shouldAppearInTransactionMail(); case self::ROLE_HERALD: return $this->shouldAppearInHerald(); case self::ROLE_DEFAULT: return true; default: throw new Exception(pht("Unknown field role '%s'!", $role)); } } /** * Allow administrators to disable this field. Most fields should allow this, * but some are fundamental to the behavior of the application and can be * locked down to avoid chaos, disorder, and the decline of civilization. * * @return bool False to prevent this field from being disabled through * configuration. * @task core */ public function canDisableField() { return true; } public function shouldDisableByDefault() { return false; } /** * Return an index string which uniquely identifies this field. * * @return string Index string which uniquely identifies this field. * @task core */ final public function getFieldIndex() { return PhabricatorHash::digestForIndex($this->getFieldKey()); } /* -( Field Proxies )------------------------------------------------------ */ /** * Proxies allow a field to use some other field's implementation for most * of their behavior while still subclassing an application field. When a * proxy is set for a field with @{method:setProxy}, all of its methods will * call through to the proxy by default. * * This is most commonly used to implement configuration-driven custom fields * using @{class:PhabricatorStandardCustomField}. * * This method must be overridden to return `true` before a field can accept * proxies. * * @return bool True if you can @{method:setProxy} this field. * @task proxy */ public function canSetProxy() { if ($this instanceof PhabricatorStandardCustomFieldInterface) { return true; } return false; } /** * Set the proxy implementation for this field. See @{method:canSetProxy} for * discussion of field proxies. * * @param PhabricatorCustomField Field implementation. * @return this */ final public function setProxy(PhabricatorCustomField $proxy) { if (!$this->canSetProxy()) { throw new PhabricatorCustomFieldNotProxyException($this); } $this->proxy = $proxy; return $this; } /** * Get the field's proxy implementation, if any. For discussion, see * @{method:canSetProxy}. * * @return PhabricatorCustomField|null Proxy field, if one is set. */ final public function getProxy() { return $this->proxy; } /* -( Contextual Data )---------------------------------------------------- */ /** * Sets the object this field belongs to. * * @param PhabricatorCustomFieldInterface The object this field belongs to. * @return this * @task context */ final public function setObject(PhabricatorCustomFieldInterface $object) { if ($this->proxy) { $this->proxy->setObject($object); return $this; } $this->object = $object; $this->didSetObject($object); return $this; } /** * Read object data into local field storage, if applicable. * * @param PhabricatorCustomFieldInterface The object this field belongs to. * @return this * @task context */ public function readValueFromObject(PhabricatorCustomFieldInterface $object) { if ($this->proxy) { $this->proxy->readValueFromObject($object); } return $this; } /** * Get the object this field belongs to. * * @return PhabricatorCustomFieldInterface The object this field belongs to. * @task context */ final public function getObject() { if ($this->proxy) { return $this->proxy->getObject(); } return $this->object; } /** * This is a hook, primarily for subclasses to load object data. * * @return PhabricatorCustomFieldInterface The object this field belongs to. * @return void */ protected function didSetObject(PhabricatorCustomFieldInterface $object) { return; } /** * @task context */ final public function setViewer(PhabricatorUser $viewer) { if ($this->proxy) { $this->proxy->setViewer($viewer); return $this; } $this->viewer = $viewer; return $this; } /** * @task context */ final public function getViewer() { if ($this->proxy) { return $this->proxy->getViewer(); } return $this->viewer; } /** * @task context */ final protected function requireViewer() { if ($this->proxy) { return $this->proxy->requireViewer(); } if (!$this->viewer) { throw new PhabricatorCustomFieldDataNotAvailableException($this); } return $this->viewer; } /* -( Rendering Utilities )------------------------------------------------ */ /** * @task render */ protected function renderHandleList(array $handles) { if (!$handles) { return null; } $out = array(); foreach ($handles as $handle) { $out[] = $handle->renderLink(); } return phutil_implode_html(phutil_tag('br'), $out); } /* -( Storage )------------------------------------------------------------ */ /** * Return true to use field storage. * * Fields which can be edited by the user will most commonly use storage, * while some other types of fields (for instance, those which just display * information in some stylized way) may not. Many builtin fields do not use * storage because their data is available on the object itself. * * If you implement this, you must also implement @{method:getValueForStorage} * and @{method:setValueFromStorage}. * * @return bool True to use storage. * @task storage */ public function shouldUseStorage() { if ($this->proxy) { return $this->proxy->shouldUseStorage(); } return false; } /** * Return a new, empty storage object. This should be a subclass of * @{class:PhabricatorCustomFieldStorage} which is bound to the application's * database. * * @return PhabricatorCustomFieldStorage New empty storage object. * @task storage */ public function newStorageObject() { // NOTE: This intentionally isn't proxied, to avoid call cycles. throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /** * Return a serialized representation of the field value, appropriate for * storing in auxiliary field storage. You must implement this method if * you implement @{method:shouldUseStorage}. * * If the field value is a scalar, it can be returned unmodiifed. If not, * it should be serialized (for example, using JSON). * * @return string Serialized field value. * @task storage */ public function getValueForStorage() { if ($this->proxy) { return $this->proxy->getValueForStorage(); } throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /** * Set the field's value given a serialized storage value. This is called * when the field is loaded; if no data is available, the value will be * null. You must implement this method if you implement * @{method:shouldUseStorage}. * * Usually, the value can be loaded directly. If it isn't a scalar, you'll * need to undo whatever serialization you applied in * @{method:getValueForStorage}. * * @param string|null Serialized field representation (from * @{method:getValueForStorage}) or null if no value has * ever been stored. * @return this * @task storage */ public function setValueFromStorage($value) { if ($this->proxy) { return $this->proxy->setValueFromStorage($value); } throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /* -( ApplicationSearch )-------------------------------------------------- */ /** * Appearing in ApplicationSearch allows a field to be indexed and searched * for. * * @return bool True to appear in ApplicationSearch. * @task appsearch */ public function shouldAppearInApplicationSearch() { if ($this->proxy) { return $this->proxy->shouldAppearInApplicationSearch(); } return false; } /** * Return one or more indexes which this field can meaningfully query against * to implement ApplicationSearch. * * Normally, you should build these using @{method:newStringIndex} and * @{method:newNumericIndex}. For example, if a field holds a numeric value * it might return a single numeric index: * * return array($this->newNumericIndex($this->getValue())); * * If a field holds a more complex value (like a list of users), it might * return several string indexes: * * $indexes = array(); * foreach ($this->getValue() as $phid) { * $indexes[] = $this->newStringIndex($phid); * } * return $indexes; * * @return list List of indexes. * @task appsearch */ public function buildFieldIndexes() { if ($this->proxy) { return $this->proxy->buildFieldIndexes(); } return array(); } /** * Return an index against which this field can be meaningfully ordered * against to implement ApplicationSearch. * * This should be a single index, normally built using * @{method:newStringIndex} and @{method:newNumericIndex}. * * The value of the index is not used. * * Return null from this method if the field can not be ordered. * * @return PhabricatorCustomFieldIndexStorage A single index to order by. * @task appsearch */ public function buildOrderIndex() { if ($this->proxy) { return $this->proxy->buildOrderIndex(); } return null; } /** * Build a new empty storage object for storing string indexes. Normally, * this should be a concrete subclass of * @{class:PhabricatorCustomFieldStringIndexStorage}. * * @return PhabricatorCustomFieldStringIndexStorage Storage object. * @task appsearch */ protected function newStringIndexStorage() { // NOTE: This intentionally isn't proxied, to avoid call cycles. throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /** * Build a new empty storage object for storing string indexes. Normally, * this should be a concrete subclass of * @{class:PhabricatorCustomFieldStringIndexStorage}. * * @return PhabricatorCustomFieldStringIndexStorage Storage object. * @task appsearch */ protected function newNumericIndexStorage() { // NOTE: This intentionally isn't proxied, to avoid call cycles. throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /** * Build and populate storage for a string index. * * @param string String to index. * @return PhabricatorCustomFieldStringIndexStorage Populated storage. * @task appsearch */ protected function newStringIndex($value) { if ($this->proxy) { return $this->proxy->newStringIndex(); } $key = $this->getFieldIndex(); return $this->newStringIndexStorage() ->setIndexKey($key) ->setIndexValue($value); } /** * Build and populate storage for a numeric index. * * @param string Numeric value to index. * @return PhabricatorCustomFieldNumericIndexStorage Populated storage. * @task appsearch */ protected function newNumericIndex($value) { if ($this->proxy) { return $this->proxy->newNumericIndex(); } $key = $this->getFieldIndex(); return $this->newNumericIndexStorage() ->setIndexKey($key) ->setIndexValue($value); } /** * Read a query value from a request, for storage in a saved query. Normally, * this method should, e.g., read a string out of the request. * * @param PhabricatorApplicationSearchEngine Engine building the query. * @param AphrontRequest Request to read from. * @return wild * @task appsearch */ public function readApplicationSearchValueFromRequest( PhabricatorApplicationSearchEngine $engine, AphrontRequest $request) { if ($this->proxy) { return $this->proxy->readApplicationSearchValueFromRequest( $engine, $request); } throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /** * Constrain a query, given a field value. Generally, this method should * use `with...()` methods to apply filters or other constraints to the * query. * * @param PhabricatorApplicationSearchEngine Engine executing the query. * @param PhabricatorCursorPagedPolicyAwareQuery Query to constrain. * @param wild Constraint provided by the user. * @return void * @task appsearch */ public function applyApplicationSearchConstraintToQuery( PhabricatorApplicationSearchEngine $engine, PhabricatorCursorPagedPolicyAwareQuery $query, $value) { if ($this->proxy) { return $this->proxy->applyApplicationSearchConstraintToQuery( $engine, $query, $value); } throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /** * Append search controls to the interface. * * @param PhabricatorApplicationSearchEngine Engine constructing the form. * @param AphrontFormView The form to update. * @param wild Value from the saved query. * @return void * @task appsearch */ public function appendToApplicationSearchForm( PhabricatorApplicationSearchEngine $engine, AphrontFormView $form, $value) { if ($this->proxy) { return $this->proxy->appendToApplicationSearchForm( $engine, $form, $value); } throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /* -( ApplicationTransactions )-------------------------------------------- */ /** * Appearing in ApplicationTrasactions allows a field to be edited using * standard workflows. * * @return bool True to appear in ApplicationTransactions. * @task appxaction */ public function shouldAppearInApplicationTransactions() { if ($this->proxy) { return $this->proxy->shouldAppearInApplicationTransactions(); } return false; } /** * @task appxaction */ public function getApplicationTransactionType() { if ($this->proxy) { return $this->proxy->getApplicationTransactionType(); } return PhabricatorTransactions::TYPE_CUSTOMFIELD; } /** * @task appxaction */ public function getApplicationTransactionMetadata() { if ($this->proxy) { return $this->proxy->getApplicationTransactionMetadata(); } return array(); } /** * @task appxaction */ public function getOldValueForApplicationTransactions() { if ($this->proxy) { return $this->proxy->getOldValueForApplicationTransactions(); } return $this->getValueForStorage(); } /** * @task appxaction */ public function getNewValueForApplicationTransactions() { if ($this->proxy) { return $this->proxy->getNewValueForApplicationTransactions(); } return $this->getValueForStorage(); } /** * @task appxaction */ public function setValueFromApplicationTransactions($value) { if ($this->proxy) { return $this->proxy->setValueFromApplicationTransactions($value); } return $this->setValueFromStorage($value); } /** * @task appxaction */ public function getNewValueFromApplicationTransactions( PhabricatorApplicationTransaction $xaction) { if ($this->proxy) { return $this->proxy->getNewValueFromApplicationTransactions($xaction); } return $xaction->getNewValue(); } /** * @task appxaction */ public function getApplicationTransactionHasEffect( PhabricatorApplicationTransaction $xaction) { if ($this->proxy) { return $this->proxy->getApplicationTransactionHasEffect($xaction); } return ($xaction->getOldValue() !== $xaction->getNewValue()); } /** * @task appxaction */ public function applyApplicationTransactionInternalEffects( PhabricatorApplicationTransaction $xaction) { if ($this->proxy) { return $this->proxy->applyApplicationTransactionInternalEffects($xaction); } return; } /** * @task appxaction */ public function getApplicationTransactionRemarkupBlocks( PhabricatorApplicationTransaction $xaction) { if ($this->proxy) { return $this->proxy->getApplicationTransactionRemarkupBlocks($xaction); } return array(); } /** * @task appxaction */ public function applyApplicationTransactionExternalEffects( PhabricatorApplicationTransaction $xaction) { if ($this->proxy) { return $this->proxy->applyApplicationTransactionExternalEffects($xaction); } if (!$this->shouldEnableForRole(self::ROLE_STORAGE)) { return; } $this->setValueFromApplicationTransactions($xaction->getNewValue()); $value = $this->getValueForStorage(); $table = $this->newStorageObject(); $conn_w = $table->establishConnection('w'); if ($value === null) { queryfx( $conn_w, 'DELETE FROM %T WHERE objectPHID = %s AND fieldIndex = %s', $table->getTableName(), $this->getObject()->getPHID(), $this->getFieldIndex()); } else { queryfx( $conn_w, 'INSERT INTO %T (objectPHID, fieldIndex, fieldValue) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE fieldValue = VALUES(fieldValue)', $table->getTableName(), $this->getObject()->getPHID(), $this->getFieldIndex(), $value); } return; } /** * Validate transactions for an object. This allows you to raise an error * when a transaction would set a field to an invalid value, or when a field * is required but no transactions provide value. * * @param PhabricatorLiskDAO Editor applying the transactions. * @param string Transaction type. This type is always * `PhabricatorTransactions::TYPE_CUSTOMFIELD`, it is provided for * convenience when constructing exceptions. * @param list Transactions being applied, * which may be empty if this field is not being edited. * @return list Validation * errors. * * @task appxaction */ public function validateApplicationTransactions( PhabricatorApplicationTransactionEditor $editor, $type, array $xactions) { if ($this->proxy) { return $this->proxy->validateApplicationTransactions( $editor, $type, $xactions); } return array(); } public function getApplicationTransactionTitle( PhabricatorApplicationTransaction $xaction) { if ($this->proxy) { return $this->proxy->getApplicationTransactionTitle( $xaction); } $author_phid = $xaction->getAuthorPHID(); return pht( '%s updated this object.', $xaction->renderHandleLink($author_phid)); } public function getApplicationTransactionTitleForFeed( PhabricatorApplicationTransaction $xaction) { if ($this->proxy) { return $this->proxy->getApplicationTransactionTitleForFeed( $xaction); } $author_phid = $xaction->getAuthorPHID(); $object_phid = $xaction->getObjectPHID(); return pht( '%s updated %s.', $xaction->renderHandleLink($author_phid), $xaction->renderHandleLink($object_phid)); } public function getApplicationTransactionHasChangeDetails( PhabricatorApplicationTransaction $xaction) { if ($this->proxy) { return $this->proxy->getApplicationTransactionHasChangeDetails( $xaction); } return false; } public function getApplicationTransactionChangeDetails( PhabricatorApplicationTransaction $xaction, PhabricatorUser $viewer) { if ($this->proxy) { return $this->proxy->getApplicationTransactionChangeDetails( $xaction, $viewer); } return null; } public function getApplicationTransactionRequiredHandlePHIDs( PhabricatorApplicationTransaction $xaction) { if ($this->proxy) { return $this->proxy->getApplicationTransactionRequiredHandlePHIDs( $xaction); } return array(); } public function shouldHideInApplicationTransactions( PhabricatorApplicationTransaction $xaction) { if ($this->proxy) { return $this->proxy->shouldHideInApplicationTransactions($xaction); } return false; } /* -( Transaction Mail )--------------------------------------------------- */ /** * @task xactionmail */ public function shouldAppearInTransactionMail() { if ($this->proxy) { return $this->proxy->shouldAppearInTransactionMail(); } return false; } /** * @task xactionmail */ public function updateTransactionMailBody( PhabricatorMetaMTAMailBody $body, PhabricatorApplicationTransactionEditor $editor, array $xactions) { if ($this->proxy) { return $this->proxy->updateTransactionMailBody($body, $editor, $xactions); } return; } /* -( Edit View )---------------------------------------------------------- */ + public function getEditEngineFields(PhabricatorEditEngine $engine) { + $field = $this->newStandardEditField($engine); + + return array( + $field, + ); + } + + protected function newEditField() { + $field = id(new PhabricatorCustomFieldEditField()) + ->setCustomField($this); + + $http_type = $this->getHTTPParameterType(); + if ($http_type) { + $field->setCustomFieldHTTPParameterType($http_type); + } + + return $field; + } + + protected function newStandardEditField() { + if ($this->proxy) { + return $this->proxy->newStandardEditField(); + } + + return $this->newEditField() + ->setKey($this->getFieldKey()) + ->setEditTypeKey('custom.'.$this->getFieldKey()) + ->setLabel($this->getFieldName()) + ->setDescription($this->getFieldDescription()) + ->setTransactionType($this->getApplicationTransactionType()) + ->setValue($this->getNewValueForApplicationTransactions()); + } + + protected function getHTTPParameterType() { + if ($this->proxy) { + return $this->proxy->getHTTPParameterType(); + } + return null; + } + /** * @task edit */ public function shouldAppearInEditView() { if ($this->proxy) { return $this->proxy->shouldAppearInEditView(); } return false; } /** * @task edit */ public function readValueFromRequest(AphrontRequest $request) { if ($this->proxy) { return $this->proxy->readValueFromRequest($request); } throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /** * @task edit */ public function getRequiredHandlePHIDsForEdit() { if ($this->proxy) { return $this->proxy->getRequiredHandlePHIDsForEdit(); } return array(); } /** * @task edit */ public function getInstructionsForEdit() { if ($this->proxy) { return $this->proxy->getInstructionsForEdit(); } return null; } /** * @task edit */ public function renderEditControl(array $handles) { if ($this->proxy) { return $this->proxy->renderEditControl($handles); } throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /* -( Property View )------------------------------------------------------ */ /** * @task view */ public function shouldAppearInPropertyView() { if ($this->proxy) { return $this->proxy->shouldAppearInPropertyView(); } return false; } /** * @task view */ public function renderPropertyViewLabel() { if ($this->proxy) { return $this->proxy->renderPropertyViewLabel(); } return $this->getFieldName(); } /** * @task view */ public function renderPropertyViewValue(array $handles) { if ($this->proxy) { return $this->proxy->renderPropertyViewValue($handles); } throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /** * @task view */ public function getStyleForPropertyView() { if ($this->proxy) { return $this->proxy->getStyleForPropertyView(); } return 'property'; } /** * @task view */ public function getIconForPropertyView() { if ($this->proxy) { return $this->proxy->getIconForPropertyView(); } return null; } /** * @task view */ public function getRequiredHandlePHIDsForPropertyView() { if ($this->proxy) { return $this->proxy->getRequiredHandlePHIDsForPropertyView(); } return array(); } /* -( List View )---------------------------------------------------------- */ /** * @task list */ public function shouldAppearInListView() { if ($this->proxy) { return $this->proxy->shouldAppearInListView(); } return false; } /** * @task list */ public function renderOnListItem(PHUIObjectItemView $view) { if ($this->proxy) { return $this->proxy->renderOnListItem($view); } throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /* -( Global Search )------------------------------------------------------ */ /** * @task globalsearch */ public function shouldAppearInGlobalSearch() { if ($this->proxy) { return $this->proxy->shouldAppearInGlobalSearch(); } return false; } /** * @task globalsearch */ public function updateAbstractDocument( PhabricatorSearchAbstractDocument $document) { if ($this->proxy) { return $this->proxy->updateAbstractDocument($document); } return $document; } /* -( Conduit )------------------------------------------------------------ */ /** * @task conduit */ public function shouldAppearInConduitDictionary() { if ($this->proxy) { return $this->proxy->shouldAppearInConduitDictionary(); } return false; } /** * @task conduit */ public function getConduitDictionaryValue() { if ($this->proxy) { return $this->proxy->getConduitDictionaryValue(); } throw new PhabricatorCustomFieldImplementationIncompleteException($this); } + public function shouldAppearInConduitTransactions() { + if ($this->proxy) { + return $this->proxy->shouldAppearInConduitDictionary(); + } + return false; + } + + /* -( Herald )------------------------------------------------------------- */ /** * Return `true` to make this field available in Herald. * * @return bool True to expose the field in Herald. * @task herald */ public function shouldAppearInHerald() { if ($this->proxy) { return $this->proxy->shouldAppearInHerald(); } return false; } /** * Get the name of the field in Herald. By default, this uses the * normal field name. * * @return string Herald field name. * @task herald */ public function getHeraldFieldName() { if ($this->proxy) { return $this->proxy->getHeraldFieldName(); } return $this->getFieldName(); } /** * Get the field value for evaluation by Herald. * * @return wild Field value. * @task herald */ public function getHeraldFieldValue() { if ($this->proxy) { return $this->proxy->getHeraldFieldValue(); } throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /** * Get the available conditions for this field in Herald. * * @return list List of Herald condition constants. * @task herald */ public function getHeraldFieldConditions() { if ($this->proxy) { return $this->proxy->getHeraldFieldConditions(); } throw new PhabricatorCustomFieldImplementationIncompleteException($this); } /** * Get the Herald value type for the given condition. * * @param const Herald condition constant. * @return const|null Herald value type, or null to use the default. * @task herald */ public function getHeraldFieldValueType($condition) { if ($this->proxy) { return $this->proxy->getHeraldFieldValueType($condition); } return null; } } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php index ac3e163f6a..1e081f5cfe 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php @@ -1,429 +1,440 @@ setAncestorClass(__CLASS__) ->setUniqueMethod('getFieldType') ->execute(); $fields = array(); foreach ($config as $key => $value) { $type = idx($value, 'type', 'text'); if (empty($types[$type])) { // TODO: We should have better typechecking somewhere, and then make // this more serious. continue; } $namespace = $template->getStandardCustomFieldNamespace(); $full_key = "std:{$namespace}:{$key}"; $template = clone $template; $standard = id(clone $types[$type]) ->setRawStandardFieldKey($key) ->setFieldKey($full_key) ->setFieldConfig($value) ->setApplicationField($template); $field = $template->setProxy($standard); $fields[] = $field; } return $fields; } public function setApplicationField( PhabricatorStandardCustomFieldInterface $application_field) { $this->applicationField = $application_field; return $this; } public function getApplicationField() { return $this->applicationField; } public function setFieldName($name) { $this->fieldName = $name; return $this; } public function getFieldValue() { return $this->fieldValue; } public function setFieldValue($value) { $this->fieldValue = $value; return $this; } public function setCaption($caption) { $this->caption = $caption; return $this; } public function getCaption() { return $this->caption; } public function setFieldDescription($description) { $this->fieldDescription = $description; return $this; } public function setFieldConfig(array $config) { foreach ($config as $key => $value) { switch ($key) { case 'name': $this->setFieldName($value); break; case 'description': $this->setFieldDescription($value); break; case 'strings': $this->setStrings($value); break; case 'caption': $this->setCaption($value); break; case 'required': if ($value) { $this->setRequired($value); $this->setFieldError(true); } break; case 'default': $this->setFieldValue($value); break; case 'type': // We set this earlier on. break; } } $this->fieldConfig = $config; return $this; } public function getFieldConfigValue($key, $default = null) { return idx($this->fieldConfig, $key, $default); } public function setFieldError($field_error) { $this->fieldError = $field_error; return $this; } public function getFieldError() { return $this->fieldError; } public function setRequired($required) { $this->required = $required; return $this; } public function getRequired() { return $this->required; } public function setRawStandardFieldKey($raw_key) { $this->rawKey = $raw_key; return $this; } public function getRawStandardFieldKey() { return $this->rawKey; } /* -( PhabricatorCustomField )--------------------------------------------- */ public function setFieldKey($field_key) { $this->fieldKey = $field_key; return $this; } public function getFieldKey() { return $this->fieldKey; } public function getFieldName() { return coalesce($this->fieldName, parent::getFieldName()); } public function getFieldDescription() { return coalesce($this->fieldDescription, parent::getFieldDescription()); } public function setStrings(array $strings) { $this->strings = $strings; return; } public function getString($key, $default = null) { return idx($this->strings, $key, $default); } public function shouldUseStorage() { try { $object = $this->newStorageObject(); return true; } catch (PhabricatorCustomFieldImplementationIncompleteException $ex) { return false; } } public function getValueForStorage() { return $this->getFieldValue(); } public function setValueFromStorage($value) { return $this->setFieldValue($value); } public function shouldAppearInApplicationTransactions() { return true; } public function shouldAppearInEditView() { return $this->getFieldConfigValue('edit', true); } public function readValueFromRequest(AphrontRequest $request) { $value = $request->getStr($this->getFieldKey()); if (!strlen($value)) { $value = null; } $this->setFieldValue($value); } public function getInstructionsForEdit() { return $this->getFieldConfigValue('instructions'); } public function getPlaceholder() { return $this->getFieldConfigValue('placeholder', null); } public function renderEditControl(array $handles) { return id(new AphrontFormTextControl()) ->setName($this->getFieldKey()) ->setCaption($this->getCaption()) ->setValue($this->getFieldValue()) ->setError($this->getFieldError()) ->setLabel($this->getFieldName()) ->setPlaceholder($this->getPlaceholder()); } public function newStorageObject() { return $this->getApplicationField()->newStorageObject(); } public function shouldAppearInPropertyView() { return $this->getFieldConfigValue('view', true); } public function renderPropertyViewValue(array $handles) { if (!strlen($this->getFieldValue())) { return null; } return $this->getFieldValue(); } public function shouldAppearInApplicationSearch() { return $this->getFieldConfigValue('search', false); } protected function newStringIndexStorage() { return $this->getApplicationField()->newStringIndexStorage(); } protected function newNumericIndexStorage() { return $this->getApplicationField()->newNumericIndexStorage(); } public function buildFieldIndexes() { return array(); } public function buildOrderIndex() { return null; } public function readApplicationSearchValueFromRequest( PhabricatorApplicationSearchEngine $engine, AphrontRequest $request) { return; } public function applyApplicationSearchConstraintToQuery( PhabricatorApplicationSearchEngine $engine, PhabricatorCursorPagedPolicyAwareQuery $query, $value) { return; } public function appendToApplicationSearchForm( PhabricatorApplicationSearchEngine $engine, AphrontFormView $form, $value) { return; } public function validateApplicationTransactions( PhabricatorApplicationTransactionEditor $editor, $type, array $xactions) { $this->setFieldError(null); $errors = parent::validateApplicationTransactions( $editor, $type, $xactions); if ($this->getRequired()) { $value = $this->getOldValueForApplicationTransactions(); $transaction = null; foreach ($xactions as $xaction) { $value = $xaction->getNewValue(); if (!$this->isValueEmpty($value)) { $transaction = $xaction; break; } } if ($this->isValueEmpty($value)) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('%s is required.', $this->getFieldName()), $transaction); $error->setIsMissingFieldError(true); $errors[] = $error; $this->setFieldError(pht('Required')); } } return $errors; } protected function isValueEmpty($value) { if (is_array($value)) { return empty($value); } return !strlen($value); } public function getApplicationTransactionTitle( PhabricatorApplicationTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); if (!$old) { return pht( '%s set %s to %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), $new); } else if (!$new) { return pht( '%s removed %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName()); } else { return pht( '%s changed %s from %s to %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), $old, $new); } } public function getApplicationTransactionTitleForFeed( PhabricatorApplicationTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $object_phid = $xaction->getObjectPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); if (!$old) { return pht( '%s set %s to %s on %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), $new, $xaction->renderHandleLink($object_phid)); } else if (!$new) { return pht( '%s removed %s on %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), $xaction->renderHandleLink($object_phid)); } else { return pht( '%s changed %s from %s to %s on %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), $old, $new, $xaction->renderHandleLink($object_phid)); } } public function getHeraldFieldValue() { return $this->getFieldValue(); } public function getFieldControlID($key = null) { $key = coalesce($key, $this->getRawStandardFieldKey()); return 'std:control:'.$key; } public function shouldAppearInGlobalSearch() { return $this->getFieldConfigValue('fulltext', false); } public function updateAbstractDocument( PhabricatorSearchAbstractDocument $document) { $field_key = $this->getFieldConfigValue('fulltext'); // If the caller or configuration didn't specify a valid field key, // generate one automatically from the field index. if (!is_string($field_key) || (strlen($field_key) != 4)) { $field_key = '!'.substr($this->getFieldIndex(), 0, 3); } $field_value = $this->getFieldValue(); if (strlen($field_value)) { $document->addField($field_key, $field_value); } } + protected function newStandardEditField() { + $short = 'custom.'.$this->getRawStandardFieldKey(); + + return parent::newStandardEditField() + ->setEditTypeKey($short); + } + + public function shouldAppearInConduitTransactions() { + return true; + } + } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php index 29192e5c17..e31b6bc6e8 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldBool.php @@ -1,132 +1,136 @@ getFieldValue(); if (strlen($value)) { $indexes[] = $this->newNumericIndex((int)$value); } return $indexes; } public function buildOrderIndex() { return $this->newNumericIndex(0); } public function readValueFromRequest(AphrontRequest $request) { $this->setFieldValue((bool)$request->getBool($this->getFieldKey())); } public function getValueForStorage() { $value = $this->getFieldValue(); if ($value !== null) { return (int)$value; } else { return null; } } public function setValueFromStorage($value) { if (strlen($value)) { $value = (bool)$value; } else { $value = null; } return $this->setFieldValue($value); } public function readApplicationSearchValueFromRequest( PhabricatorApplicationSearchEngine $engine, AphrontRequest $request) { return $request->getStr($this->getFieldKey()); } public function applyApplicationSearchConstraintToQuery( PhabricatorApplicationSearchEngine $engine, PhabricatorCursorPagedPolicyAwareQuery $query, $value) { if ($value == 'require') { $query->withApplicationSearchContainsConstraint( $this->newNumericIndex(null), 1); } } public function appendToApplicationSearchForm( PhabricatorApplicationSearchEngine $engine, AphrontFormView $form, $value) { $form->appendChild( id(new AphrontFormSelectControl()) ->setLabel($this->getFieldName()) ->setName($this->getFieldKey()) ->setValue($value) ->setOptions( array( '' => $this->getString('search.default', pht('(Any)')), 'require' => $this->getString('search.require', pht('Require')), ))); } public function renderEditControl(array $handles) { return id(new AphrontFormCheckboxControl()) ->setLabel($this->getFieldName()) ->setCaption($this->getCaption()) ->addCheckbox( $this->getFieldKey(), 1, $this->getString('edit.checkbox'), (bool)$this->getFieldValue()); } public function renderPropertyViewValue(array $handles) { $value = $this->getFieldValue(); if ($value) { return $this->getString('view.yes', pht('Yes')); } else { return null; } } public function getApplicationTransactionTitle( PhabricatorApplicationTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); if ($new) { return pht( '%s checked %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName()); } else { return pht( '%s unchecked %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName()); } } public function shouldAppearInHerald() { return true; } public function getHeraldFieldConditions() { return array( HeraldAdapter::CONDITION_IS_TRUE, HeraldAdapter::CONDITION_IS_FALSE, ); } + protected function getHTTPParameterType() { + return new AphrontBoolHTTPParameterType(); + } + } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php index c5b86bb097..eb6ea96bf4 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldDate.php @@ -1,194 +1,200 @@ getFieldValue(); if (strlen($value)) { $indexes[] = $this->newNumericIndex((int)$value); } return $indexes; } public function buildOrderIndex() { return $this->newNumericIndex(0); } public function getValueForStorage() { $value = $this->getFieldValue(); if (strlen($value)) { return (int)$value; } else { return null; } } public function setValueFromStorage($value) { if (strlen($value)) { $value = (int)$value; } else { $value = null; } return $this->setFieldValue($value); } public function renderEditControl(array $handles) { return $this->newDateControl(); } public function readValueFromRequest(AphrontRequest $request) { $control = $this->newDateControl(); $control->setUser($request->getUser()); $value = $control->readValueFromRequest($request); $this->setFieldValue($value); } public function renderPropertyViewValue(array $handles) { $value = $this->getFieldValue(); if (!$value) { return null; } return phabricator_datetime($value, $this->getViewer()); } private function newDateControl() { $control = id(new AphrontFormDateControl()) ->setLabel($this->getFieldName()) ->setName($this->getFieldKey()) ->setUser($this->getViewer()) ->setCaption($this->getCaption()) ->setAllowNull(!$this->getRequired()); // If the value is already numeric, treat it as an epoch timestamp and set // it directly. Otherwise, it's likely a field default, which we let users // specify as a string. Parse the string into an epoch. $value = $this->getFieldValue(); if (!ctype_digit($value)) { $value = PhabricatorTime::parseLocalTime($value, $this->getViewer()); } // If we don't have anything valid, make sure we pass `null`, since the // control special-cases that. $control->setValue(nonempty($value, null)); return $control; } public function readApplicationSearchValueFromRequest( PhabricatorApplicationSearchEngine $engine, AphrontRequest $request) { $key = $this->getFieldKey(); return array( 'min' => $request->getStr($key.'.min'), 'max' => $request->getStr($key.'.max'), ); } public function applyApplicationSearchConstraintToQuery( PhabricatorApplicationSearchEngine $engine, PhabricatorCursorPagedPolicyAwareQuery $query, $value) { $viewer = $this->getViewer(); if (!is_array($value)) { $value = array(); } $min_str = idx($value, 'min', ''); if (strlen($min_str)) { $min = PhabricatorTime::parseLocalTime($min_str, $viewer); } else { $min = null; } $max_str = idx($value, 'max', ''); if (strlen($max_str)) { $max = PhabricatorTime::parseLocalTime($max_str, $viewer); } else { $max = null; } if (($min !== null) || ($max !== null)) { $query->withApplicationSearchRangeConstraint( $this->newNumericIndex(null), $min, $max); } } public function appendToApplicationSearchForm( PhabricatorApplicationSearchEngine $engine, AphrontFormView $form, $value) { if (!is_array($value)) { $value = array(); } $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('%s After', $this->getFieldName())) ->setName($this->getFieldKey().'.min') ->setValue(idx($value, 'min', ''))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('%s Before', $this->getFieldName())) ->setName($this->getFieldKey().'.max') ->setValue(idx($value, 'max', ''))); } public function getApplicationTransactionTitle( PhabricatorApplicationTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $viewer = $this->getViewer(); $old_date = null; if ($old) { $old_date = phabricator_datetime($old, $viewer); } $new_date = null; if ($new) { $new_date = phabricator_datetime($new, $viewer); } if (!$old) { return pht( '%s set %s to %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), $new_date); } else if (!$new) { return pht( '%s removed %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName()); } else { return pht( '%s changed %s from %s to %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), $old_date, $new_date); } } + public function shouldAppearInConduitTransactions() { + // TODO: Dates are complicated and we don't yet support handling them from + // Conduit. + return false; + } + } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php index cbb89275f9..8c54b45c3d 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldInt.php @@ -1,116 +1,120 @@ getFieldValue(); if (strlen($value)) { $indexes[] = $this->newNumericIndex((int)$value); } return $indexes; } public function buildOrderIndex() { return $this->newNumericIndex(0); } public function getValueForStorage() { $value = $this->getFieldValue(); if (strlen($value)) { return $value; } else { return null; } } public function setValueFromStorage($value) { if (strlen($value)) { $value = (int)$value; } else { $value = null; } return $this->setFieldValue($value); } public function readApplicationSearchValueFromRequest( PhabricatorApplicationSearchEngine $engine, AphrontRequest $request) { return $request->getStr($this->getFieldKey()); } public function applyApplicationSearchConstraintToQuery( PhabricatorApplicationSearchEngine $engine, PhabricatorCursorPagedPolicyAwareQuery $query, $value) { if (strlen($value)) { $query->withApplicationSearchContainsConstraint( $this->newNumericIndex(null), $value); } } public function appendToApplicationSearchForm( PhabricatorApplicationSearchEngine $engine, AphrontFormView $form, $value) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel($this->getFieldName()) ->setName($this->getFieldKey()) ->setValue($value)); } public function validateApplicationTransactions( PhabricatorApplicationTransactionEditor $editor, $type, array $xactions) { $errors = parent::validateApplicationTransactions( $editor, $type, $xactions); foreach ($xactions as $xaction) { $value = $xaction->getNewValue(); if (strlen($value)) { if (!preg_match('/^-?\d+/', $value)) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht('%s must be an integer.', $this->getFieldName()), $xaction); $this->setFieldError(pht('Invalid')); } } } return $errors; } public function getApplicationTransactionHasEffect( PhabricatorApplicationTransaction $xaction) { $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); if (!strlen($old) && strlen($new)) { return true; } else if (strlen($old) && !strlen($new)) { return true; } else { return ((int)$old !== (int)$new); } } + protected function getHTTPParameterType() { + return new AphrontIntHTTPParameterType(); + } + } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldLink.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldLink.php index cd16c930d7..91352e3d60 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldLink.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldLink.php @@ -1,83 +1,87 @@ getFieldValue(); if (strlen($value)) { $indexes[] = $this->newStringIndex($value); } return $indexes; } public function renderPropertyViewValue(array $handles) { $value = $this->getFieldValue(); if (!strlen($value)) { return null; } if (!PhabricatorEnv::isValidRemoteURIForLink($value)) { return $value; } return phutil_tag( 'a', array('href' => $value, 'target' => '_blank'), $value); } public function readApplicationSearchValueFromRequest( PhabricatorApplicationSearchEngine $engine, AphrontRequest $request) { return $request->getStr($this->getFieldKey()); } public function applyApplicationSearchConstraintToQuery( PhabricatorApplicationSearchEngine $engine, PhabricatorCursorPagedPolicyAwareQuery $query, $value) { if (strlen($value)) { $query->withApplicationSearchContainsConstraint( $this->newStringIndex(null), $value); } } public function appendToApplicationSearchForm( PhabricatorApplicationSearchEngine $engine, AphrontFormView $form, $value) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel($this->getFieldName()) ->setName($this->getFieldKey()) ->setValue($value)); } public function shouldAppearInHerald() { return true; } public function getHeraldFieldConditions() { return array( HeraldAdapter::CONDITION_CONTAINS, HeraldAdapter::CONDITION_NOT_CONTAINS, HeraldAdapter::CONDITION_IS, HeraldAdapter::CONDITION_IS_NOT, HeraldAdapter::CONDITION_REGEXP, ); } + protected function getHTTPParameterType() { + return new AphrontStringHTTPParameterType(); + } + } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php index 4c3159a621..216c7ccb63 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php @@ -1,216 +1,227 @@ getFieldValue(); if (is_array($value)) { foreach ($value as $phid) { $indexes[] = $this->newStringIndex($phid); } } return $indexes; } public function readValueFromRequest(AphrontRequest $request) { $value = $request->getArr($this->getFieldKey()); $this->setFieldValue($value); } public function getValueForStorage() { $value = $this->getFieldValue(); if (!$value) { return null; } return json_encode(array_values($value)); } public function setValueFromStorage($value) { + // NOTE: We're accepting either a JSON string (a real storage value) or + // an array (from HTTP parameter prefilling). This is a little hacky, but + // should hold until this can get cleaned up more thoroughly. + // TODO: Clean this up. + $result = array(); - if ($value) { + if (!is_array($value)) { $value = json_decode($value, true); if (is_array($value)) { $result = array_values($value); } } + $this->setFieldValue($value); + return $this; } public function readApplicationSearchValueFromRequest( PhabricatorApplicationSearchEngine $engine, AphrontRequest $request) { return $request->getArr($this->getFieldKey()); } public function applyApplicationSearchConstraintToQuery( PhabricatorApplicationSearchEngine $engine, PhabricatorCursorPagedPolicyAwareQuery $query, $value) { if ($value) { $query->withApplicationSearchContainsConstraint( $this->newStringIndex(null), $value); } } public function getRequiredHandlePHIDsForPropertyView() { $value = $this->getFieldValue(); if ($value) { return $value; } return array(); } public function renderPropertyViewValue(array $handles) { $value = $this->getFieldValue(); if (!$value) { return null; } $handles = mpull($handles, 'renderLink'); $handles = phutil_implode_html(', ', $handles); return $handles; } public function getRequiredHandlePHIDsForEdit() { $value = $this->getFieldValue(); if ($value) { return $value; } else { return array(); } } public function getApplicationTransactionRequiredHandlePHIDs( PhabricatorApplicationTransaction $xaction) { $old = $this->decodeValue($xaction->getOldValue()); $new = $this->decodeValue($xaction->getNewValue()); $add = array_diff($new, $old); $rem = array_diff($old, $new); return array_merge($add, $rem); } public function getApplicationTransactionTitle( PhabricatorApplicationTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $old = $this->decodeValue($xaction->getOldValue()); $new = $this->decodeValue($xaction->getNewValue()); $add = array_diff($new, $old); $rem = array_diff($old, $new); if ($add && !$rem) { return pht( '%s updated %s, added %d: %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), phutil_count($add), $xaction->renderHandleList($add)); } else if ($rem && !$add) { return pht( '%s updated %s, removed %s: %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), phutil_count($rem), $xaction->renderHandleList($rem)); } else { return pht( '%s updated %s, added %s: %s; removed %s: %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), phutil_count($add), $xaction->renderHandleList($add), phutil_count($rem), $xaction->renderHandleList($rem)); } } public function validateApplicationTransactions( PhabricatorApplicationTransactionEditor $editor, $type, array $xactions) { $errors = parent::validateApplicationTransactions( $editor, $type, $xactions); // If the user is adding PHIDs, make sure the new PHIDs are valid and // visible to the actor. It's OK for a user to edit a field which includes // some invalid or restricted values, but they can't add new ones. foreach ($xactions as $xaction) { $old = $this->decodeValue($xaction->getOldValue()); $new = $this->decodeValue($xaction->getNewValue()); $add = array_diff($new, $old); $invalid = PhabricatorObjectQuery::loadInvalidPHIDsForViewer( $editor->getActor(), $add); if ($invalid) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'Some of the selected PHIDs in field "%s" are invalid or '. 'restricted: %s.', $this->getFieldName(), implode(', ', $invalid)), $xaction); $errors[] = $error; $this->setFieldError(pht('Invalid')); } } return $errors; } public function shouldAppearInHerald() { return true; } public function getHeraldFieldConditions() { return array( HeraldAdapter::CONDITION_INCLUDE_ALL, HeraldAdapter::CONDITION_INCLUDE_ANY, HeraldAdapter::CONDITION_INCLUDE_NONE, HeraldAdapter::CONDITION_EXISTS, HeraldAdapter::CONDITION_NOT_EXISTS, ); } public function getHeraldFieldValue() { // If the field has a `null` value, make sure we hand an `array()` to // Herald. $value = parent::getHeraldFieldValue(); if ($value) { return $value; } return array(); } protected function decodeValue($value) { $value = json_decode($value); if (!is_array($value)) { $value = array(); } return $value; } + protected function getHTTPParameterType() { + return new AphrontPHIDListHTTPParameterType(); + } + } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php index e1b3a158cd..894d48dbb3 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldRemarkup.php @@ -1,99 +1,103 @@ setUser($this->getViewer()) ->setLabel($this->getFieldName()) ->setName($this->getFieldKey()) ->setCaption($this->getCaption()) ->setValue($this->getFieldValue()); } public function getStyleForPropertyView() { return 'block'; } public function getApplicationTransactionRemarkupBlocks( PhabricatorApplicationTransaction $xaction) { return array( $xaction->getNewValue(), ); } public function renderPropertyViewValue(array $handles) { $value = $this->getFieldValue(); if (!strlen($value)) { return null; } // TODO: Once this stabilizes, it would be nice to let fields batch this. // For now, an extra query here and there on object detail pages isn't the // end of the world. $viewer = $this->getViewer(); return PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff()) ->setContent($value) ->setPReserveLinebreaks(true), 'default', $viewer); } public function getApplicationTransactionTitle( PhabricatorApplicationTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); return pht( '%s edited %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName()); } public function getApplicationTransactionTitleForFeed( PhabricatorApplicationTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $object_phid = $xaction->getObjectPHID(); return pht( '%s edited %s on %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), $xaction->renderHandleLink($object_phid)); } public function getApplicationTransactionHasChangeDetails( PhabricatorApplicationTransaction $xaction) { return true; } public function getApplicationTransactionChangeDetails( PhabricatorApplicationTransaction $xaction, PhabricatorUser $viewer) { return $xaction->renderTextCorpusChangeDetails( $viewer, $xaction->getOldValue(), $xaction->getNewValue()); } public function shouldAppearInHerald() { return true; } public function getHeraldFieldConditions() { return array( HeraldAdapter::CONDITION_CONTAINS, HeraldAdapter::CONDITION_NOT_CONTAINS, HeraldAdapter::CONDITION_IS, HeraldAdapter::CONDITION_IS_NOT, HeraldAdapter::CONDITION_REGEXP, ); } + protected function getHTTPParameterType() { + return new AphrontStringHTTPParameterType(); + } + } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php index 7870d2acbd..06b5d5e01b 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldSelect.php @@ -1,139 +1,143 @@ getFieldValue(); if (strlen($value)) { $indexes[] = $this->newStringIndex($value); } return $indexes; } public function readApplicationSearchValueFromRequest( PhabricatorApplicationSearchEngine $engine, AphrontRequest $request) { return $request->getArr($this->getFieldKey()); } public function applyApplicationSearchConstraintToQuery( PhabricatorApplicationSearchEngine $engine, PhabricatorCursorPagedPolicyAwareQuery $query, $value) { if ($value) { $query->withApplicationSearchContainsConstraint( $this->newStringIndex(null), $value); } } public function appendToApplicationSearchForm( PhabricatorApplicationSearchEngine $engine, AphrontFormView $form, $value) { if (!is_array($value)) { $value = array(); } $value = array_fuse($value); $control = id(new AphrontFormCheckboxControl()) ->setLabel($this->getFieldName()); foreach ($this->getOptions() as $name => $option) { $control->addCheckbox( $this->getFieldKey().'[]', $name, $option, isset($value[$name])); } $form->appendChild($control); } public function getOptions() { return $this->getFieldConfigValue('options', array()); } public function renderEditControl(array $handles) { return id(new AphrontFormSelectControl()) ->setLabel($this->getFieldName()) ->setCaption($this->getCaption()) ->setName($this->getFieldKey()) ->setValue($this->getFieldValue()) ->setOptions($this->getOptions()); } public function renderPropertyViewValue(array $handles) { if (!strlen($this->getFieldValue())) { return null; } return idx($this->getOptions(), $this->getFieldValue()); } public function getApplicationTransactionTitle( PhabricatorApplicationTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $old = idx($this->getOptions(), $old, $old); $new = idx($this->getOptions(), $new, $new); if (!$old) { return pht( '%s set %s to %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), $new); } else if (!$new) { return pht( '%s removed %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName()); } else { return pht( '%s changed %s from %s to %s.', $xaction->renderHandleLink($author_phid), $this->getFieldName(), $old, $new); } } public function shouldAppearInHerald() { return true; } public function getHeraldFieldConditions() { return array( HeraldAdapter::CONDITION_IS_ANY, HeraldAdapter::CONDITION_IS_NOT_ANY, ); } public function getHeraldFieldValueType($condition) { $parameters = array( 'object' => get_class($this->getObject()), 'role' => PhabricatorCustomField::ROLE_HERALD, 'key' => $this->getFieldKey(), ); $datasource = id(new PhabricatorStandardSelectCustomFieldDatasource()) ->setParameters($parameters); return id(new HeraldTokenizerFieldValue()) ->setKey('custom.'.$this->getFieldKey()) ->setDatasource($datasource) ->setValueMap($this->getOptions()); } + protected function getHTTPParameterType() { + return new AphrontSelectHTTPParameterType(); + } + } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php index ca519b0c79..4f6590b6a4 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldText.php @@ -1,66 +1,70 @@ getFieldValue(); if (strlen($value)) { $indexes[] = $this->newStringIndex($value); } return $indexes; } public function readApplicationSearchValueFromRequest( PhabricatorApplicationSearchEngine $engine, AphrontRequest $request) { return $request->getStr($this->getFieldKey()); } public function applyApplicationSearchConstraintToQuery( PhabricatorApplicationSearchEngine $engine, PhabricatorCursorPagedPolicyAwareQuery $query, $value) { if (strlen($value)) { $query->withApplicationSearchContainsConstraint( $this->newStringIndex(null), $value); } } public function appendToApplicationSearchForm( PhabricatorApplicationSearchEngine $engine, AphrontFormView $form, $value) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel($this->getFieldName()) ->setName($this->getFieldKey()) ->setValue($value)); } public function shouldAppearInHerald() { return true; } public function getHeraldFieldConditions() { return array( HeraldAdapter::CONDITION_CONTAINS, HeraldAdapter::CONDITION_NOT_CONTAINS, HeraldAdapter::CONDITION_IS, HeraldAdapter::CONDITION_IS_NOT, HeraldAdapter::CONDITION_REGEXP, ); } + protected function getHTTPParameterType() { + return new AphrontStringHTTPParameterType(); + } + } diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php index 2f1e6db53a..17b082e583 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php @@ -1,14 +1,18 @@ array( 'No daemon with id %s exists!', 'No daemons with ids %s exist!', ), 'These %d configuration value(s) are related:' => array( 'This configuration value is related:', 'These configuration values are related:', ), '%s Task(s)' => array('Task', 'Tasks'), '%s ERROR(S)' => array('ERROR', 'ERRORS'), '%d Error(s)' => array('%d Error', '%d Errors'), '%d Warning(s)' => array('%d Warning', '%d Warnings'), '%d Auto-Fix(es)' => array('%d Auto-Fix', '%d Auto-Fixes'), '%d Advice(s)' => array('%d Advice', '%d Pieces of Advice'), '%d Detail(s)' => array('%d Detail', '%d Details'), '(%d line(s))' => array('(%d line)', '(%d lines)'), '%d line(s)' => array('%d line', '%d lines'), '%d path(s)' => array('%d path', '%d paths'), '%d diff(s)' => array('%d diff', '%d diffs'), '%s Answer(s)' => array('%s Answer', '%s Answers'), 'Show %d Comment(s)' => array('Show %d Comment', 'Show %d Comments'), '%s DIFF LINK(S)' => array('DIFF LINK', 'DIFF LINKS'), 'You successfully created %d diff(s).' => array( 'You successfully created %d diff.', 'You successfully created %d diffs.', ), 'Diff creation failed; see body for %s error(s).' => array( 'Diff creation failed; see body for error.', 'Diff creation failed; see body for errors.', ), 'There are %d raw fact(s) in storage.' => array( 'There is %d raw fact in storage.', 'There are %d raw facts in storage.', ), 'There are %d aggregate fact(s) in storage.' => array( 'There is %d aggregate fact in storage.', 'There are %d aggregate facts in storage.', ), - '%d Commit(s) Awaiting Audit' => array( - '%d Commit Awaiting Audit', - '%d Commits Awaiting Audit', + '%s Commit(s) Awaiting Audit' => array( + '%s Commit Awaiting Audit', + '%s Commits Awaiting Audit', ), - '%d Problem Commit(s)' => array( - '%d Problem Commit', - '%d Problem Commits', + '%s Problem Commit(s)' => array( + '%s Problem Commit', + '%s Problem Commits', ), - '%d Review(s) Blocking Others' => array( - '%d Review Blocking Others', - '%d Reviews Blocking Others', + '%s Review(s) Blocking Others' => array( + '%s Review Blocking Others', + '%s Reviews Blocking Others', ), - '%d Review(s) Need Attention' => array( - '%d Review Needs Attention', - '%d Reviews Need Attention', + '%s Review(s) Need Attention' => array( + '%s Review Needs Attention', + '%s Reviews Need Attention', ), - '%d Review(s) Waiting on Others' => array( - '%d Review Waiting on Others', - '%d Reviews Waiting on Others', + '%s Review(s) Waiting on Others' => array( + '%s Review Waiting on Others', + '%s Reviews Waiting on Others', ), - '%d Active Review(s)' => array( - '%d Active Review', - '%d Active Reviews', + '%s Active Review(s)' => array( + '%s Active Review', + '%s Active Reviews', ), - '%d Flagged Object(s)' => array( - '%d Flagged Object', - '%d Flagged Objects', + '%s Flagged Object(s)' => array( + '%s Flagged Object', + '%s Flagged Objects', ), - '%d Object(s) Tracked' => array( - '%d Object Tracked', - '%d Objects Tracked', + '%s Object(s) Tracked' => array( + '%s Object Tracked', + '%s Objects Tracked', ), - '%d Assigned Task(s)' => array( - '%d Assigned Task', - '%d Assigned Tasks', + '%s Assigned Task(s)' => array( + '%s Assigned Task', + '%s Assigned Tasks', ), 'Show %d Lint Message(s)' => array( 'Show %d Lint Message', 'Show %d Lint Messages', ), 'Hide %d Lint Message(s)' => array( 'Hide %d Lint Message', 'Hide %d Lint Messages', ), 'This is a binary file. It is %s byte(s) in length.' => array( 'This is a binary file. It is %s byte in length.', 'This is a binary file. It is %s bytes in length.', ), '%s Action(s) Have No Effect' => array( 'Action Has No Effect', 'Actions Have No Effect', ), '%s Action(s) With No Effect' => array( 'Action With No Effect', 'Actions With No Effect', ), 'Some of your %s action(s) have no effect:' => array( 'One of your actions has no effect:', 'Some of your actions have no effect:', ), 'Apply remaining %d action(s)?' => array( 'Apply remaining action?', 'Apply remaining actions?', ), 'Apply %d Other Action(s)' => array( 'Apply Remaining Action', 'Apply Remaining Actions', ), 'The %s action(s) you are taking have no effect:' => array( 'The action you are taking has no effect:', 'The actions you are taking have no effect:', ), '%s edited member(s), added %d: %s; removed %d: %s.' => '%s edited members, added: %3$s; removed: %5$s.', '%s added %s member(s): %s.' => array( array( '%s added a member: %3$s.', '%s added members: %3$s.', ), ), '%s removed %s member(s): %s.' => array( array( '%s removed a member: %3$s.', '%s removed members: %3$s.', ), ), '%s edited project(s), added %s: %s; removed %s: %s.' => '%s edited projects, added: %3$s; removed: %5$s.', '%s added %s project(s): %s.' => array( array( '%s added a project: %3$s.', '%s added projects: %3$s.', ), ), '%s removed %s project(s): %s.' => array( array( '%s removed a project: %3$s.', '%s removed projects: %3$s.', ), ), '%s merged %s task(s): %s.' => array( array( '%s merged a task: %3$s.', '%s merged tasks: %3$s.', ), ), '%s merged %s task(s) %s into %s.' => array( array( '%s merged %3$s into %4$s.', '%s merged tasks %3$s into %4$s.', ), ), '%s added %s voting user(s): %s.' => array( array( '%s added a voting user: %3$s.', '%s added voting users: %3$s.', ), ), '%s removed %s voting user(s): %s.' => array( array( '%s removed a voting user: %3$s.', '%s removed voting users: %3$s.', ), ), '%s added %s blocking task(s): %s.' => array( array( '%s added a blocking task: %3$s.', '%s added blocking tasks: %3$s.', ), ), '%s added %s blocked task(s): %s.' => array( array( '%s added a blocked task: %3$s.', '%s added blocked tasks: %3$s.', ), ), '%s removed %s blocking task(s): %s.' => array( array( '%s removed a blocking task: %3$s.', '%s removed blocking tasks: %3$s.', ), ), '%s removed %s blocked task(s): %s.' => array( array( '%s removed a blocked task: %3$s.', '%s removed blocked tasks: %3$s.', ), ), '%s added %s blocking task(s) for %s: %s.' => array( array( '%s added a blocking task for %3$s: %4$s.', '%s added blocking tasks for %3$s: %4$s.', ), ), '%s added %s blocked task(s) for %s: %s.' => array( array( '%s added a blocked task for %3$s: %4$s.', '%s added blocked tasks for %3$s: %4$s.', ), ), '%s removed %s blocking task(s) for %s: %s.' => array( array( '%s removed a blocking task for %3$s: %4$s.', '%s removed blocking tasks for %3$s: %4$s.', ), ), '%s removed %s blocked task(s) for %s: %s.' => array( array( '%s removed a blocked task for %3$s: %4$s.', '%s removed blocked tasks for %3$s: %4$s.', ), ), '%s edited blocking task(s), added %s: %s; removed %s: %s.' => '%s edited blocking tasks, added: %3$s; removed: %5$s.', '%s edited blocking task(s) for %s, added %s: %s; removed %s: %s.' => '%s edited blocking tasks for %s, added: %4$s; removed: %6$s.', '%s edited blocked task(s), added %s: %s; removed %s: %s.' => '%s edited blocked tasks, added: %3$s; removed: %5$s.', '%s edited blocked task(s) for %s, added %s: %s; removed %s: %s.' => '%s edited blocked tasks for %s, added: %4$s; removed: %6$s.', '%s edited answer(s), added %s: %s; removed %d: %s.' => '%s edited answers, added: %3$s; removed: %5$s.', '%s added %s answer(s): %s.' => array( array( '%s added an answer: %3$s.', '%s added answers: %3$s.', ), ), '%s removed %s answer(s): %s.' => array( array( '%s removed a answer: %3$s.', '%s removed answers: %3$s.', ), ), '%s edited question(s), added %s: %s; removed %s: %s.' => '%s edited questions, added: %3$s; removed: %5$s.', '%s added %s question(s): %s.' => array( array( '%s added a question: %3$s.', '%s added questions: %3$s.', ), ), '%s removed %s question(s): %s.' => array( array( '%s removed a question: %3$s.', '%s removed questions: %3$s.', ), ), '%s edited mock(s), added %s: %s; removed %s: %s.' => '%s edited mocks, added: %3$s; removed: %5$s.', '%s added %s mock(s): %s.' => array( array( '%s added a mock: %3$s.', '%s added mocks: %3$s.', ), ), '%s removed %s mock(s): %s.' => array( array( '%s removed a mock: %3$s.', '%s removed mocks: %3$s.', ), ), '%s added %s task(s): %s.' => array( array( '%s added a task: %3$s.', '%s added tasks: %3$s.', ), ), '%s removed %s task(s): %s.' => array( array( '%s removed a task: %3$s.', '%s removed tasks: %3$s.', ), ), '%s edited file(s), added %s: %s; removed %s: %s.' => '%s edited files, added: %3$s; removed: %5$s.', '%s added %s file(s): %s.' => array( array( '%s added a file: %3$s.', '%s added files: %3$s.', ), ), '%s removed %s file(s): %s.' => array( array( '%s removed a file: %3$s.', '%s removed files: %3$s.', ), ), '%s edited contributor(s), added %s: %s; removed %s: %s.' => '%s edited contributors, added: %3$s; removed: %5$s.', '%s added %s contributor(s): %s.' => array( array( '%s added a contributor: %3$s.', '%s added contributors: %3$s.', ), ), '%s removed %s contributor(s): %s.' => array( array( '%s removed a contributor: %3$s.', '%s removed contributors: %3$s.', ), ), '%s edited %s reviewer(s), added %s: %s; removed %s: %s.' => '%s edited reviewers, added: %4$s; removed: %6$s.', '%s edited %s reviewer(s) for %s, added %s: %s; removed %s: %s.' => '%s edited reviewers for %3$s, added: %5$s; removed: %7$s.', '%s added %s reviewer(s): %s.' => array( array( '%s added a reviewer: %3$s.', '%s added reviewers: %3$s.', ), ), '%s added %s reviewer(s) for %s: %s.' => array( array( '%s added a reviewer for %3$s: %4$s.', '%s added reviewers for %3$s: %4$s.', ), ), '%s removed %s reviewer(s): %s.' => array( array( '%s removed a reviewer: %3$s.', '%s removed reviewers: %3$s.', ), ), '%s removed %s reviewer(s) for %s: %s.' => array( array( '%s removed a reviewer for %3$s: %4$s.', '%s removed reviewers for %3$s: %4$s.', ), ), '%d other(s)' => array( '1 other', '%d others', ), '%s edited subscriber(s), added %d: %s; removed %d: %s.' => '%s edited subscribers, added: %3$s; removed: %5$s.', '%s added %d subscriber(s): %s.' => array( array( '%s added a subscriber: %3$s.', '%s added subscribers: %3$s.', ), ), '%s removed %d subscriber(s): %s.' => array( array( '%s removed a subscriber: %3$s.', '%s removed subscribers: %3$s.', ), ), '%s edited watcher(s), added %s: %s; removed %d: %s.' => '%s edited watchers, added: %3$s; removed: %5$s.', '%s added %s watcher(s): %s.' => array( array( '%s added a watcher: %3$s.', '%s added watchers: %3$s.', ), ), '%s removed %s watcher(s): %s.' => array( array( '%s removed a watcher: %3$s.', '%s removed watchers: %3$s.', ), ), '%s edited participant(s), added %d: %s; removed %d: %s.' => '%s edited participants, added: %3$s; removed: %5$s.', '%s added %d participant(s): %s.' => array( array( '%s added a participant: %3$s.', '%s added participants: %3$s.', ), ), '%s removed %d participant(s): %s.' => array( array( '%s removed a participant: %3$s.', '%s removed participants: %3$s.', ), ), '%s edited image(s), added %d: %s; removed %d: %s.' => '%s edited images, added: %3$s; removed: %5$s', '%s added %d image(s): %s.' => array( array( '%s added an image: %3$s.', '%s added images: %3$s.', ), ), '%s removed %d image(s): %s.' => array( array( '%s removed an image: %3$s.', '%s removed images: %3$s.', ), ), '%s Line(s)' => array( '%s Line', '%s Lines', ), 'Indexing %d object(s) of type %s.' => array( 'Indexing %d object of type %s.', 'Indexing %d object of type %s.', ), 'Run these %d command(s):' => array( 'Run this command:', 'Run these commands:', ), 'Install these %d PHP extension(s):' => array( 'Install this PHP extension:', 'Install these PHP extensions:', ), 'The current Phabricator configuration has these %d value(s):' => array( 'The current Phabricator configuration has this value:', 'The current Phabricator configuration has these values:', ), 'The current MySQL configuration has these %d value(s):' => array( 'The current MySQL configuration has this value:', 'The current MySQL configuration has these values:', ), 'You can update these %d value(s) here:' => array( 'You can update this value here:', 'You can update these values here:', ), 'The current PHP configuration has these %d value(s):' => array( 'The current PHP configuration has this value:', 'The current PHP configuration has these values:', ), 'To update these %d value(s), edit your PHP configuration file.' => array( 'To update this %d value, edit your PHP configuration file.', 'To update these %d values, edit your PHP configuration file.', ), 'To update these %d value(s), edit your PHP configuration file, located '. 'here:' => array( 'To update this value, edit your PHP configuration file, located '. 'here:', 'To update these values, edit your PHP configuration file, located '. 'here:', ), 'PHP also loaded these %s configuration file(s):' => array( 'PHP also loaded this configuration file:', 'PHP also loaded these configuration files:', ), 'You have %d unresolved setup issue(s)...' => array( 'You have an unresolved setup issue...', 'You have %d unresolved setup issues...', ), '%s added %d inline comment(s).' => array( array( '%s added an inline comment.', '%s added inline comments.', ), ), '%s comment(s)' => array('%s comment', '%s comments'), '%s rejection(s)' => array('%s rejection', '%s rejections'), '%s update(s)' => array('%s update', '%s updates'), 'This configuration value is defined in these %d '. 'configuration source(s): %s.' => array( 'This configuration value is defined in this '. 'configuration source: %2$s.', 'This configuration value is defined in these %d '. 'configuration sources: %s.', ), '%s Open Pull Request(s)' => array( '%s Open Pull Request', '%s Open Pull Requests', ), 'Stale (%s day(s))' => array( 'Stale (%s day)', 'Stale (%s days)', ), 'Old (%s day(s))' => array( 'Old (%s day)', 'Old (%s days)', ), '%s Commit(s)' => array( '%s Commit', '%s Commits', ), '%s attached %d file(s): %s.' => array( array( '%s attached a file: %3$s.', '%s attached files: %3$s.', ), ), '%s detached %d file(s): %s.' => array( array( '%s detached a file: %3$s.', '%s detached files: %3$s.', ), ), '%s changed file(s), attached %d: %s; detached %d: %s.' => '%s changed files, attached: %3$s; detached: %5$s.', '%s added %s dependencie(s): %s.' => array( array( '%s added a dependency: %3$s.', '%s added dependencies: %3$s.', ), ), '%s added %s dependencie(s) for %s: %s.' => array( array( '%s added a dependency for %3$s: %4$s.', '%s added dependencies for %3$s: %4$s.', ), ), '%s removed %s dependencie(s): %s.' => array( array( '%s removed a dependency: %3$s.', '%s removed dependencies: %3$s.', ), ), '%s removed %s dependencie(s) for %s: %s.' => array( array( '%s removed a dependency for %3$s: %4$s.', '%s removed dependencies for %3$s: %4$s.', ), ), '%s edited dependencie(s), added %s: %s; removed %s: %s.' => array( '%s edited dependencies, added: %3$s; removed: %5$s.', ), '%s edited dependencie(s) for %s, added %s: %s; removed %s: %s.' => array( '%s edited dependencies for %s, added: %3$s; removed: %5$s.', ), '%s added %s dependent revision(s): %s.' => array( array( '%s added a dependent revision: %3$s.', '%s added dependent revisions: %3$s.', ), ), '%s added %s dependent revision(s) for %s: %s.' => array( array( '%s added a dependent revision for %3$s: %4$s.', '%s added dependent revisions for %3$s: %4$s.', ), ), '%s removed %s dependent revision(s): %s.' => array( array( '%s removed a dependent revision: %3$s.', '%s removed dependent revisions: %3$s.', ), ), '%s removed %s dependent revision(s) for %s: %s.' => array( array( '%s removed a dependent revision for %3$s: %4$s.', '%s removed dependent revisions for %3$s: %4$s.', ), ), '%s added %s commit(s): %s.' => array( array( '%s added a commit: %3$s.', '%s added commits: %3$s.', ), ), '%s removed %s commit(s): %s.' => array( array( '%s removed a commit: %3$s.', '%s removed commits: %3$s.', ), ), '%s edited commit(s), added %s: %s; removed %s: %s.' => '%s edited commits, added %3$s; removed %5$s.', '%s added %s reverted commit(s): %s.' => array( array( '%s added a reverted commit: %3$s.', '%s added reverted commits: %3$s.', ), ), '%s removed %s reverted commit(s): %s.' => array( array( '%s removed a reverted commit: %3$s.', '%s removed reverted commits: %3$s.', ), ), '%s edited reverted commit(s), added %s: %s; removed %s: %s.' => '%s edited reverted commits, added %3$s; removed %5$s.', '%s added %s reverted commit(s) for %s: %s.' => array( array( '%s added a reverted commit for %3$s: %4$s.', '%s added reverted commits for %3$s: %4$s.', ), ), '%s removed %s reverted commit(s) for %s: %s.' => array( array( '%s removed a reverted commit for %3$s: %4$s.', '%s removed reverted commits for %3$s: %4$s.', ), ), '%s edited reverted commit(s) for %s, added %s: %s; removed %s: %s.' => '%s edited reverted commits for %2$s, added %4$s; removed %6$s.', '%s added %s reverting commit(s): %s.' => array( array( '%s added a reverting commit: %3$s.', '%s added reverting commits: %3$s.', ), ), '%s removed %s reverting commit(s): %s.' => array( array( '%s removed a reverting commit: %3$s.', '%s removed reverting commits: %3$s.', ), ), '%s edited reverting commit(s), added %s: %s; removed %s: %s.' => '%s edited reverting commits, added %3$s; removed %5$s.', '%s added %s reverting commit(s) for %s: %s.' => array( array( '%s added a reverting commit for %3$s: %4$s.', '%s added reverting commitsi for %3$s: %4$s.', ), ), '%s removed %s reverting commit(s) for %s: %s.' => array( array( '%s removed a reverting commit for %3$s: %4$s.', '%s removed reverting commits for %3$s: %4$s.', ), ), '%s edited reverting commit(s) for %s, added %s: %s; removed %s: %s.' => '%s edited reverting commits for %s, added %4$s; removed %6$s.', '%s changed project member(s), added %d: %s; removed %d: %s.' => '%s changed project members, added %3$s; removed %5$s.', '%s added %d project member(s): %s.' => array( array( '%s added a member: %3$s.', '%s added members: %3$s.', ), ), '%s removed %d project member(s): %s.' => array( array( '%s removed a member: %3$s.', '%s removed members: %3$s.', ), ), '%d project hashtag(s) are already used: %s.' => array( 'Project hashtag %2$s is already used.', '%d project hashtags are already used: %2$s.', ), '%s changed project hashtag(s), added %d: %s; removed %d: %s.' => '%s changed project hashtags, added %3$s; removed %5$s.', '%s added %d project hashtag(s): %s.' => array( array( '%s added a hashtag: %3$s.', '%s added hashtags: %3$s.', ), ), '%s removed %d project hashtag(s): %s.' => array( array( '%s removed a hashtag: %3$s.', '%s removed hashtags: %3$s.', ), ), '%s changed %s hashtag(s), added %d: %s; removed %d: %s.' => '%s changed hashtags for %s, added %4$s; removed %6$s.', '%s added %d %s hashtag(s): %s.' => array( array( '%s added a hashtag to %3$s: %4$s.', '%s added hashtags to %3$s: %4$s.', ), ), '%s removed %d %s hashtag(s): %s.' => array( array( '%s removed a hashtag from %3$s: %4$s.', '%s removed hashtags from %3$s: %4$s.', ), ), '%d User(s) Need Approval' => array( '%d User Needs Approval', '%d Users Need Approval', ), '%s older changes(s) are hidden.' => array( '%d older change is hidden.', '%d older changes are hidden.', ), '%s, %s line(s)' => array( array( '%s, %s line', '%s, %s lines', ), ), '%s pushed %d commit(s) to %s.' => array( array( '%s pushed a commit to %3$s.', '%s pushed %d commits to %s.', ), ), '%s commit(s)' => array( '1 commit', '%s commits', ), '%s removed %s JIRA issue(s): %s.' => array( array( '%s removed a JIRA issue: %3$s.', '%s removed JIRA issues: %3$s.', ), ), '%s added %s JIRA issue(s): %s.' => array( array( '%s added a JIRA issue: %3$s.', '%s added JIRA issues: %3$s.', ), ), '%s added %s required legal document(s): %s.' => array( array( '%s added a required legal document: %3$s.', '%s added required legal documents: %3$s.', ), ), '%s updated JIRA issue(s): added %s %s; removed %d %s.' => '%s updated JIRA issues: added %3$s; removed %5$s.', '%s edited %s task(s), added %s: %s; removed %s: %s.' => '%s edited tasks, added %4$s; removed %6$s.', '%s added %s task(s) to %s: %s.' => array( array( '%s added a task to %3$s: %4$s.', '%s added tasks to %3$s: %4$s.', ), ), '%s removed %s task(s) from %s: %s.' => array( array( '%s removed a task from %3$s: %4$s.', '%s removed tasks from %3$s: %4$s.', ), ), '%s edited %s task(s) for %s, added %s: %s; removed %s: %s.' => '%s edited tasks for %3$s, added: %5$s; removed %7$s.', '%s edited %s commit(s), added %s: %s; removed %s: %s.' => '%s edited commits, added %4$s; removed %6$s.', '%s added %s commit(s) to %s: %s.' => array( array( '%s added a commit to %3$s: %4$s.', '%s added commits to %3$s: %4$s.', ), ), '%s removed %s commit(s) from %s: %s.' => array( array( '%s removed a commit from %3$s: %4$s.', '%s removed commits from %3$s: %4$s.', ), ), '%s edited %s commit(s) for %s, added %s: %s; removed %s: %s.' => '%s edited commits for %3$s, added: %5$s; removed %7$s.', '%s added %s revision(s): %s.' => array( array( '%s added a revision: %3$s.', '%s added revisions: %3$s.', ), ), '%s removed %s revision(s): %s.' => array( array( '%s removed a revision: %3$s.', '%s removed revisions: %3$s.', ), ), '%s edited %s revision(s), added %s: %s; removed %s: %s.' => '%s edited revisions, added %4$s; removed %6$s.', '%s added %s revision(s) to %s: %s.' => array( array( '%s added a revision to %3$s: %4$s.', '%s added revisions to %3$s: %4$s.', ), ), '%s removed %s revision(s) from %s: %s.' => array( array( '%s removed a revision from %3$s: %4$s.', '%s removed revisions from %3$s: %4$s.', ), ), '%s edited %s revision(s) for %s, added %s: %s; removed %s: %s.' => '%s edited revisions for %3$s, added: %5$s; removed %7$s.', '%s edited %s project(s), added %s: %s; removed %s: %s.' => '%s edited projects, added %4$s; removed %6$s.', '%s added %s project(s) to %s: %s.' => array( array( '%s added a project to %3$s: %4$s.', '%s added projects to %3$s: %4$s.', ), ), '%s removed %s project(s) from %s: %s.' => array( array( '%s removed a project from %3$s: %4$s.', '%s removed projects from %3$s: %4$s.', ), ), '%s edited %s project(s) for %s, added %s: %s; removed %s: %s.' => '%s edited projects for %3$s, added: %5$s; removed %7$s.', '%s added %s panel(s): %s.' => array( array( '%s added a panel: %3$s.', '%s added panels: %3$s.', ), ), '%s removed %s panel(s): %s.' => array( array( '%s removed a panel: %3$s.', '%s removed panels: %3$s.', ), ), '%s edited %s panel(s), added %s: %s; removed %s: %s.' => '%s edited panels, added %4$s; removed %6$s.', '%s added %s dashboard(s): %s.' => array( array( '%s added a dashboard: %3$s.', '%s added dashboards: %3$s.', ), ), '%s removed %s dashboard(s): %s.' => array( array( '%s removed a dashboard: %3$s.', '%s removed dashboards: %3$s.', ), ), '%s edited %s dashboard(s), added %s: %s; removed %s: %s.' => '%s edited dashboards, added %4$s; removed %6$s.', '%s added %s edge(s): %s.' => array( array( '%s added an edge: %3$s.', '%s added edges: %3$s.', ), ), '%s added %s edge(s) to %s: %s.' => array( array( '%s added an edge to %3$s: %4$s.', '%s added edges to %3$s: %4$s.', ), ), '%s removed %s edge(s): %s.' => array( array( '%s removed an edge: %3$s.', '%s removed edges: %3$s.', ), ), '%s removed %s edge(s) from %s: %s.' => array( array( '%s removed an edge from %3$s: %4$s.', '%s removed edges from %3$s: %4$s.', ), ), '%s edited edge(s), added %s: %s; removed %s: %s.' => '%s edited edges, added: %3$s; removed: %5$s.', '%s edited %s edge(s) for %s, added %s: %s; removed %s: %s.' => '%s edited edges for %3$s, added: %5$s; removed %7$s.', '%s added %s member(s) for %s: %s.' => array( array( '%s added a member for %3$s: %4$s.', '%s added members for %3$s: %4$s.', ), ), '%s removed %s member(s) for %s: %s.' => array( array( '%s removed a member for %3$s: %4$s.', '%s removed members for %3$s: %4$s.', ), ), '%s edited %s member(s) for %s, added %s: %s; removed %s: %s.' => '%s edited members for %3$s, added: %5$s; removed %7$s.', '%d related link(s):' => array( 'Related link:', 'Related links:', ), 'You have %d unpaid invoice(s).' => array( 'You have an unpaid invoice.', 'You have unpaid invoices.', ), 'The configurations differ in the following %s way(s):' => array( 'The configurations differ:', 'The configurations differ in these ways:', ), 'Phabricator is configured with an email domain whitelist (in %s), so '. 'only users with a verified email address at one of these %s '. 'allowed domain(s) will be able to register an account: %s' => array( array( 'Phabricator is configured with an email domain whitelist (in %s), '. 'so only users with a verified email address at %3$s will be '. 'allowed to register an account.', 'Phabricator is configured with an email domain whitelist (in %s), '. 'so only users with a verified email address at one of these '. 'allowed domains will be able to register an account: %3$s', ), ), 'Show First %d Line(s)' => array( 'Show First Line', 'Show First %d Lines', ), "\xE2\x96\xB2 Show %d Line(s)" => array( "\xE2\x96\xB2 Show Line", "\xE2\x96\xB2 Show %d Lines", ), 'Show All %d Line(s)' => array( 'Show Line', 'Show All %d Lines', ), "\xE2\x96\xBC Show %d Line(s)" => array( "\xE2\x96\xBC Show Line", "\xE2\x96\xBC Show %d Lines", ), 'Show Last %d Line(s)' => array( 'Show Last Line', 'Show Last %d Lines', ), '%s marked %s inline comment(s) as done and %s inline comment(s) as '. 'not done.' => array( array( array( '%s marked an inline comment as done and an inline comment '. 'as not done.', '%s marked an inline comment as done and %3$s inline comments '. 'as not done.', ), array( '%s marked %s inline comments as done and an inline comment '. 'as not done.', '%s marked %s inline comments as done and %s inline comments '. 'as done.', ), ), ), '%s marked %s inline comment(s) as done.' => array( array( '%s marked an inline comment as done.', '%s marked %s inline comments as done.', ), ), '%s marked %s inline comment(s) as not done.' => array( array( '%s marked an inline comment as not done.', '%s marked %s inline comments as not done.', ), ), 'These %s object(s) will be destroyed forever:' => array( 'This object will be destroyed forever:', 'These objects will be destroyed forever:', ), 'Are you absolutely certain you want to destroy these %s '. 'object(s)?' => array( 'Are you absolutely certain you want to destroy this object?', 'Are you absolutely certain you want to destroy these objects?', ), '%s added %s owner(s): %s.' => array( array( '%s added an owner: %3$s.', '%s added owners: %3$s.', ), ), '%s removed %s owner(s): %s.' => array( array( '%s removed an owner: %3$s.', '%s removed owners: %3$s.', ), ), '%s changed %s package owner(s), added %s: %s; removed %s: %s.' => array( '%s changed package owners, added: %4$s; removed: %6$s.', ), 'Found %s book(s).' => array( 'Found %s book.', 'Found %s books.', ), 'Found %s file(s)...' => array( 'Found %s file...', 'Found %s files...', ), 'Found %s file(s) in project.' => array( 'Found %s file in project.', 'Found %s files in project.', ), 'Found %s unatomized, uncached file(s).' => array( 'Found %s unatomized, uncached file.', 'Found %s unatomized, uncached files.', ), 'Found %s file(s) to atomize.' => array( 'Found %s file to atomize.', 'Found %s files to atomize.', ), 'Atomizing %s file(s).' => array( 'Atomizing %s file.', 'Atomizing %s files.', ), 'Creating %s document(s).' => array( 'Creating %s document.', 'Creating %s documents.', ), 'Deleting %s document(s).' => array( 'Deleting %s document.', 'Deleting %s documents.', ), 'Found %s obsolete atom(s) in graph.' => array( 'Found %s obsolete atom in graph.', 'Found %s obsolete atoms in graph.', ), 'Found %s new atom(s) in graph.' => array( 'Found %s new atom in graph.', 'Found %s new atoms in graph.', ), 'This call takes %s parameter(s), but only %s are documented.' => array( array( 'This call takes %s parameter, but only %s is documented.', 'This call takes %s parameter, but only %s are documented.', ), array( 'This call takes %s parameters, but only %s is documented.', 'This call takes %s parameters, but only %s are documented.', ), ), '%s Passed Test(s)' => '%s Passed', '%s Failed Test(s)' => '%s Failed', '%s Skipped Test(s)' => '%s Skipped', '%s Broken Test(s)' => '%s Broken', '%s Unsound Test(s)' => '%s Unsound', '%s Other Test(s)' => '%s Other', '%s Bulk Task(s)' => array( '%s Task', '%s Tasks', ), '%s added %s badge(s) for %s: %s.' => array( array( '%s added a badge for %s: %3$s.', '%s added badges for %s: %3$s.', ), ), '%s added %s badge(s): %s.' => array( array( '%s added a badge: %3$s.', '%s added badges: %3$s.', ), ), '%s awarded %s recipient(s) for %s: %s.' => array( array( '%s awarded %3$s to %4$s.', '%s awarded %3$s to multiple recipients: %4$s.', ), ), '%s awarded %s recipients(s): %s.' => array( array( '%s awarded a recipient: %3$s.', '%s awarded multiple recipients: %3$s.', ), ), '%s edited badge(s) for %s, added %s: %s; revoked %s: %s.' => array( array( '%s edited badges for %s, added %s: %s; revoked %s: %s.', '%s edited badges for %s, added %s: %s; revoked %s: %s.', ), ), '%s edited badge(s), added %s: %s; revoked %s: %s.' => array( array( '%s edited badges, added %s: %s; revoked %s: %s.', '%s edited badges, added %s: %s; revoked %s: %s.', ), ), '%s edited recipient(s) for %s, awarded %s: %s; revoked %s: %s.' => array( array( '%s edited recipients for %s, awarded %s: %s; revoked %s: %s.', '%s edited recipients for %s, awarded %s: %s; revoked %s: %s.', ), ), '%s edited recipient(s), awarded %s: %s; revoked %s: %s.' => array( array( '%s edited recipients, awarded %s: %s; revoked %s: %s.', '%s edited recipients, awarded %s: %s; revoked %s: %s.', ), ), '%s revoked %s badge(s) for %s: %s.' => array( array( '%s revoked a badge for %3$s: %4$s.', '%s revoked multiple badges for %3$s: %4$s.', ), ), '%s revoked %s badge(s): %s.' => array( array( '%s revoked a badge: %3$s.', '%s revoked multiple badges: %3$s.', ), ), '%s revoked %s recipient(s) for %s: %s.' => array( array( '%s revoked %3$s from %4$s.', '%s revoked multiple recipients for %3$s: %4$s.', ), ), '%s revoked %s recipients(s): %s.' => array( array( '%s revoked a recipient: %3$s.', '%s revoked multiple recipients: %3$s.', ), ), '%s automatically subscribed target(s) were not affected: %s.' => array( 'An automatically subscribed target was not affected: %2$s.', 'Automatically subscribed targets were not affected: %2$s.', ), 'Declined to resubscribe %s target(s) because they previously '. 'unsubscribed: %s.' => array( 'Delined to resubscribe a target because they previously '. 'unsubscribed: %2$s.', 'Declined to resubscribe targets because they previously '. 'unsubscribed: %2$s.', ), '%s target(s) are not subscribed: %s.' => array( 'A target is not subscribed: %2$s.', 'Targets are not subscribed: %2$s.', ), '%s target(s) are already subscribed: %s.' => array( 'A target is already subscribed: %2$s.', 'Targets are already subscribed: %2$s.', ), 'Added %s subscriber(s): %s.' => array( 'Added a subscriber: %2$s.', 'Added subscribers: %2$s.', ), 'Removed %s subscriber(s): %s.' => array( 'Removed a subscriber: %2$s.', 'Removed subscribers: %2$s.', ), 'Queued email to be delivered to %s target(s): %s.' => array( 'Queued email to be delivered to target: %2$s.', 'Queued email to be delivered to targets: %2$s.', ), 'Queued email to be delivered to %s target(s), ignoring their '. 'notification preferences: %s.' => array( 'Queued email to be delivered to target, ignoring notification '. 'preferences: %2$s.', 'Queued email to be delivered to targets, ignoring notification '. 'preferences: %2$s.', ), '%s project(s) are not associated: %s.' => array( 'A project is not associated: %2$s.', 'Projects are not associated: %2$s.', ), '%s project(s) are already associated: %s.' => array( 'A project is already associated: %2$s.', 'Projects are already associated: %2$s.', ), 'Added %s project(s): %s.' => array( 'Added a project: %2$s.', 'Added projects: %2$s.', ), 'Removed %s project(s): %s.' => array( 'Removed a project: %2$s.', 'Removed projects: %2$s.', ), 'Added %s reviewer(s): %s.' => array( 'Added a reviewer: %2$s.', 'Added reviewers: %2$s.', ), 'Added %s blocking reviewer(s): %s.' => array( 'Added a blocking reviewer: %2$s.', 'Added blocking reviewers: %2$s.', ), 'Required %s signature(s): %s.' => array( 'Required a signature: %2$s.', 'Required signatures: %2$s.', ), 'Started %s build(s): %s.' => array( 'Started a build: %2$s.', 'Started builds: %2$s.', ), 'Added %s auditor(s): %s.' => array( 'Added an auditor: %2$s.', 'Added auditors: %2$s.', ), '%s target(s) do not have permission to see this object: %s.' => array( 'A target does not have permission to see this object: %2$s.', 'Targets do not have permission to see this object: %2$s.', ), 'This action has no effect on %s target(s): %s.' => array( 'This action has no effect on a target: %2$s.', 'This action has no effect on targets: %2$s.', ), 'Mail sent in the last %s day(s).' => array( 'Mail sent in the last day.', 'Mail sent in the last %s days.', ), '%s Day(s)' => array( '%s Day', '%s Days', ), '%s Day(s) Ago' => array( '%s Day Ago', '%s Days Ago', ), 'Setting retention policy for "%s" to %s day(s).' => array( 'Setting retention policy for "%s" to one day.', 'Setting retention policy for "%s" to %s days.', ), 'Waiting %s second(s) for lease to activate.' => array( 'Waiting a second for lease to activate.', 'Waiting %s seconds for lease to activate.', ), '%s changed %s automation blueprint(s), added %s: %s; removed %s: %s.' => '%s changed automation blueprints, added: %4$s; removed: %6$s.', '%s added %s automation blueprint(s): %s.' => array( array( '%s added an automation blueprint: %3$s.', '%s added automation blueprints: %3$s.', ), ), '%s removed %s automation blueprint(s): %s.' => array( array( '%s removed an automation blueprint: %3$s.', '%s removed automation blueprints: %3$s.', ), ), 'WARNING: There are %s unapproved authorization(s)!' => array( 'WARNING: There is an unapproved authorization!', 'WARNING: There are unapproved authorizations!', ), 'Found %s Open Resource(s)' => array( 'Found %s Open Resource', 'Found %s Open Resources', ), '%s Open Resource(s) Remain' => array( '%s Open Resource Remain', '%s Open Resources Remain', ), 'Found %s Blueprint(s)' => array( 'Found %s Blueprint', 'Found %s Blueprints', ), '%s Blueprint(s) Can Allocate' => array( '%s Blueprint Can Allocate', '%s Blueprints Can Allocate', ), '%s Blueprint(s) Enabled' => array( '%s Blueprint Enabled', '%s Blueprints Enabled', ), '%s Event(s)' => array( '%s Event', '%s Events', ), '%s Unit(s)' => array( '%s Unit', '%s Units', ), 'QUEUEING TASKS (%s Commit(s)):' => array( 'QUEUEING TASKS (%s Commit):', 'QUEUEING TASKS (%s Commits):', ), 'Found %s total commit(s); updating...' => array( 'Found %s total commit; updating...', 'Found %s total commits; updating...', ), 'Not enough process slots to schedule the other %s '. 'repository(s) for updates yet.' => array( 'Not enough process slots to schedule the other '.' repository for update yet.', 'Not enough process slots to schedule the other %s '. 'repositories for updates yet.', ), ); } } diff --git a/src/infrastructure/lint/linter/PhabricatorJavelinLinter.php b/src/infrastructure/lint/linter/PhabricatorJavelinLinter.php index ac2957a4af..027e544776 100644 --- a/src/infrastructure/lint/linter/PhabricatorJavelinLinter.php +++ b/src/infrastructure/lint/linter/PhabricatorJavelinLinter.php @@ -1,290 +1,290 @@ symbolsBinary === null) { list($err, $stdout) = exec_manual('which javelinsymbols'); $this->symbolsBinary = ($err ? false : rtrim($stdout)); } return $this->symbolsBinary; } public function willLintPaths(array $paths) { if (!$this->getBinaryPath()) { return; } $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/scripts/__init_script__.php'; $futures = array(); foreach ($paths as $path) { if ($this->shouldIgnorePath($path)) { continue; } $future = $this->newSymbolsFuture($path); $futures[$path] = $future; } foreach (id(new FutureIterator($futures))->limit(8) as $path => $future) { $this->symbols[$path] = $future->resolvex(); } } public function getLinterName() { return 'JAVELIN'; } public function getLinterConfigurationName() { return 'javelin'; } public function getLintSeverityMap() { return array( self::LINT_MISSING_BINARY => ArcanistLintSeverity::SEVERITY_WARNING, ); } public function getLintNameMap() { return array( self::LINT_PRIVATE_ACCESS => pht('Private Method/Member Access'), self::LINT_MISSING_DEPENDENCY => pht('Missing Javelin Dependency'), self::LINT_UNNECESSARY_DEPENDENCY => pht('Unnecessary Javelin Dependency'), self::LINT_UNKNOWN_DEPENDENCY => pht('Unknown Javelin Dependency'), self::LINT_MISSING_BINARY => pht('`%s` Not In Path', 'javelinsymbols'), ); } public function getCacheGranularity() { - return ArcanistLinter::GRANULARITY_REPOSITORY; + return parent::GRANULARITY_REPOSITORY; } public function getCacheVersion() { $version = '0'; $binary_path = $this->getBinaryPath(); if ($binary_path) { $version .= '-'.md5_file($binary_path); } return $version; } private function shouldIgnorePath($path) { return preg_match('@/__tests__/|externals/javelin/docs/@', $path); } public function lintPath($path) { if ($this->shouldIgnorePath($path)) { return; } if (!$this->symbolsBinary) { if (!$this->haveWarnedAboutBinary) { $this->haveWarnedAboutBinary = true; // TODO: Write build documentation for the Javelin binaries and point // the user at it. $this->raiseLintAtLine( 1, 0, self::LINT_MISSING_BINARY, pht( "The '%s' binary in the Javelin project is not available in %s, ". "so the Javelin linter can't run. This isn't a big concern, ". "but means some Javelin problems can't be automatically detected.", 'javelinsymbols', '$PATH')); } return; } list($uses, $installs) = $this->getUsedAndInstalledSymbolsForPath($path); foreach ($uses as $symbol => $line) { $parts = explode('.', $symbol); foreach ($parts as $part) { if ($part[0] == '_' && $part[1] != '_') { $base = implode('.', array_slice($parts, 0, 2)); if (!array_key_exists($base, $installs)) { $this->raiseLintAtLine( $line, 0, self::LINT_PRIVATE_ACCESS, pht( "This file accesses private symbol '%s' across file ". "boundaries. You may only access private members and methods ". "from the file where they are defined.", $symbol)); } break; } } } $external_classes = array(); foreach ($uses as $symbol => $line) { $parts = explode('.', $symbol); $class = implode('.', array_slice($parts, 0, 2)); if (!array_key_exists($class, $external_classes) && !array_key_exists($class, $installs)) { $external_classes[$class] = $line; } } $celerity = CelerityResourceMap::getNamedInstance('phabricator'); $path = preg_replace( '@^externals/javelinjs/src/@', 'webroot/rsrc/js/javelin/', $path); $need = $external_classes; $resource_name = substr($path, strlen('webroot/')); $requires = $celerity->getRequiredSymbolsForName($resource_name); if (!$requires) { $requires = array(); } foreach ($requires as $key => $requires_symbol) { $requires_name = $celerity->getResourceNameForSymbol($requires_symbol); if ($requires_name === null) { $this->raiseLintAtLine( 0, 0, self::LINT_UNKNOWN_DEPENDENCY, pht( "This file %s component '%s', but it does not exist. ". "You may need to rebuild the Celerity map.", '@requires', $requires_symbol)); unset($requires[$key]); continue; } if (preg_match('/\\.css$/', $requires_name)) { // If JS requires CSS, just assume everything is fine. unset($requires[$key]); } else { $symbol_path = 'webroot/'.$requires_name; list($ignored, $req_install) = $this->getUsedAndInstalledSymbolsForPath( $symbol_path); if (array_intersect_key($req_install, $external_classes)) { $need = array_diff_key($need, $req_install); unset($requires[$key]); } } } foreach ($need as $class => $line) { $this->raiseLintAtLine( $line, 0, self::LINT_MISSING_DEPENDENCY, pht( "This file uses '%s' but does not @requires the component ". "which installs it. You may need to rebuild the Celerity map.", $class)); } foreach ($requires as $component) { $this->raiseLintAtLine( 0, 0, self::LINT_UNNECESSARY_DEPENDENCY, pht( "This file %s component '%s' but does not use anything it provides.", '@requires', $component)); } } private function loadSymbols($path) { if (empty($this->symbols[$path])) { $this->symbols[$path] = $this->newSymbolsFuture($path)->resolvex(); } return $this->symbols[$path]; } private function newSymbolsFuture($path) { $future = new ExecFuture('javelinsymbols # %s', $path); $future->write($this->getData($path)); return $future; } private function getUsedAndInstalledSymbolsForPath($path) { list($symbols) = $this->loadSymbols($path); $symbols = trim($symbols); $uses = array(); $installs = array(); if (empty($symbols)) { // This file has no symbols. return array($uses, $installs); } $symbols = explode("\n", trim($symbols)); foreach ($symbols as $line) { $matches = null; if (!preg_match('/^([?+\*])([^:]*):(\d+)$/', $line, $matches)) { throw new Exception( pht('Received malformed output from `%s`.', 'javelinsymbols')); } $type = $matches[1]; $symbol = $matches[2]; $line = $matches[3]; switch ($type) { case '?': $uses[$symbol] = $line; break; case '+': $installs['JX.'.$symbol] = $line; break; } } $contents = $this->getData($path); $matches = null; $count = preg_match_all( '/@javelin-installs\W+(\S+)/', $contents, $matches, PREG_PATTERN_ORDER); if ($count) { foreach ($matches[1] as $symbol) { $installs[$symbol] = 0; } } return array($uses, $installs); } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php index e7437a30b9..465f8728cc 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php @@ -1,66 +1,64 @@ setName('adjust') ->setExamples('**adjust** [__options__]') ->setSynopsis( pht( 'Make schemata adjustments to correct issues with characters sets, '. 'collations, and keys.')) ->setArguments( array( array( 'name' => 'unsafe', 'help' => pht( 'Permit adjustments which truncate data. This option may '. 'destroy some data, but the lost data is usually not '. 'important (most commonly, the ends of very long object '. 'titles).'), ), )); } - public function execute(PhutilArgumentParser $args) { - $force = $args->getArg('force'); + public function didExecute(PhutilArgumentParser $args) { $unsafe = $args->getArg('unsafe'); - $dry_run = $args->getArg('dryrun'); $this->requireAllPatchesApplied(); - return $this->adjustSchemata($force, $unsafe, $dry_run); + return $this->adjustSchemata($unsafe); } private function requireAllPatchesApplied() { $api = $this->getAPI(); $applied = $api->getAppliedPatches(); if ($applied === null) { throw new PhutilArgumentUsageException( pht( 'You have not initialized the database yet. You must initialize '. 'the database before you can adjust schemata. Run `%s` '. 'to initialize the database.', 'storage upgrade')); } $applied = array_fuse($applied); $patches = $this->getPatches(); $patches = mpull($patches, null, 'getFullKey'); $missing = array_diff_key($patches, $applied); if ($missing) { throw new PhutilArgumentUsageException( pht( 'You have not applied all available storage patches yet. You must '. 'apply all available patches before you can adjust schemata. '. 'Run `%s` to show patch status, and `%s` to apply missing patches.', 'storage status', 'storage upgrade')); } } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php index 5509a32d73..b10e9b9e41 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php @@ -1,23 +1,22 @@ setName('databases') ->setExamples('**databases** [__options__]') ->setSynopsis(pht('List Phabricator databases.')); } - public function execute(PhutilArgumentParser $args) { - $api = $this->getAPI(); + public function didExecute(PhutilArgumentParser $args) { + $api = $this->getAPI(); $patches = $this->getPatches(); - $databases = $api->getDatabaseList($patches, $only_living = true); + $databases = $api->getDatabaseList($patches, true); echo implode("\n", $databases)."\n"; - return 0; } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php index 6c1c216359..9bf5906148 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php @@ -1,84 +1,89 @@ setName('destroy') ->setExamples('**destroy** [__options__]') ->setSynopsis(pht('Permanently destroy all storage and data.')) ->setArguments( array( array( 'name' => 'unittest-fixtures', 'help' => pht( 'Restrict **destroy** operations to databases created '. 'by %s test fixtures.', 'PhabricatorTestCase'), ), )); } - public function execute(PhutilArgumentParser $args) { - $is_dry = $args->getArg('dryrun'); - $is_force = $args->getArg('force'); + public function didExecute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); - if (!$is_dry && !$is_force) { - echo phutil_console_wrap( - pht( - 'Are you completely sure you really want to permanently destroy all '. - 'storage for Phabricator data? This operation can not be undone and '. - 'your data will not be recoverable if you proceed.')); + if (!$this->isDryRun() && !$this->isForce()) { + $console->writeOut( + phutil_console_wrap( + pht( + 'Are you completely sure you really want to permanently destroy '. + 'all storage for Phabricator data? This operation can not be '. + 'undone and your data will not be recoverable if you proceed.'))); if (!phutil_console_confirm(pht('Permanently destroy all data?'))) { - echo pht('Cancelled.')."\n"; + $console->writeOut("%s\n", pht('Cancelled.')); exit(1); } if (!phutil_console_confirm(pht('Really destroy all data forever?'))) { - echo pht('Cancelled.')."\n"; + $console->writeOut("%s\n", pht('Cancelled.')); exit(1); } } - $api = $this->getAPI(); + $api = $this->getAPI(); $patches = $this->getPatches(); if ($args->getArg('unittest-fixtures')) { $conn = $api->getConn(null); $databases = queryfx_all( $conn, 'SELECT DISTINCT(TABLE_SCHEMA) AS db '. 'FROM INFORMATION_SCHEMA.TABLES '. 'WHERE TABLE_SCHEMA LIKE %>', PhabricatorTestCase::NAMESPACE_PREFIX); $databases = ipull($databases, 'db'); } else { - $databases = $api->getDatabaseList($patches); + $databases = $api->getDatabaseList($patches); $databases[] = $api->getDatabaseName('meta_data'); + // These are legacy databases that were dropped long ago. See T2237. $databases[] = $api->getDatabaseName('phid'); $databases[] = $api->getDatabaseName('directory'); } foreach ($databases as $database) { - if ($is_dry) { - echo pht("DRYRUN: Would drop database '%s'.", $database)."\n"; + if ($this->isDryRun()) { + $console->writeOut( + "%s\n", + pht("DRYRUN: Would drop database '%s'.", $database)); } else { - echo pht("Dropping database '%s'...", $database)."\n"; + $console->writeOut( + "%s\n", + pht("Dropping database '%s'...", $database)); queryfx( $api->getConn(null), 'DROP DATABASE IF EXISTS %T', $database); } } - if (!$is_dry) { - echo pht('Storage was destroyed.')."\n"; + if (!$this->isDryRun()) { + $console->writeOut("%s\n", pht('Storage was destroyed.')); } return 0; } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php index e9a09bccd1..38ce117a08 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -1,57 +1,58 @@ setName('dump') ->setExamples('**dump** [__options__]') ->setSynopsis(pht('Dump all data in storage to stdout.')); } - public function execute(PhutilArgumentParser $args) { - $console = PhutilConsole::getConsole(); - $api = $this->getAPI(); + public function didExecute(PhutilArgumentParser $args) { + $api = $this->getAPI(); $patches = $this->getPatches(); + $console = PhutilConsole::getConsole(); + $applied = $api->getAppliedPatches(); if ($applied === null) { $namespace = $api->getNamespace(); $console->writeErr( pht( '**Storage Not Initialized**: There is no database storage '. 'initialized in this storage namespace ("%s"). Use '. '**%s** to initialize storage.', $namespace, - 'storage upgrade')); + './bin/storage upgrade')); return 1; } - $databases = $api->getDatabaseList($patches, $only_living = true); + $databases = $api->getDatabaseList($patches, true); list($host, $port) = $this->getBareHostAndPort($api->getHost()); $flag_password = ''; $password = $api->getPassword(); if ($password) { if (strlen($password->openEnvelope())) { $flag_password = csprintf('-p%P', $password); } } $flag_port = $port ? csprintf('--port %d', $port) : ''; return phutil_passthru( 'mysqldump --hex-blob --single-transaction --default-character-set=utf8 '. '-u %s %C -h %s %C --databases %Ls', $api->getUser(), $flag_password, $host, $flag_port, $databases); } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php index 8e373d0cf6..d65b9bd85a 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php @@ -1,101 +1,101 @@ setName('probe') ->setExamples('**probe**') ->setSynopsis(pht('Show approximate table sizes.')); } - public function execute(PhutilArgumentParser $args) { + public function didExecute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); $console->writeErr( "%s\n", pht('Analyzing table sizes (this may take a moment)...')); - $api = $this->getAPI(); - $patches = $this->getPatches(); - $databases = $api->getDatabaseList($patches, $only_living = true); + $api = $this->getAPI(); + $patches = $this->getPatches(); + $databases = $api->getDatabaseList($patches, true); $conn_r = $api->getConn(null); $data = array(); foreach ($databases as $database) { queryfx($conn_r, 'USE %C', $database); $tables = queryfx_all( $conn_r, 'SHOW TABLE STATUS'); $tables = ipull($tables, null, 'Name'); $data[$database] = $tables; } $totals = array_fill_keys(array_keys($data), 0); $overall = 0; foreach ($data as $db => $tables) { foreach ($tables as $table => $info) { $table_size = $info['Data_length'] + $info['Index_length']; $data[$db][$table]['_totalSize'] = $table_size; $totals[$db] += $table_size; $overall += $table_size; } } asort($totals); $table = id(new PhutilConsoleTable()) ->setShowHeader(false) ->setPadding(2) ->addColumn('name', array('title' => pht('Database / Table'))) ->addColumn('size', array('title' => pht('Size'))) ->addColumn('percentage', array('title' => pht('Percentage'))); foreach ($totals as $db => $size) { list($database_size, $database_percentage) = $this->formatSize( $totals[$db], $overall); $table->addRow(array( 'name' => tsprintf('**%s**', $db), 'size' => tsprintf('**%s**', $database_size), 'percentage' => tsprintf('**%s**', $database_percentage), )); $data[$db] = isort($data[$db], '_totalSize'); foreach ($data[$db] as $table_name => $info) { list($table_size, $table_percentage) = $this->formatSize( $info['_totalSize'], $overall); $table->addRow(array( 'name' => ' '.$table_name, 'size' => $table_size, 'percentage' => $table_percentage, )); } } list($overall_size, $overall_percentage) = $this->formatSize( $overall, $overall); $table->addRow(array( 'name' => tsprintf('**%s**', pht('TOTAL')), 'size' => tsprintf('**%s**', $overall_size), 'percentage' => tsprintf('**%s**', $overall_percentage), )); $table->draw(); return 0; } private function formatSize($n, $o) { return array( sprintf('%8.8s MB', number_format($n / (1024 * 1024), 1)), sprintf('%3.1f%%', 100 * ($n / $o)), ); } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php index 561e01cedd..2d67d97e48 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php @@ -1,165 +1,169 @@ setName('quickstart') ->setExamples('**quickstart** [__options__]') ->setSynopsis( pht( 'Generate a new quickstart database dump. This command is mostly '. 'useful when developing Phabricator.')) ->setArguments( array( array( 'name' => 'output', 'param' => 'file', 'help' => pht('Specify output file to write.'), ), )); } public function execute(PhutilArgumentParser $args) { + parent::execute($args); + $output = $args->getArg('output'); if (!$output) { throw new PhutilArgumentUsageException( pht( 'Specify a file to write with `%s`.', '--output')); } $namespace = 'phabricator_quickstart_'.Filesystem::readRandomCharacters(8); $bin = dirname(phutil_get_library_root('phabricator')).'/bin/storage'; if (!$this->getAPI()->isCharacterSetAvailable('utf8mb4')) { throw new PhutilArgumentUsageException( pht( 'You can only generate a new quickstart file if MySQL supports '. - 'the utf8mb4 character set (available in MySQL 5.5 and newer). The '. - 'configured server does not support utf8mb4.')); + 'the %s character set (available in MySQL 5.5 and newer). The '. + 'configured server does not support %s.', + 'utf8mb4', + 'utf8mb4')); } $err = phutil_passthru( '%s upgrade --force --no-quickstart --namespace %s', $bin, $namespace); if ($err) { return $err; } $err = phutil_passthru( '%s adjust --force --namespace %s', $bin, $namespace); if ($err) { return $err; } $tmp = new TempFile(); $err = phutil_passthru( '%s dump --namespace %s > %s', $bin, $namespace, $tmp); if ($err) { return $err; } $err = phutil_passthru( '%s destroy --force --namespace %s', $bin, $namespace); if ($err) { return $err; } $dump = Filesystem::readFile($tmp); $dump = str_replace( $namespace, '{$NAMESPACE}', $dump); // NOTE: This is a hack. We can not use `binary` for these columns, because // they are a part of a fulltext index. This regex is avoiding matching a // possible NOT NULL at the end of the line. $old = $dump; $dump = preg_replace( '/`corpus` longtext CHARACTER SET .*? COLLATE [^\s,]+/mi', '`corpus` longtext CHARACTER SET {$CHARSET_FULLTEXT} '. 'COLLATE {$COLLATE_FULLTEXT}', $dump); if ($dump == $old) { // If we didn't make any changes, yell about it. We'll produce an invalid // dump otherwise. throw new PhutilArgumentUsageException( pht( 'Failed to apply hack to adjust %s search column!', 'FULLTEXT')); } $dump = str_replace( 'utf8mb4_bin', '{$COLLATE_TEXT}', $dump); $dump = str_replace( 'utf8mb4_unicode_ci', '{$COLLATE_SORT}', $dump); $dump = str_replace( 'utf8mb4', '{$CHARSET}', $dump); $old = $dump; $dump = preg_replace( '/CHARACTER SET {\$CHARSET} COLLATE {\$COLLATE_SORT}/mi', 'CHARACTER SET {$CHARSET_SORT} COLLATE {$COLLATE_SORT}', $dump); if ($dump == $old) { throw new PhutilArgumentUsageException( pht('Failed to adjust SORT columns!')); } // Strip out a bunch of unnecessary commands which make the dump harder // to handle and slower to import. // Remove character set adjustments and key disables. $dump = preg_replace( '(^/\*.*\*/;$)m', '', $dump); // Remove comments. $dump = preg_replace('/^--.*$/m', '', $dump); // Remove table drops, locks, and unlocks. These are never relevant when - // performing q quickstart. + // performing a quickstart. $dump = preg_replace( '/^(DROP TABLE|LOCK TABLES|UNLOCK TABLES).*$/m', '', $dump); // Collapse adjacent newlines. $dump = preg_replace('/\n\s*\n/', "\n", $dump); $dump = str_replace(';', ";\n", $dump); $dump = trim($dump)."\n"; Filesystem::writeFile($output, $dump); $console = PhutilConsole::getConsole(); $console->writeOut( "** %s ** %s\n", pht('SUCCESS'), pht('Wrote fresh quickstart SQL.')); return 0; } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php index 9de4499b23..ad4960ceba 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php @@ -1,94 +1,100 @@ setName('renamespace') ->setExamples( '**renamespace** [__options__] '. '--in __dump.sql__ --from __old__ --to __new__ > __out.sql__') ->setSynopsis(pht('Change the database namespace of a .sql dump file.')) ->setArguments( array( array( 'name' => 'in', 'param' => 'file', 'help' => pht('SQL dumpfile to process.'), ), array( 'name' => 'from', 'param' => 'namespace', 'help' => pht('Current database namespace used by dumpfile.'), ), array( 'name' => 'to', 'param' => 'namespace', 'help' => pht('Desired database namespace for output.'), ), )); } - public function execute(PhutilArgumentParser $args) { + public function didExecute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); $in = $args->getArg('in'); if (!strlen($in)) { throw new PhutilArgumentUsageException( - pht('Specify the dumpfile to read with --in.')); + pht( + 'Specify the dumpfile to read with %s.', + '--in')); } $from = $args->getArg('from'); if (!strlen($from)) { throw new PhutilArgumentUsageException( - pht('Specify namespace to rename from with --from.')); + pht( + 'Specify namespace to rename from with %s.', + '--from')); } $to = $args->getArg('to'); if (!strlen($to)) { throw new PhutilArgumentUsageException( - pht('Specify namespace to rename to with --to.')); + pht( + 'Specify namespace to rename to with %s.', + '--to')); } $patterns = array( 'use' => '@^(USE `)([^_]+)(_.*)$@', 'create' => '@^(CREATE DATABASE /\*.*?\*/ `)([^_]+)(_.*)$@', ); $found = array_fill_keys(array_keys($patterns), 0); $matches = null; foreach (new LinesOfALargeFile($in) as $line) { foreach ($patterns as $key => $pattern) { if (preg_match($pattern, $line, $matches)) { $namespace = $matches[2]; if ($namespace != $from) { throw new Exception( pht( 'Expected namespace "%s", found "%s": %s.', $from, $namespace, $line)); } $line = $matches[1].$to.$matches[3]; $found[$key]++; } } echo $line."\n"; } // Give the user a chance to catch things if the results are crazy. $console->writeErr( pht( 'Adjusted **%s** create statements and **%s** use statements.', new PhutilNumber($found['create']), new PhutilNumber($found['use']))."\n"); return 0; } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php index 58a7c072a3..c66fc73e98 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php @@ -1,38 +1,40 @@ setName('shell') ->setExamples('**shell** [__options__]') ->setSynopsis(pht('Launch an interactive shell.')); } public function execute(PhutilArgumentParser $args) { + + $api = $this->getAPI(); list($host, $port) = $this->getBareHostAndPort($api->getHost()); $flag_port = $port ? csprintf('--port %d', $port) : ''; $flag_password = ''; $password = $api->getPassword(); if ($password) { if (strlen($password->openEnvelope())) { $flag_password = csprintf('--password=%P', $password); } } return phutil_passthru( 'mysql --default-character-set=utf8 '. '-u %s %C -h %s %C', $api->getUser(), $flag_password, $host, $flag_port); } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementStatusWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementStatusWorkflow.php index 774ae8445c..be4f8e5434 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementStatusWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementStatusWorkflow.php @@ -1,61 +1,61 @@ setName('status') ->setExamples('**status** [__options__]') ->setSynopsis(pht('Show patch application status.')); } - public function execute(PhutilArgumentParser $args) { - $api = $this->getAPI(); + public function didExecute(PhutilArgumentParser $args) { + $api = $this->getAPI(); $patches = $this->getPatches(); $applied = $api->getAppliedPatches(); if ($applied === null) { echo phutil_console_format( "**%s**: %s\n", pht('Database Not Initialized'), - pht('Run **%s** to initialize.', 'storage upgrade')); + pht('Run **%s** to initialize.', './bin/storage upgrade')); return 1; } $table = id(new PhutilConsoleTable()) ->setShowHeader(false) - ->addColumn('id', array('title' => pht('ID'))) - ->addColumn('status', array('title' => pht('Status'))) + ->addColumn('id', array('title' => pht('ID'))) + ->addColumn('status', array('title' => pht('Status'))) ->addColumn('duration', array('title' => pht('Duration'))) - ->addColumn('type', array('title' => pht('Type'))) - ->addColumn('name', array('title' => pht('Name'))); + ->addColumn('type', array('title' => pht('Type'))) + ->addColumn('name', array('title' => pht('Name'))); $durations = $api->getPatchDurations(); foreach ($patches as $patch) { $duration = idx($durations, $patch->getFullKey()); if ($duration === null) { $duration = '-'; } else { $duration = pht('%s us', new PhutilNumber($duration)); } $table->addRow(array( 'id' => $patch->getFullKey(), 'status' => in_array($patch->getFullKey(), $applied) ? pht('Applied') : pht('Not Applied'), 'duration' => $duration, 'type' => $patch->getType(), 'name' => $patch->getName(), )); } $table->draw(); return 0; } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php index d5a4f41cad..70dc90fdf7 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php @@ -1,230 +1,88 @@ setName('upgrade') ->setExamples('**upgrade** [__options__]') ->setSynopsis(pht('Upgrade database schemata.')) ->setArguments( array( array( 'name' => 'apply', 'param' => 'patch', 'help' => pht( 'Apply __patch__ explicitly. This is an advanced feature for '. 'development and debugging; you should not normally use this '. 'flag. This skips adjustment.'), ), array( 'name' => 'no-quickstart', 'help' => pht( - 'Build storage patch-by-patch from scatch, even if it could '. + 'Build storage patch-by-patch from scratch, even if it could '. 'be loaded from the quickstart template.'), ), array( 'name' => 'init-only', 'help' => pht( 'Initialize storage only; do not apply patches or adjustments.'), ), array( 'name' => 'no-adjust', 'help' => pht( 'Do not apply storage adjustments after storage upgrades.'), ), )); } - public function execute(PhutilArgumentParser $args) { - $is_dry = $args->getArg('dryrun'); - $is_force = $args->getArg('force'); - - $api = $this->getAPI(); + public function didExecute(PhutilArgumentParser $args) { + $console = PhutilConsole::getConsole(); $patches = $this->getPatches(); - if (!$is_dry && !$is_force) { - echo phutil_console_wrap( - pht( - 'Before running storage upgrades, you should take down the '. - 'Phabricator web interface and stop any running Phabricator '. - 'daemons (you can disable this warning with %s).', - '--force')); + if (!$this->isDryRun() && !$this->isForce()) { + $console->writeOut( + phutil_console_wrap( + pht( + 'Before running storage upgrades, you should take down the '. + 'Phabricator web interface and stop any running Phabricator '. + 'daemons (you can disable this warning with %s).', + '--force'))); if (!phutil_console_confirm(pht('Are you ready to continue?'))) { - echo pht('Cancelled.')."\n"; + $console->writeOut("%s\n", pht('Cancelled.')); return 1; } } $apply_only = $args->getArg('apply'); if ($apply_only) { if (empty($patches[$apply_only])) { throw new PhutilArgumentUsageException( pht( "%s argument '%s' is not a valid patch. ". "Use '%s' to show patch status.", '--apply', $apply_only, - 'storage status')); + './bin/storage status')); } } $no_quickstart = $args->getArg('no-quickstart'); - $init_only = $args->getArg('init-only'); - $no_adjust = $args->getArg('no-adjust'); - - $applied = $api->getAppliedPatches(); - if ($applied === null) { - - if ($is_dry) { - echo pht( - "DRYRUN: Patch metadata storage doesn't exist yet, ". - "it would be created.\n"); - return 0; - } - - if ($apply_only) { - throw new PhutilArgumentUsageException( - pht( - 'Storage has not been initialized yet, you must initialize '. - 'storage before selectively applying patches.')); - return 1; - } - - $legacy = $api->getLegacyPatches($patches); - if ($legacy || $no_quickstart || $init_only) { - - // If we have legacy patches, we can't quickstart. - - $api->createDatabase('meta_data'); - $api->createTable( - 'meta_data', - 'patch_status', - array( - 'patch VARCHAR(255) NOT NULL PRIMARY KEY COLLATE utf8_general_ci', - 'applied INT UNSIGNED NOT NULL', - )); - - foreach ($legacy as $patch) { - $api->markPatchApplied($patch); - } - } else { - echo pht('Loading quickstart template...')."\n"; - $root = dirname(phutil_get_library_root('phabricator')); - $sql = $root.'/resources/sql/quickstart.sql'; - $api->applyPatchSQL($sql); - } - } - - if ($init_only) { - echo pht('Storage initialized.')."\n"; - return 0; - } - - $applied = $api->getAppliedPatches(); - $applied = array_fuse($applied); - - $skip_mark = false; - if ($apply_only) { - if (isset($applied[$apply_only])) { - - unset($applied[$apply_only]); - $skip_mark = true; - - if (!$is_force && !$is_dry) { - echo phutil_console_wrap( - pht( - "Patch '%s' has already been applied. Are you sure you want ". - "to apply it again? This may put your storage in a state ". - "that the upgrade scripts can not automatically manage.", - $apply_only)); - if (!phutil_console_confirm(pht('Apply patch again?'))) { - echo pht('Cancelled.')."\n"; - return 1; - } - } - } - } - - while (true) { - $applied_something = false; - foreach ($patches as $key => $patch) { - if (isset($applied[$key])) { - unset($patches[$key]); - continue; - } - - if ($apply_only && $apply_only != $key) { - unset($patches[$key]); - continue; - } - - $can_apply = true; - foreach ($patch->getAfter() as $after) { - if (empty($applied[$after])) { - if ($apply_only) { - echo pht( - "Unable to apply patch '%s' because it depends ". - "on patch '%s', which has not been applied.\n", - $apply_only, - $after); - return 1; - } - $can_apply = false; - break; - } - } + $init_only = $args->getArg('init-only'); + $no_adjust = $args->getArg('no-adjust'); - if (!$can_apply) { - continue; - } + $this->upgradeSchemata($apply_only, $no_quickstart, $init_only); - $applied_something = true; - - if ($is_dry) { - echo pht("DRYRUN: Would apply patch '%s'.", $key)."\n"; - } else { - echo pht("Applying patch '%s'...", $key)."\n"; - - $t_begin = microtime(true); - $api->applyPatch($patch); - $t_end = microtime(true); - - if (!$skip_mark) { - $api->markPatchApplied($key, ($t_end - $t_begin)); - } - } - - unset($patches[$key]); - $applied[$key] = true; - } - - if (!$applied_something) { - if (count($patches)) { - throw new Exception( - pht( - 'Some patches could not be applied: %s', - implode(', ', array_keys($patches)))); - } else if (!$is_dry && !$apply_only) { - echo pht( - "Storage is up to date. Use '%s' for details.", - 'storage status')."\n"; - } - break; - } - } - - $console = PhutilConsole::getConsole(); if ($no_adjust || $init_only || $apply_only) { $console->writeOut( "%s\n", pht('Declining to apply storage adjustments.')); return 0; } else { - return $this->adjustSchemata($is_force, $unsafe = false, $is_dry); + return $this->adjustSchemata(false); } } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php index 3203998a40..cbb88ce818 100644 --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php @@ -1,683 +1,902 @@ patches = $patches; + final public function getAPI() { + return $this->api; + } + + final public function setAPI(PhabricatorStorageManagementAPI $api) { + $this->api = $api; + return $this; + } + + final protected function isDryRun() { + return $this->dryRun; + } + + final protected function setDryRun($dry_run) { + $this->dryRun = $dry_run; + return $this; + } + + final protected function isForce() { + return $this->force; + } + + final protected function setForce($force) { + $this->force = $force; return $this; } public function getPatches() { return $this->patches; } - final public function setAPI(PhabricatorStorageManagementAPI $api) { - $this->api = $api; + public function setPatches(array $patches) { + assert_instances_of($patches, 'PhabricatorStoragePatch'); + $this->patches = $patches; return $this; } - final public function getAPI() { - return $this->api; + + public function execute(PhutilArgumentParser $args) { + $this->setDryRun($args->getArg('dryrun')); + $this->setForce($args->getArg('force')); + + $this->didExecute($args); } + public function didExecute(PhutilArgumentParser $args) {} + private function loadSchemata() { $query = id(new PhabricatorConfigSchemaQuery()) ->setAPI($this->getAPI()); $actual = $query->loadActualSchema(); $expect = $query->loadExpectedSchema(); $comp = $query->buildComparisonSchema($expect, $actual); return array($comp, $expect, $actual); } - protected function adjustSchemata($force, $unsafe, $dry_run) { + final protected function adjustSchemata($unsafe) { + $lock = $this->lock(); + + try { + $this->doAdjustSchemata($unsafe); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + } + + final private function doAdjustSchemata($unsafe) { $console = PhutilConsole::getConsole(); $console->writeOut( "%s\n", pht('Verifying database schemata...')); list($adjustments, $errors) = $this->findAdjustments(); $api = $this->getAPI(); if (!$adjustments) { $console->writeOut( "%s\n", pht('Found no adjustments for schemata.')); return $this->printErrors($errors, 0); } - if (!$force && !$api->isCharacterSetAvailable('utf8mb4')) { + if (!$this->force && !$api->isCharacterSetAvailable('utf8mb4')) { $message = pht( "You have an old version of MySQL (older than 5.5) which does not ". "support the utf8mb4 character set. We strongly recomend upgrading to ". "5.5 or newer.\n\n". "If you apply adjustments now and later update MySQL to 5.5 or newer, ". "you'll need to apply adjustments again (and they will take a long ". "time).\n\n". "You can exit this workflow, update MySQL now, and then run this ". "workflow again. This is recommended, but may cause a lot of downtime ". "right now.\n\n". "You can exit this workflow, continue using Phabricator without ". "applying adjustments, update MySQL at a later date, and then run ". "this workflow again. This is also a good approach, and will let you ". "delay downtime until later.\n\n". "You can proceed with this workflow, and then optionally update ". "MySQL at a later date. After you do, you'll need to apply ". "adjustments again.\n\n". "For more information, see \"Managing Storage Adjustments\" in ". "the documentation."); $console->writeOut( "\n** %s **\n\n%s\n", pht('OLD MySQL VERSION'), phutil_console_wrap($message)); $prompt = pht('Continue with old MySQL version?'); if (!phutil_console_confirm($prompt, $default_no = true)) { return; } } $table = id(new PhutilConsoleTable()) ->addColumn('database', array('title' => pht('Database'))) ->addColumn('table', array('title' => pht('Table'))) ->addColumn('name', array('title' => pht('Name'))) ->addColumn('info', array('title' => pht('Issues'))); foreach ($adjustments as $adjust) { $info = array(); foreach ($adjust['issues'] as $issue) { $info[] = PhabricatorConfigStorageSchema::getIssueName($issue); } $table->addRow(array( 'database' => $adjust['database'], 'table' => idx($adjust, 'table'), 'name' => idx($adjust, 'name'), 'info' => implode(', ', $info), )); } $console->writeOut("\n\n"); $table->draw(); - if ($dry_run) { + if ($this->dryRun) { $console->writeOut( "%s\n", pht('DRYRUN: Would apply adjustments.')); return 0; - } else if (!$force) { + } else if (!$this->force) { $console->writeOut( "\n%s\n", pht( "Found %s issues(s) with schemata, detailed above.\n\n". "You can review issues in more detail from the web interface, ". "in Config > Database Status. To better understand the adjustment ". "workflow, see \"Managing Storage Adjustments\" in the ". "documentation.\n\n". "MySQL needs to copy table data to make some adjustments, so these ". "migrations may take some time.", phutil_count($adjustments))); $prompt = pht('Fix these schema issues?'); if (!phutil_console_confirm($prompt, $default_no = true)) { return 1; } } $console->writeOut( "%s\n", pht('Fixing schema issues...')); $conn = $api->getConn(null); if ($unsafe) { queryfx($conn, 'SET SESSION sql_mode = %s', ''); } else { queryfx($conn, 'SET SESSION sql_mode = %s', 'STRICT_ALL_TABLES'); } $failed = array(); // We make changes in several phases. $phases = array( // Drop surplus autoincrements. This allows us to drop primary keys on // autoincrement columns. 'drop_auto', // Drop all keys we're going to adjust. This prevents them from // interfering with column changes. 'drop_keys', // Apply all database, table, and column changes. 'main', // Restore adjusted keys. 'add_keys', // Add missing autoincrements. 'add_auto', ); $bar = id(new PhutilConsoleProgressBar()) ->setTotal(count($adjustments) * count($phases)); foreach ($phases as $phase) { foreach ($adjustments as $adjust) { try { switch ($adjust['kind']) { case 'database': if ($phase == 'main') { queryfx( $conn, 'ALTER DATABASE %T CHARACTER SET = %s COLLATE = %s', $adjust['database'], $adjust['charset'], $adjust['collation']); } break; case 'table': if ($phase == 'main') { queryfx( $conn, 'ALTER TABLE %T.%T COLLATE = %s', $adjust['database'], $adjust['table'], $adjust['collation']); } break; case 'column': $apply = false; $auto = false; $new_auto = idx($adjust, 'auto'); if ($phase == 'drop_auto') { if ($new_auto === false) { $apply = true; $auto = false; } } else if ($phase == 'main') { $apply = true; if ($new_auto === false) { $auto = false; } else { $auto = $adjust['is_auto']; } } else if ($phase == 'add_auto') { if ($new_auto === true) { $apply = true; $auto = true; } } if ($apply) { $parts = array(); if ($auto) { $parts[] = qsprintf( $conn, 'AUTO_INCREMENT'); } if ($adjust['charset']) { $parts[] = qsprintf( $conn, 'CHARACTER SET %Q COLLATE %Q', $adjust['charset'], $adjust['collation']); } queryfx( $conn, 'ALTER TABLE %T.%T MODIFY %T %Q %Q %Q', $adjust['database'], $adjust['table'], $adjust['name'], $adjust['type'], implode(' ', $parts), $adjust['nullable'] ? 'NULL' : 'NOT NULL'); } break; case 'key': if (($phase == 'drop_keys') && $adjust['exists']) { if ($adjust['name'] == 'PRIMARY') { $key_name = 'PRIMARY KEY'; } else { $key_name = qsprintf($conn, 'KEY %T', $adjust['name']); } queryfx( $conn, 'ALTER TABLE %T.%T DROP %Q', $adjust['database'], $adjust['table'], $key_name); } if (($phase == 'add_keys') && $adjust['keep']) { // Different keys need different creation syntax. Notable // special cases are primary keys and fulltext keys. if ($adjust['name'] == 'PRIMARY') { $key_name = 'PRIMARY KEY'; } else if ($adjust['indexType'] == 'FULLTEXT') { $key_name = qsprintf($conn, 'FULLTEXT %T', $adjust['name']); } else { if ($adjust['unique']) { $key_name = qsprintf( $conn, 'UNIQUE KEY %T', $adjust['name']); } else { $key_name = qsprintf( $conn, '/* NONUNIQUE */ KEY %T', $adjust['name']); } } queryfx( $conn, 'ALTER TABLE %T.%T ADD %Q (%Q)', $adjust['database'], $adjust['table'], $key_name, implode(', ', $adjust['columns'])); } break; default: throw new Exception( pht('Unknown schema adjustment kind "%s"!', $adjust['kind'])); } } catch (AphrontQueryException $ex) { $failed[] = array($adjust, $ex); } $bar->update(1); } } $bar->done(); if (!$failed) { $console->writeOut( "%s\n", pht('Completed fixing all schema issues.')); $err = 0; } else { $table = id(new PhutilConsoleTable()) ->addColumn('target', array('title' => pht('Target'))) ->addColumn('error', array('title' => pht('Error'))); foreach ($failed as $failure) { list($adjust, $ex) = $failure; $pieces = array_select_keys( $adjust, array('database', 'table', 'name')); $pieces = array_filter($pieces); $target = implode('.', $pieces); $table->addRow( array( 'target' => $target, 'error' => $ex->getMessage(), )); } $console->writeOut("\n"); $table->draw(); $console->writeOut( "\n%s\n", pht('Failed to make some schema adjustments, detailed above.')); $console->writeOut( "%s\n", pht( 'For help troubleshooting adjustments, see "Managing Storage '. 'Adjustments" in the documentation.')); $err = 1; } return $this->printErrors($errors, $err); } private function findAdjustments() { list($comp, $expect, $actual) = $this->loadSchemata(); $issue_charset = PhabricatorConfigStorageSchema::ISSUE_CHARSET; $issue_collation = PhabricatorConfigStorageSchema::ISSUE_COLLATION; $issue_columntype = PhabricatorConfigStorageSchema::ISSUE_COLUMNTYPE; $issue_surpluskey = PhabricatorConfigStorageSchema::ISSUE_SURPLUSKEY; $issue_missingkey = PhabricatorConfigStorageSchema::ISSUE_MISSINGKEY; $issue_columns = PhabricatorConfigStorageSchema::ISSUE_KEYCOLUMNS; $issue_unique = PhabricatorConfigStorageSchema::ISSUE_UNIQUE; $issue_longkey = PhabricatorConfigStorageSchema::ISSUE_LONGKEY; $issue_auto = PhabricatorConfigStorageSchema::ISSUE_AUTOINCREMENT; $adjustments = array(); $errors = array(); foreach ($comp->getDatabases() as $database_name => $database) { foreach ($this->findErrors($database) as $issue) { $errors[] = array( 'database' => $database_name, 'issue' => $issue, ); } $expect_database = $expect->getDatabase($database_name); $actual_database = $actual->getDatabase($database_name); if (!$expect_database || !$actual_database) { // If there's a real issue here, skip this stuff. continue; } $issues = array(); if ($database->hasIssue($issue_charset)) { $issues[] = $issue_charset; } if ($database->hasIssue($issue_collation)) { $issues[] = $issue_collation; } if ($issues) { $adjustments[] = array( 'kind' => 'database', 'database' => $database_name, 'issues' => $issues, 'charset' => $expect_database->getCharacterSet(), 'collation' => $expect_database->getCollation(), ); } foreach ($database->getTables() as $table_name => $table) { foreach ($this->findErrors($table) as $issue) { $errors[] = array( 'database' => $database_name, 'table' => $table_name, 'issue' => $issue, ); } $expect_table = $expect_database->getTable($table_name); $actual_table = $actual_database->getTable($table_name); if (!$expect_table || !$actual_table) { continue; } $issues = array(); if ($table->hasIssue($issue_collation)) { $issues[] = $issue_collation; } if ($issues) { $adjustments[] = array( 'kind' => 'table', 'database' => $database_name, 'table' => $table_name, 'issues' => $issues, 'collation' => $expect_table->getCollation(), ); } foreach ($table->getColumns() as $column_name => $column) { foreach ($this->findErrors($column) as $issue) { $errors[] = array( 'database' => $database_name, 'table' => $table_name, 'name' => $column_name, 'issue' => $issue, ); } $expect_column = $expect_table->getColumn($column_name); $actual_column = $actual_table->getColumn($column_name); if (!$expect_column || !$actual_column) { continue; } $issues = array(); if ($column->hasIssue($issue_collation)) { $issues[] = $issue_collation; } if ($column->hasIssue($issue_charset)) { $issues[] = $issue_charset; } if ($column->hasIssue($issue_columntype)) { $issues[] = $issue_columntype; } if ($column->hasIssue($issue_auto)) { $issues[] = $issue_auto; } if ($issues) { if ($expect_column->getCharacterSet() === null) { // For non-text columns, we won't be specifying a collation or // character set. $charset = null; $collation = null; } else { $charset = $expect_column->getCharacterSet(); $collation = $expect_column->getCollation(); } $adjustment = array( 'kind' => 'column', 'database' => $database_name, 'table' => $table_name, 'name' => $column_name, 'issues' => $issues, 'collation' => $collation, 'charset' => $charset, 'type' => $expect_column->getColumnType(), // NOTE: We don't adjust column nullability because it is // dangerous, so always use the current nullability. 'nullable' => $actual_column->getNullable(), // NOTE: This always stores the current value, because we have // to make these updates separately. 'is_auto' => $actual_column->getAutoIncrement(), ); if ($column->hasIssue($issue_auto)) { $adjustment['auto'] = $expect_column->getAutoIncrement(); } $adjustments[] = $adjustment; } } foreach ($table->getKeys() as $key_name => $key) { foreach ($this->findErrors($key) as $issue) { $errors[] = array( 'database' => $database_name, 'table' => $table_name, 'name' => $key_name, 'issue' => $issue, ); } $expect_key = $expect_table->getKey($key_name); $actual_key = $actual_table->getKey($key_name); $issues = array(); $keep_key = true; if ($key->hasIssue($issue_surpluskey)) { $issues[] = $issue_surpluskey; $keep_key = false; } if ($key->hasIssue($issue_missingkey)) { $issues[] = $issue_missingkey; } if ($key->hasIssue($issue_columns)) { $issues[] = $issue_columns; } if ($key->hasIssue($issue_unique)) { $issues[] = $issue_unique; } // NOTE: We can't really fix this, per se, but we may need to remove // the key to change the column type. In the best case, the new // column type won't be overlong and recreating the key really will // fix the issue. In the worst case, we get the right column type and // lose the key, which is still better than retaining the key having // the wrong column type. if ($key->hasIssue($issue_longkey)) { $issues[] = $issue_longkey; } if ($issues) { $adjustment = array( 'kind' => 'key', 'database' => $database_name, 'table' => $table_name, 'name' => $key_name, 'issues' => $issues, 'exists' => (bool)$actual_key, 'keep' => $keep_key, ); if ($keep_key) { $adjustment += array( 'columns' => $expect_key->getColumnNames(), 'unique' => $expect_key->getUnique(), 'indexType' => $expect_key->getIndexType(), ); } $adjustments[] = $adjustment; } } } } return array($adjustments, $errors); } private function findErrors(PhabricatorConfigStorageSchema $schema) { $result = array(); foreach ($schema->getLocalIssues() as $issue) { $status = PhabricatorConfigStorageSchema::getIssueStatus($issue); if ($status == PhabricatorConfigStorageSchema::STATUS_FAIL) { $result[] = $issue; } } return $result; } private function printErrors(array $errors, $default_return) { if (!$errors) { return $default_return; } $console = PhutilConsole::getConsole(); $table = id(new PhutilConsoleTable()) ->addColumn('target', array('title' => pht('Target'))) ->addColumn('error', array('title' => pht('Error'))); $any_surplus = false; $all_surplus = true; foreach ($errors as $error) { $pieces = array_select_keys( $error, array('database', 'table', 'name')); $pieces = array_filter($pieces); $target = implode('.', $pieces); $name = PhabricatorConfigStorageSchema::getIssueName($error['issue']); if ($error['issue'] === PhabricatorConfigStorageSchema::ISSUE_SURPLUS) { $any_surplus = true; } else { $all_surplus = false; } $table->addRow( array( 'target' => $target, 'error' => $name, )); } $console->writeOut("\n"); $table->draw(); $console->writeOut("\n"); $message = array(); if ($all_surplus) { $message[] = pht( 'You have surplus schemata (extra tables or columns which Phabricator '. 'does not expect). For information on resolving these '. 'issues, see the "Surplus Schemata" section in the "Managing Storage '. 'Adjustments" article in the documentation.'); } else { $message[] = pht( 'The schemata have errors (detailed above) which the adjustment '. 'workflow can not fix.'); if ($any_surplus) { $message[] = pht( 'Some of these errors are caused by surplus schemata (extra '. 'tables or columns which Phabricator does not expect). These are '. 'not serious. For information on resolving these issues, see the '. '"Surplus Schemata" section in the "Managing Storage Adjustments" '. 'article in the documentation.'); } $message[] = pht( 'If you are not developing Phabricator itself, report this issue to '. 'the upstream.'); $message[] = pht( 'If you are developing Phabricator, these errors usually indicate '. 'that your schema specifications do not agree with the schemata your '. 'code actually builds.'); } $message = implode("\n\n", $message); if ($all_surplus) { $console->writeOut( "** %s **\n\n%s\n", pht('SURPLUS SCHEMATA'), phutil_console_wrap($message)); } else { $console->writeOut( "** %s **\n\n%s\n", pht('SCHEMATA ERRORS'), phutil_console_wrap($message)); } return 2; } + final protected function upgradeSchemata( + $apply_only = null, + $no_quickstart = false, + $init_only = false) { + + $lock = $this->lock(); + + try { + $this->doUpgradeSchemata($apply_only, $no_quickstart, $init_only); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + } + + final private function doUpgradeSchemata( + $apply_only, + $no_quickstart, + $init_only) { + + $api = $this->getAPI(); + + $applied = $this->getApi()->getAppliedPatches(); + if ($applied === null) { + if ($this->dryRun) { + echo pht( + "DRYRUN: Patch metadata storage doesn't exist yet, ". + "it would be created.\n"); + return 0; + } + + if ($apply_only) { + throw new PhutilArgumentUsageException( + pht( + 'Storage has not been initialized yet, you must initialize '. + 'storage before selectively applying patches.')); + return 1; + } + + $legacy = $api->getLegacyPatches($this->patches); + if ($legacy || $no_quickstart || $init_only) { + + // If we have legacy patches, we can't quickstart. + + $api->createDatabase('meta_data'); + $api->createTable( + 'meta_data', + 'patch_status', + array( + 'patch VARCHAR(255) NOT NULL PRIMARY KEY COLLATE utf8_general_ci', + 'applied INT UNSIGNED NOT NULL', + )); + + foreach ($legacy as $patch) { + $api->markPatchApplied($patch); + } + } else { + echo pht('Loading quickstart template...')."\n"; + $root = dirname(phutil_get_library_root('phabricator')); + $sql = $root.'/resources/sql/quickstart.sql'; + $api->applyPatchSQL($sql); + } + } + + if ($init_only) { + echo pht('Storage initialized.')."\n"; + return 0; + } + + $applied = $api->getAppliedPatches(); + $applied = array_fuse($applied); + + $skip_mark = false; + if ($apply_only) { + if (isset($applied[$apply_only])) { + + unset($applied[$apply_only]); + $skip_mark = true; + + if (!$this->force && !$this->dryRun) { + echo phutil_console_wrap( + pht( + "Patch '%s' has already been applied. Are you sure you want ". + "to apply it again? This may put your storage in a state ". + "that the upgrade scripts can not automatically manage.", + $apply_only)); + if (!phutil_console_confirm(pht('Apply patch again?'))) { + echo pht('Cancelled.')."\n"; + return 1; + } + } + } + } + + while (true) { + $applied_something = false; + foreach ($this->patches as $key => $patch) { + if (isset($applied[$key])) { + unset($this->patches[$key]); + continue; + } + + if ($apply_only && $apply_only != $key) { + unset($this->patches[$key]); + continue; + } + + $can_apply = true; + foreach ($patch->getAfter() as $after) { + if (empty($applied[$after])) { + if ($apply_only) { + echo pht( + "Unable to apply patch '%s' because it depends ". + "on patch '%s', which has not been applied.\n", + $apply_only, + $after); + return 1; + } + $can_apply = false; + break; + } + } + + if (!$can_apply) { + continue; + } + + $applied_something = true; + + if ($this->dryRun) { + echo pht("DRYRUN: Would apply patch '%s'.", $key)."\n"; + } else { + echo pht("Applying patch '%s'...", $key)."\n"; + + $t_begin = microtime(true); + $api->applyPatch($patch); + $t_end = microtime(true); + + if (!$skip_mark) { + $api->markPatchApplied($key, ($t_end - $t_begin)); + } + } + + unset($this->patches[$key]); + $applied[$key] = true; + } + + if (!$applied_something) { + if (count($this->patches)) { + throw new Exception( + pht( + 'Some patches could not be applied: %s', + implode(', ', array_keys($this->patches)))); + } else if (!$this->dryRun && !$apply_only) { + echo pht( + "Storage is up to date. Use '%s' for details.", + 'storage status')."\n"; + } + break; + } + } + } + final protected function getBareHostAndPort($host) { // Split out port information, since the command-line client requires a // separate flag for the port. $uri = new PhutilURI('mysql://'.$host); if ($uri->getPort()) { $port = $uri->getPort(); $bare_hostname = $uri->getDomain(); } else { $port = null; $bare_hostname = $host; } return array($bare_hostname, $port); } + /** + * Acquires a @{class:PhabricatorGlobalLock}. + * + * @return PhabricatorGlobalLock + */ + final protected function lock() { + return PhabricatorGlobalLock::newLock(__CLASS__) + ->useSpecificConnection($this->getApi()->getConn(null)) + ->lock(); + } + } diff --git a/src/infrastructure/util/PhabricatorGlobalLock.php b/src/infrastructure/util/PhabricatorGlobalLock.php index 41558531d7..394f57d9a9 100644 --- a/src/infrastructure/util/PhabricatorGlobalLock.php +++ b/src/infrastructure/util/PhabricatorGlobalLock.php @@ -1,122 +1,137 @@ lock(); * do_contentious_things(); * $lock->unlock(); * * NOTE: This lock is not completely global; it is namespaced to the active * storage namespace so that unit tests running in separate table namespaces * are isolated from one another. * * @task construct Constructing Locks * @task impl Implementation */ final class PhabricatorGlobalLock extends PhutilLock { private $conn; private static $pool = array(); /* -( Constructing Locks )------------------------------------------------- */ public static function newLock($name) { $namespace = PhabricatorLiskDAO::getStorageNamespace(); $namespace = PhabricatorHash::digestToLength($namespace, 20); $full_name = 'ph:'.$namespace.':'.$name; $length_limit = 64; if (strlen($full_name) > $length_limit) { throw new Exception( pht( 'Lock name "%s" is too long (full lock name is "%s"). The '. 'full lock name must not be longer than %s bytes.', $name, $full_name, new PhutilNumber($length_limit))); } $lock = self::getLock($full_name); if (!$lock) { $lock = new PhabricatorGlobalLock($full_name); self::registerLock($lock); } return $lock; } + /** + * Use a specific database connection for locking. + * + * By default, `PhabricatorGlobalLock` will lock on the "repository" database + * (somewhat arbitrarily). In most cases this is fine, but this method can + * be used to lock on a specific connection. + * + * @param AphrontDatabaseConnection + * @return this + */ + public function useSpecificConnection(AphrontDatabaseConnection $conn) { + $this->conn = $conn; + return $this; + } + /* -( Implementation )----------------------------------------------------- */ protected function doLock($wait) { $conn = $this->conn; if (!$conn) { // Try to reuse a connection from the connection pool. $conn = array_pop(self::$pool); } if (!$conn) { // NOTE: Using the 'repository' database somewhat arbitrarily, mostly // because the first client of locks is the repository daemons. We must // always use the same database for all locks, but don't access any // tables so we could use any valid database. We could build a // database-free connection instead, but that's kind of messy and we // might forget about it in the future if we vertically partition the // application. $dao = new PhabricatorRepository(); // NOTE: Using "force_new" to make sure each lock is on its own // connection. $conn = $dao->establishConnection('w', $force_new = true); - - // NOTE: Since MySQL will disconnect us if we're idle for too long, we set - // the wait_timeout to an enormous value, to allow us to hold the - // connection open indefinitely (or, at least, for 24 days). - $max_allowed_timeout = 2147483; - queryfx($conn, 'SET wait_timeout = %d', $max_allowed_timeout); } + // NOTE: Since MySQL will disconnect us if we're idle for too long, we set + // the wait_timeout to an enormous value, to allow us to hold the + // connection open indefinitely (or, at least, for 24 days). + $max_allowed_timeout = 2147483; + queryfx($conn, 'SET wait_timeout = %d', $max_allowed_timeout); + $result = queryfx_one( $conn, 'SELECT GET_LOCK(%s, %f)', $this->getName(), $wait); $ok = head($result); if (!$ok) { throw new PhutilLockException($this->getName()); } $this->conn = $conn; } protected function doUnlock() { queryfx( $this->conn, 'SELECT RELEASE_LOCK(%s)', $this->getName()); $this->conn->close(); self::$pool[] = $this->conn; $this->conn = null; } } diff --git a/src/view/phui/PHUIDocumentSummaryView.php b/src/view/phui/PHUIDocumentSummaryView.php new file mode 100644 index 0000000000..8b4ad26dd5 --- /dev/null +++ b/src/view/phui/PHUIDocumentSummaryView.php @@ -0,0 +1,96 @@ +title = $title; + return $this; + } + + public function setSubtitle($subtitle) { + $this->subtitle = $subtitle; + return $this; + } + + public function setImage($image) { + $this->image = $image; + return $this; + } + + public function setImageHref($image_href) { + $this->imageHref = $image_href; + return $this; + } + + public function setHref($href) { + $this->href = $href; + return $this; + } + + public function setSummary($summary) { + $this->summary = $summary; + return $this; + } + + public function setDraft($draft) { + $this->draft = $draft; + return $this; + } + + protected function getTagAttributes() { + $classes = array(); + $classes[] = 'phui-document-summary-view'; + $classes[] = 'phabricator-remarkup'; + + if ($this->draft) { + $classes[] = 'is-draft'; + } + + return array( + 'class' => implode(' ', $classes), + ); + } + + protected function getTagContent() { + require_celerity_resource('phui-document-summary-view-css'); + + $title = phutil_tag( + 'a', + array( + 'href' => $this->href, + ), + $this->title); + + $header = phutil_tag( + 'h2', + array( + 'class' => 'remarkup-header', + ), + $title); + + $subtitle = phutil_tag( + 'div', + array( + 'class' => 'phui-document-summary-subtitle', + ), + $this->subtitle); + + $body = phutil_tag( + 'div', + array( + 'class' => 'phui-document-summary-body', + ), + $this->summary); + + return array($header, $subtitle, $body); + } + +} diff --git a/src/view/phui/PHUIDocumentViewPro.php b/src/view/phui/PHUIDocumentViewPro.php index 94854fba96..c18732e2c6 100644 --- a/src/view/phui/PHUIDocumentViewPro.php +++ b/src/view/phui/PHUIDocumentViewPro.php @@ -1,136 +1,125 @@ setTall(true); $this->header = $header; return $this; } public function setBook($name, $description) { $this->bookname = $name; $this->bookdescription = $description; return $this; } public function setFluid($fluid) { $this->fluid = $fluid; return $this; } - public function setPropertyList($view) { - $this->propertyList = $view; - return $this; - } - public function setToc($toc) { $this->toc = $toc; return $this; } protected function getTagAttributes() { $classes = array(); + $classes[] = 'phui-document-container'; if ($this->fluid) { $classes[] = 'phui-document-fluid'; } return array( - 'class' => $classes, + 'class' => implode(' ', $classes), ); } protected function getTagContent() { require_celerity_resource('phui-document-view-css'); require_celerity_resource('phui-document-view-pro-css'); Javelin::initBehavior('phabricator-reveal-content'); $classes = array(); $classes[] = 'phui-document-view'; $classes[] = 'phui-document-view-pro'; $book = null; if ($this->bookname) { $book = pht('%s (%s)', $this->bookname, $this->bookdescription); } $main_content = $this->renderChildren(); if ($book) { $this->header->setSubheader($book); } $table_of_contents = null; if ($this->toc) { $toc = array(); $toc_id = celerity_generate_unique_node_id(); $toc[] = id(new PHUIButtonView()) ->setTag('a') ->setIconFont('fa-align-left') ->setColor(PHUIButtonView::SIMPLE) ->addClass('phui-document-toc') ->addSigil('jx-toggle-class') ->setMetaData(array( 'map' => array( $toc_id => 'phui-document-toc-open', ), )); $toc[] = phutil_tag( 'div', array( 'class' => 'phui-list-sidenav phui-document-toc-list', ), $this->toc); $table_of_contents = phutil_tag( 'div', array( 'class' => 'phui-document-toc-container', 'id' => $toc_id, ), $toc); } $content_inner = phutil_tag( 'div', array( 'class' => 'phui-document-inner', ), array( $table_of_contents, $this->header, $main_content, )); $content = phutil_tag( 'div', array( 'class' => 'phui-document-content', ), $content_inner); - $view = phutil_tag( - 'div', - array( - 'class' => implode(' ', $classes), - ), - $content); - - $list = null; - if ($this->propertyList) { - $list = phutil_tag_div('phui-document-properties', $this->propertyList); - } + return phutil_tag( + 'div', + array( + 'class' => implode(' ', $classes), + ), + $content); - return array($view, $list); } } diff --git a/src/view/widget/bars/AphrontGlyphBarView.php b/src/view/widget/bars/AphrontGlyphBarView.php index 5b8d606a27..b91a714b26 100644 --- a/src/view/widget/bars/AphrontGlyphBarView.php +++ b/src/view/widget/bars/AphrontGlyphBarView.php @@ -1,102 +1,102 @@ value = $value; return $this; } public function setMax($max) { $this->max = $max; return $this; } public function setNumGlyphs($nn) { $this->numGlyphs = $nn; return $this; } public function setGlyph(PhutilSafeHTML $fg_glyph) { $this->fgGlyph = $fg_glyph; return $this; } public function setBackgroundGlyph(PhutilSafeHTML $bg_glyph) { $this->bgGlyph = $bg_glyph; return $this; } protected function getRatio() { return min($this->value, $this->max) / $this->max; } public function render() { require_celerity_resource('aphront-bars'); $ratio = $this->getRatio(); $percentage = 100 * $ratio; $is_star = false; if ($this->fgGlyph) { $fg_glyph = $this->fgGlyph; if ($this->bgGlyph) { $bg_glyph = $this->bgGlyph; } else { $bg_glyph = $fg_glyph; } } else { $is_star = true; $fg_glyph = self::BLACK_STAR; $bg_glyph = self::WHITE_STAR; } $fg_glyphs = array_fill(0, $this->numGlyphs, $fg_glyph); $bg_glyphs = array_fill(0, $this->numGlyphs, $bg_glyph); $color = $this->getColor(); return phutil_tag( 'div', array( 'class' => "aphront-bar glyph color-{$color}", ), array( phutil_tag( 'div', array( 'class' => 'glyphs'.($is_star ? ' starstar' : ''), ), array( phutil_tag( 'div', array( 'class' => 'fg', 'style' => "width: {$percentage}%;", ), $fg_glyphs), phutil_tag( 'div', array(), $bg_glyphs), )), phutil_tag( 'div', array('class' => 'caption'), $this->getCaption()), )); } } diff --git a/src/view/widget/bars/AphrontProgressBarView.php b/src/view/widget/bars/AphrontProgressBarView.php index 7117435aa5..0a967a4fa5 100644 --- a/src/view/widget/bars/AphrontProgressBarView.php +++ b/src/view/widget/bars/AphrontProgressBarView.php @@ -1,58 +1,58 @@ value = $value; return $this; } public function setMax($max) { $this->max = $max; return $this; } public function setAlt($text) { $this->alt = $text; return $this; } protected function getRatio() { return min($this->value, $this->max) / $this->max; } public function render() { require_celerity_resource('aphront-bars'); $ratio = $this->getRatio(); $width = self::WIDTH * $ratio; $color = $this->getColor(); return phutil_tag_div( "aphront-bar progress color-{$color}", array( phutil_tag( 'div', array('title' => $this->alt), phutil_tag( 'div', array('style' => "width: {$width}px;"), '')), phutil_tag( 'span', array(), $this->getCaption()), )); } } diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index 35ccc5b1b3..9670ddd7ae 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -1,203 +1,203 @@ /** * @provides phabricator-standard-page-view */ .phabricator-anchor-view, .phabricator-anchor-navigation-marker { position: absolute; margin-top: -15px; } .phabricator-chromeless-page .phabricator-standard-page { background: transparent; border-width: 0px; } .phabricator-standard-page-body { clear: both; } .phabricator-standard-page-footer { text-align: right; - margin: 4px 16px; + margin: 32px 16px 16px; padding: 12px 0; - border-top: 1px solid {$lightgreyborder}; + border-top: 1px solid rgba(71, 87, 120, 0.20); color: {$greytext}; } .device .phabricator-standard-page-footer { margin: 4px 8px; } !print .phabricator-standard-page-footer { display: none; } .device-desktop .has-local-nav + .phabricator-standard-page-footer { margin-left: 221px; } .device-desktop div.phabricator-icon-nav + .phabricator-standard-page-footer { margin-left: 58px; } .device .phabricator-side-menu-home + .phabricator-standard-page-footer { display: none; } .keyboard-shortcut-help td, .keyboard-shortcut-help th { padding: 8px; vertical-align: middle; } .keyboard-shortcut-help th { white-space: nowrap; color: {$greytext}; } .keyboard-shortcut-help kbd { background: #222222; padding: 6px; color: #ffffff; font-weight: bold; border: 1px solid #555555; } .keyboard-focus-focus-reticle { background: #ffffd3; position: absolute; border: 1px solid #999900; } a.handle-status-closed { text-decoration: line-through; color: #676767; } a.handle-status-closed:hover { text-decoration: line-through; color: #19558D; } a.handle-availability-disabled, a.handle-availability-none, a.handle-availability-partial { padding-left: 11px; background-repeat: no-repeat; background-position: -4px center; } a.handle-availability-none { background-image: url(/rsrc/image/icon/fatcow/bullet_red.png); } a.handle-availability-partial { background-image: url(/rsrc/image/icon/fatcow/bullet_orange.png); } a.handle-availability-disabled { background-image: url(/rsrc/image/icon/fatcow/bullet_black.png); } .aphront-developer-error-callout { position: relative; padding: 2em; background: #aa0000; color: white; text-align: center; font-size: {$smallerfontsize}; } .setup-warning-callout { padding: 8px 16px; background: {$lightred}; border-bottom: 1px solid {$sh-redborder}; position: relative; } .setup-warning-callout a { color: {$red}; } .phui-handle .phui-icon-view { display: inline-block; margin: 2px 2px -2px 0; } .jx-scrollbar-frame { position: relative; overflow: hidden; } .jx-scrollbar-viewport { position: absolute; overflow-x: hidden; overflow-y: scroll; top: 0; bottom: 0; left: 0; right: 0; } /* Fixes so pages actually print when magic scrollbar is present */ !print .main-page-frame { position: static; overflow: visible; } !print .jx-scrollbar-viewport { position: static; width: auto !important; height: auto !important; } .jx-scrollbar-test { position: absolute; left: -300px; } .jx-scrollbar-bar { position: absolute; top: 0; right: 0; bottom: 7px; width: 11px; } .jx-scrollbar-bar .jx-scrollbar-handle { position: absolute; right: 2px; -webkit-border-radius: 7px; -moz-border-radius: 7px; border-radius: 7px; min-height: 10px; width: 7px; opacity: 0; -webkit-transition: opacity 0.2s linear; -moz-transition: opacity 0.2s linear; -o-transition: opacity 0.2s linear; -ms-transition: opacity 0.2s linear; transition: opacity 0.2s linear; background: #6c6e71; -webkit-background-clip: padding-box; -moz-background-clip: padding; } .jx-scrollbar-bar:hover .jx-scrollbar-handle { opacity: 0.7; -webkit-transition: opacity 0 linear; -moz-transition: opacity 0 linear; -o-transition: opacity 0 linear; -ms-transition: opacity 0 linear; transition: opacity 0 linear; } .jx-scrollbar-bar .jx-scrollbar-visible { opacity: 0.7; } .jx-scrollbar-link { position: absolute; left: -50px; } diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index 095602b22b..67cb65c614 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -1,9 +1,36 @@ /** * @provides phame-css */ -.fb-comments, -.fb-comments span, -.fb-comments iframe[style] { - width: 100% !important; +.phame-blog-description { + max-width: 800px; + margin: 32px auto; + position: relative; + padding: 0 8px; +} + +.phame-blog-description-name { + font-weight: bold; + font-size: {$biggerfontsize}; + margin: 0 0 4px 50px; +} + +.phame-blog-description-content { + margin-left: 50px; +} + +.phame-blog-description-image { + display: inline-block; + background-repeat: no-repeat; + background-size: 100%; + box-shadow: inset 0 0 0 1px rgba(55,55,55,.15); + width: 40px; + height: 40px; + border-radius: 3px; + position: absolute; +} + +.phame-blog-description + .phui-property-list-section { + border-top: 1px solid rgba(71, 87, 120, 0.20); + padding-top: 16px; } diff --git a/webroot/rsrc/css/core/core.css b/webroot/rsrc/css/core/core.css index c7229a54ae..87e6a98cce 100644 --- a/webroot/rsrc/css/core/core.css +++ b/webroot/rsrc/css/core/core.css @@ -1,179 +1,175 @@ /** * @provides phabricator-core-css */ body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, p, blockquote, th, td, button { margin: 0; padding: 0; border: 0; } table { border-collapse: collapse; border-spacing: 0; } fieldset, img { border: 0; } address, caption, cite, code, dfn, th, var { font-style: normal; font-weight: normal; } ol, ul { list-style: none; } caption, th { text-align: left; } td, th { vertical-align: top; } h1, h2, h3, h4, h5, h6 { font-size: 100%; font-weight: bold; } body { font: {$basefont}; direction: ltr; text-align: left; unicode-bidi: embed; background: {$page.background.light}; /* By default, the iPhone zooms all text on the page by some percentage when you rotate from portrait mode to landscape mode. Disable this, since it breaks lots of things and prevents you from using landscape to see more columns in source code views. */ -webkit-text-size-adjust: none; /* Prevent content from resizing abruptly when shifting between scrollable and unscrollable pages. */ overflow-y: scroll; } textarea { font: inherit; } table { font-size: inherit; font: 100%; } h1 { font-size: 16px; } h2 { font-size: 14px; } a { -moz-outline-style: none; text-decoration: none; color: {$anchor}; cursor: pointer; } a:hover { text-decoration: underline; } img { display: block; } .busy { position: fixed; bottom: 8px; right: 8px; width: 32px; height: 32px; } .with-durable-column .busy { right: 308px; } .busy .phui-icon-view { font-size: 32px; } .grouped:after { content: ""; display: table; clear: both; } hr { height: 1px; background: #bbbbbb; border: none; } .aural-only { position: absolute !important; clip: rect(1px, 1px, 1px, 1px); /* NOTE: Without this, Safari sometimes lays these elements out at normal size. An example is the label on the comment action menu on timelines. */ width: 0; height: 0; overflow: hidden; } .visual-only { /* These elements are hidden by the 'aria-hidden' attribute. */ } .audible .aural-only { clip: auto; width: auto; height: auto; overflow: auto; background: #006699; color: #ffffff; } .audible .aural-only a { color: #ffffff; font-weight: bold; } .audible .visual-only { position: absolute !important; background: #990066; opacity: 0.25; } .routing-bar { position: fixed; top: 0; width: 100%; height: 2px; background: {$darkbluetext}; z-index: 80; box-shadow: 0 2px 1px rgba(0, 128, 255, 0.25); } .routing-progress { position: fixed; top: 0; left: 0; height: 2px; background: {$sky}; } - -html body.pro-white-background { - background-color: white; -} diff --git a/webroot/rsrc/css/core/remarkup.css b/webroot/rsrc/css/core/remarkup.css index f7b2c22364..12a45d3c5f 100644 --- a/webroot/rsrc/css/core/remarkup.css +++ b/webroot/rsrc/css/core/remarkup.css @@ -1,534 +1,532 @@ /** * @provides phabricator-remarkup-css */ .phabricator-remarkup { line-height: 1.51em; word-break: break-word; } .phabricator-remarkup p { margin: 0 0 12px; } .PhabricatorMonospaced, .phabricator-remarkup .remarkup-code-block .remarkup-code { font: 10px/13px "Menlo", "Consolas", "Monaco", monospace; } .platform-windows .PhabricatorMonospaced, .platform-windows .phabricator-remarkup .remarkup-code-block .remarkup-code { font: 11px/13px "Menlo", "Consolas", "Monaco", monospace; } .phabricator-remarkup .remarkup-code-block { margin: 12px 0; white-space: pre; } .phabricator-remarkup .remarkup-code-header { padding: 2px 8px; font-size: 13px; font-weight: bold; background: {$sh-yellowbackground}; display: inline-block; } .phabricator-remarkup .code-block-counterexample .remarkup-code-header { background-color: {$sh-redbackground}; } .phabricator-remarkup .remarkup-code-block pre { background: #FEF9ED; border: 1px solid {$sh-lightyellowborder}; display: block; color: #000; overflow: auto; padding: 8px; } .phabricator-remarkup pre.remarkup-counterexample { border: 1px solid {$sh-lightredborder}; background-color: {$sh-redbackground}; } .phabricator-remarkup tt.remarkup-monospaced { color: #000; background: rgba(71,87,120,0.1); padding: 1px 4px; border-radius: 3px; white-space: pre-wrap; } /* NOTE: You can currently produce this with [[link | `name`]]. Restore the link color. */ .phabricator-remarkup a tt.remarkup-monospaced { color: {$anchor}; } .phabricator-remarkup ul.remarkup-list { list-style: disc; margin: 12px 0 12px 30px; } .phabricator-remarkup ol.remarkup-list { list-style: decimal; margin: 12px 0 12px 30px; } .phabricator-remarkup ol ol.remarkup-list { list-style: upper-alpha; } - .phabricator-remarkup ol ol ol.remarkup-list { list-style: lower-alpha; } - .phabricator-remarkup ol ol ol ol.remarkup-list { list-style: lower-roman; } .phabricator-remarkup ul.remarkup-list-with-checkmarks { list-style: none; margin-left: 18px; } .phabricator-remarkup .remarkup-list-with-checkmarks input { margin-right: 2px; } .phabricator-remarkup .remarkup-list-with-checkmarks .remarkup-checked-item { color: {$greytext}; } .phabricator-remarkup ul.remarkup-list ol.remarkup-list, .phabricator-remarkup ul.remarkup-list ul.remarkup-list, .phabricator-remarkup ol.remarkup-list ol.remarkup-list, .phabricator-remarkup ol.remarkup-list ul.remarkup-list { margin: 4px 0 4px 24px; } .phabricator-remarkup .remarkup-list-item { - line-height: 1.6em; + line-height: 1.7em; } .phabricator-remarkup li.phantom-item, .phabricator-remarkup li.phantom-item { list-style-type: none; } .phabricator-remarkup h1.remarkup-header { font-size: 24px; line-height: 1.625em; margin: 24px 0 4px; } .phabricator-remarkup h2.remarkup-header { font-size: 20px; line-height: 1.5em; margin: 20px 0 4px; } .phabricator-remarkup h3.remarkup-header { font-size: 18px; line-height: 1.375em; margin: 20px 0 4px; } .phabricator-remarkup h4.remarkup-header { font-size: 16px; line-height: 1.25em; margin: 12px 0 4px; } .phabricator-remarkup h5.remarkup-header { font-size: 15px; line-height: 1.125em; margin: 8px 0 4px; } .phabricator-remarkup h6.remarkup-header { font-size: 14px; line-height: 1em; margin: 4px 0; } .phabricator-remarkup h3.remarkup-header + h4.remarkup-header { color: {$bluetext}; font-weight: normal; margin-bottom: 16px; margin-top: -4px; } .phabricator-remarkup blockquote { border-left: 3px solid {$sh-blueborder}; color: {$darkbluetext}; font-style: italic; margin: 4px 0 12px 0; padding: 8px 12px; background-color: {$lightbluebackground}; } -.phabricator-remarkup blockquote p { - margin: 0; +.phabricator-remarkup blockquote *:last-child { + margin-bottom: 0; } .phabricator-remarkup blockquote blockquote { background-color: rgba(175,175,175, .1); } .phabricator-remarkup blockquote em { font-style: normal; } .phabricator-remarkup blockquote div.remarkup-reply-head { font-style: normal; padding-bottom: 4px; } .phabricator-remarkup blockquote div.remarkup-reply-head .phui-tag-core { background-color: transparent; border: none; padding: 0; color: {$darkbluetext}; } .phabricator-remarkup img.remarkup-proxy-image { max-width: 640px; max-height: 640px; } .phabricator-remarkup audio { display: block; margin: 16px auto; min-width: 240px; width: 50%; } .phabricator-remarkup-mention-exists { font-weight: bold; background: #e6f3ff; } .phabricator-remarkup-mention-disabled { font-weight: bold; background: #dddddd; } .phui-remarkup-preview .phabricator-remarkup-mention-unknown, .aphront-panel-preview .phabricator-remarkup-mention-unknown { font-weight: bold; background: #ffaaaa; } .phabricator-remarkup-mention-nopermission .phui-tag-core { background: {$lightgreybackground}; color: {$lightgreytext}; } .phabricator-remarkup .remarkup-note { margin: 16px 0; padding: 12px; border-left: 3px solid {$blue}; background: {$lightblue}; } .phabricator-remarkup .remarkup-warning { margin: 16px 0; padding: 12px; border-left: 3px solid {$yellow}; background: {$lightyellow}; } .phabricator-remarkup .remarkup-important { margin: 16px 0; padding: 12px; border-left: 3px solid {$red}; background: {$lightred}; } .phabricator-remarkup .remarkup-note .remarkup-monospaced, .phabricator-remarkup .remarkup-important .remarkup-monospaced, .phabricator-remarkup .remarkup-warning .remarkup-monospaced { background-color: rgba(150,150,150,.2); } .phabricator-remarkup .remarkup-note-word { font-weight: bold; color: {$darkbluetext}; } .phabricator-remarkup-toc { float: right; border-left: 1px solid {$lightblueborder}; background: #fff; width: 160px; padding-left: 8px; margin: 0 0 4px 8px; } .phabricator-remarkup-toc-header { font-size: 13px; line-height: 13px; color: {$darkbluetext}; font-weight: bold; margin-bottom: 4px; } .phabricator-remarkup-toc ul { padding: 0; margin: 0; list-style: none; overflow: hidden; } .phabricator-remarkup-toc ul ul { margin: 0 0 0 8px; } .phabricator-remarkup-toc ul li { padding: 0; margin: 0; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .phabricator-remarkup-embed-layout-right { text-align: right; } .phabricator-remarkup-embed-layout-center { text-align: center; } .phabricator-remarkup-embed-layout-inline { display: inline; } .phabricator-remarkup-embed-float-right { float: right; margin: .5em 1em 0; } .phabricator-remarkup-embed-layout-link { padding-left: 20px; background: url(/rsrc/image/icon/fatcow/page_white_put.png) 0 0 no-repeat; } .phabricator-remarkup-embed-float-left { float: left; margin: .5em 1em 0; } .phabricator-remarkup-embed-image { display: inline-block; border: 3px solid white; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.20); } .phabricator-remarkup-embed-image-full { display: inline-block; max-width: 100%; } .phabricator-remarkup-embed-image-full img { height: auto; max-width: 100%; } .phabricator-remarkup .remarkup-table-wrap { overflow-x: auto; } .phabricator-remarkup table.remarkup-table { border-collapse: separate; border-spacing: 1px; background: {$lightblueborder}; margin: 12px 0; word-break: normal; } .phabricator-remarkup table.remarkup-table th { font-weight: bold; padding: 4px 6px; background: {$lightbluebackground}; } .phabricator-remarkup table.remarkup-table td { background: #ffffff; padding: 3px 6px; } body div.phabricator-remarkup.remarkup-has-toc .phabricator-remarkup-toc + .remarkup-header { margin-top: 0; padding-top: 0; } body .phabricator-standard-page div.phabricator-remarkup *:first-child, body .phabricator-standard-page div.phabricator-remarkup .remarkup-header + * { margin-top: 0; } body div.phabricator-remarkup > *:last-child { margin-bottom: 0; } .remarkup-assist-textarea { border-left-color: {$blueborder}; border-right-color: {$blueborder}; border-bottom-color: {$blueborder}; border-top-color: {$thinblueborder}; border-radius: 0; box-shadow: none; -webkit-box-shadow: none; /* Set line height explicitly so the metrics and the real textarea are forced to the same value. */ line-height: 1.25em; /* Prevent Safari and Chrome users from dragging the textarea any wider, because the top bar won't resize along with it. */ resize: vertical; } var.remarkup-assist-textarea { /* This is an invisible element used to measure the size of text in the textarea so we can float typeaheads over the cursor position. */ display: block; border-color: orange; box-sizing: border-box; padding: 4px 6px; white-space: pre-wrap; visibility: hidden; } .remarkup-assist-textarea:focus { border: 1px solid rgba(82, 168, 236, 0.8); } .remarkup-assist-bar { height: 26px; border-width: 1px 1px 0; border-style: solid; border-top-color: {$blueborder}; border-left-color: {$blueborder}; border-right-color: {$blueborder}; background: {$lightbluebackground}; overflow: hidden; } .remarkup-assist-button { display: block; padding: 4px 5px; float: left; } .remarkup-assist-button:hover { background-color: rgba(100,100,100,.15); } .remarkup-assist-button:hover .phui-icon-view.phui-font-fa { color: {$darkbluetext}; } .remarkup-assist-button:active { outline: none; } .remarkup-assist-button:focus { outline: none; } .remarkup-assist-separator { display: block; float: left; margin: 7px 4px; height: 14px; width: 0px; border-right: 1px solid #cccccc; } .remarkup-interpreter-error { padding: 8px; border: 1px solid {$sh-redborder}; background-color: {$sh-redbackground}; } .remarkup-cowsay { white-space: pre-wrap; } .remarkup-figlet { white-space: pre-wrap; } .remarkup-assist { width: 14px; height: 14px; overflow: hidden; text-align: center; vertical-align: middle; } .remarkup-assist-right { float: right; } .jx-order-mask { background: white; opacity: 1.0; } .remarkup-control-fullscreen-mode { position: fixed; top: -1px; bottom: -1px; left: -1px; right: -1px; } .remarkup-control-fullscreen-mode textarea.remarkup-assist-textarea { position: absolute; top: 27px; left: 0; right: 0; bottom: 0; /* NOTE: This doesn't work in Firefox, there's a JS behavior to correct it. */ height: auto; border-width: 1px 0 0 0; outline: none; } .phabricator-image-macro-hero { margin: auto; max-width: 95%; } .phabricator-remarkup-macro { height: auto; max-width: 100%; } .remarkup-nav-sequence-arrow { color: {$lightgreytext}; } .phabricator-remarkup hr { background: {$thinblueborder}; margin: 24px 0; } .phabricator-remarkup .remarkup-highlight { background-color: {$lightviolet}; padding: 0 4px; } diff --git a/webroot/rsrc/css/phui/phui-document-pro.css b/webroot/rsrc/css/phui/phui-document-pro.css index 7d211ab956..ab3a22656c 100644 --- a/webroot/rsrc/css/phui/phui-document-pro.css +++ b/webroot/rsrc/css/phui/phui-document-pro.css @@ -1,189 +1,197 @@ /** * @provides phui-document-view-pro-css */ -.phui-document-view.phui-document-view-pro, -.phui-document-view-pro-box, -.phui-document-properties { +.phui-document-view.phui-document-view-pro { max-width: 800px; - padding: 0 16px; + padding: 16px 16px 64px 16px; + margin: 0 auto; +} + +.phui-document-container { + background-color: #fff; position: relative; - margin: 16px auto; + border-bottom: 1px solid #dedee1; } +.phui-document-view-pro-box, .phui-document-properties { - max-width: 768px; - background-color: {$lightgreybackground}; + max-width: 800px; + margin: 0 auto; +} + +.phui-property-list-section { + max-width: 800px; margin: 16px auto; - border-radius: 3px; } -.device .phui-document-properties { +.device .phui-property-list-section { margin: 0 8px 16px; } .device-phone .phui-document-view.phui-document-view-pro { padding: 0 8px; margin: 0 auto; } .phui-document-view-pro .phui-document-toc { position: absolute; - top: 18px; + top: 34px; left: -36px; } a.button.phui-document-toc { display: inline-block; height: 16px; width: 16px; padding: 3px 8px 4px 8px; } .phui-document-view-pro .phui-document-toc-list { margin: 8px; border: 1px solid {$blueborder}; border-radius: 3px; box-shadow: {$dropshadow}; width: 200px; position: absolute; z-index: 30; background-color: #fff; - top: 38px; + top: 52px; left: -44px; } .device .phui-document-view-pro .phui-document-toc { display: none; } .phui-document-toc-list { display: none; } .phui-document-toc-open .phui-document-toc-list { display: block; } .phui-document-toc-open .phui-document-toc { background-color: {$blue}; } .phui-document-toc-open .phui-document-toc .phui-icon-view { color: #fff; } .phui-document-view-pro .phui-document-toc-content { margin: 4px 12px; } .phui-document-view-pro .phui-document-toc-header { font-weight: bold; color: {$bluetext}; margin-bottom: 8px; } .phui-document-view-pro .phui-document-toc-content li { margin: 4px 8px; } .phui-document-view-pro .phui-document-content .phabricator-remarkup { padding: 16px 0; line-height: 1.7em; } .device-desktop .phui-document-view.phui-document-view-pro { border: 0; } .phui-document-view.phui-document-view-pro .phui-header-shell { background: transparent; border-bottom: 1px solid {$thinblueborder}; } .phui-document-view.phui-document-view-pro .phui-header-shell { margin: 0; padding: 16px 0 32px; } .device-phone .phui-document-view.phui-document-view-pro .phui-header-shell { margin: 8px 0 0 0; padding: 8px 0 20px; } .phui-document-view.phui-document-view-pro .phui-header-tall .phui-header-header { font-size: 24px; line-height: 30px; color: #000; } .device-phone .phui-document-view-pro .phui-header-subheader { display: block; padding: 8px 0 0 0; } .phui-document-view-pro .phui-info-view { margin: 16px 0 0 0; } .phui-document-view-pro-box .phui-timeline-view { - padding: 0; + padding: 16px 0 0 0; background: none; + border-top: 1px solid rgba(71, 87, 120, 0.20); } .phui-document-view-pro-box .phui-timeline-image { border-radius: 25px; } .phui-document-view-pro-box .phui-timeline-wedge { display: none; } .phui-document-view-pro-box .phui-timeline-major-event .phui-timeline-group { border: none; } .phui-document-view-pro-box .phui-timeline-major-event .phui-timeline-content { border: none; } .device-desktop .phui-document-view-pro-box .phui-timeline-event-view.phui-timeline-minor-event { margin-left: 62px; } .phui-document-view-pro-box .phui-timeline-title { - border-radius: 3px; - background-color: {$lightgreybackground}; + border-top-right-radius: 3px; + border-top-left-radius: 3px; + background-color: #fff; + border-bottom: 1px solid #F1F1F4; } .phui-document-view-pro-box .phui-timeline-title-with-icon { padding-left: 12px; } .phui-document-view-pro-box .phui-timeline-icon-fill { display: none; } .phui-document-view-pro-box .phui-timeline-major-event .phui-timeline-content .phui-timeline-core-content { - padding-bottom: 24px; + } .phui-document-view-pro-box .phui-object-box { - background-color: {$lightgreybackground}; - border: none; margin: 0; } .phui-document-view-pro-box .phui-object-box .phui-form-view { padding-bottom: 0; } .phui-document-view-pro-box .phui-object-box .remarkup-assist-textarea { height: 9em; } diff --git a/webroot/rsrc/css/phui/phui-document-summary.css b/webroot/rsrc/css/phui/phui-document-summary.css new file mode 100644 index 0000000000..df484e0453 --- /dev/null +++ b/webroot/rsrc/css/phui/phui-document-summary.css @@ -0,0 +1,35 @@ +/** + * @provides phui-document-summary-view-css + */ + +.phui-document-summary-view { + margin-top: 8px; + border-bottom: 1px solid rgba(55,55,55,.1); +} + +.phui-document-summary-view.is-draft { + opacity: 0.5; +} + +body .phui-document-view .phui-document-summary-view h2.remarkup-header { + margin: 0; + padding: 0; +} + +.phui-document-summary-view h2.remarkup-header a { + color: #000; +} + +.phui-document-summary-view h2.remarkup-header a:hover { + color: {$violet}; + text-decoration: none; +} + +.phui-document-summary-subtitle { + color: {$lightgreytext}; + font-size: {$normalfontsize}; +} + +.phui-document-summary-subtitle a { + color: {$darkbluetext}; +} diff --git a/webroot/rsrc/image/avatar.png b/webroot/rsrc/image/avatar.png index 5266f4c09d..43a4ebceca 100644 Binary files a/webroot/rsrc/image/avatar.png and b/webroot/rsrc/image/avatar.png differ diff --git a/webroot/rsrc/image/people/user0.png b/webroot/rsrc/image/people/user0.png new file mode 100644 index 0000000000..43a4ebceca Binary files /dev/null and b/webroot/rsrc/image/people/user0.png differ diff --git a/webroot/rsrc/image/people/user1.png b/webroot/rsrc/image/people/user1.png new file mode 100644 index 0000000000..0f94001ed7 Binary files /dev/null and b/webroot/rsrc/image/people/user1.png differ diff --git a/webroot/rsrc/image/people/user2.png b/webroot/rsrc/image/people/user2.png new file mode 100644 index 0000000000..9629b2d38f Binary files /dev/null and b/webroot/rsrc/image/people/user2.png differ diff --git a/webroot/rsrc/image/people/user3.png b/webroot/rsrc/image/people/user3.png new file mode 100644 index 0000000000..f2f5e2338d Binary files /dev/null and b/webroot/rsrc/image/people/user3.png differ diff --git a/webroot/rsrc/image/people/user4.png b/webroot/rsrc/image/people/user4.png new file mode 100644 index 0000000000..75227cdb8d Binary files /dev/null and b/webroot/rsrc/image/people/user4.png differ diff --git a/webroot/rsrc/image/people/user5.png b/webroot/rsrc/image/people/user5.png new file mode 100644 index 0000000000..3e8aad37dc Binary files /dev/null and b/webroot/rsrc/image/people/user5.png differ diff --git a/webroot/rsrc/image/people/user6.png b/webroot/rsrc/image/people/user6.png new file mode 100644 index 0000000000..20eb2cd9af Binary files /dev/null and b/webroot/rsrc/image/people/user6.png differ diff --git a/webroot/rsrc/image/people/user7.png b/webroot/rsrc/image/people/user7.png new file mode 100644 index 0000000000..a224cdabdf Binary files /dev/null and b/webroot/rsrc/image/people/user7.png differ diff --git a/webroot/rsrc/image/people/user8.png b/webroot/rsrc/image/people/user8.png new file mode 100644 index 0000000000..e0d9fa506d Binary files /dev/null and b/webroot/rsrc/image/people/user8.png differ diff --git a/webroot/rsrc/image/people/user9.png b/webroot/rsrc/image/people/user9.png new file mode 100644 index 0000000000..001f696b94 Binary files /dev/null and b/webroot/rsrc/image/people/user9.png differ diff --git a/webroot/rsrc/js/application/transactions/behavior-comment-actions.js b/webroot/rsrc/js/application/transactions/behavior-comment-actions.js new file mode 100644 index 0000000000..8e89490be8 --- /dev/null +++ b/webroot/rsrc/js/application/transactions/behavior-comment-actions.js @@ -0,0 +1,84 @@ +/** + * @provides javelin-behavior-comment-actions + * @requires javelin-behavior + * javelin-stratcom + * javelin-workflow + * javelin-dom + * phuix-form-control-view + * phuix-icon-view + */ + +JX.behavior('comment-actions', function(config) { + var action_map = config.actions; + + var action_node = JX.$(config.actionID); + var form_node = JX.$(config.formID); + var input_node = JX.$(config.inputID); + + var rows = {}; + + JX.DOM.listen(action_node, 'change', null, function() { + var options = action_node.options; + var option; + + var selected = action_node.value; + action_node.value = '+'; + + for (var ii = 0; ii < options.length; ii++) { + option = options[ii]; + if (option.value == selected) { + add_row(option); + break; + } + } + }); + + JX.DOM.listen(form_node, 'submit', null, function() { + var data = []; + + for (var k in rows) { + data.push({ + type: k, + value: rows[k].getValue() + }); + } + + input_node.value = JX.JSON.stringify(data); + }); + + function add_row(option) { + var action = action_map[option.value]; + if (!action) { + return; + } + + option.disabled = true; + + var icon = new JX.PHUIXIconView() + .setIcon('fa-times-circle'); + var remove = JX.$N('a', {href: '#'}, icon.getNode()); + + var control = new JX.PHUIXFormControl() + .setLabel(action.label) + .setError(remove) + .setControl('tokenizer', action.spec); + var node = control.getNode(); + + rows[action.key] = control; + + JX.DOM.listen(remove, 'click', null, function(e) { + e.kill(); + JX.DOM.remove(node); + delete rows[action.key]; + option.disabled = false; + }); + + // TODO: Grotesque. + action_node + .parentNode + .parentNode + .parentNode + .insertBefore(node, action_node.parentNode.parentNode.nextSibling); + } + +}); diff --git a/webroot/rsrc/js/phuix/PHUIXFormControl.js b/webroot/rsrc/js/phuix/PHUIXFormControl.js new file mode 100644 index 0000000000..94ec0f5c78 --- /dev/null +++ b/webroot/rsrc/js/phuix/PHUIXFormControl.js @@ -0,0 +1,133 @@ +/** + * @provides phuix-form-control-view + * @requires javelin-install + * javelin-dom + */ + +JX.install('PHUIXFormControl', { + + members: { + _node: null, + _labelNode: null, + _errorNode: null, + _inputNode: null, + _valueSetCallback: null, + _valueGetCallback: null, + + setLabel: function(label) { + JX.DOM.setContent(this._getLabelNode(), label); + return this; + }, + + setError: function(error) { + JX.DOM.setContent(this._getErrorNode(), error); + return this; + }, + + setControl: function(type, spec) { + var node = this._getInputNode(); + + var input; + switch (type) { + case 'tokenizer': + input = this._newTokenizer(spec); + break; + default: + // TODO: Default or better error? + JX.$E('Bad Input Type'); + return; + } + + JX.DOM.setContent(node, input.node); + this._valueGetCallback = input.get; + this._valueSetCallback = input.set; + + return this; + }, + + setValue: function(value) { + this._valueSetCallback(value); + return this; + }, + + getValue: function() { + return this._valueGetCallback(); + }, + + getNode: function() { + if (!this._node) { + + var attrs = { + className: 'aphront-form-control grouped' + }; + + var content = [ + this._getLabelNode(), + this._getErrorNode(), + this._getInputNode() + ]; + + this._node = JX.$N('div', attrs, content); + } + + return this._node; + }, + + _getLabelNode: function() { + if (!this._labelNode) { + var attrs = { + className: 'aphront-form-label' + }; + + this._labelNode = JX.$N('label', attrs); + } + + return this._labelNode; + }, + + _getErrorNode: function() { + if (!this._errorNode) { + var attrs = { + className: 'aphront-form-error' + }; + + this._errorNode = JX.$N('span', attrs); + } + + return this._errorNode; + }, + + _getInputNode: function() { + if (!this._inputNode) { + var attrs = { + className: 'aphront-form-input' + }; + + this._inputNode = JX.$N('div', attrs); + } + + return this._inputNode; + }, + + _newTokenizer: function(spec) { + var build = JX.Prefab.newTokenizerFromTemplate( + spec.markup, + spec.config); + build.tokenizer.start(); + + return { + node: build.node, + get: function() { + return JX.keys(build.tokenizer.getTokens()); + }, + set: function(map) { + for (var k in map) { + build.tokenizer.addToken(k, map[k]); + } + } + }; + } + + } + +}); diff --git a/webroot/rsrc/js/phuix/PHUIXIconView.js b/webroot/rsrc/js/phuix/PHUIXIconView.js new file mode 100644 index 0000000000..f341e54967 --- /dev/null +++ b/webroot/rsrc/js/phuix/PHUIXIconView.js @@ -0,0 +1,47 @@ +/** + * @provides phuix-icon-view + * @requires javelin-install + * javelin-dom + */ + +JX.install('PHUIXIconView', { + + members: { + _node: null, + _icon: null, + _color: null, + + setIcon: function(icon) { + var node = this.getNode(); + if (this._icon) { + JX.DOM.alterClass(node, this._icon, false); + } + this._icon = icon; + JX.DOM.alterClass(node, this._icon, true); + return this; + }, + + setColor: function(color) { + var node = this.getNode(); + if (this._color) { + JX.DOM.alterClass(node, this._color, false); + } + this._color = color; + JX.DOM.alterClass(node, this._color, true); + return this; + }, + + getNode: function() { + if (!this._node) { + var attrs = { + className: 'phui-icon-view phui-font-fa' + }; + + this._node = JX.$N('span', attrs); + } + + return this._node; + } + } + +});