diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6fd8ff642a..25bd89cbb4 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,1629 +1,1627 @@ array( 'Aphront304Response' => 'aphront/response/304', 'Aphront400Response' => 'aphront/response/400', 'Aphront403Response' => 'aphront/response/403', 'Aphront404Response' => 'aphront/response/404', 'AphrontAjaxResponse' => 'aphront/response/ajax', 'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration', 'AphrontAttachedFileView' => 'view/control/attachedfile', 'AphrontCSRFException' => 'aphront/exception/csrf', 'AphrontCalendarMonthView' => 'applications/calendar/view/month', 'AphrontContextBarView' => 'view/layout/contextbar', 'AphrontController' => 'aphront/controller', 'AphrontCrumbsView' => 'view/layout/crumbs', 'AphrontDatabaseConnection' => 'storage/connection/base', 'AphrontDatabaseTransactionState' => 'storage/transaction', 'AphrontDefaultApplicationConfiguration' => 'aphront/default/configuration', 'AphrontDialogResponse' => 'aphront/response/dialog', 'AphrontDialogView' => 'view/dialog', 'AphrontErrorView' => 'view/form/error', 'AphrontException' => 'aphront/exception/base', 'AphrontFilePreviewView' => 'view/layout/filepreview', 'AphrontFileResponse' => 'aphront/response/file', 'AphrontFormCheckboxControl' => 'view/form/control/checkbox', 'AphrontFormControl' => 'view/form/control/base', 'AphrontFormDividerControl' => 'view/form/control/divider', 'AphrontFormDragAndDropUploadControl' => 'view/form/control/draganddropupload', 'AphrontFormFileControl' => 'view/form/control/file', 'AphrontFormLayoutView' => 'view/form/layout', 'AphrontFormMarkupControl' => 'view/form/control/markup', 'AphrontFormPasswordControl' => 'view/form/control/password', 'AphrontFormRadioButtonControl' => 'view/form/control/radio', 'AphrontFormRecaptchaControl' => 'view/form/control/recaptcha', 'AphrontFormSelectControl' => 'view/form/control/select', 'AphrontFormStaticControl' => 'view/form/control/static', 'AphrontFormSubmitControl' => 'view/form/control/submit', 'AphrontFormTextAreaControl' => 'view/form/control/textarea', 'AphrontFormTextControl' => 'view/form/control/text', 'AphrontFormToggleButtonsControl' => 'view/form/control/togglebuttons', 'AphrontFormTokenizerControl' => 'view/form/control/tokenizer', 'AphrontFormView' => 'view/form/base', 'AphrontHTTPSink' => 'aphront/sink/base', 'AphrontHTTPSinkTestCase' => 'aphront/sink/base/__tests__', 'AphrontHeadsupActionListView' => 'view/layout/headsup/actionlist', 'AphrontHeadsupActionView' => 'view/layout/headsup/action', 'AphrontIsolatedDatabaseConnection' => 'storage/connection/isolated', 'AphrontIsolatedDatabaseConnectionTestCase' => 'storage/connection/isolated/__tests__', 'AphrontIsolatedHTTPSink' => 'aphront/sink/test', 'AphrontJSONResponse' => 'aphront/response/json', 'AphrontJavelinView' => 'view/javelin-view', 'AphrontKeyboardShortcutsAvailableView' => 'view/widget/keyboardshortcuts', 'AphrontListFilterView' => 'view/layout/listfilter', 'AphrontMiniPanelView' => 'view/layout/minipanel', 'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql', 'AphrontMySQLDatabaseConnectionTestCase' => 'storage/connection/mysql/__tests__', 'AphrontNullView' => 'view/null', 'AphrontPHPHTTPSink' => 'aphront/sink/php', 'AphrontPageView' => 'view/page/base', 'AphrontPagerView' => 'view/control/pager', 'AphrontPanelView' => 'view/layout/panel', 'AphrontPlainTextResponse' => 'aphront/response/plaintext', 'AphrontQueryAccessDeniedException' => 'storage/exception/accessdenied', 'AphrontQueryConnectionException' => 'storage/exception/connection', 'AphrontQueryConnectionLostException' => 'storage/exception/connectionlost', 'AphrontQueryCountException' => 'storage/exception/count', 'AphrontQueryDuplicateKeyException' => 'storage/exception/duplicatekey', 'AphrontQueryException' => 'storage/exception/base', 'AphrontQueryObjectMissingException' => 'storage/exception/objectmissing', 'AphrontQueryParameterException' => 'storage/exception/parameter', 'AphrontQueryRecoverableException' => 'storage/exception/recoverable', 'AphrontQuerySchemaException' => 'storage/exception/schema', 'AphrontRedirectException' => 'aphront/exception/redirect', 'AphrontRedirectResponse' => 'aphront/response/redirect', 'AphrontReloadResponse' => 'aphront/response/reload', 'AphrontRequest' => 'aphront/request', 'AphrontRequestFailureView' => 'view/page/failure', 'AphrontRequestTestCase' => 'aphront/request/__tests__', 'AphrontResponse' => 'aphront/response/base', 'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/scopeguard', 'AphrontSideNavFilterView' => 'view/layout/sidenavfilter', 'AphrontSideNavView' => 'view/layout/sidenav', 'AphrontTableView' => 'view/control/table', 'AphrontTokenizerTemplateView' => 'view/control/tokenizer', 'AphrontTypeaheadTemplateView' => 'view/control/typeahead', 'AphrontURIMapper' => 'aphront/mapper', 'AphrontView' => 'view/base', 'AphrontWebpageResponse' => 'aphront/response/webpage', 'AphrontWriteGuard' => 'aphront/writeguard', 'CelerityAPI' => 'infrastructure/celerity/api', 'CelerityResourceController' => 'infrastructure/celerity/controller', 'CelerityResourceGraph' => 'infrastructure/celerity/graph', 'CelerityResourceMap' => 'infrastructure/celerity/map', 'CelerityStaticResourceResponse' => 'infrastructure/celerity/response', 'ConduitAPIMethod' => 'applications/conduit/method/base', 'ConduitAPIRequest' => 'applications/conduit/protocol/request', 'ConduitAPIResponse' => 'applications/conduit/protocol/response', 'ConduitAPI_arcanist_Method' => 'applications/conduit/method/arcanist/base', 'ConduitAPI_arcanist_projectinfo_Method' => 'applications/conduit/method/arcanist/projectinfo', 'ConduitAPI_audit_Method' => 'applications/conduit/method/audit/base', 'ConduitAPI_audit_query_Method' => 'applications/conduit/method/audit/query', 'ConduitAPI_chatlog_Method' => 'applications/conduit/method/chatlog/base', 'ConduitAPI_chatlog_query_Method' => 'applications/conduit/method/chatlog/query', 'ConduitAPI_chatlog_record_Method' => 'applications/conduit/method/chatlog/record', 'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect', 'ConduitAPI_conduit_getcertificate_Method' => 'applications/conduit/method/conduit/getcertificate', 'ConduitAPI_conduit_ping_Method' => 'applications/conduit/method/conduit/ping', 'ConduitAPI_daemon_launched_Method' => 'applications/conduit/method/daemon/launched', 'ConduitAPI_daemon_log_Method' => 'applications/conduit/method/daemon/log', 'ConduitAPI_differential_createcomment_Method' => 'applications/conduit/method/differential/createcomment', 'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff', 'ConduitAPI_differential_createrevision_Method' => 'applications/conduit/method/differential/createrevision', 'ConduitAPI_differential_find_Method' => 'applications/conduit/method/differential/find', 'ConduitAPI_differential_getalldiffs_Method' => 'applications/conduit/method/differential/getalldiffs', 'ConduitAPI_differential_getcommitmessage_Method' => 'applications/conduit/method/differential/getcommitmessage', 'ConduitAPI_differential_getcommitpaths_Method' => 'applications/conduit/method/differential/getcommitpaths', 'ConduitAPI_differential_getdiff_Method' => 'applications/conduit/method/differential/getdiff', 'ConduitAPI_differential_getrevision_Method' => 'applications/conduit/method/differential/getrevision', 'ConduitAPI_differential_getrevisioncomments_Method' => 'applications/conduit/method/differential/getrevisioncomments', 'ConduitAPI_differential_getrevisionfeedback_Method' => 'applications/conduit/method/differential/getrevisionfeedback', 'ConduitAPI_differential_markcommitted_Method' => 'applications/conduit/method/differential/markcommitted', 'ConduitAPI_differential_parsecommitmessage_Method' => 'applications/conduit/method/differential/parsecommitmessage', 'ConduitAPI_differential_query_Method' => 'applications/conduit/method/differential/query', 'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty', 'ConduitAPI_differential_updaterevision_Method' => 'applications/conduit/method/differential/updaterevision', 'ConduitAPI_differential_updatetaskrevisionassoc_Method' => 'applications/conduit/method/differential/updatetaskrevisionassoc', 'ConduitAPI_differential_updateunitresults_Method' => 'applications/conduit/method/differential/updateunitresults', 'ConduitAPI_diffusion_findsymbols_Method' => 'applications/conduit/method/diffusion/findsymbols', 'ConduitAPI_diffusion_getcommits_Method' => 'applications/conduit/method/diffusion/getcommits', 'ConduitAPI_diffusion_getrecentcommitsbypath_Method' => 'applications/conduit/method/diffusion/getrecentcommitsbypath', 'ConduitAPI_feed_publish_Method' => 'applications/conduit/method/feed/publish', 'ConduitAPI_feed_query_Method' => 'applications/conduit/method/feed/query', 'ConduitAPI_file_download_Method' => 'applications/conduit/method/file/download', 'ConduitAPI_file_info_Method' => 'applications/conduit/method/file/info', 'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload', 'ConduitAPI_macro_Method' => 'applications/conduit/method/macro/base', 'ConduitAPI_macro_query_Method' => 'applications/conduit/method/macro/query', 'ConduitAPI_maniphest_Method' => 'applications/conduit/method/maniphest/base', 'ConduitAPI_maniphest_createtask_Method' => 'applications/conduit/method/maniphest/createtask', 'ConduitAPI_maniphest_find_Method' => 'applications/conduit/method/maniphest/find', 'ConduitAPI_maniphest_gettasktransactions_Method' => 'applications/conduit/method/maniphest/gettasktransactions', 'ConduitAPI_maniphest_info_Method' => 'applications/conduit/method/maniphest/info', 'ConduitAPI_maniphest_update_Method' => 'applications/conduit/method/maniphest/update', 'ConduitAPI_paste_Method' => 'applications/conduit/method/paste/base', 'ConduitAPI_paste_create_Method' => 'applications/conduit/method/paste/create', 'ConduitAPI_paste_info_Method' => 'applications/conduit/method/paste/info', 'ConduitAPI_path_getowners_Method' => 'applications/conduit/method/path/getowners', 'ConduitAPI_phid_Method' => 'applications/conduit/method/phid/base', 'ConduitAPI_phid_info_Method' => 'applications/conduit/method/phid/info', 'ConduitAPI_phid_query_Method' => 'applications/conduit/method/phid/query', 'ConduitAPI_phriction_Method' => 'applications/conduit/method/phriction/base', 'ConduitAPI_phriction_edit_Method' => 'applications/conduit/method/phriction/edit', 'ConduitAPI_phriction_history_Method' => 'applications/conduit/method/phriction/history', 'ConduitAPI_phriction_info_Method' => 'applications/conduit/method/phriction/info', 'ConduitAPI_project_Method' => 'applications/conduit/method/project/base', 'ConduitAPI_project_query_Method' => 'applications/conduit/method/project/query', 'ConduitAPI_remarkup_process_Method' => 'applications/conduit/method/remarkup/process', 'ConduitAPI_slowvote_info_Method' => 'applications/conduit/method/slowvote/info', 'ConduitAPI_user_Method' => 'applications/conduit/method/user/base', 'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find', 'ConduitAPI_user_info_Method' => 'applications/conduit/method/user/info', 'ConduitAPI_user_whoami_Method' => 'applications/conduit/method/user/whoami', 'ConduitException' => 'applications/conduit/protocol/exception', 'DarkConsole' => 'aphront/console/api', 'DarkConsoleConfigPlugin' => 'aphront/console/plugin/config', 'DarkConsoleController' => 'aphront/console/controller', 'DarkConsoleCore' => 'aphront/console/core', 'DarkConsoleErrorLogPlugin' => 'aphront/console/plugin/errorlog', 'DarkConsoleErrorLogPluginAPI' => 'aphront/console/plugin/errorlog/api', 'DarkConsoleEventPlugin' => 'aphront/console/plugin/event', 'DarkConsoleEventPluginAPI' => 'aphront/console/plugin/event/api', 'DarkConsolePlugin' => 'aphront/console/plugin/base', 'DarkConsoleRequestPlugin' => 'aphront/console/plugin/request', 'DarkConsoleServicesPlugin' => 'aphront/console/plugin/services', 'DarkConsoleXHProfPlugin' => 'aphront/console/plugin/xhprof', 'DarkConsoleXHProfPluginAPI' => 'aphront/console/plugin/xhprof/api', 'DatabaseConfigurationProvider' => 'applications/base/storage/configuration', 'DifferentialAction' => 'applications/differential/constants/action', 'DifferentialActionHasNoEffectException' => 'applications/differential/exception/noeffect', 'DifferentialAddCommentView' => 'applications/differential/view/addcomment', 'DifferentialAffectedPath' => 'applications/differential/storage/affectedpath', 'DifferentialApplyPatchFieldSpecification' => 'applications/differential/field/specification/applypatch', 'DifferentialArcanistProjectFieldSpecification' => 'applications/differential/field/specification/arcanistproject', 'DifferentialAuditorsFieldSpecification' => 'applications/differential/field/specification/auditors', 'DifferentialAuthorFieldSpecification' => 'applications/differential/field/specification/author', 'DifferentialAuxiliaryField' => 'applications/differential/storage/auxiliaryfield', 'DifferentialBlameRevisionFieldSpecification' => 'applications/differential/field/specification/blamerev', 'DifferentialBranchFieldSpecification' => 'applications/differential/field/specification/branch', 'DifferentialCCWelcomeMail' => 'applications/differential/mail/ccwelcome', 'DifferentialCCsFieldSpecification' => 'applications/differential/field/specification/ccs', 'DifferentialChangeType' => 'applications/differential/constants/changetype', 'DifferentialChangeset' => 'applications/differential/storage/changeset', 'DifferentialChangesetDetailView' => 'applications/differential/view/changesetdetailview', 'DifferentialChangesetListView' => 'applications/differential/view/changesetlistview', 'DifferentialChangesetParser' => 'applications/differential/parser/changeset', 'DifferentialChangesetViewController' => 'applications/differential/controller/changesetview', 'DifferentialComment' => 'applications/differential/storage/comment', 'DifferentialCommentEditor' => 'applications/differential/editor/comment', 'DifferentialCommentMail' => 'applications/differential/mail/comment', 'DifferentialCommentPreviewController' => 'applications/differential/controller/commentpreview', 'DifferentialCommentSaveController' => 'applications/differential/controller/commentsave', 'DifferentialCommitsFieldSpecification' => 'applications/differential/field/specification/commits', 'DifferentialController' => 'applications/differential/controller/base', 'DifferentialDAO' => 'applications/differential/storage/base', 'DifferentialDateCreatedFieldSpecification' => 'applications/differential/field/specification/datecreated', 'DifferentialDateModifiedFieldSpecification' => 'applications/differential/field/specification/datemodified', 'DifferentialDefaultFieldSelector' => 'applications/differential/field/selector/default', 'DifferentialDependenciesFieldSpecification' => 'applications/differential/field/specification/dependencies', 'DifferentialDiff' => 'applications/differential/storage/diff', 'DifferentialDiffContentMail' => 'applications/differential/mail/diffcontent', 'DifferentialDiffCreateController' => 'applications/differential/controller/diffcreate', 'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty', 'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents', 'DifferentialDiffViewController' => 'applications/differential/controller/diffview', 'DifferentialException' => 'applications/differential/exception/base', 'DifferentialExceptionMail' => 'applications/differential/mail/exception', 'DifferentialExportPatchFieldSpecification' => 'applications/differential/field/specification/exportpatch', 'DifferentialFieldDataNotAvailableException' => 'applications/differential/field/exception/notavailable', 'DifferentialFieldParseException' => 'applications/differential/field/exception/parse', 'DifferentialFieldSelector' => 'applications/differential/field/selector/base', 'DifferentialFieldSpecification' => 'applications/differential/field/specification/base', 'DifferentialFieldSpecificationIncompleteException' => 'applications/differential/field/exception/incomplete', 'DifferentialFieldValidationException' => 'applications/differential/field/exception/validation', 'DifferentialGitSVNIDFieldSpecification' => 'applications/differential/field/specification/gitsvnid', 'DifferentialHostFieldSpecification' => 'applications/differential/field/specification/host', 'DifferentialHunk' => 'applications/differential/storage/hunk', 'DifferentialInlineComment' => 'applications/differential/storage/inlinecomment', 'DifferentialInlineCommentEditController' => 'applications/differential/controller/inlinecommentedit', 'DifferentialInlineCommentEditView' => 'applications/differential/view/inlinecommentedit', 'DifferentialInlineCommentPreviewController' => 'applications/differential/controller/inlinecommentpreview', 'DifferentialInlineCommentView' => 'applications/differential/view/inlinecomment', 'DifferentialLinesFieldSpecification' => 'applications/differential/field/specification/lines', 'DifferentialLintFieldSpecification' => 'applications/differential/field/specification/lint', 'DifferentialLintStatus' => 'applications/differential/constants/lintstatus', 'DifferentialLocalCommitsView' => 'applications/differential/view/localcommits', 'DifferentialMail' => 'applications/differential/mail/base', 'DifferentialManiphestTasksFieldSpecification' => 'applications/differential/field/specification/maniphesttasks', 'DifferentialNewDiffMail' => 'applications/differential/mail/newdiff', 'DifferentialPathFieldSpecification' => 'applications/differential/field/specification/path', 'DifferentialPrimaryPaneView' => 'applications/differential/view/primarypane', 'DifferentialReplyHandler' => 'applications/differential/replyhandler', 'DifferentialRevertPlanFieldSpecification' => 'applications/differential/field/specification/revertplan', 'DifferentialReviewRequestMail' => 'applications/differential/mail/reviewrequest', 'DifferentialReviewedByFieldSpecification' => 'applications/differential/field/specification/reviewedby', 'DifferentialReviewersFieldSpecification' => 'applications/differential/field/specification/reviewers', 'DifferentialRevision' => 'applications/differential/storage/revision', 'DifferentialRevisionCommentListView' => 'applications/differential/view/revisioncommentlist', 'DifferentialRevisionCommentView' => 'applications/differential/view/revisioncomment', 'DifferentialRevisionControlSystem' => 'applications/differential/constants/revisioncontrolsystem', 'DifferentialRevisionDetailRenderer' => 'applications/differential/controller/customrenderer', 'DifferentialRevisionDetailView' => 'applications/differential/view/revisiondetail', 'DifferentialRevisionEditController' => 'applications/differential/controller/revisionedit', 'DifferentialRevisionEditor' => 'applications/differential/editor/revision', 'DifferentialRevisionIDFieldParserTestCase' => 'applications/differential/field/specification/revisionid/__tests__', 'DifferentialRevisionIDFieldSpecification' => 'applications/differential/field/specification/revisionid', 'DifferentialRevisionListController' => 'applications/differential/controller/revisionlist', 'DifferentialRevisionListData' => 'applications/differential/data/revisionlist', 'DifferentialRevisionListView' => 'applications/differential/view/revisionlist', 'DifferentialRevisionQuery' => 'applications/differential/query/revision', 'DifferentialRevisionStatsController' => 'applications/differential/controller/revisionstats', 'DifferentialRevisionStatsView' => 'applications/differential/view/revisionstats', 'DifferentialRevisionStatusFieldSpecification' => 'applications/differential/field/specification/revisionstatus', 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/revisionupdatehistory', 'DifferentialRevisionViewController' => 'applications/differential/controller/revisionview', 'DifferentialSubscribeController' => 'applications/differential/controller/subscribe', 'DifferentialSummaryFieldSpecification' => 'applications/differential/field/specification/summary', 'DifferentialTasksAttacher' => 'applications/differential/tasks', 'DifferentialTestPlanFieldSpecification' => 'applications/differential/field/specification/testplan', 'DifferentialTitleFieldSpecification' => 'applications/differential/field/specification/title', 'DifferentialUnitFieldSpecification' => 'applications/differential/field/specification/unit', 'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus', 'DifferentialUnitTestResult' => 'applications/differential/constants/unittestresult', 'DiffusionBranchInformation' => 'applications/diffusion/data/branch', 'DiffusionBranchQuery' => 'applications/diffusion/query/branch/base', 'DiffusionBranchTableView' => 'applications/diffusion/view/branchtable', 'DiffusionBrowseController' => 'applications/diffusion/controller/browse', 'DiffusionBrowseFileController' => 'applications/diffusion/controller/file', 'DiffusionBrowseQuery' => 'applications/diffusion/query/browse/base', 'DiffusionBrowseTableView' => 'applications/diffusion/view/browsetable', 'DiffusionChangeController' => 'applications/diffusion/controller/change', 'DiffusionCommentListView' => 'applications/diffusion/view/commentlist', 'DiffusionCommentView' => 'applications/diffusion/view/comment', 'DiffusionCommitChangeTableView' => 'applications/diffusion/view/commitchangetable', 'DiffusionCommitController' => 'applications/diffusion/controller/commit', 'DiffusionContainsQuery' => 'applications/diffusion/query/contains/base', 'DiffusionController' => 'applications/diffusion/controller/base', 'DiffusionDiffController' => 'applications/diffusion/controller/diff', 'DiffusionDiffQuery' => 'applications/diffusion/query/diff/base', 'DiffusionEmptyResultView' => 'applications/diffusion/view/emptyresult', 'DiffusionFileContent' => 'applications/diffusion/data/filecontent', 'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/base', 'DiffusionGitBranchQuery' => 'applications/diffusion/query/branch/git', 'DiffusionGitBranchQueryTestCase' => 'applications/diffusion/query/branch/git/__tests__', 'DiffusionGitBrowseQuery' => 'applications/diffusion/query/browse/git', 'DiffusionGitContainsQuery' => 'applications/diffusion/query/contains/git', 'DiffusionGitDiffQuery' => 'applications/diffusion/query/diff/git', 'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/git', 'DiffusionGitHistoryQuery' => 'applications/diffusion/query/history/git', 'DiffusionGitLastModifiedQuery' => 'applications/diffusion/query/lastmodified/git', 'DiffusionGitRequest' => 'applications/diffusion/request/git', 'DiffusionHistoryController' => 'applications/diffusion/controller/history', 'DiffusionHistoryQuery' => 'applications/diffusion/query/history/base', 'DiffusionHistoryTableView' => 'applications/diffusion/view/historytable', 'DiffusionHomeController' => 'applications/diffusion/controller/home', 'DiffusionLastModifiedController' => 'applications/diffusion/controller/lastmodified', 'DiffusionLastModifiedQuery' => 'applications/diffusion/query/lastmodified/base', 'DiffusionMercurialBranchQuery' => 'applications/diffusion/query/branch/mercurial', 'DiffusionMercurialBrowseQuery' => 'applications/diffusion/query/browse/mercurial', 'DiffusionMercurialContainsQuery' => 'applications/diffusion/query/contains/mercurial', 'DiffusionMercurialDiffQuery' => 'applications/diffusion/query/diff/mercurial', 'DiffusionMercurialFileContentQuery' => 'applications/diffusion/query/filecontent/mercurial', 'DiffusionMercurialHistoryQuery' => 'applications/diffusion/query/history/mercurial', 'DiffusionMercurialLastModifiedQuery' => 'applications/diffusion/query/lastmodified/mercurial', 'DiffusionMercurialRequest' => 'applications/diffusion/request/mercurial', 'DiffusionPathChange' => 'applications/diffusion/data/pathchange', 'DiffusionPathChangeQuery' => 'applications/diffusion/query/pathchange/base', 'DiffusionPathCompleteController' => 'applications/diffusion/controller/pathcomplete', 'DiffusionPathIDQuery' => 'applications/diffusion/query/pathid/base', 'DiffusionPathQueryTestCase' => 'applications/diffusion/query/pathid/base/__tests__', 'DiffusionPathValidateController' => 'applications/diffusion/controller/pathvalidate', 'DiffusionQuery' => 'applications/diffusion/query/base', 'DiffusionRepositoryController' => 'applications/diffusion/controller/repository', 'DiffusionRepositoryPath' => 'applications/diffusion/data/repositorypath', 'DiffusionRequest' => 'applications/diffusion/request/base', 'DiffusionSVNContainsQuery' => 'applications/diffusion/query/contains/svn', 'DiffusionSvnBrowseQuery' => 'applications/diffusion/query/browse/svn', 'DiffusionSvnDiffQuery' => 'applications/diffusion/query/diff/svn', 'DiffusionSvnFileContentQuery' => 'applications/diffusion/query/filecontent/svn', 'DiffusionSvnHistoryQuery' => 'applications/diffusion/query/history/svn', 'DiffusionSvnLastModifiedQuery' => 'applications/diffusion/query/lastmodified/svn', 'DiffusionSvnRequest' => 'applications/diffusion/request/svn', 'DiffusionSymbolController' => 'applications/diffusion/controller/symbol', 'DiffusionSymbolQuery' => 'applications/diffusion/query/symbol', 'DiffusionView' => 'applications/diffusion/view/base', 'DrydockAllocator' => 'applications/drydock/allocator/resource', 'DrydockAllocatorWorker' => 'applications/drydock/allocator/worker', 'DrydockBlueprint' => 'applications/drydock/blueprint/base', 'DrydockCommandInterface' => 'applications/drydock/interface/command/base', 'DrydockConstants' => 'applications/drydock/constants/base', 'DrydockController' => 'applications/drydock/controller/base', 'DrydockDAO' => 'applications/drydock/storage/base', 'DrydockEC2HostBlueprint' => 'applications/drydock/blueprint/ec2host', 'DrydockInterface' => 'applications/drydock/interface/base', 'DrydockLease' => 'applications/drydock/storage/lease', 'DrydockLeaseListController' => 'applications/drydock/controller/leaselist', 'DrydockLeaseStatus' => 'applications/drydock/constants/leasestatus', 'DrydockLocalCommandInterface' => 'applications/drydock/interface/command/local', 'DrydockLocalHostBlueprint' => 'applications/drydock/blueprint/localhost', 'DrydockRemoteHostBlueprint' => 'applications/drydock/blueprint/remotehost', 'DrydockResource' => 'applications/drydock/storage/resource', 'DrydockResourceAllocateController' => 'applications/drydock/controller/resourceallocate', 'DrydockResourceListController' => 'applications/drydock/controller/resourcelist', 'DrydockResourceStatus' => 'applications/drydock/constants/resourcestatus', 'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/ssh', 'HeraldAction' => 'applications/herald/storage/action', 'HeraldActionConfig' => 'applications/herald/config/action', 'HeraldAllRulesController' => 'applications/herald/controller/all', 'HeraldApplyTranscript' => 'applications/herald/storage/transcript/apply', 'HeraldCommitAdapter' => 'applications/herald/adapter/commit', 'HeraldCondition' => 'applications/herald/storage/condition', 'HeraldConditionConfig' => 'applications/herald/config/condition', 'HeraldConditionTranscript' => 'applications/herald/storage/transcript/condition', 'HeraldContentTypeConfig' => 'applications/herald/config/contenttype', 'HeraldController' => 'applications/herald/controller/base', 'HeraldDAO' => 'applications/herald/storage/base', 'HeraldDeleteController' => 'applications/herald/controller/delete', 'HeraldDifferentialRevisionAdapter' => 'applications/herald/adapter/differential', 'HeraldDryRunAdapter' => 'applications/herald/adapter/dryrun', 'HeraldEffect' => 'applications/herald/engine/effect', 'HeraldEngine' => 'applications/herald/engine/engine', 'HeraldFieldConfig' => 'applications/herald/config/field', 'HeraldHomeController' => 'applications/herald/controller/home', 'HeraldInvalidConditionException' => 'applications/herald/engine/engine/exception', 'HeraldInvalidFieldException' => 'applications/herald/engine/engine/exception', 'HeraldNewController' => 'applications/herald/controller/new', 'HeraldObjectAdapter' => 'applications/herald/adapter/base', 'HeraldObjectTranscript' => 'applications/herald/storage/transcript/object', 'HeraldRecursiveConditionsException' => 'applications/herald/engine/engine/exception', 'HeraldRepetitionPolicyConfig' => 'applications/herald/config/repetitionpolicy', 'HeraldRule' => 'applications/herald/storage/rule', 'HeraldRuleController' => 'applications/herald/controller/rule', 'HeraldRuleEdit' => 'applications/herald/storage/edithistory', 'HeraldRuleEditHistoryController' => 'applications/herald/controller/edithistory', 'HeraldRuleEditHistoryView' => 'applications/herald/view/edithistory', 'HeraldRuleListView' => 'applications/herald/view/rulelist', 'HeraldRuleTranscript' => 'applications/herald/storage/transcript/rule', 'HeraldRuleTypeConfig' => 'applications/herald/config/ruletype', 'HeraldTestConsoleController' => 'applications/herald/controller/test', 'HeraldTranscript' => 'applications/herald/storage/transcript/base', 'HeraldTranscriptController' => 'applications/herald/controller/transcript', 'HeraldTranscriptListController' => 'applications/herald/controller/transcriptlist', 'HeraldValueTypeConfig' => 'applications/herald/config/valuetype', 'Javelin' => 'infrastructure/javelin/api', 'JavelinReactorExample' => 'applications/uiexample/examples/reactor', 'JavelinViewExample' => 'applications/uiexample/examples/client', 'JavelinViewExampleServerView' => 'applications/uiexample/examples/client', 'LiskDAO' => 'storage/lisk/dao', 'LiskEphemeralObjectException' => 'storage/lisk/dao', 'LiskIsolationTestCase' => 'storage/lisk/dao/__tests__', 'LiskIsolationTestDAO' => 'storage/lisk/dao/__tests__', 'LiskIsolationTestDAOException' => 'storage/lisk/dao/__tests__', 'ManiphestAction' => 'applications/maniphest/constants/action', 'ManiphestAuxiliaryFieldDefaultSpecification' => 'applications/maniphest/auxiliaryfield/default', 'ManiphestAuxiliaryFieldSpecification' => 'applications/maniphest/auxiliaryfield/base', 'ManiphestAuxiliaryFieldTypeException' => 'applications/maniphest/auxiliaryfield/typeexception', 'ManiphestAuxiliaryFieldValidationException' => 'applications/maniphest/auxiliaryfield/validationexception', 'ManiphestBatchEditController' => 'applications/maniphest/controller/batch', 'ManiphestConstants' => 'applications/maniphest/constants/base', 'ManiphestController' => 'applications/maniphest/controller/base', 'ManiphestDAO' => 'applications/maniphest/storage/base', 'ManiphestDefaultTaskExtensions' => 'applications/maniphest/extensions/task', 'ManiphestExportController' => 'applications/maniphest/controller/export', 'ManiphestReplyHandler' => 'applications/maniphest/replyhandler', 'ManiphestReportController' => 'applications/maniphest/controller/report', 'ManiphestTask' => 'applications/maniphest/storage/task', 'ManiphestTaskAuxiliaryStorage' => 'applications/maniphest/storage/auxiliary', 'ManiphestTaskDescriptionChangeController' => 'applications/maniphest/controller/descriptionchange', - 'ManiphestTaskDescriptionDiffController' => 'applications/maniphest/controller/descriptiondiff', 'ManiphestTaskDescriptionPreviewController' => 'applications/maniphest/controller/descriptionpreview', 'ManiphestTaskDetailController' => 'applications/maniphest/controller/taskdetail', 'ManiphestTaskEditController' => 'applications/maniphest/controller/taskedit', 'ManiphestTaskExtensions' => 'applications/maniphest/extensions/base', 'ManiphestTaskListController' => 'applications/maniphest/controller/tasklist', 'ManiphestTaskListView' => 'applications/maniphest/view/tasklist', 'ManiphestTaskOwner' => 'applications/maniphest/constants/owner', 'ManiphestTaskPriority' => 'applications/maniphest/constants/priority', 'ManiphestTaskProject' => 'applications/maniphest/storage/taskproject', 'ManiphestTaskQuery' => 'applications/maniphest/query', 'ManiphestTaskStatus' => 'applications/maniphest/constants/status', 'ManiphestTaskSubscriber' => 'applications/maniphest/storage/subscriber', 'ManiphestTaskSummaryView' => 'applications/maniphest/view/tasksummary', 'ManiphestTransaction' => 'applications/maniphest/storage/transaction', 'ManiphestTransactionDetailView' => 'applications/maniphest/view/transactiondetail', 'ManiphestTransactionEditor' => 'applications/maniphest/editor/transaction', 'ManiphestTransactionListView' => 'applications/maniphest/view/transactionlist', 'ManiphestTransactionPreviewController' => 'applications/maniphest/controller/transactionpreview', 'ManiphestTransactionSaveController' => 'applications/maniphest/controller/transactionsave', 'ManiphestTransactionType' => 'applications/maniphest/constants/transactiontype', 'ManiphestView' => 'applications/maniphest/view/base', 'MetaMTAConstants' => 'applications/metamta/constants/base', 'MetaMTANotificationType' => 'applications/metamta/constants/notificationtype', 'Phabricator404Controller' => 'applications/base/controller/404', 'PhabricatorAphlictTestPageController' => 'applications/notifications/controller/test', 'PhabricatorAuditActionConstants' => 'applications/audit/constants/action', 'PhabricatorAuditAddCommentController' => 'applications/audit/controller/addcomment', 'PhabricatorAuditComment' => 'applications/audit/storage/auditcomment', 'PhabricatorAuditCommentEditor' => 'applications/audit/editor/comment', 'PhabricatorAuditCommitListView' => 'applications/audit/view/commitlist', 'PhabricatorAuditCommitQuery' => 'applications/audit/query/commit', 'PhabricatorAuditCommitStatusConstants' => 'applications/audit/constants/commitstatus', 'PhabricatorAuditController' => 'applications/audit/controller/base', 'PhabricatorAuditDAO' => 'applications/audit/storage/base', 'PhabricatorAuditListController' => 'applications/audit/controller/list', 'PhabricatorAuditListView' => 'applications/audit/view/list', 'PhabricatorAuditPreviewController' => 'applications/audit/controller/preview', 'PhabricatorAuditQuery' => 'applications/audit/query/audit', 'PhabricatorAuditReplyHandler' => 'applications/audit/replyhandler', 'PhabricatorAuditStatusConstants' => 'applications/audit/constants/status', 'PhabricatorAuthController' => 'applications/auth/controller/base', 'PhabricatorCalendarBrowseController' => 'applications/calendar/controller/browse', 'PhabricatorCalendarController' => 'applications/calendar/controller/base', 'PhabricatorChatLogChannelListController' => 'applications/chatlog/controller/channellist', 'PhabricatorChatLogChannelLogController' => 'applications/chatlog/controller/channellog', 'PhabricatorChatLogConstants' => 'applications/chatlog/constants/base', 'PhabricatorChatLogController' => 'applications/chatlog/controller/base', 'PhabricatorChatLogDAO' => 'applications/chatlog/storage/base', 'PhabricatorChatLogEvent' => 'applications/chatlog/storage/event', 'PhabricatorChatLogEventType' => 'applications/chatlog/constants/eventtype', 'PhabricatorChatLogQuery' => 'applications/chatlog/query', 'PhabricatorConduitAPIController' => 'applications/conduit/controller/api', 'PhabricatorConduitCertificateToken' => 'applications/conduit/storage/token', 'PhabricatorConduitConnectionLog' => 'applications/conduit/storage/connectionlog', 'PhabricatorConduitConsoleController' => 'applications/conduit/controller/console', 'PhabricatorConduitController' => 'applications/conduit/controller/base', 'PhabricatorConduitDAO' => 'applications/conduit/storage/base', 'PhabricatorConduitLogController' => 'applications/conduit/controller/log', 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/methodcalllog', 'PhabricatorConduitTokenController' => 'applications/conduit/controller/token', 'PhabricatorContentSource' => 'applications/metamta/contentsource/source', 'PhabricatorContentSourceView' => 'applications/metamta/contentsource/view', 'PhabricatorController' => 'applications/base/controller/base', 'PhabricatorCountdownController' => 'applications/countdown/controller/base', 'PhabricatorCountdownDAO' => 'applications/countdown/storage/base', 'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/delete', 'PhabricatorCountdownEditController' => 'applications/countdown/controller/edit', 'PhabricatorCountdownListController' => 'applications/countdown/controller/list', 'PhabricatorCountdownViewController' => 'applications/countdown/controller/view', 'PhabricatorDaemon' => 'infrastructure/daemon/base', 'PhabricatorDaemonCombinedLogController' => 'applications/daemon/controller/combined', 'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/console', 'PhabricatorDaemonControl' => 'infrastructure/daemon/control', 'PhabricatorDaemonController' => 'applications/daemon/controller/base', 'PhabricatorDaemonDAO' => 'infrastructure/daemon/storage/base', 'PhabricatorDaemonLog' => 'infrastructure/daemon/storage/log', 'PhabricatorDaemonLogEvent' => 'infrastructure/daemon/storage/event', 'PhabricatorDaemonLogEventsView' => 'applications/daemon/view/daemonlogevents', 'PhabricatorDaemonLogListController' => 'applications/daemon/controller/loglist', 'PhabricatorDaemonLogListView' => 'applications/daemon/view/daemonloglist', 'PhabricatorDaemonLogViewController' => 'applications/daemon/controller/logview', 'PhabricatorDaemonReference' => 'infrastructure/daemon/control/reference', 'PhabricatorDaemonTimelineConsoleController' => 'applications/daemon/controller/timeline', 'PhabricatorDaemonTimelineEventController' => 'applications/daemon/controller/timelineevent', 'PhabricatorDefaultFileStorageEngineSelector' => 'applications/files/engineselector/default', 'PhabricatorDefaultSearchEngineSelector' => 'applications/search/selector/default', 'PhabricatorDifferenceEngine' => 'infrastructure/diff/engine', 'PhabricatorDirectoryCategory' => 'applications/directory/storage/category', 'PhabricatorDirectoryCategoryDeleteController' => 'applications/directory/controller/categorydelete', 'PhabricatorDirectoryCategoryEditController' => 'applications/directory/controller/categoryedit', 'PhabricatorDirectoryCategoryViewController' => 'applications/directory/controller/categoryview', 'PhabricatorDirectoryController' => 'applications/directory/controller/base', 'PhabricatorDirectoryDAO' => 'applications/directory/storage/base', 'PhabricatorDirectoryEditController' => 'applications/directory/controller/edit', 'PhabricatorDirectoryItem' => 'applications/directory/storage/item', 'PhabricatorDirectoryItemDeleteController' => 'applications/directory/controller/itemdelete', 'PhabricatorDirectoryItemEditController' => 'applications/directory/controller/itemedit', 'PhabricatorDirectoryMainController' => 'applications/directory/controller/main', 'PhabricatorDisabledUserController' => 'applications/auth/controller/disabled', 'PhabricatorDraft' => 'applications/draft/storage/draft', 'PhabricatorDraftDAO' => 'applications/draft/storage/base', 'PhabricatorEmailLoginController' => 'applications/auth/controller/email', 'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken', 'PhabricatorEnv' => 'infrastructure/env', 'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__', 'PhabricatorEvent' => 'infrastructure/events/event', 'PhabricatorEventEngine' => 'infrastructure/events/engine', 'PhabricatorEventType' => 'infrastructure/events/constant/type', 'PhabricatorFeedBuilder' => 'applications/feed/builder/feed', 'PhabricatorFeedConstants' => 'applications/feed/constants/base', 'PhabricatorFeedController' => 'applications/feed/controller/base', 'PhabricatorFeedDAO' => 'applications/feed/storage/base', 'PhabricatorFeedPublicStreamController' => 'applications/feed/controller/publicstream', 'PhabricatorFeedQuery' => 'applications/feed/query', 'PhabricatorFeedStory' => 'applications/feed/story/base', 'PhabricatorFeedStoryAudit' => 'applications/feed/story/audit', 'PhabricatorFeedStoryData' => 'applications/feed/storage/story', 'PhabricatorFeedStoryDifferential' => 'applications/feed/story/differential', 'PhabricatorFeedStoryManiphest' => 'applications/feed/story/maniphest', 'PhabricatorFeedStoryPhriction' => 'applications/feed/story/phriction', 'PhabricatorFeedStoryProject' => 'applications/feed/story/project', 'PhabricatorFeedStoryPublisher' => 'applications/feed/publisher', 'PhabricatorFeedStoryReference' => 'applications/feed/storage/storyreference', 'PhabricatorFeedStoryStatus' => 'applications/feed/story/status', 'PhabricatorFeedStoryTypeConstants' => 'applications/feed/constants/story', 'PhabricatorFeedStoryUnknown' => 'applications/feed/story/unknown', 'PhabricatorFeedStoryView' => 'applications/feed/view/story', 'PhabricatorFeedView' => 'applications/feed/view/base', 'PhabricatorFile' => 'applications/files/storage/file', 'PhabricatorFileController' => 'applications/files/controller/base', 'PhabricatorFileDAO' => 'applications/files/storage/base', 'PhabricatorFileDataController' => 'applications/files/controller/data', 'PhabricatorFileDeleteController' => 'applications/files/controller/delete', 'PhabricatorFileDropUploadController' => 'applications/files/controller/dropupload', 'PhabricatorFileImageMacro' => 'applications/files/storage/imagemacro', 'PhabricatorFileInfoController' => 'applications/files/controller/info', 'PhabricatorFileListController' => 'applications/files/controller/list', 'PhabricatorFileMacroDeleteController' => 'applications/files/controller/macrodelete', 'PhabricatorFileMacroEditController' => 'applications/files/controller/macroedit', 'PhabricatorFileMacroListController' => 'applications/files/controller/macrolist', 'PhabricatorFileProxyController' => 'applications/files/controller/proxy', 'PhabricatorFileProxyImage' => 'applications/files/storage/proxyimage', 'PhabricatorFileSideNavView' => 'applications/files/view/sidenav', 'PhabricatorFileStorageBlob' => 'applications/files/storage/storageblob', 'PhabricatorFileStorageEngine' => 'applications/files/engine/base', 'PhabricatorFileStorageEngineSelector' => 'applications/files/engineselector/base', 'PhabricatorFileTransformController' => 'applications/files/controller/transform', 'PhabricatorFileUploadController' => 'applications/files/controller/upload', 'PhabricatorFileUploadException' => 'applications/files/exception/upload', 'PhabricatorFileUploadView' => 'applications/files/view/upload', 'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/garbagecollector', 'PhabricatorGoodForNothingWorker' => 'infrastructure/daemon/workers/worker/goodfornothing', 'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector', 'PhabricatorHash' => 'infrastructure/util/hash', 'PhabricatorHelpController' => 'applications/help/controller/base', 'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/keyboardshortcut', 'PhabricatorIRCBot' => 'infrastructure/daemon/irc/bot', 'PhabricatorIRCDifferentialNotificationHandler' => 'infrastructure/daemon/irc/handler/differentialnotification', 'PhabricatorIRCHandler' => 'infrastructure/daemon/irc/handler/base', 'PhabricatorIRCLogHandler' => 'infrastructure/daemon/irc/handler/log', 'PhabricatorIRCMacroHandler' => 'infrastructure/daemon/irc/handler/macro', 'PhabricatorIRCMessage' => 'infrastructure/daemon/irc/message', 'PhabricatorIRCObjectNameHandler' => 'infrastructure/daemon/irc/handler/objectname', 'PhabricatorIRCProtocolHandler' => 'infrastructure/daemon/irc/handler/protocol', 'PhabricatorIRCWhatsNewHandler' => 'infrastructure/daemon/irc/handler/whatsnew', 'PhabricatorImageTransformer' => 'applications/files/transform', 'PhabricatorInfrastructureTestCase' => 'infrastructure/__tests__', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/javelin', 'PhabricatorJumpNavHandler' => 'applications/search/engine/jumpnav', 'PhabricatorLintEngine' => 'infrastructure/lint/engine', 'PhabricatorLiskDAO' => 'applications/base/storage/lisk', 'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/localdisk', 'PhabricatorLocalTimeTestCase' => 'view/utils/__tests__', 'PhabricatorLoginController' => 'applications/auth/controller/login', 'PhabricatorLoginValidateController' => 'applications/auth/controller/validate', 'PhabricatorLogoutController' => 'applications/auth/controller/logout', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base', 'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/amazonses', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/phpmailerlite', 'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/sendgrid', 'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/test', 'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/base', 'PhabricatorMarkupEngine' => 'applications/markup/engine', 'PhabricatorMetaMTAAttachment' => 'applications/metamta/storage/mail', 'PhabricatorMetaMTAController' => 'applications/metamta/controller/base', 'PhabricatorMetaMTADAO' => 'applications/metamta/storage/base', 'PhabricatorMetaMTAEmailBodyParser' => 'applications/metamta/parser', 'PhabricatorMetaMTAEmailBodyParserTestCase' => 'applications/metamta/parser/__tests__', 'PhabricatorMetaMTAListController' => 'applications/metamta/controller/list', 'PhabricatorMetaMTAMail' => 'applications/metamta/storage/mail', 'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/mail/__tests__', 'PhabricatorMetaMTAMailingList' => 'applications/metamta/storage/mailinglist', 'PhabricatorMetaMTAMailingListEditController' => 'applications/metamta/controller/mailinglistedit', 'PhabricatorMetaMTAMailingListsController' => 'applications/metamta/controller/mailinglists', 'PhabricatorMetaMTAReceiveController' => 'applications/metamta/controller/receive', 'PhabricatorMetaMTAReceivedListController' => 'applications/metamta/controller/receivedlist', 'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/receivedmail', 'PhabricatorMetaMTASendController' => 'applications/metamta/controller/send', 'PhabricatorMetaMTASendGridReceiveController' => 'applications/metamta/controller/sendgridreceive', 'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view', 'PhabricatorMetaMTAWorker' => 'applications/metamta/worker', 'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/mysql', 'PhabricatorNotificationsController' => 'applications/notifications/controller/base', 'PhabricatorOAuthClientAuthorization' => 'applications/oauthserver/storage/clientauthorization', 'PhabricatorOAuthClientAuthorizationBaseController' => 'applications/oauthserver/controller/clientauthorization/base', 'PhabricatorOAuthClientAuthorizationDeleteController' => 'applications/oauthserver/controller/clientauthorization/delete', 'PhabricatorOAuthClientAuthorizationEditController' => 'applications/oauthserver/controller/clientauthorization/edit', 'PhabricatorOAuthClientAuthorizationListController' => 'applications/oauthserver/controller/clientauthorization/list', 'PhabricatorOAuthClientBaseController' => 'applications/oauthserver/controller/client/base', 'PhabricatorOAuthClientDeleteController' => 'applications/oauthserver/controller/client/delete', 'PhabricatorOAuthClientEditController' => 'applications/oauthserver/controller/client/edit', 'PhabricatorOAuthClientListController' => 'applications/oauthserver/controller/client/list', 'PhabricatorOAuthClientViewController' => 'applications/oauthserver/controller/client/view', 'PhabricatorOAuthDefaultRegistrationController' => 'applications/auth/controller/oauthregistration/default', 'PhabricatorOAuthDiagnosticsController' => 'applications/auth/controller/oauthdiagnostics', 'PhabricatorOAuthFailureView' => 'applications/auth/view/oauthfailure', 'PhabricatorOAuthLoginController' => 'applications/auth/controller/oauth', 'PhabricatorOAuthProvider' => 'applications/auth/oauth/provider/base', 'PhabricatorOAuthProviderFacebook' => 'applications/auth/oauth/provider/facebook', 'PhabricatorOAuthProviderGitHub' => 'applications/auth/oauth/provider/github', 'PhabricatorOAuthProviderGoogle' => 'applications/auth/oauth/provider/google', 'PhabricatorOAuthProviderPhabricator' => 'applications/auth/oauth/provider/phabricator', 'PhabricatorOAuthRegistrationController' => 'applications/auth/controller/oauthregistration/base', 'PhabricatorOAuthResponse' => 'applications/oauthserver/response', 'PhabricatorOAuthServer' => 'applications/oauthserver/server', 'PhabricatorOAuthServerAccessToken' => 'applications/oauthserver/storage/accesstoken', 'PhabricatorOAuthServerAuthController' => 'applications/oauthserver/controller/auth', 'PhabricatorOAuthServerAuthorizationCode' => 'applications/oauthserver/storage/authorizationcode', 'PhabricatorOAuthServerClient' => 'applications/oauthserver/storage/client', 'PhabricatorOAuthServerController' => 'applications/oauthserver/controller/base', 'PhabricatorOAuthServerDAO' => 'applications/oauthserver/storage/base', 'PhabricatorOAuthServerScope' => 'applications/oauthserver/scope', 'PhabricatorOAuthServerTestCase' => 'applications/oauthserver/server/__tests__', 'PhabricatorOAuthServerTestController' => 'applications/oauthserver/controller/test', 'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/token', 'PhabricatorOAuthUnlinkController' => 'applications/auth/controller/unlink', 'PhabricatorObjectAttachmentEditor' => 'applications/search/editor/attach', 'PhabricatorObjectGraph' => 'applications/phid/graph', 'PhabricatorObjectHandle' => 'applications/phid/handle', 'PhabricatorObjectHandleConstants' => 'applications/phid/handle/const/base', 'PhabricatorObjectHandleData' => 'applications/phid/handle/data', 'PhabricatorObjectHandleStatus' => 'applications/phid/handle/const/status', 'PhabricatorObjectSelectorDialog' => 'view/control/objectselector', 'PhabricatorOwnerPathQuery' => 'applications/owners/query/path', 'PhabricatorOwnersController' => 'applications/owners/controller/base', 'PhabricatorOwnersDAO' => 'applications/owners/storage/base', 'PhabricatorOwnersDeleteController' => 'applications/owners/controller/delete', 'PhabricatorOwnersDetailController' => 'applications/owners/controller/detail', 'PhabricatorOwnersEditController' => 'applications/owners/controller/edit', 'PhabricatorOwnersListController' => 'applications/owners/controller/list', 'PhabricatorOwnersOwner' => 'applications/owners/storage/owner', 'PhabricatorOwnersPackage' => 'applications/owners/storage/package', 'PhabricatorOwnersPath' => 'applications/owners/storage/path', 'PhabricatorPHID' => 'applications/phid/storage/phid', 'PhabricatorPHIDConstants' => 'applications/phid/constants', 'PhabricatorPHIDController' => 'applications/phid/controller/base', 'PhabricatorPHIDDAO' => 'applications/phid/storage/base', 'PhabricatorPHIDLookupController' => 'applications/phid/controller/lookup', 'PhabricatorPaste' => 'applications/paste/storage/paste', 'PhabricatorPasteController' => 'applications/paste/controller/base', 'PhabricatorPasteDAO' => 'applications/paste/storage/base', 'PhabricatorPasteListController' => 'applications/paste/controller/list', 'PhabricatorPasteViewController' => 'applications/paste/controller/view', 'PhabricatorPeopleController' => 'applications/people/controller/base', 'PhabricatorPeopleEditController' => 'applications/people/controller/edit', 'PhabricatorPeopleListController' => 'applications/people/controller/list', 'PhabricatorPeopleLogsController' => 'applications/people/controller/logs', 'PhabricatorPeopleProfileController' => 'applications/people/controller/profile', 'PhabricatorProfileHeaderView' => 'view/layout/profileheader', 'PhabricatorProject' => 'applications/project/storage/project', 'PhabricatorProjectAffiliation' => 'applications/project/storage/affiliation', 'PhabricatorProjectConstants' => 'applications/project/constants/base', 'PhabricatorProjectController' => 'applications/project/controller/base', 'PhabricatorProjectCreateController' => 'applications/project/controller/create', 'PhabricatorProjectDAO' => 'applications/project/storage/base', 'PhabricatorProjectEditor' => 'applications/project/editor/project', 'PhabricatorProjectListController' => 'applications/project/controller/list', 'PhabricatorProjectNameCollisionException' => 'applications/project/exception/namecollison', 'PhabricatorProjectProfile' => 'applications/project/storage/profile', 'PhabricatorProjectProfileController' => 'applications/project/controller/profile', 'PhabricatorProjectProfileEditController' => 'applications/project/controller/profileedit', 'PhabricatorProjectQuery' => 'applications/project/query/project', 'PhabricatorProjectStatus' => 'applications/project/constants/status', 'PhabricatorProjectSubproject' => 'applications/project/storage/subproject', 'PhabricatorProjectTransaction' => 'applications/project/storage/transaction', 'PhabricatorProjectTransactionType' => 'applications/project/constants/transaction', 'PhabricatorProjectUpdateController' => 'applications/project/controller/update', 'PhabricatorRedirectController' => 'applications/base/controller/redirect', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/refresh', 'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential', 'PhabricatorRemarkupRuleDifferentialHandle' => 'infrastructure/markup/remarkup/markuprule/handle/differential', 'PhabricatorRemarkupRuleDiffusion' => 'infrastructure/markup/remarkup/markuprule/diffusion', 'PhabricatorRemarkupRuleEmbedFile' => 'infrastructure/markup/remarkup/markuprule/embedobject', 'PhabricatorRemarkupRuleImageMacro' => 'infrastructure/markup/remarkup/markuprule/imagemacro', 'PhabricatorRemarkupRuleManiphest' => 'infrastructure/markup/remarkup/markuprule/maniphest', 'PhabricatorRemarkupRuleManiphestHandle' => 'infrastructure/markup/remarkup/markuprule/handle/maniphest', 'PhabricatorRemarkupRuleMention' => 'infrastructure/markup/remarkup/markuprule/mention', 'PhabricatorRemarkupRuleObjectHandle' => 'infrastructure/markup/remarkup/markuprule/handle', 'PhabricatorRemarkupRuleObjectName' => 'infrastructure/markup/remarkup/markuprule/objectname', 'PhabricatorRemarkupRulePaste' => 'infrastructure/markup/remarkup/markuprule/paste', 'PhabricatorRemarkupRulePhriction' => 'infrastructure/markup/remarkup/markuprule/phriction', 'PhabricatorRemarkupRuleProxyImage' => 'infrastructure/markup/remarkup/markuprule/proxyimage', 'PhabricatorRemarkupRuleYoutube' => 'infrastructure/markup/remarkup/markuprule/youtube', 'PhabricatorRepository' => 'applications/repository/storage/repository', 'PhabricatorRepositoryArcanistProject' => 'applications/repository/storage/arcanistproject', 'PhabricatorRepositoryArcanistProjectEditController' => 'applications/repository/controller/arcansistprojectedit', 'PhabricatorRepositoryAuditRequest' => 'applications/repository/storage/auditrequest', 'PhabricatorRepositoryCommit' => 'applications/repository/storage/commit', 'PhabricatorRepositoryCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/base', 'PhabricatorRepositoryCommitData' => 'applications/repository/storage/commitdata', 'PhabricatorRepositoryCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/base', 'PhabricatorRepositoryCommitHeraldWorker' => 'applications/repository/worker/herald', 'PhabricatorRepositoryCommitMessageDetailParser' => 'applications/repository/parser/base', 'PhabricatorRepositoryCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/base', 'PhabricatorRepositoryCommitOwnersWorker' => 'applications/repository/worker/owner', 'PhabricatorRepositoryCommitParserWorker' => 'applications/repository/worker/base', 'PhabricatorRepositoryCommitTaskDaemon' => 'applications/repository/daemon/committask', 'PhabricatorRepositoryController' => 'applications/repository/controller/base', 'PhabricatorRepositoryCreateController' => 'applications/repository/controller/create', 'PhabricatorRepositoryDAO' => 'applications/repository/storage/base', 'PhabricatorRepositoryDaemon' => 'applications/repository/daemon/base', 'PhabricatorRepositoryDefaultCommitMessageDetailParser' => 'applications/repository/parser/default', 'PhabricatorRepositoryDeleteController' => 'applications/repository/controller/delete', 'PhabricatorRepositoryEditController' => 'applications/repository/controller/edit', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/git', 'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/git', 'PhabricatorRepositoryGitCommitDiscoveryDaemonTestCase' => 'applications/repository/daemon/commitdiscovery/git/__tests__', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/git', 'PhabricatorRepositoryGitFetchDaemon' => 'applications/repository/daemon/gitfetch', 'PhabricatorRepositoryListController' => 'applications/repository/controller/list', 'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/mercurial', 'PhabricatorRepositoryMercurialCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/mercurial', 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/mercurial', 'PhabricatorRepositoryMercurialPullDaemon' => 'applications/repository/daemon/mercurialpull', 'PhabricatorRepositoryPullLocalDaemon' => 'applications/repository/daemon/pulllocal', 'PhabricatorRepositoryShortcut' => 'applications/repository/storage/shortcut', 'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/svn', 'PhabricatorRepositorySvnCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/svn', 'PhabricatorRepositorySvnCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/svn', 'PhabricatorRepositorySymbol' => 'applications/repository/storage/symbol', 'PhabricatorRepositoryTestCase' => 'applications/repository/storage/repository/__tests__', 'PhabricatorRepositoryType' => 'applications/repository/constants/repositorytype', 'PhabricatorS3FileStorageEngine' => 'applications/files/engine/s3', 'PhabricatorSQLPatchList' => 'infrastructure/setup/sql', 'PhabricatorSearchAbstractDocument' => 'applications/search/index/abstractdocument', 'PhabricatorSearchAttachController' => 'applications/search/controller/attach', 'PhabricatorSearchBaseController' => 'applications/search/controller/base', 'PhabricatorSearchCommitIndexer' => 'applications/search/index/indexer/repository', 'PhabricatorSearchController' => 'applications/search/controller/search', 'PhabricatorSearchDAO' => 'applications/search/storage/base', 'PhabricatorSearchDifferentialIndexer' => 'applications/search/index/indexer/differential', 'PhabricatorSearchDocument' => 'applications/search/storage/document/document', 'PhabricatorSearchDocumentField' => 'applications/search/storage/document/field', 'PhabricatorSearchDocumentIndexer' => 'applications/search/index/indexer/base', 'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/relationship', 'PhabricatorSearchEngine' => 'applications/search/engine/base', 'PhabricatorSearchEngineMySQL' => 'applications/search/engine/mysql', 'PhabricatorSearchEngineSelector' => 'applications/search/selector/base', 'PhabricatorSearchField' => 'applications/search/constants/field', 'PhabricatorSearchIndexController' => 'applications/search/controller/index', 'PhabricatorSearchManiphestIndexer' => 'applications/search/index/indexer/maniphest', 'PhabricatorSearchPhrictionIndexer' => 'applications/search/index/indexer/phriction', 'PhabricatorSearchQuery' => 'applications/search/storage/query', 'PhabricatorSearchRelationship' => 'applications/search/constants/relationship', 'PhabricatorSearchResultView' => 'applications/search/view/searchresult', 'PhabricatorSearchScope' => 'applications/search/constants/scope', 'PhabricatorSearchSelectController' => 'applications/search/controller/select', 'PhabricatorSearchUserIndexer' => 'applications/search/index/indexer/user', 'PhabricatorSetup' => 'infrastructure/setup', 'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/choice', 'PhabricatorSlowvoteComment' => 'applications/slowvote/storage/comment', 'PhabricatorSlowvoteController' => 'applications/slowvote/controller/base', 'PhabricatorSlowvoteCreateController' => 'applications/slowvote/controller/create', 'PhabricatorSlowvoteDAO' => 'applications/slowvote/storage/base', 'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/list', 'PhabricatorSlowvoteOption' => 'applications/slowvote/storage/option', 'PhabricatorSlowvotePoll' => 'applications/slowvote/storage/poll', 'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/poll', 'PhabricatorStandardPageView' => 'view/page/standard', 'PhabricatorStatusController' => 'applications/status/base', 'PhabricatorSymbolNameLinter' => 'infrastructure/lint/hook/xhpastsymbolname', 'PhabricatorSyntaxHighlighter' => 'applications/markup/syntax', 'PhabricatorTaskmasterDaemon' => 'infrastructure/daemon/workers/taskmaster', 'PhabricatorTestCase' => 'infrastructure/testing/testcase', 'PhabricatorTimelineCursor' => 'infrastructure/daemon/timeline/storage/cursor', 'PhabricatorTimelineDAO' => 'infrastructure/daemon/timeline/storage/base', 'PhabricatorTimelineEvent' => 'infrastructure/daemon/timeline/storage/event', 'PhabricatorTimelineEventData' => 'infrastructure/daemon/timeline/storage/eventdata', 'PhabricatorTimelineIterator' => 'infrastructure/daemon/timeline/cursor/iterator', 'PhabricatorTimer' => 'applications/countdown/storage/timer', 'PhabricatorTransactionView' => 'view/layout/transaction', 'PhabricatorTransformedFile' => 'applications/files/storage/transformed', 'PhabricatorTrivialTestCase' => 'infrastructure/testing/testcase/__tests__', 'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common', 'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base', 'PhabricatorUIExample' => 'applications/uiexample/examples/base', 'PhabricatorUIExampleController' => 'applications/uiexample/controller/base', 'PhabricatorUIExampleRenderController' => 'applications/uiexample/controller/render', 'PhabricatorUIListFilterExample' => 'applications/uiexample/examples/listfilter', 'PhabricatorUIPagerExample' => 'applications/uiexample/examples/pager', 'PhabricatorUser' => 'applications/people/storage/user', 'PhabricatorUserAccountSettingsPanelController' => 'applications/people/controller/settings/panels/account', 'PhabricatorUserConduitSettingsPanelController' => 'applications/people/controller/settings/panels/conduit', 'PhabricatorUserDAO' => 'applications/people/storage/base', 'PhabricatorUserEmailPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/emailpref', 'PhabricatorUserEmailSettingsPanelController' => 'applications/people/controller/settings/panels/email', 'PhabricatorUserLog' => 'applications/people/storage/log', 'PhabricatorUserOAuthInfo' => 'applications/people/storage/useroauthinfo', 'PhabricatorUserOAuthSettingsPanelController' => 'applications/people/controller/settings/panels/oauth', 'PhabricatorUserPasswordSettingsPanelController' => 'applications/people/controller/settings/panels/password', 'PhabricatorUserPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/preferences', 'PhabricatorUserPreferences' => 'applications/people/storage/preferences', 'PhabricatorUserProfile' => 'applications/people/storage/profile', 'PhabricatorUserProfileSettingsPanelController' => 'applications/people/controller/settings/panels/profile', 'PhabricatorUserSSHKey' => 'applications/people/storage/usersshkey', 'PhabricatorUserSSHKeysSettingsPanelController' => 'applications/people/controller/settings/panels/sshkeys', 'PhabricatorUserSettingsController' => 'applications/people/controller/settings', 'PhabricatorUserSettingsPanelController' => 'applications/people/controller/settings/panels/base', 'PhabricatorUserTestCase' => 'applications/people/storage/user/__tests__', 'PhabricatorWorker' => 'infrastructure/daemon/workers/worker', 'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/base', 'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/task', 'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/taskdata', 'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/workertaskdetail', 'PhabricatorWorkerTaskUpdateController' => 'applications/daemon/controller/workertaskupdate', 'PhabricatorXHPASTViewController' => 'applications/xhpastview/controller/base', 'PhabricatorXHPASTViewDAO' => 'applications/xhpastview/storage/base', 'PhabricatorXHPASTViewFrameController' => 'applications/xhpastview/controller/viewframe', 'PhabricatorXHPASTViewFramesetController' => 'applications/xhpastview/controller/viewframeset', 'PhabricatorXHPASTViewInputController' => 'applications/xhpastview/controller/viewinput', 'PhabricatorXHPASTViewPanelController' => 'applications/xhpastview/controller/viewpanel', 'PhabricatorXHPASTViewParseTree' => 'applications/xhpastview/storage/parsetree', 'PhabricatorXHPASTViewRunController' => 'applications/xhpastview/controller/run', 'PhabricatorXHPASTViewStreamController' => 'applications/xhpastview/controller/viewstream', 'PhabricatorXHPASTViewTreeController' => 'applications/xhpastview/controller/viewtree', 'PhabricatorXHProfController' => 'applications/xhprof/controller/base', 'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/profile', 'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/symbol', 'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/toplevel', 'PhabricatorXHProfProfileView' => 'applications/xhprof/view/base', 'PhrictionActionConstants' => 'applications/phriction/constants/action', 'PhrictionChangeType' => 'applications/phriction/constants/changetype', 'PhrictionConstants' => 'applications/phriction/constants/base', 'PhrictionContent' => 'applications/phriction/storage/content', 'PhrictionController' => 'applications/phriction/controller/base', 'PhrictionDAO' => 'applications/phriction/storage/base', 'PhrictionDeleteController' => 'applications/phriction/controller/delete', 'PhrictionDiffController' => 'applications/phriction/controller/diff', 'PhrictionDocument' => 'applications/phriction/storage/document', 'PhrictionDocumentController' => 'applications/phriction/controller/document', 'PhrictionDocumentEditor' => 'applications/phriction/editor/document', 'PhrictionDocumentPreviewController' => 'applications/phriction/controller/documentpreview', 'PhrictionDocumentStatus' => 'applications/phriction/constants/documentstatus', 'PhrictionDocumentTestCase' => 'applications/phriction/storage/document/__tests__', 'PhrictionEditController' => 'applications/phriction/controller/edit', 'PhrictionHistoryController' => 'applications/phriction/controller/history', 'PhrictionListController' => 'applications/phriction/controller/list', 'QueryFormattingTestCase' => 'storage/qsprintf/__tests__', ), 'function' => array( '__phabricator_format_local_time' => 'view/utils', '_qsprintf_check_scalar_type' => 'storage/qsprintf', '_qsprintf_check_type' => 'storage/qsprintf', 'celerity_generate_unique_node_id' => 'infrastructure/celerity/api', 'celerity_register_resource_map' => 'infrastructure/celerity/map', 'javelin_render_tag' => 'infrastructure/javelin/markup', 'phabricator_date' => 'view/utils', 'phabricator_datetime' => 'view/utils', 'phabricator_format_relative_time' => 'view/utils', 'phabricator_format_units_generic' => 'view/utils', 'phabricator_render_form' => 'infrastructure/javelin/markup', 'phabricator_time' => 'view/utils', 'qsprintf' => 'storage/qsprintf', 'queryfx' => 'storage/queryfx', 'queryfx_all' => 'storage/queryfx', 'queryfx_one' => 'storage/queryfx', 'require_celerity_resource' => 'infrastructure/celerity/api', 'vqsprintf' => 'storage/qsprintf', 'vqueryfx' => 'storage/queryfx', 'vqueryfx_all' => 'storage/queryfx', 'xsprintf_query' => 'storage/qsprintf', ), 'requires_class' => array( 'Aphront304Response' => 'AphrontResponse', 'Aphront400Response' => 'AphrontResponse', 'Aphront403Response' => 'AphrontWebpageResponse', 'Aphront404Response' => 'AphrontWebpageResponse', 'AphrontAjaxResponse' => 'AphrontResponse', 'AphrontAttachedFileView' => 'AphrontView', 'AphrontCSRFException' => 'AphrontException', 'AphrontCalendarMonthView' => 'AphrontView', 'AphrontContextBarView' => 'AphrontView', 'AphrontCrumbsView' => 'AphrontView', 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', 'AphrontDialogResponse' => 'AphrontResponse', 'AphrontDialogView' => 'AphrontView', 'AphrontErrorView' => 'AphrontView', 'AphrontFilePreviewView' => 'AphrontView', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', 'AphrontFormDividerControl' => 'AphrontFormControl', 'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl', 'AphrontFormFileControl' => 'AphrontFormControl', 'AphrontFormLayoutView' => 'AphrontView', 'AphrontFormMarkupControl' => 'AphrontFormControl', 'AphrontFormPasswordControl' => 'AphrontFormControl', 'AphrontFormRadioButtonControl' => 'AphrontFormControl', 'AphrontFormRecaptchaControl' => 'AphrontFormControl', 'AphrontFormSelectControl' => 'AphrontFormControl', 'AphrontFormStaticControl' => 'AphrontFormControl', 'AphrontFormSubmitControl' => 'AphrontFormControl', 'AphrontFormTextAreaControl' => 'AphrontFormControl', 'AphrontFormTextControl' => 'AphrontFormControl', 'AphrontFormToggleButtonsControl' => 'AphrontFormControl', 'AphrontFormTokenizerControl' => 'AphrontFormControl', 'AphrontFormView' => 'AphrontView', 'AphrontHTTPSinkTestCase' => 'PhabricatorTestCase', 'AphrontHeadsupActionListView' => 'AphrontView', 'AphrontHeadsupActionView' => 'AphrontView', 'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontIsolatedDatabaseConnectionTestCase' => 'PhabricatorTestCase', 'AphrontIsolatedHTTPSink' => 'AphrontHTTPSink', 'AphrontJSONResponse' => 'AphrontResponse', 'AphrontJavelinView' => 'AphrontView', 'AphrontKeyboardShortcutsAvailableView' => 'AphrontView', 'AphrontListFilterView' => 'AphrontView', 'AphrontMiniPanelView' => 'AphrontView', 'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontMySQLDatabaseConnectionTestCase' => 'PhabricatorTestCase', 'AphrontNullView' => 'AphrontView', 'AphrontPHPHTTPSink' => 'AphrontHTTPSink', 'AphrontPageView' => 'AphrontView', 'AphrontPagerView' => 'AphrontView', 'AphrontPanelView' => 'AphrontView', 'AphrontPlainTextResponse' => 'AphrontResponse', 'AphrontQueryAccessDeniedException' => 'AphrontQueryRecoverableException', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 'AphrontQueryCountException' => 'AphrontQueryException', 'AphrontQueryDuplicateKeyException' => 'AphrontQueryException', 'AphrontQueryObjectMissingException' => 'AphrontQueryException', 'AphrontQueryParameterException' => 'AphrontQueryException', 'AphrontQueryRecoverableException' => 'AphrontQueryException', 'AphrontQuerySchemaException' => 'AphrontQueryException', 'AphrontRedirectException' => 'AphrontException', 'AphrontRedirectResponse' => 'AphrontResponse', 'AphrontReloadResponse' => 'AphrontRedirectResponse', 'AphrontRequestFailureView' => 'AphrontView', 'AphrontRequestTestCase' => 'PhabricatorTestCase', 'AphrontSideNavFilterView' => 'AphrontView', 'AphrontSideNavView' => 'AphrontView', 'AphrontTableView' => 'AphrontView', 'AphrontTokenizerTemplateView' => 'AphrontView', 'AphrontTypeaheadTemplateView' => 'AphrontView', 'AphrontWebpageResponse' => 'AphrontResponse', 'CelerityResourceController' => 'AphrontController', 'CelerityResourceGraph' => 'AbstractDirectedGraph', 'ConduitAPI_arcanist_Method' => 'ConduitAPIMethod', 'ConduitAPI_arcanist_projectinfo_Method' => 'ConduitAPI_arcanist_Method', 'ConduitAPI_audit_Method' => 'ConduitAPIMethod', 'ConduitAPI_audit_query_Method' => 'ConduitAPI_audit_Method', 'ConduitAPI_chatlog_Method' => 'ConduitAPIMethod', 'ConduitAPI_chatlog_query_Method' => 'ConduitAPI_chatlog_Method', 'ConduitAPI_chatlog_record_Method' => 'ConduitAPI_chatlog_Method', 'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod', 'ConduitAPI_conduit_getcertificate_Method' => 'ConduitAPIMethod', 'ConduitAPI_conduit_ping_Method' => 'ConduitAPIMethod', 'ConduitAPI_daemon_launched_Method' => 'ConduitAPIMethod', 'ConduitAPI_daemon_log_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_createcomment_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_createrevision_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_find_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getalldiffs_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getcommitmessage_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getcommitpaths_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getdiff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getrevision_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getrevisioncomments_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_getrevisionfeedback_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_markcommitted_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_parsecommitmessage_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_query_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_updaterevision_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_updatetaskrevisionassoc_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_updateunitresults_Method' => 'ConduitAPIMethod', 'ConduitAPI_diffusion_findsymbols_Method' => 'ConduitAPIMethod', 'ConduitAPI_diffusion_getcommits_Method' => 'ConduitAPIMethod', 'ConduitAPI_diffusion_getrecentcommitsbypath_Method' => 'ConduitAPIMethod', 'ConduitAPI_feed_publish_Method' => 'ConduitAPIMethod', 'ConduitAPI_feed_query_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_download_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_info_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod', 'ConduitAPI_macro_Method' => 'ConduitAPIMethod', 'ConduitAPI_macro_query_Method' => 'ConduitAPI_macro_Method', 'ConduitAPI_maniphest_Method' => 'ConduitAPIMethod', 'ConduitAPI_maniphest_createtask_Method' => 'ConduitAPI_maniphest_Method', 'ConduitAPI_maniphest_find_Method' => 'ConduitAPI_maniphest_Method', 'ConduitAPI_maniphest_gettasktransactions_Method' => 'ConduitAPI_maniphest_Method', 'ConduitAPI_maniphest_info_Method' => 'ConduitAPI_maniphest_Method', 'ConduitAPI_maniphest_update_Method' => 'ConduitAPI_maniphest_Method', 'ConduitAPI_paste_Method' => 'ConduitAPIMethod', 'ConduitAPI_paste_create_Method' => 'ConduitAPI_paste_Method', 'ConduitAPI_paste_info_Method' => 'ConduitAPI_paste_Method', 'ConduitAPI_path_getowners_Method' => 'ConduitAPIMethod', 'ConduitAPI_phid_Method' => 'ConduitAPIMethod', 'ConduitAPI_phid_info_Method' => 'ConduitAPI_phid_Method', 'ConduitAPI_phid_query_Method' => 'ConduitAPI_phid_Method', 'ConduitAPI_phriction_Method' => 'ConduitAPIMethod', 'ConduitAPI_phriction_edit_Method' => 'ConduitAPI_phriction_Method', 'ConduitAPI_phriction_history_Method' => 'ConduitAPI_phriction_Method', 'ConduitAPI_phriction_info_Method' => 'ConduitAPI_phriction_Method', 'ConduitAPI_project_Method' => 'ConduitAPIMethod', 'ConduitAPI_project_query_Method' => 'ConduitAPI_project_Method', 'ConduitAPI_remarkup_process_Method' => 'ConduitAPIMethod', 'ConduitAPI_slowvote_info_Method' => 'ConduitAPIMethod', 'ConduitAPI_user_Method' => 'ConduitAPIMethod', 'ConduitAPI_user_find_Method' => 'ConduitAPI_user_Method', 'ConduitAPI_user_info_Method' => 'ConduitAPI_user_Method', 'ConduitAPI_user_whoami_Method' => 'ConduitAPI_user_Method', 'DarkConsoleConfigPlugin' => 'DarkConsolePlugin', 'DarkConsoleController' => 'PhabricatorController', 'DarkConsoleErrorLogPlugin' => 'DarkConsolePlugin', 'DarkConsoleEventPlugin' => 'DarkConsolePlugin', 'DarkConsoleEventPluginAPI' => 'PhutilEventListener', 'DarkConsoleRequestPlugin' => 'DarkConsolePlugin', 'DarkConsoleServicesPlugin' => 'DarkConsolePlugin', 'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin', 'DifferentialActionHasNoEffectException' => 'DifferentialException', 'DifferentialAddCommentView' => 'AphrontView', 'DifferentialAffectedPath' => 'DifferentialDAO', 'DifferentialApplyPatchFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialArcanistProjectFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialAuditorsFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialAuthorFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialAuxiliaryField' => 'DifferentialDAO', 'DifferentialBlameRevisionFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialBranchFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail', 'DifferentialCCsFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialChangeset' => 'DifferentialDAO', 'DifferentialChangesetDetailView' => 'AphrontView', 'DifferentialChangesetListView' => 'AphrontView', 'DifferentialChangesetViewController' => 'DifferentialController', 'DifferentialComment' => 'DifferentialDAO', 'DifferentialCommentMail' => 'DifferentialMail', 'DifferentialCommentPreviewController' => 'DifferentialController', 'DifferentialCommentSaveController' => 'DifferentialController', 'DifferentialCommitsFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialController' => 'PhabricatorController', 'DifferentialDAO' => 'PhabricatorLiskDAO', 'DifferentialDateCreatedFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialDateModifiedFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialDefaultFieldSelector' => 'DifferentialFieldSelector', 'DifferentialDependenciesFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialDiff' => 'DifferentialDAO', 'DifferentialDiffContentMail' => 'DifferentialMail', 'DifferentialDiffCreateController' => 'DifferentialController', 'DifferentialDiffProperty' => 'DifferentialDAO', 'DifferentialDiffTableOfContentsView' => 'AphrontView', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialExceptionMail' => 'DifferentialMail', 'DifferentialExportPatchFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialGitSVNIDFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialHostFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialHunk' => 'DifferentialDAO', 'DifferentialInlineComment' => 'DifferentialDAO', 'DifferentialInlineCommentEditController' => 'DifferentialController', 'DifferentialInlineCommentEditView' => 'AphrontView', 'DifferentialInlineCommentPreviewController' => 'DifferentialController', 'DifferentialInlineCommentView' => 'AphrontView', 'DifferentialLinesFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialLintFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialLocalCommitsView' => 'AphrontView', 'DifferentialManiphestTasksFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail', 'DifferentialPathFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialPrimaryPaneView' => 'AphrontView', 'DifferentialReplyHandler' => 'PhabricatorMailReplyHandler', 'DifferentialRevertPlanFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialReviewRequestMail' => 'DifferentialMail', 'DifferentialReviewedByFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialReviewersFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialRevision' => 'DifferentialDAO', 'DifferentialRevisionCommentListView' => 'AphrontView', 'DifferentialRevisionCommentView' => 'AphrontView', 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionIDFieldParserTestCase' => 'PhabricatorTestCase', 'DifferentialRevisionIDFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionListView' => 'AphrontView', 'DifferentialRevisionStatsController' => 'DifferentialController', 'DifferentialRevisionStatsView' => 'AphrontView', 'DifferentialRevisionStatusFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'DifferentialSubscribeController' => 'DifferentialController', 'DifferentialSummaryFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialTestPlanFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialTitleFieldSpecification' => 'DifferentialFieldSpecification', 'DifferentialUnitFieldSpecification' => 'DifferentialFieldSpecification', 'DiffusionBranchTableView' => 'DiffusionView', 'DiffusionBrowseController' => 'DiffusionController', 'DiffusionBrowseFileController' => 'DiffusionController', 'DiffusionBrowseTableView' => 'DiffusionView', 'DiffusionChangeController' => 'DiffusionController', 'DiffusionCommentListView' => 'AphrontView', 'DiffusionCommentView' => 'AphrontView', 'DiffusionCommitChangeTableView' => 'DiffusionView', 'DiffusionCommitController' => 'DiffusionController', 'DiffusionContainsQuery' => 'DiffusionQuery', 'DiffusionController' => 'PhabricatorController', 'DiffusionDiffController' => 'DiffusionController', 'DiffusionEmptyResultView' => 'DiffusionView', 'DiffusionGitBranchQuery' => 'DiffusionBranchQuery', 'DiffusionGitBranchQueryTestCase' => 'PhabricatorTestCase', 'DiffusionGitBrowseQuery' => 'DiffusionBrowseQuery', 'DiffusionGitContainsQuery' => 'DiffusionContainsQuery', 'DiffusionGitDiffQuery' => 'DiffusionDiffQuery', 'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionGitHistoryQuery' => 'DiffusionHistoryQuery', 'DiffusionGitLastModifiedQuery' => 'DiffusionLastModifiedQuery', 'DiffusionGitRequest' => 'DiffusionRequest', 'DiffusionHistoryController' => 'DiffusionController', 'DiffusionHistoryTableView' => 'DiffusionView', 'DiffusionHomeController' => 'DiffusionController', 'DiffusionLastModifiedController' => 'DiffusionController', 'DiffusionMercurialBranchQuery' => 'DiffusionBranchQuery', 'DiffusionMercurialBrowseQuery' => 'DiffusionBrowseQuery', 'DiffusionMercurialContainsQuery' => 'DiffusionContainsQuery', 'DiffusionMercurialDiffQuery' => 'DiffusionDiffQuery', 'DiffusionMercurialFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionMercurialHistoryQuery' => 'DiffusionHistoryQuery', 'DiffusionMercurialLastModifiedQuery' => 'DiffusionLastModifiedQuery', 'DiffusionMercurialRequest' => 'DiffusionRequest', 'DiffusionPathCompleteController' => 'DiffusionController', 'DiffusionPathQueryTestCase' => 'PhabricatorTestCase', 'DiffusionPathValidateController' => 'DiffusionController', 'DiffusionRepositoryController' => 'DiffusionController', 'DiffusionSVNContainsQuery' => 'DiffusionContainsQuery', 'DiffusionSvnBrowseQuery' => 'DiffusionBrowseQuery', 'DiffusionSvnDiffQuery' => 'DiffusionDiffQuery', 'DiffusionSvnFileContentQuery' => 'DiffusionFileContentQuery', 'DiffusionSvnHistoryQuery' => 'DiffusionHistoryQuery', 'DiffusionSvnLastModifiedQuery' => 'DiffusionLastModifiedQuery', 'DiffusionSvnRequest' => 'DiffusionRequest', 'DiffusionSymbolController' => 'DiffusionController', 'DiffusionView' => 'AphrontView', 'DrydockAllocatorWorker' => 'PhabricatorWorker', 'DrydockCommandInterface' => 'DrydockInterface', 'DrydockController' => 'PhabricatorController', 'DrydockDAO' => 'PhabricatorLiskDAO', 'DrydockEC2HostBlueprint' => 'DrydockRemoteHostBlueprint', 'DrydockLease' => 'DrydockDAO', 'DrydockLeaseListController' => 'DrydockController', 'DrydockLeaseStatus' => 'DrydockConstants', 'DrydockLocalCommandInterface' => 'DrydockCommandInterface', 'DrydockLocalHostBlueprint' => 'DrydockBlueprint', 'DrydockRemoteHostBlueprint' => 'DrydockBlueprint', 'DrydockResource' => 'DrydockDAO', 'DrydockResourceAllocateController' => 'DrydockController', 'DrydockResourceListController' => 'DrydockController', 'DrydockResourceStatus' => 'DrydockConstants', 'DrydockSSHCommandInterface' => 'DrydockCommandInterface', 'HeraldAction' => 'HeraldDAO', 'HeraldAllRulesController' => 'HeraldController', 'HeraldApplyTranscript' => 'HeraldDAO', 'HeraldCommitAdapter' => 'HeraldObjectAdapter', 'HeraldCondition' => 'HeraldDAO', 'HeraldController' => 'PhabricatorController', 'HeraldDAO' => 'PhabricatorLiskDAO', 'HeraldDeleteController' => 'HeraldController', 'HeraldDifferentialRevisionAdapter' => 'HeraldObjectAdapter', 'HeraldDryRunAdapter' => 'HeraldObjectAdapter', 'HeraldHomeController' => 'HeraldController', 'HeraldNewController' => 'HeraldController', 'HeraldRule' => 'HeraldDAO', 'HeraldRuleController' => 'HeraldController', 'HeraldRuleEdit' => 'HeraldDAO', 'HeraldRuleEditHistoryController' => 'HeraldController', 'HeraldRuleEditHistoryView' => 'AphrontView', 'HeraldRuleListView' => 'AphrontView', 'HeraldTestConsoleController' => 'HeraldController', 'HeraldTranscript' => 'HeraldDAO', 'HeraldTranscriptController' => 'HeraldController', 'HeraldTranscriptListController' => 'HeraldController', 'JavelinReactorExample' => 'PhabricatorUIExample', 'JavelinViewExample' => 'PhabricatorUIExample', 'JavelinViewExampleServerView' => 'AphrontView', 'LiskIsolationTestCase' => 'PhabricatorTestCase', 'LiskIsolationTestDAO' => 'LiskDAO', 'ManiphestAction' => 'PhrictionConstants', 'ManiphestAuxiliaryFieldDefaultSpecification' => 'ManiphestAuxiliaryFieldSpecification', 'ManiphestBatchEditController' => 'ManiphestController', 'ManiphestController' => 'PhabricatorController', 'ManiphestDAO' => 'PhabricatorLiskDAO', 'ManiphestDefaultTaskExtensions' => 'ManiphestTaskExtensions', 'ManiphestExportController' => 'ManiphestController', 'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler', 'ManiphestReportController' => 'ManiphestController', 'ManiphestTask' => 'ManiphestDAO', 'ManiphestTaskAuxiliaryStorage' => 'ManiphestDAO', 'ManiphestTaskDescriptionChangeController' => 'ManiphestController', - 'ManiphestTaskDescriptionDiffController' => 'ManiphestTaskDescriptionChangeController', 'ManiphestTaskDescriptionPreviewController' => 'ManiphestController', 'ManiphestTaskDetailController' => 'ManiphestController', 'ManiphestTaskEditController' => 'ManiphestController', 'ManiphestTaskListController' => 'ManiphestController', 'ManiphestTaskListView' => 'ManiphestView', 'ManiphestTaskOwner' => 'ManiphestConstants', 'ManiphestTaskPriority' => 'ManiphestConstants', 'ManiphestTaskProject' => 'ManiphestDAO', 'ManiphestTaskStatus' => 'ManiphestConstants', 'ManiphestTaskSubscriber' => 'ManiphestDAO', 'ManiphestTaskSummaryView' => 'ManiphestView', 'ManiphestTransaction' => 'ManiphestDAO', 'ManiphestTransactionDetailView' => 'ManiphestView', 'ManiphestTransactionListView' => 'ManiphestView', 'ManiphestTransactionPreviewController' => 'ManiphestController', 'ManiphestTransactionSaveController' => 'ManiphestController', 'ManiphestTransactionType' => 'ManiphestConstants', 'ManiphestView' => 'AphrontView', 'MetaMTANotificationType' => 'MetaMTAConstants', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAphlictTestPageController' => 'PhabricatorNotificationsController', 'PhabricatorAuditAddCommentController' => 'PhabricatorAuditController', 'PhabricatorAuditComment' => 'PhabricatorAuditDAO', 'PhabricatorAuditCommitListView' => 'AphrontView', 'PhabricatorAuditController' => 'PhabricatorController', 'PhabricatorAuditDAO' => 'PhabricatorLiskDAO', 'PhabricatorAuditListController' => 'PhabricatorAuditController', 'PhabricatorAuditListView' => 'AphrontView', 'PhabricatorAuditPreviewController' => 'PhabricatorAuditController', 'PhabricatorAuditReplyHandler' => 'PhabricatorMailReplyHandler', 'PhabricatorAuthController' => 'PhabricatorController', 'PhabricatorCalendarBrowseController' => 'PhabricatorCalendarController', 'PhabricatorCalendarController' => 'PhabricatorController', 'PhabricatorChatLogChannelListController' => 'PhabricatorChatLogController', 'PhabricatorChatLogChannelLogController' => 'PhabricatorChatLogController', 'PhabricatorChatLogController' => 'PhabricatorController', 'PhabricatorChatLogDAO' => 'PhabricatorLiskDAO', 'PhabricatorChatLogEvent' => 'PhabricatorChatLogDAO', 'PhabricatorChatLogEventType' => 'PhabricatorChatLogConstants', 'PhabricatorConduitAPIController' => 'PhabricatorConduitController', 'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO', 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitConsoleController' => 'PhabricatorConduitController', 'PhabricatorConduitController' => 'PhabricatorController', 'PhabricatorConduitDAO' => 'PhabricatorLiskDAO', 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitTokenController' => 'PhabricatorConduitController', 'PhabricatorContentSourceView' => 'AphrontView', 'PhabricatorController' => 'AphrontController', 'PhabricatorCountdownController' => 'PhabricatorController', 'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO', 'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController', 'PhabricatorCountdownEditController' => 'PhabricatorCountdownController', 'PhabricatorCountdownListController' => 'PhabricatorCountdownController', 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', 'PhabricatorDaemon' => 'PhutilDaemon', 'PhabricatorDaemonCombinedLogController' => 'PhabricatorDaemonController', 'PhabricatorDaemonConsoleController' => 'PhabricatorDaemonController', 'PhabricatorDaemonController' => 'PhabricatorController', 'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO', 'PhabricatorDaemonLog' => 'PhabricatorDaemonDAO', 'PhabricatorDaemonLogEvent' => 'PhabricatorDaemonDAO', 'PhabricatorDaemonLogEventsView' => 'AphrontView', 'PhabricatorDaemonLogListController' => 'PhabricatorDaemonController', 'PhabricatorDaemonLogListView' => 'AphrontView', 'PhabricatorDaemonLogViewController' => 'PhabricatorDaemonController', 'PhabricatorDaemonTimelineConsoleController' => 'PhabricatorDaemonController', 'PhabricatorDaemonTimelineEventController' => 'PhabricatorDaemonController', 'PhabricatorDefaultFileStorageEngineSelector' => 'PhabricatorFileStorageEngineSelector', 'PhabricatorDefaultSearchEngineSelector' => 'PhabricatorSearchEngineSelector', 'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryViewController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryController' => 'PhabricatorController', 'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorDirectoryEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController', 'PhabricatorDisabledUserController' => 'PhabricatorAuthController', 'PhabricatorDraft' => 'PhabricatorDraftDAO', 'PhabricatorDraftDAO' => 'PhabricatorLiskDAO', 'PhabricatorEmailLoginController' => 'PhabricatorAuthController', 'PhabricatorEmailTokenController' => 'PhabricatorAuthController', 'PhabricatorEnvTestCase' => 'PhabricatorTestCase', 'PhabricatorEvent' => 'PhutilEvent', 'PhabricatorEventType' => 'PhutilEventType', 'PhabricatorFeedController' => 'PhabricatorController', 'PhabricatorFeedDAO' => 'PhabricatorLiskDAO', 'PhabricatorFeedPublicStreamController' => 'PhabricatorFeedController', 'PhabricatorFeedStoryAudit' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryData' => 'PhabricatorFeedDAO', 'PhabricatorFeedStoryDifferential' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryManiphest' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryPhriction' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryProject' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO', 'PhabricatorFeedStoryStatus' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryTypeConstants' => 'PhabricatorFeedConstants', 'PhabricatorFeedStoryUnknown' => 'PhabricatorFeedStory', 'PhabricatorFeedStoryView' => 'PhabricatorFeedView', 'PhabricatorFeedView' => 'AphrontView', 'PhabricatorFile' => 'PhabricatorFileDAO', 'PhabricatorFileController' => 'PhabricatorController', 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', 'PhabricatorFileDataController' => 'PhabricatorFileController', 'PhabricatorFileDeleteController' => 'PhabricatorFileController', 'PhabricatorFileDropUploadController' => 'PhabricatorFileController', 'PhabricatorFileImageMacro' => 'PhabricatorFileDAO', 'PhabricatorFileInfoController' => 'PhabricatorFileController', 'PhabricatorFileListController' => 'PhabricatorFileController', 'PhabricatorFileMacroDeleteController' => 'PhabricatorFileController', 'PhabricatorFileMacroEditController' => 'PhabricatorFileController', 'PhabricatorFileMacroListController' => 'PhabricatorFileController', 'PhabricatorFileProxyController' => 'PhabricatorFileController', 'PhabricatorFileProxyImage' => 'PhabricatorFileDAO', 'PhabricatorFileSideNavView' => 'AphrontView', 'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO', 'PhabricatorFileTransformController' => 'PhabricatorFileController', 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileUploadView' => 'AphrontView', 'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon', 'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker', 'PhabricatorHelpController' => 'PhabricatorController', 'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController', 'PhabricatorIRCBot' => 'PhabricatorDaemon', 'PhabricatorIRCDifferentialNotificationHandler' => 'PhabricatorIRCHandler', 'PhabricatorIRCLogHandler' => 'PhabricatorIRCHandler', 'PhabricatorIRCMacroHandler' => 'PhabricatorIRCHandler', 'PhabricatorIRCObjectNameHandler' => 'PhabricatorIRCHandler', 'PhabricatorIRCProtocolHandler' => 'PhabricatorIRCHandler', 'PhabricatorIRCWhatsNewHandler' => 'PhabricatorIRCHandler', 'PhabricatorInfrastructureTestCase' => 'PhabricatorTestCase', 'PhabricatorJavelinLinter' => 'ArcanistLinter', 'PhabricatorLintEngine' => 'PhutilLintEngine', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorLocalTimeTestCase' => 'PhabricatorTestCase', 'PhabricatorLoginController' => 'PhabricatorAuthController', 'PhabricatorLoginValidateController' => 'PhabricatorAuthController', 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMetaMTAController' => 'PhabricatorController', 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO', 'PhabricatorMetaMTAEmailBodyParserTestCase' => 'PhabricatorTestCase', 'PhabricatorMetaMTAListController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase', 'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingListEditController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAReceiveController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAReceivedListController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTASendGridReceiveController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAWorker' => 'PhabricatorWorker', 'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine', 'PhabricatorNotificationsController' => 'PhabricatorController', 'PhabricatorOAuthClientAuthorization' => 'PhabricatorOAuthServerDAO', 'PhabricatorOAuthClientAuthorizationBaseController' => 'PhabricatorOAuthServerController', 'PhabricatorOAuthClientAuthorizationDeleteController' => 'PhabricatorOAuthClientAuthorizationBaseController', 'PhabricatorOAuthClientAuthorizationEditController' => 'PhabricatorOAuthClientAuthorizationBaseController', 'PhabricatorOAuthClientAuthorizationListController' => 'PhabricatorOAuthClientAuthorizationBaseController', 'PhabricatorOAuthClientBaseController' => 'PhabricatorOAuthServerController', 'PhabricatorOAuthClientDeleteController' => 'PhabricatorOAuthClientBaseController', 'PhabricatorOAuthClientEditController' => 'PhabricatorOAuthClientBaseController', 'PhabricatorOAuthClientListController' => 'PhabricatorOAuthClientBaseController', 'PhabricatorOAuthClientViewController' => 'PhabricatorOAuthClientBaseController', 'PhabricatorOAuthDefaultRegistrationController' => 'PhabricatorOAuthRegistrationController', 'PhabricatorOAuthDiagnosticsController' => 'PhabricatorAuthController', 'PhabricatorOAuthFailureView' => 'AphrontView', 'PhabricatorOAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorOAuthProviderFacebook' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthProviderGitHub' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthProviderGoogle' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthProviderPhabricator' => 'PhabricatorOAuthProvider', 'PhabricatorOAuthRegistrationController' => 'PhabricatorAuthController', 'PhabricatorOAuthResponse' => 'AphrontResponse', 'PhabricatorOAuthServerAccessToken' => 'PhabricatorOAuthServerDAO', 'PhabricatorOAuthServerAuthController' => 'PhabricatorAuthController', 'PhabricatorOAuthServerAuthorizationCode' => 'PhabricatorOAuthServerDAO', 'PhabricatorOAuthServerClient' => 'PhabricatorOAuthServerDAO', 'PhabricatorOAuthServerController' => 'PhabricatorController', 'PhabricatorOAuthServerDAO' => 'PhabricatorLiskDAO', 'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase', 'PhabricatorOAuthServerTestController' => 'PhabricatorOAuthServerController', 'PhabricatorOAuthServerTokenController' => 'PhabricatorAuthController', 'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController', 'PhabricatorObjectGraph' => 'AbstractDirectedGraph', 'PhabricatorObjectHandleStatus' => 'PhabricatorObjectHandleConstants', 'PhabricatorOwnersController' => 'PhabricatorController', 'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO', 'PhabricatorOwnersDeleteController' => 'PhabricatorOwnersController', 'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController', 'PhabricatorOwnersEditController' => 'PhabricatorOwnersController', 'PhabricatorOwnersListController' => 'PhabricatorOwnersController', 'PhabricatorOwnersOwner' => 'PhabricatorOwnersDAO', 'PhabricatorOwnersPackage' => 'PhabricatorOwnersDAO', 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', 'PhabricatorPHID' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDController' => 'PhabricatorController', 'PhabricatorPHIDDAO' => 'PhabricatorLiskDAO', 'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController', 'PhabricatorPaste' => 'PhabricatorPasteDAO', 'PhabricatorPasteController' => 'PhabricatorController', 'PhabricatorPasteDAO' => 'PhabricatorLiskDAO', 'PhabricatorPasteListController' => 'PhabricatorPasteController', 'PhabricatorPasteViewController' => 'PhabricatorPasteController', 'PhabricatorPeopleController' => 'PhabricatorController', 'PhabricatorPeopleEditController' => 'PhabricatorPeopleController', 'PhabricatorPeopleListController' => 'PhabricatorPeopleController', 'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController', 'PhabricatorProfileHeaderView' => 'AphrontView', 'PhabricatorProject' => 'PhabricatorProjectDAO', 'PhabricatorProjectAffiliation' => 'PhabricatorProjectDAO', 'PhabricatorProjectController' => 'PhabricatorController', 'PhabricatorProjectCreateController' => 'PhabricatorProjectController', 'PhabricatorProjectDAO' => 'PhabricatorLiskDAO', 'PhabricatorProjectListController' => 'PhabricatorProjectController', 'PhabricatorProjectProfile' => 'PhabricatorProjectDAO', 'PhabricatorProjectProfileController' => 'PhabricatorProjectController', 'PhabricatorProjectProfileEditController' => 'PhabricatorProjectController', 'PhabricatorProjectSubproject' => 'PhabricatorProjectDAO', 'PhabricatorProjectTransaction' => 'PhabricatorProjectDAO', 'PhabricatorProjectTransactionType' => 'PhabricatorProjectConstants', 'PhabricatorProjectUpdateController' => 'PhabricatorProjectController', 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', 'PhabricatorRemarkupRuleDifferential' => 'PhabricatorRemarkupRuleObjectName', 'PhabricatorRemarkupRuleDifferentialHandle' => 'PhabricatorRemarkupRuleObjectHandle', 'PhabricatorRemarkupRuleDiffusion' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleEmbedFile' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleImageMacro' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleManiphest' => 'PhabricatorRemarkupRuleObjectName', 'PhabricatorRemarkupRuleManiphestHandle' => 'PhabricatorRemarkupRuleObjectHandle', 'PhabricatorRemarkupRuleMention' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleObjectHandle' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleObjectName' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRulePaste' => 'PhabricatorRemarkupRuleObjectName', 'PhabricatorRemarkupRulePhriction' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleProxyImage' => 'PhutilRemarkupRule', 'PhabricatorRemarkupRuleYoutube' => 'PhutilRemarkupRule', 'PhabricatorRepository' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryArcanistProject' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryArcanistProjectEditController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryAuditRequest' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryCommit' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryCommitDiscoveryDaemon' => 'PhabricatorRepositoryDaemon', 'PhabricatorRepositoryCommitHeraldWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitMessageParserWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitOwnersWorker' => 'PhabricatorRepositoryCommitParserWorker', 'PhabricatorRepositoryCommitParserWorker' => 'PhabricatorWorker', 'PhabricatorRepositoryCommitTaskDaemon' => 'PhabricatorRepositoryDaemon', 'PhabricatorRepositoryController' => 'PhabricatorController', 'PhabricatorRepositoryCreateController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorRepositoryDaemon' => 'PhabricatorDaemon', 'PhabricatorRepositoryDefaultCommitMessageDetailParser' => 'PhabricatorRepositoryCommitMessageDetailParser', 'PhabricatorRepositoryDeleteController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryEditController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon', 'PhabricatorRepositoryGitCommitDiscoveryDaemonTestCase' => 'PhabricatorTestCase', 'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositoryGitFetchDaemon' => 'PhabricatorRepositoryPullLocalDaemon', 'PhabricatorRepositoryListController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryMercurialCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositoryMercurialCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon', 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositoryMercurialPullDaemon' => 'PhabricatorRepositoryPullLocalDaemon', 'PhabricatorRepositoryPullLocalDaemon' => 'PhabricatorRepositoryDaemon', 'PhabricatorRepositoryShortcut' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker', 'PhabricatorRepositorySvnCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon', 'PhabricatorRepositorySvnCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositorySymbol' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryTestCase' => 'PhabricatorTestCase', 'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine', - 'PhabricatorSearchAttachController' => 'PhabricatorSearchController', + 'PhabricatorSearchAttachController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchBaseController' => 'PhabricatorController', 'PhabricatorSearchCommitIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 'PhabricatorSearchDifferentialIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO', 'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO', 'PhabricatorSearchEngineMySQL' => 'PhabricatorSearchEngine', 'PhabricatorSearchIndexController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchManiphestIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchPhrictionIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchQuery' => 'PhabricatorSearchDAO', 'PhabricatorSearchResultView' => 'AphrontView', - 'PhabricatorSearchSelectController' => 'PhabricatorSearchController', + 'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchUserIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvoteComment' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvoteController' => 'PhabricatorController', 'PhabricatorSlowvoteCreateController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteDAO' => 'PhabricatorLiskDAO', 'PhabricatorSlowvoteListController' => 'PhabricatorSlowvoteController', 'PhabricatorSlowvoteOption' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvotePoll' => 'PhabricatorSlowvoteDAO', 'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController', 'PhabricatorStandardPageView' => 'AphrontPageView', 'PhabricatorStatusController' => 'PhabricatorController', 'PhabricatorSymbolNameLinter' => 'ArcanistXHPASTLintNamingHook', 'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon', 'PhabricatorTestCase' => 'ArcanistPhutilTestCase', 'PhabricatorTimelineCursor' => 'PhabricatorTimelineDAO', 'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO', 'PhabricatorTimelineEvent' => 'PhabricatorTimelineDAO', 'PhabricatorTimelineEventData' => 'PhabricatorTimelineDAO', 'PhabricatorTimer' => 'PhabricatorCountdownDAO', 'PhabricatorTransactionView' => 'AphrontView', 'PhabricatorTransformedFile' => 'PhabricatorFileDAO', 'PhabricatorTrivialTestCase' => 'PhabricatorTestCase', 'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', 'PhabricatorUIExampleController' => 'PhabricatorController', 'PhabricatorUIExampleRenderController' => 'PhabricatorUIExampleController', 'PhabricatorUIListFilterExample' => 'PhabricatorUIExample', 'PhabricatorUIPagerExample' => 'PhabricatorUIExample', 'PhabricatorUser' => 'PhabricatorUserDAO', 'PhabricatorUserAccountSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserConduitSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', 'PhabricatorUserEmailPreferenceSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserEmailSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserLog' => 'PhabricatorUserDAO', 'PhabricatorUserOAuthInfo' => 'PhabricatorUserDAO', 'PhabricatorUserOAuthSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserPasswordSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserPreferenceSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserPreferences' => 'PhabricatorUserDAO', 'PhabricatorUserProfile' => 'PhabricatorUserDAO', 'PhabricatorUserProfileSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserSSHKey' => 'PhabricatorUserDAO', 'PhabricatorUserSSHKeysSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 'PhabricatorUserSettingsController' => 'PhabricatorPeopleController', 'PhabricatorUserSettingsPanelController' => 'PhabricatorPeopleController', 'PhabricatorUserTestCase' => 'PhabricatorTestCase', 'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO', 'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO', 'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO', 'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController', 'PhabricatorWorkerTaskUpdateController' => 'PhabricatorDaemonController', 'PhabricatorXHPASTViewController' => 'PhabricatorController', 'PhabricatorXHPASTViewDAO' => 'PhabricatorLiskDAO', 'PhabricatorXHPASTViewFrameController' => 'PhabricatorXHPASTViewController', 'PhabricatorXHPASTViewFramesetController' => 'PhabricatorXHPASTViewController', 'PhabricatorXHPASTViewInputController' => 'PhabricatorXHPASTViewPanelController', 'PhabricatorXHPASTViewPanelController' => 'PhabricatorXHPASTViewController', 'PhabricatorXHPASTViewParseTree' => 'PhabricatorXHPASTViewDAO', 'PhabricatorXHPASTViewRunController' => 'PhabricatorXHPASTViewController', 'PhabricatorXHPASTViewStreamController' => 'PhabricatorXHPASTViewPanelController', 'PhabricatorXHPASTViewTreeController' => 'PhabricatorXHPASTViewPanelController', 'PhabricatorXHProfController' => 'PhabricatorController', 'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController', 'PhabricatorXHProfProfileSymbolView' => 'PhabricatorXHProfProfileView', 'PhabricatorXHProfProfileTopLevelView' => 'PhabricatorXHProfProfileView', 'PhabricatorXHProfProfileView' => 'AphrontView', 'PhrictionActionConstants' => 'PhrictionConstants', 'PhrictionChangeType' => 'PhrictionConstants', 'PhrictionContent' => 'PhrictionDAO', 'PhrictionController' => 'PhabricatorController', 'PhrictionDAO' => 'PhabricatorLiskDAO', 'PhrictionDeleteController' => 'PhrictionController', 'PhrictionDiffController' => 'PhrictionController', 'PhrictionDocument' => 'PhrictionDAO', 'PhrictionDocumentController' => 'PhrictionController', 'PhrictionDocumentPreviewController' => 'PhrictionController', 'PhrictionDocumentStatus' => 'PhrictionConstants', 'PhrictionDocumentTestCase' => 'PhabricatorTestCase', 'PhrictionEditController' => 'PhrictionController', 'PhrictionHistoryController' => 'PhrictionController', 'PhrictionListController' => 'PhrictionController', 'QueryFormattingTestCase' => 'PhabricatorTestCase', ), 'requires_interface' => array( ), )); diff --git a/src/aphront/console/controller/DarkConsoleController.php b/src/aphront/console/controller/DarkConsoleController.php index 92bc1be753..9b0aebe7df 100644 --- a/src/aphront/console/controller/DarkConsoleController.php +++ b/src/aphront/console/controller/DarkConsoleController.php @@ -1,60 +1,60 @@ getRequest(); $user = $request->getUser(); $visible = $request->getStr('visible'); if (strlen($visible)) { $user->setConsoleVisible((int)$visible); $user->save(); return new AphrontAjaxResponse(); } $tab = $request->getStr('tab'); if (strlen($tab)) { $user->setConsoleTab($tab); $user->save(); return new AphrontAjaxResponse(); } if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) { $user->setConsoleEnabled(!$user->getConsoleEnabled()); if ($user->getConsoleEnabled()) { $user->setConsoleVisible(true); } $user->save(); if ($request->isAjax()) { return new AphrontRedirectResponse(); } else { return id(new AphrontRedirectResponse())->setURI('/'); } } } } diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index 6e29a1abe9..60ec197c3a 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -1,628 +1,626 @@ getResourceURIMapRules() + array( '/(?:(?Pjump)/)?' => 'PhabricatorDirectoryMainController', '/(?:(?Pfeed)/)' => array( 'public/' => 'PhabricatorFeedPublicStreamController', '(?:(?P[^/]+)/)?' => 'PhabricatorDirectoryMainController', ), '/directory/' => array( '(?P\d+)/' => 'PhabricatorDirectoryCategoryViewController', 'edit/' => 'PhabricatorDirectoryEditController', 'item/edit/(?:(?P\d+)/)?' => 'PhabricatorDirectoryItemEditController', 'item/delete/(?P\d+)/' => 'PhabricatorDirectoryItemDeleteController', 'category/edit/(?:(?P\d+)/)?' => 'PhabricatorDirectoryCategoryEditController', 'category/delete/(?P\d+)/' => 'PhabricatorDirectoryCategoryDeleteController', ), '/file/' => array( '' => 'PhabricatorFileListController', 'filter/(?P\w+)/' => 'PhabricatorFileListController', 'upload/' => 'PhabricatorFileUploadController', 'dropupload/' => 'PhabricatorFileDropUploadController', 'delete/(?P\d+)/' => 'PhabricatorFileDeleteController', 'info/(?P[^/]+)/' => 'PhabricatorFileInfoController', 'data/(?P[^/]+)/(?P[^/]+)/.*' => 'PhabricatorFileDataController', // TODO: This is a deprecated version of /data/. Remove it after // old links have had a chance to rot. 'alt/(?P[^/]+)/(?P[^/]+)/' => 'PhabricatorFileDataController', 'macro/' => array( '' => 'PhabricatorFileMacroListController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorFileMacroEditController', 'delete/(?P\d+)/' => 'PhabricatorFileMacroDeleteController', ), 'proxy/' => 'PhabricatorFileProxyController', 'xform/(?P[^/]+)/(?P[^/]+)/' => 'PhabricatorFileTransformController', ), '/phid/' => array( '' => 'PhabricatorPHIDLookupController', ), '/people/' => array( '' => 'PhabricatorPeopleListController', 'logs/' => 'PhabricatorPeopleLogsController', 'edit/(?:(?P\d+)/(?:(?P\w+)/)?)?' => 'PhabricatorPeopleEditController', ), '/p/(?P\w+)/(?:(?P\w+)/)?' => 'PhabricatorPeopleProfileController', '/conduit/' => array( '' => 'PhabricatorConduitConsoleController', 'method/(?P[^/]+)/' => 'PhabricatorConduitConsoleController', 'log/' => 'PhabricatorConduitLogController', 'log/view/(?P[^/]+)/' => 'PhabricatorConduitLogController', 'token/' => 'PhabricatorConduitTokenController', ), '/api/(?P[^/]+)' => 'PhabricatorConduitAPIController', '/D(?P\d+)' => 'DifferentialRevisionViewController', '/differential/' => array( '' => 'DifferentialRevisionListController', 'filter/(?P\w+)/(?:(?P\w+)/)?' => 'DifferentialRevisionListController', 'stats/(?P\w+)/' => 'DifferentialRevisionStatsController', 'diff/' => array( '(?P\d+)/' => 'DifferentialDiffViewController', 'create/' => 'DifferentialDiffCreateController', ), 'changeset/' => 'DifferentialChangesetViewController', 'revision/edit/(?:(?P\d+)/)?' => 'DifferentialRevisionEditController', 'comment/' => array( 'preview/(?P\d+)/' => 'DifferentialCommentPreviewController', 'save/' => 'DifferentialCommentSaveController', 'inline/' => array( 'preview/(?P\d+)/' => 'DifferentialInlineCommentPreviewController', 'edit/(?P\d+)/' => 'DifferentialInlineCommentEditController', ), ), 'subscribe/(?Padd|rem)/(?P\d+)/' => 'DifferentialSubscribeController', ), '/typeahead/' => array( 'common/(?P\w+)/' => 'PhabricatorTypeaheadCommonDatasourceController', ), '/mail/' => array( '' => 'PhabricatorMetaMTAListController', 'send/' => 'PhabricatorMetaMTASendController', 'view/(?P\d+)/' => 'PhabricatorMetaMTAViewController', 'lists/' => 'PhabricatorMetaMTAMailingListsController', 'lists/edit/(?:(?P\d+)/)?' => 'PhabricatorMetaMTAMailingListEditController', 'receive/' => 'PhabricatorMetaMTAReceiveController', 'received/' => 'PhabricatorMetaMTAReceivedListController', 'sendgrid/' => 'PhabricatorMetaMTASendGridReceiveController', ), '/login/' => array( '' => 'PhabricatorLoginController', 'email/' => 'PhabricatorEmailLoginController', 'etoken/(?P\w+)/' => 'PhabricatorEmailTokenController', 'refresh/' => 'PhabricatorRefreshCSRFController', 'validate/' => 'PhabricatorLoginValidateController', ), '/logout/' => 'PhabricatorLogoutController', '/oauth/' => array( '(?P\w+)/' => array( 'login/' => 'PhabricatorOAuthLoginController', 'diagnose/' => 'PhabricatorOAuthDiagnosticsController', 'unlink/' => 'PhabricatorOAuthUnlinkController', ), ), '/oauthserver/' => array( 'auth/' => 'PhabricatorOAuthServerAuthController', 'test/' => 'PhabricatorOAuthServerTestController', 'token/' => 'PhabricatorOAuthServerTokenController', 'clientauthorization/' => array( '' => 'PhabricatorOAuthClientAuthorizationListController', 'delete/(?P[^/]+)/' => 'PhabricatorOAuthClientAuthorizationDeleteController', 'edit/(?P[^/]+)/' => 'PhabricatorOAuthClientAuthorizationEditController', ), 'client/' => array( '' => 'PhabricatorOAuthClientListController', 'create/' => 'PhabricatorOAuthClientEditController', 'delete/(?P[^/]+)/' => 'PhabricatorOAuthClientDeleteController', 'edit/(?P[^/]+)/' => 'PhabricatorOAuthClientEditController', 'view/(?P[^/]+)/' => 'PhabricatorOAuthClientViewController', ), ), '/xhprof/' => array( 'profile/(?P[^/]+)/' => 'PhabricatorXHProfProfileController', ), '/~/' => 'DarkConsoleController', '/settings/' => array( '(?:page/(?P[^/]+)/)?' => 'PhabricatorUserSettingsController', ), '/maniphest/' => array( '' => 'ManiphestTaskListController', 'view/(?P\w+)/' => 'ManiphestTaskListController', 'report/(?:(?P\w+)/)?' => 'ManiphestReportController', 'batch/' => 'ManiphestBatchEditController', 'task/' => array( 'create/' => 'ManiphestTaskEditController', 'edit/(?P\d+)/' => 'ManiphestTaskEditController', - 'descriptionchange/(?P\d+)/' => + 'descriptionchange/(?:(?P\d+)/)?' => 'ManiphestTaskDescriptionChangeController', - 'descriptiondiff/' => - 'ManiphestTaskDescriptionDiffController', 'descriptionpreview/' => 'ManiphestTaskDescriptionPreviewController', ), 'transaction/' => array( 'save/' => 'ManiphestTransactionSaveController', 'preview/(?P\d+)/' => 'ManiphestTransactionPreviewController', ), 'export/(?P[^/]+)/' => 'ManiphestExportController', ), '/T(?P\d+)' => 'ManiphestTaskDetailController', '/repository/' => array( '' => 'PhabricatorRepositoryListController', 'create/' => 'PhabricatorRepositoryCreateController', 'edit/(?P\d+)/(?:(?P\w+)?/)?' => 'PhabricatorRepositoryEditController', 'delete/(?P\d+)/' => 'PhabricatorRepositoryDeleteController', 'project/(?P\d+)/' => 'PhabricatorRepositoryArcanistProjectEditController', ), '/search/' => array( '' => 'PhabricatorSearchController', '(?P[^/]+)/' => 'PhabricatorSearchController', 'attach/(?P[^/]+)/(?P\w+)/(?:(?P\w+)/)?' => 'PhabricatorSearchAttachController', 'select/(?P\w+)/' => 'PhabricatorSearchSelectController', 'index/(?P[^/]+)/' => 'PhabricatorSearchIndexController', ), '/project/' => array( '' => 'PhabricatorProjectListController', 'filter/(?P[^/]+)/' => 'PhabricatorProjectListController', 'edit/(?P\d+)/' => 'PhabricatorProjectProfileEditController', 'view/(?P\d+)/(?:(?P\w+)/)?' => 'PhabricatorProjectProfileController', 'create/' => 'PhabricatorProjectCreateController', 'update/(?P\d+)/(?P[^/]+)/' => 'PhabricatorProjectUpdateController', ), '/r(?P[A-Z]+)(?P[a-z0-9]+)' => 'DiffusionCommitController', '/diffusion/' => array( '' => 'DiffusionHomeController', '(?P[A-Z]+)/' => array( '' => 'DiffusionRepositoryController', 'repository/'. '(?P[^/]+)/' => 'DiffusionRepositoryController', 'change/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?' => 'DiffusionChangeController', 'history/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?' => 'DiffusionHistoryController', 'browse/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?'. '(?:[$](?P\d+(?:-\d+)?))?' => 'DiffusionBrowseController', 'diff/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?' => 'DiffusionDiffController', 'lastmodified/'. '(?P.*?)'. '(?:[;](?P[a-z0-9]+))?' => 'DiffusionLastModifiedController', ), 'services/' => array( 'path/' => array( 'complete/' => 'DiffusionPathCompleteController', 'validate/' => 'DiffusionPathValidateController', ), ), 'symbol/(?P[^/]+)/' => 'DiffusionSymbolController', ), '/daemon/' => array( 'task/(?P\d+)/' => 'PhabricatorWorkerTaskDetailController', 'task/(?P\d+)/(?P[^/]+)/' => 'PhabricatorWorkerTaskUpdateController', 'log/' => array( '' => 'PhabricatorDaemonLogListController', 'combined/' => 'PhabricatorDaemonCombinedLogController', '(?P\d+)/' => 'PhabricatorDaemonLogViewController', ), 'timeline/' => 'PhabricatorDaemonTimelineConsoleController', 'timeline/(?P\d+)/' => 'PhabricatorDaemonTimelineEventController', '' => 'PhabricatorDaemonConsoleController', ), '/herald/' => array( '' => 'HeraldHomeController', 'view/(?P[^/]+)/' => array( '' => 'HeraldHomeController', '(?Pglobal)/' => 'HeraldHomeController' ), 'new/(?:(?P[^/]+)/)?' => 'HeraldNewController', 'rule/(?:(?P\d+)/)?' => 'HeraldRuleController', 'history/(?P\d+)/' => 'HeraldRuleEditHistoryController', 'delete/(?P\d+)/' => 'HeraldDeleteController', 'test/' => 'HeraldTestConsoleController', 'all/' => array( '' => 'HeraldAllRulesController', 'view/(?P[^/]+)/' => 'HeraldAllRulesController', ), 'transcript/' => 'HeraldTranscriptListController', 'transcript/(?P\d+)/(?:(?P\w+)/)?' => 'HeraldTranscriptController', ), '/uiexample/' => array( '' => 'PhabricatorUIExampleRenderController', 'view/(?P[^/]+)/' => 'PhabricatorUIExampleRenderController', ), '/owners/' => array( '' => 'PhabricatorOwnersListController', 'view/(?P[^/]+)/' => 'PhabricatorOwnersListController', 'edit/(?P\d+)/' => 'PhabricatorOwnersEditController', 'new/' => 'PhabricatorOwnersEditController', 'package/(?P\d+)/' => 'PhabricatorOwnersDetailController', 'delete/(?P\d+)/' => 'PhabricatorOwnersDeleteController', ), '/audit/' => array( '' => 'PhabricatorAuditListController', 'view/(?P[^/]+)/(?:(?P[^/]+)/)?' => 'PhabricatorAuditListController', 'addcomment/' => 'PhabricatorAuditAddCommentController', 'preview/(?P\d+)/' => 'PhabricatorAuditPreviewController', ), '/xhpast/' => array( '' => 'PhabricatorXHPASTViewRunController', 'view/(?P\d+)/' => 'PhabricatorXHPASTViewFrameController', 'frameset/(?P\d+)/' => 'PhabricatorXHPASTViewFramesetController', 'input/(?P\d+)/' => 'PhabricatorXHPASTViewInputController', 'tree/(?P\d+)/' => 'PhabricatorXHPASTViewTreeController', 'stream/(?P\d+)/' => 'PhabricatorXHPASTViewStreamController', ), '/status/' => 'PhabricatorStatusController', '/paste/' => array( '' => 'PhabricatorPasteListController', 'filter/(?P\w+)/' => 'PhabricatorPasteListController', ), '/P(?P\d+)' => 'PhabricatorPasteViewController', '/help/' => array( 'keyboardshortcut/' => 'PhabricatorHelpKeyboardShortcutController', ), '/countdown/' => array( '' => 'PhabricatorCountdownListController', '(?P\d+)/' => 'PhabricatorCountdownViewController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorCountdownEditController', 'delete/(?P\d+)/' => 'PhabricatorCountdownDeleteController' ), '/V(?P\d+)' => 'PhabricatorSlowvotePollController', '/vote/' => array( '(?:view/(?P\w+)/)?' => 'PhabricatorSlowvoteListController', 'create/' => 'PhabricatorSlowvoteCreateController', ), // Match "/w/" with slug "/". '/w(?P/)' => 'PhrictionDocumentController', // Match "/w/x/y/z/" with slug "x/y/z/". '/w/(?P.+/)' => 'PhrictionDocumentController', '/phriction/' => array( '' => 'PhrictionListController', 'list/(?P[^/]+)/' => 'PhrictionListController', 'history(?P/)' => 'PhrictionHistoryController', 'history/(?P.+/)' => 'PhrictionHistoryController', 'edit/(?:(?P\d+)/)?' => 'PhrictionEditController', 'delete/(?P\d+)/' => 'PhrictionDeleteController', 'preview/' => 'PhrictionDocumentPreviewController', 'diff/(?P\d+)/' => 'PhrictionDiffController', ), '/calendar/' => array( '' => 'PhabricatorCalendarBrowseController', ), '/drydock/' => array( '' => 'DrydockResourceListController', 'resource/' => 'DrydockResourceListController', 'resource/allocate/' => 'DrydockResourceAllocateController', 'host/' => array( '' => 'DrydockHostListController', 'edit/' => 'DrydockHostEditController', 'edit/(?P\d+)/' => 'DrydockhostEditController', ), 'lease/' => 'DrydockLeaseListController', ), '/chatlog/' => array( '' => 'PhabricatorChatLogChannelListController', 'channel/(?P[^/]+)/' => 'PhabricatorChatLogChannelLogController', ), '/aphlict/' => 'PhabricatorAphlictTestPageController', ); } protected function getResourceURIMapRules() { return array( '/res/' => array( '(?Ppkg/)?(?P[a-f0-9]{8})/(?P.+\.(?:css|js))' => 'CelerityResourceController', ), ); } public function buildRequest() { $request = new AphrontRequest($this->getHost(), $this->getPath()); $request->setRequestData($_GET + $_POST); $request->setApplicationConfiguration($this); return $request; } public function handleException(Exception $ex) { // Always log the unhandled exception. phlog($ex); $class = phutil_escape_html(get_class($ex)); $message = phutil_escape_html($ex->getMessage()); if (PhabricatorEnv::getEnvConfig('phabricator.show-stack-traces')) { $trace = $this->renderStackTrace($ex->getTrace()); } else { $trace = null; } $content = '
'. '
'.$message.'
'. $trace. '
'; $user = $this->getRequest()->getUser(); if (!$user) { // If we hit an exception very early, we won't have a user. $user = new PhabricatorUser(); } $dialog = new AphrontDialogView(); $dialog ->setTitle('Unhandled Exception ("'.$class.'")') ->setClass('aphront-exception-dialog') ->setUser($user) ->appendChild($content); if ($this->getRequest()->isAjax()) { $dialog->addCancelButton('/', 'Close'); } $response = new AphrontDialogResponse(); $response->setDialog($dialog); return $response; } public function willSendResponse(AphrontResponse $response) { $request = $this->getRequest(); $response->setRequest($request); if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax()) { $view = new PhabricatorStandardPageView(); $view->setRequest($request); $view->appendChild( '
'. $response->buildResponseString(). '
'); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } else { return id(new AphrontAjaxResponse()) ->setContent(array( 'dialog' => $response->buildResponseString(), )); } } else if ($response instanceof AphrontRedirectResponse) { if ($request->isAjax()) { return id(new AphrontAjaxResponse()) ->setContent( array( 'redirect' => $response->getURI(), )); } } return $response; } public function build404Controller() { return array(new Phabricator404Controller($this->getRequest()), array()); } public function buildRedirectController($uri) { return array( new PhabricatorRedirectController($this->getRequest()), array( 'uri' => $uri, )); } private function renderStackTrace($trace) { $libraries = PhutilBootloader::getInstance()->getAllLibraries(); // TODO: Make this configurable? $host = 'https://secure.phabricator.com'; $browse = array( 'arcanist' => $host.'/diffusion/ARC/browse/origin:master/src/', 'phutil' => $host.'/diffusion/PHU/browse/origin:master/src/', 'phabricator' => $host.'/diffusion/P/browse/origin:master/src/', ); $rows = array(); $depth = count($trace); foreach ($trace as $part) { $lib = null; $file = idx($part, 'file'); $relative = $file; foreach ($libraries as $library) { $root = phutil_get_library_root($library); if (Filesystem::isDescendant($file, $root)) { $lib = $library; $relative = Filesystem::readablePath($file, $root); break; } } $where = ''; if (isset($part['class'])) { $where .= $part['class'].'::'; } if (isset($part['function'])) { $where .= $part['function'].'()'; } if ($file) { if (isset($browse[$lib])) { $file_name = phutil_render_tag( 'a', array( 'href' => $browse[$lib].$relative.'$'.$part['line'], 'title' => $file, 'target' => '_blank', ), phutil_escape_html($relative)); } else { $file_name = phutil_render_tag( 'span', array( 'title' => $file, ), phutil_escape_html($relative)); } $file_name = $file_name.' : '.(int)$part['line']; } else { $file_name = '(Internal)'; } $rows[] = array( $depth--, phutil_escape_html($lib), $file_name, phutil_escape_html($where), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Depth', 'Library', 'File', 'Where', )); $table->setColumnClasses( array( 'n', '', '', 'wide', )); return '
'. '
Stack Trace
'. $table->render(). '
'; } } diff --git a/src/applications/auth/controller/disabled/PhabricatorDisabledUserController.php b/src/applications/auth/controller/disabled/PhabricatorDisabledUserController.php index e689876b49..02008b7b28 100644 --- a/src/applications/auth/controller/disabled/PhabricatorDisabledUserController.php +++ b/src/applications/auth/controller/disabled/PhabricatorDisabledUserController.php @@ -1,43 +1,44 @@ getRequest(); $user = $request->getUser(); if (!$user->getIsDisabled()) { return new Aphront404Response(); } $failure_view = new AphrontRequestFailureView(); $failure_view->setHeader('Account Disabled'); $failure_view->appendChild('

Your account has been disabled.

'); return $this->buildStandardPageResponse( $failure_view, array( 'title' => 'Account Disabled', )); } } diff --git a/src/applications/auth/controller/email/PhabricatorEmailLoginController.php b/src/applications/auth/controller/email/PhabricatorEmailLoginController.php index 1179a494a9..314e0b2597 100644 --- a/src/applications/auth/controller/email/PhabricatorEmailLoginController.php +++ b/src/applications/auth/controller/email/PhabricatorEmailLoginController.php @@ -1,161 +1,162 @@ getRequest(); if (!PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) { return new Aphront400Response(); } $e_email = true; $e_captcha = true; $errors = array(); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); if ($request->isFormPost()) { $e_email = null; $e_captcha = 'Again'; $captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request); if (!$captcha_ok) { $errors[] = "Captcha response is incorrect, try again."; $e_captcha = 'Invalid'; } $email = $request->getStr('email'); if (!strlen($email)) { $errors[] = "You must provide an email address."; $e_email = 'Required'; } if (!$errors) { // NOTE: Don't validate the email unless the captcha is good; this makes // it expensive to fish for valid email addresses while giving the user // a better error if they goof their email. $target_user = id(new PhabricatorUser())->loadOneWhere( 'email = %s', $email); if (!$target_user) { $errors[] = "There is no account associated with that email address."; $e_email = "Invalid"; } if (!$errors) { $uri = $target_user->getEmailLoginURI(); if ($is_serious) { $body = <<setSubject('[Phabricator] Password Reset'); $mail->addTos( array( $target_user->getPHID(), )); $mail->setBody($body); $mail->saveAndSend(); $view = new AphrontRequestFailureView(); $view->setHeader('Check Your Email'); $view->appendChild( '

An email has been sent with a link you can use to login.

'); return $this->buildStandardPageResponse( $view, array( 'title' => 'Email Sent', )); } } } $email_auth = new AphrontFormView(); $email_auth ->setAction('/login/email/') ->setUser($request->getUser()) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Email') ->setName('email') ->setValue($request->getStr('email')) ->setError($e_email)) ->appendChild( id(new AphrontFormRecaptchaControl()) ->setLabel('Captcha') ->setError($e_captcha)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Send Email')); $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Login Error'); $error_view->setErrors($errors); } $panel = new AphrontPanelView(); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild('

Forgot Password / Email Login

'); $panel->appendChild($email_auth); return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Create New Account', )); } } diff --git a/src/applications/auth/controller/emailtoken/PhabricatorEmailTokenController.php b/src/applications/auth/controller/emailtoken/PhabricatorEmailTokenController.php index 2a875164cf..746841c8f2 100644 --- a/src/applications/auth/controller/emailtoken/PhabricatorEmailTokenController.php +++ b/src/applications/auth/controller/emailtoken/PhabricatorEmailTokenController.php @@ -1,100 +1,101 @@ token = $data['token']; } public function processRequest() { $request = $this->getRequest(); if (!PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) { return new Aphront400Response(); } if ($request->getUser()->getPHID()) { $view = new AphrontRequestFailureView(); $view->setHeader('Already Logged In'); $view->appendChild( '

You are already logged in.

'); $view->appendChild( '
'. 'Return Home'. '
'); return $this->buildStandardPageResponse( $view, array( 'title' => 'Already Logged In', )); } $token = $this->token; $email = $request->getStr('email'); $target_user = id(new PhabricatorUser())->loadOneWhere( 'email = %s', $email); if (!$target_user || !$target_user->validateEmailToken($token)) { $view = new AphrontRequestFailureView(); $view->setHeader('Unable to Login'); $view->appendChild( '

The authentication information in the link you clicked is '. 'invalid or out of date. Make sure you are copy-and-pasting the '. 'entire link into your browser. You can try again, or request '. 'a new email.

'); $view->appendChild( ''); return $this->buildStandardPageResponse( $view, array( 'title' => 'Email Sent', )); } $session_key = $target_user->establishSession('web'); $request->setCookie('phusr', $target_user->getUsername()); $request->setCookie('phsid', $session_key); if (PhabricatorEnv::getEnvConfig('account.editable')) { $next = '/settings/page/password/?token='.$token; } else { $next = '/'; } $uri = new PhutilURI('/login/validate/'); $uri->setQueryParams( array( 'phusr' => $target_user->getUsername(), 'next' => $next, )); return id(new AphrontRedirectResponse()) ->setURI((string)$uri); } } diff --git a/src/applications/auth/controller/login/PhabricatorLoginController.php b/src/applications/auth/controller/login/PhabricatorLoginController.php index 0cfffcc5ad..95e4dbdb45 100644 --- a/src/applications/auth/controller/login/PhabricatorLoginController.php +++ b/src/applications/auth/controller/login/PhabricatorLoginController.php @@ -1,255 +1,256 @@ getRequest(); if ($request->getUser()->getPHID()) { // Kick the user out if they're already logged in. return id(new AphrontRedirectResponse())->setURI('/'); } if ($request->isConduit()) { // A common source of errors in Conduit client configuration is getting // the request path wrong. The client will end up here, so make some // effort to give them a comprehensible error message. $request_path = $this->getRequest()->getPath(); $conduit_path = '/api/'; $example_path = '/api/conduit.ping'; $message = "ERROR: You are making a Conduit API request to '{$request_path}', ". "but the correct HTTP request path to use in order to access a ". "Conduit method is '{$conduit_path}' (for example, ". "'{$example_path}'). Check your configuration."; return id(new AphrontPlainTextResponse())->setContent($message); } $next_uri = $this->getRequest()->getPath(); if ($next_uri == '/login/') { $next_uri = '/'; } if (!$request->isFormPost()) { $request->setCookie('next_uri', $next_uri); } $password_auth = PhabricatorEnv::getEnvConfig('auth.password-auth-enabled'); $forms = array(); $errors = array(); if ($password_auth) { $require_captcha = false; $e_captcha = true; $username_or_email = $request->getCookie('phusr'); if ($request->isFormPost()) { if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) { $failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP( PhabricatorUserLog::ACTION_LOGIN_FAILURE, 60 * 15); if (count($failed_attempts) > 5) { $require_captcha = true; if (!AphrontFormRecaptchaControl::processCaptcha($request)) { if (AphrontFormRecaptchaControl::hasCaptchaResponse($request)) { $e_captcha = 'Invalid'; $errors[] = 'CAPTCHA was not entered correctly.'; } else { $e_captcha = 'Required'; $errors[] = 'Too many login failures recently. You must '. 'submit a CAPTCHA with your login request.'; } } } } $username_or_email = $request->getStr('username_or_email'); $user = id(new PhabricatorUser())->loadOneWhere( 'username = %s', $username_or_email); if (!$user) { $user = id(new PhabricatorUser())->loadOneWhere( 'email = %s', $username_or_email); } if (!$errors) { // Perform username/password tests only if we didn't get rate limited // by the CAPTCHA. if (!$user || !$user->comparePassword($request->getStr('password'))) { $errors[] = 'Bad username/password.'; } } if (!$errors) { $session_key = $user->establishSession('web'); $request->setCookie('phusr', $user->getUsername()); $request->setCookie('phsid', $session_key); $uri = new PhutilURI('/login/validate/'); $uri->setQueryParams( array( 'phusr' => $user->getUsername(), )); return id(new AphrontRedirectResponse()) ->setURI((string)$uri); } else { $log = PhabricatorUserLog::newLog( null, $user, PhabricatorUserLog::ACTION_LOGIN_FAILURE); $log->save(); $request->clearCookie('phusr'); $request->clearCookie('phsid'); } } if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Login Failed'); $error_view->setErrors($errors); } else { $error_view = null; } $form = new AphrontFormView(); $form ->setUser($request->getUser()) ->setAction('/login/') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Username/Email') ->setName('username_or_email') ->setValue($username_or_email)) ->appendChild( id(new AphrontFormPasswordControl()) ->setLabel('Password') ->setName('password') ->setCaption( ''. 'Forgot your password? / Email Login')); if ($require_captcha) { $form->appendChild( id(new AphrontFormRecaptchaControl()) ->setError($e_captcha)); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Login')); // $panel->setCreateButton('Register New Account', '/login/register/'); $forms['Phabricator Login'] = $form; } $providers = PhabricatorOAuthProvider::getAllProviders(); foreach ($providers as $provider) { $enabled = $provider->isProviderEnabled(); if (!$enabled) { continue; } $auth_uri = $provider->getAuthURI(); $redirect_uri = $provider->getRedirectURI(); $client_id = $provider->getClientID(); $provider_name = $provider->getProviderName(); $minimum_scope = $provider->getMinimumScope(); $extra_auth = $provider->getExtraAuthParameters(); // TODO: In theory we should use 'state' to prevent CSRF, but the total // effect of the CSRF attack is that an attacker can cause a user to login // to Phabricator if they're already logged into some OAuth provider. This // does not seem like the most severe threat in the world, and generating // CSRF for logged-out users is vaugely tricky. if ($provider->isProviderRegistrationEnabled()) { $title = "Login or Register with {$provider_name}"; $body = 'Login or register for Phabricator using your '. phutil_escape_html($provider_name).' account.'; $button = "Login or Register with {$provider_name}"; } else { $title = "Login with {$provider_name}"; $body = 'Login to your existing Phabricator account using your '. phutil_escape_html($provider_name).' account.

'. 'You can not use '. phutil_escape_html($provider_name).' to register a new '. 'account.'; $button = "Login with {$provider_name}"; } $auth_form = new AphrontFormView(); $auth_form ->setAction($auth_uri) ->addHiddenInput('client_id', $client_id) ->addHiddenInput('redirect_uri', $redirect_uri) ->addHiddenInput('scope', $minimum_scope); foreach ($extra_auth as $key => $value) { $auth_form->addHiddenInput($key, $value); } $auth_form ->setUser($request->getUser()) ->setMethod('GET') ->appendChild( '

'.$body.'

') ->appendChild( id(new AphrontFormSubmitControl()) ->setValue("{$button} \xC2\xBB")); $forms[$title] = $auth_form; } $panel = new AphrontPanelView(); $panel->setWidth(AphrontPanelView::WIDTH_FORM); foreach ($forms as $name => $form) { $panel->appendChild('

'.$name.'

'); $panel->appendChild($form); $panel->appendChild('
'); } return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Login', )); } } diff --git a/src/applications/auth/controller/logout/PhabricatorLogoutController.php b/src/applications/auth/controller/logout/PhabricatorLogoutController.php index 3db12be594..1a717265ea 100644 --- a/src/applications/auth/controller/logout/PhabricatorLogoutController.php +++ b/src/applications/auth/controller/logout/PhabricatorLogoutController.php @@ -1,58 +1,59 @@ getRequest(); $user = $request->getUser(); if ($request->isFormPost()) { $log = PhabricatorUserLog::newLog( $user, $user, PhabricatorUserLog::ACTION_LOGOUT); $log->save(); // Destroy the user's session in the database so logout works even if // their cookies have some issues. We'll detect cookie issues when they // try to login again and tell them to clear any junk. $phsid = $request->getCookie('phsid'); if ($phsid) { $user->destroySession($phsid); } $request->clearCookie('phsid'); return id(new AphrontRedirectResponse()) ->setURI('/login/'); } return id(new AphrontRedirectResponse())->setURI('/'); } } diff --git a/src/applications/auth/controller/oauth/PhabricatorOAuthLoginController.php b/src/applications/auth/controller/oauth/PhabricatorOAuthLoginController.php index f24f952412..3184ee194c 100644 --- a/src/applications/auth/controller/oauth/PhabricatorOAuthLoginController.php +++ b/src/applications/auth/controller/oauth/PhabricatorOAuthLoginController.php @@ -1,341 +1,342 @@ provider = PhabricatorOAuthProvider::newProvider($data['provider']); } public function processRequest() { $current_user = $this->getRequest()->getUser(); $provider = $this->provider; if (!$provider->isProviderEnabled()) { return new Aphront400Response(); } $provider_name = $provider->getProviderName(); $provider_key = $provider->getProviderKey(); $request = $this->getRequest(); if ($request->getStr('error')) { $error_view = id(new PhabricatorOAuthFailureView()) ->setRequest($request); return $this->buildErrorResponse($error_view); } $error_response = $this->retrieveAccessToken($provider); if ($error_response) { return $error_response; } $userinfo_uri = new PhutilURI($provider->getUserInfoURI()); $userinfo_uri->setQueryParams( array( 'access_token' => $this->accessToken, )); $user_data = @file_get_contents($userinfo_uri); $provider->setUserData($user_data); $provider->setAccessToken($this->accessToken); $user_id = $provider->retrieveUserID(); $provider_key = $provider->getProviderKey(); $oauth_info = $this->retrieveOAuthInfo($provider); if ($current_user->getPHID()) { if ($oauth_info->getID()) { if ($oauth_info->getUserID() != $current_user->getID()) { $dialog = new AphrontDialogView(); $dialog->setUser($current_user); $dialog->setTitle('Already Linked to Another Account'); $dialog->appendChild( hsprintf( '

The %s account you just authorized is already linked to '. 'another Phabricator account. Before you can associate your %s '. 'account with this Phabriactor account, you must unlink it from '. 'the Phabricator account it is currently linked to.

', $provider_name, $provider_name)); $dialog->addCancelButton('/settings/page/'.$provider_key.'/'); return id(new AphrontDialogResponse())->setDialog($dialog); } else { return id(new AphrontRedirectResponse()) ->setURI('/settings/page/'.$provider_key.'/'); } } $existing_oauth = id(new PhabricatorUserOAuthInfo())->loadOneWhere( 'userID = %d AND oauthProvider = %s', $current_user->getID(), $provider_key); if ($existing_oauth) { $dialog = new AphrontDialogView(); $dialog->setUser($current_user); $dialog->setTitle('Already Linked to an Account From This Provider'); $dialog->appendChild( hsprintf( '

The account you are logged in with is already linked to a %s '. 'account. Before you can link it to a different %s account, you '. 'must unlink the old account.

', $provider_name, $provider_name)); $dialog->addCancelButton('/settings/page/'.$provider_key.'/'); return id(new AphrontDialogResponse())->setDialog($dialog); } if (!$request->isDialogFormPost()) { $dialog = new AphrontDialogView(); $dialog->setUser($current_user); $dialog->setTitle('Link '.$provider_name.' Account'); $dialog->appendChild( hsprintf( '

Link your %s account to your Phabricator account?

', $provider_name)); $dialog->addHiddenInput('token', $provider->getAccessToken()); $dialog->addHiddenInput('expires', $oauth_info->getTokenExpires()); $dialog->addHiddenInput('state', $this->oauthState); $dialog->addSubmitButton('Link Accounts'); $dialog->addCancelButton('/settings/page/'.$provider_key.'/'); return id(new AphrontDialogResponse())->setDialog($dialog); } $oauth_info->setUserID($current_user->getID()); $this->saveOAuthInfo($oauth_info); return id(new AphrontRedirectResponse()) ->setURI('/settings/page/'.$provider_key.'/'); } // Login with known auth. if ($oauth_info->getID()) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $known_user = id(new PhabricatorUser())->load($oauth_info->getUserID()); $request->getApplicationConfiguration()->willAuthenticateUserWithOAuth( $known_user, $oauth_info, $provider); $session_key = $known_user->establishSession('web'); $this->saveOAuthInfo($oauth_info); $request->setCookie('phusr', $known_user->getUsername()); $request->setCookie('phsid', $session_key); $uri = new PhutilURI('/login/validate/'); $uri->setQueryParams( array( 'phusr' => $known_user->getUsername(), )); return id(new AphrontRedirectResponse())->setURI((string)$uri); } $oauth_email = $provider->retrieveUserEmail(); if ($oauth_email) { $known_email = id(new PhabricatorUser()) ->loadOneWhere('email = %s', $oauth_email); if ($known_email) { $dialog = new AphrontDialogView(); $dialog->setUser($current_user); $dialog->setTitle('Already Linked to Another Account'); $dialog->appendChild( hsprintf( '

The %s account you just authorized has an email address which '. 'is already in use by another Phabricator account. To link the '. 'accounts, log in to your Phabricator account and then go to '. 'Settings.

', $provider_name)); $dialog->addCancelButton('/login/'); return id(new AphrontDialogResponse())->setDialog($dialog); } } if (!$provider->isProviderRegistrationEnabled()) { $dialog = new AphrontDialogView(); $dialog->setUser($current_user); $dialog->setTitle('No Account Registration With '.$provider_name); $dialog->appendChild( hsprintf( '

You can not register a new account using %s; you can only use '. 'your %s account to log into an existing Phabricator account which '. 'you have registered through other means.

', $provider_name, $provider_name)); $dialog->addCancelButton('/login/'); return id(new AphrontDialogResponse())->setDialog($dialog); } $class = PhabricatorEnv::getEnvConfig('controller.oauth-registration'); PhutilSymbolLoader::loadClass($class); $controller = newv($class, array($this->getRequest())); $controller->setOAuthProvider($provider); $controller->setOAuthInfo($oauth_info); $controller->setOAuthState($this->oauthState); return $this->delegateToController($controller); } private function buildErrorResponse(PhabricatorOAuthFailureView $view) { $provider = $this->provider; $provider_name = $provider->getProviderName(); $view->setOAuthProvider($provider); return $this->buildStandardPageResponse( $view, array( 'title' => $provider_name.' Auth Failed', )); } private function retrieveAccessToken(PhabricatorOAuthProvider $provider) { $request = $this->getRequest(); $token = $request->getStr('token'); if ($token) { $this->tokenExpires = $request->getInt('expires'); $this->accessToken = $token; $this->oauthState = $request->getStr('state'); return null; } $client_id = $provider->getClientID(); $client_secret = $provider->getClientSecret(); $redirect_uri = $provider->getRedirectURI(); $auth_uri = $provider->getTokenURI(); $code = $request->getStr('code'); $query_data = array( 'client_id' => $client_id, 'client_secret' => $client_secret, 'redirect_uri' => $redirect_uri, 'code' => $code, ) + $provider->getExtraTokenParameters(); $post_data = http_build_query($query_data); $post_length = strlen($post_data); $stream_context = stream_context_create( array( 'http' => array( 'method' => 'POST', 'header' => "Content-Type: application/x-www-form-urlencoded\r\n". "Content-Length: {$post_length}\r\n", 'content' => $post_data, ), )); $stream = fopen($auth_uri, 'r', false, $stream_context); $response = false; $meta = null; if ($stream) { $meta = stream_get_meta_data($stream); $response = stream_get_contents($stream); fclose($stream); } if ($response === false) { return $this->buildErrorResponse(new PhabricatorOAuthFailureView()); } $data = $provider->decodeTokenResponse($response); $token = idx($data, 'access_token'); if (!$token) { return $this->buildErrorResponse(new PhabricatorOAuthFailureView()); } if (idx($data, 'expires')) { $this->tokenExpires = time() + $data['expires']; } $this->accessToken = $token; $this->oauthState = $request->getStr('state'); return null; } private function retrieveOAuthInfo(PhabricatorOAuthProvider $provider) { $oauth_info = id(new PhabricatorUserOAuthInfo())->loadOneWhere( 'oauthProvider = %s and oauthUID = %s', $provider->getProviderKey(), $provider->retrieveUserID()); if (!$oauth_info) { $oauth_info = new PhabricatorUserOAuthInfo(); $oauth_info->setOAuthProvider($provider->getProviderKey()); $oauth_info->setOAuthUID($provider->retrieveUserID()); } $oauth_info->setAccountURI($provider->retrieveUserAccountURI()); $oauth_info->setAccountName($provider->retrieveUserAccountName()); $oauth_info->setToken($provider->getAccessToken()); $oauth_info->setTokenStatus(PhabricatorUserOAuthInfo::TOKEN_STATUS_GOOD); // If we have out-of-date expiration info, just clear it out. Then replace // it with good info if the provider gave it to us. $expires = $oauth_info->getTokenExpires(); if ($expires <= time()) { $expires = null; } if ($this->tokenExpires) { $expires = $this->tokenExpires; } $oauth_info->setTokenExpires($expires); return $oauth_info; } private function saveOAuthInfo(PhabricatorUserOAuthInfo $info) { // UNGUARDED WRITES: Logging-in users don't have their CSRF set up yet. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $info->save(); } } diff --git a/src/applications/auth/controller/oauthdiagnostics/PhabricatorOAuthDiagnosticsController.php b/src/applications/auth/controller/oauthdiagnostics/PhabricatorOAuthDiagnosticsController.php index f7bb38bde5..81460bd9f5 100644 --- a/src/applications/auth/controller/oauthdiagnostics/PhabricatorOAuthDiagnosticsController.php +++ b/src/applications/auth/controller/oauthdiagnostics/PhabricatorOAuthDiagnosticsController.php @@ -1,226 +1,226 @@ provider = PhabricatorOAuthProvider::newProvider($data['provider']); } public function processRequest() { $provider = $this->provider; $auth_enabled = $provider->isProviderEnabled(); $client_id = $provider->getClientID(); $client_secret = $provider->getClientSecret(); $key = $provider->getProviderKey(); $name = phutil_escape_html($provider->getProviderName()); $res_ok = 'OK'; $res_no = 'NO'; $res_na = 'N/A'; $results = array(); $auth_key = $key . '.auth-enabled'; if (!$auth_enabled) { $results[$auth_key] = array( $res_no, 'false', $name . ' authentication is disabled in the configuration. Edit the '. 'Phabricator configuration to enable "'.$auth_key.'".'); } else { $results[$auth_key] = array( $res_ok, 'true', $name.' authentication is enabled.'); } $client_id_key = $key. '.application-id'; if (!$client_id) { $results[$client_id_key] = array( $res_no, null, 'No '.$name.' Application ID is configured. Edit the Phabricator '. 'configuration to specify an application ID in '. '"'.$client_id_key.'". '.$provider->renderGetClientIDHelp()); } else { $results[$client_id_key] = array( $res_ok, $client_id, 'Application ID is set.'); } $client_secret_key = $key.'.application-secret'; if (!$client_secret) { $results[$client_secret_key] = array( $res_no, null, 'No '.$name.' Application secret is configured. Edit the '. 'Phabricator configuration to specify an Application Secret, in '. '"'.$client_secret_key.'". '.$provider->renderGetClientSecretHelp()); } else { $results[$client_secret_key] = array( $res_ok, "It's a secret!", 'Application secret is set.'); } $timeout = stream_context_create( array( 'http' => array( 'ignore_errors' => true, 'timeout' => 5, ), )); $timeout_strict = stream_context_create( array( 'http' => array( 'timeout' => 5, ), )); $internet = @file_get_contents("http://google.com/", false, $timeout); if ($internet === false) { $results['internet'] = array( $res_no, null, 'Unable to make an HTTP request to Google. Check your outbound '. 'internet connection and firewall/filtering settings.'); } else { $results['internet'] = array( $res_ok, null, 'Internet seems OK.'); } $test_uris = $provider->getTestURIs(); foreach ($test_uris as $uri) { $success = @file_get_contents($uri, false, $timeout); if ($success === false) { $results[$uri] = array( $res_no, null, "Unable to make an HTTP request to {$uri}. {$name} may be ". 'down or inaccessible.'); } else { $results[$uri] = array( $res_ok, null, 'Made a request to '.$uri.'.'); } } $test_uri = new PhutilURI($provider->getTokenURI()); $test_uri->setQueryParams( array( 'client_id' => $client_id, 'client_secret' => $client_secret, 'grant_type' => 'client_credentials', )); $token_value = @file_get_contents($test_uri, false, $timeout); $token_strict = @file_get_contents($test_uri, false, $timeout_strict); if ($token_value === false) { $results['App Login'] = array( $res_no, null, "Unable to perform an application login with your Application ID and ". "Application Secret. You may have mistyped or misconfigured them; ". "{$name} may have revoked your authorization; or {$name} may be ". "having technical problems."); } else { if ($token_strict) { $results['App Login'] = array( $res_ok, '(A Valid Token)', "Raw application login to {$name} works."); } else { $data = json_decode($token_value, true); if (!is_array($data)) { $results['App Login'] = array( $res_no, $token_value, "Application Login failed but the provider did not respond ". "with valid JSON error information. {$name} may be experiencing ". "technical problems."); } else { $results['App Login'] = array( $res_no, null, "Application Login failed with error: ".$token_value); } } } return $this->renderResults($results); } private function renderResults($results) { $provider = $this->provider; $rows = array(); foreach ($results as $key => $result) { $rows[] = array( phutil_escape_html($key), $result[0], phutil_escape_html($result[1]), phutil_escape_html($result[2]), ); } $table_view = new AphrontTableView($rows); $table_view->setHeaders( array( 'Test', 'Result', 'Value', 'Details', )); $table_view->setColumnClasses( array( null, null, null, 'wide', )); $title = $provider->getProviderName() . ' Auth Diagnostics'; $panel_view = new AphrontPanelView(); $panel_view->setHeader($title); $panel_view->appendChild( '

These tests may be able to '. 'help diagnose the root cause of problems you experience with '. $provider->getProviderName() . ' Authentication. Reload the page to run the tests again.

'); $panel_view->appendChild($table_view); return $this->buildStandardPageResponse( $panel_view, array( 'title' => $title, )); } } diff --git a/src/applications/auth/controller/oauthregistration/default/PhabricatorOAuthDefaultRegistrationController.php b/src/applications/auth/controller/oauthregistration/default/PhabricatorOAuthDefaultRegistrationController.php index f29fca7e5a..63af461081 100644 --- a/src/applications/auth/controller/oauthregistration/default/PhabricatorOAuthDefaultRegistrationController.php +++ b/src/applications/auth/controller/oauthregistration/default/PhabricatorOAuthDefaultRegistrationController.php @@ -1,182 +1,182 @@ getOAuthProvider(); $oauth_info = $this->getOAuthInfo(); $request = $this->getRequest(); $errors = array(); $e_username = true; $e_email = true; $e_realname = true; $user = new PhabricatorUser(); $user->setUsername($provider->retrieveUserAccountName()); $user->setRealName($provider->retrieveUserRealName()); $user->setEmail($provider->retrieveUserEmail()); if ($request->isFormPost()) { $user->setUsername($request->getStr('username')); $username = $user->getUsername(); if (!strlen($user->getUsername())) { $e_username = 'Required'; $errors[] = 'Username is required.'; } else if (!PhabricatorUser::validateUsername($username)) { $e_username = 'Invalid'; $errors[] = 'Username must consist of only numbers and letters.'; } else { $e_username = null; } if ($user->getEmail() === null) { $user->setEmail($request->getStr('email')); if (!strlen($user->getEmail())) { $e_email = 'Required'; $errors[] = 'Email is required.'; } else { $e_email = null; } } if (!strlen($user->getRealName())) { $user->setRealName($request->getStr('realname')); if (!strlen($user->getRealName())) { $e_realname = 'Required'; $errors[] = 'Real name is required.'; } else { $e_realname = null; } } if (!$errors) { $image = $provider->retrieveUserProfileImage(); if ($image) { $file = PhabricatorFile::newFromFileData( $image, array( 'name' => $provider->getProviderKey().'-profile.jpg', 'authorPHID' => $user->getPHID(), )); $user->setProfileImagePHID($file->getPHID()); } try { $user->save(); $oauth_info->setUserID($user->getID()); $oauth_info->save(); $session_key = $user->establishSession('web'); $request->setCookie('phusr', $user->getUsername()); $request->setCookie('phsid', $session_key); return id(new AphrontRedirectResponse())->setURI('/'); } catch (AphrontQueryDuplicateKeyException $exception) { $same_username = id(new PhabricatorUser())->loadOneWhere( 'userName = %s', $user->getUserName()); $same_email = id(new PhabricatorUser())->loadOneWhere( 'email = %s', $user->getEmail()); if ($same_username) { $e_username = 'Duplicate'; $errors[] = 'That username or email is not unique.'; } else if ($same_email) { $e_email = 'Duplicate'; $errors[] = 'That email is not unique.'; } else { throw $exception; } } } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Registration Failed'); $error_view->setErrors($errors); } // Strip the URI down to the path, because otherwise we'll trigger // external CSRF protection (by having a protocol in the form "action") // and generate a form with no CSRF token. $action_uri = new PhutilURI($provider->getRedirectURI()); $action_path = $action_uri->getPath(); $form = new AphrontFormView(); $form ->addHiddenInput('token', $provider->getAccessToken()) ->addHiddenInput('expires', $oauth_info->getTokenExpires()) ->addHiddenInput('state', $this->getOAuthState()) ->setUser($request->getUser()) ->setAction($action_path) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Username') ->setName('username') ->setValue($user->getUsername()) ->setError($e_username)); if ($provider->retrieveUserEmail() === null) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel('Email') ->setName('email') ->setValue($request->getStr('email')) ->setError($e_email)); } if ($provider->retrieveUserRealName () === null) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel('Real Name') ->setName('realname') ->setValue($request->getStr('realname')) ->setError($e_realname)); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Create Account')); $panel = new AphrontPanelView(); $panel->setHeader('Create New Account'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Create New Account', )); } } diff --git a/src/applications/auth/controller/refresh/PhabricatorRefreshCSRFController.php b/src/applications/auth/controller/refresh/PhabricatorRefreshCSRFController.php index f83abdd600..abd10ac037 100644 --- a/src/applications/auth/controller/refresh/PhabricatorRefreshCSRFController.php +++ b/src/applications/auth/controller/refresh/PhabricatorRefreshCSRFController.php @@ -1,32 +1,32 @@ getRequest(); $user = $request->getUser(); return id(new AphrontAjaxResponse()) ->setContent( array( 'token' => $user->getCSRFToken(), )); } } diff --git a/src/applications/auth/controller/unlink/PhabricatorOAuthUnlinkController.php b/src/applications/auth/controller/unlink/PhabricatorOAuthUnlinkController.php index d94c9af343..6d7de0192f 100644 --- a/src/applications/auth/controller/unlink/PhabricatorOAuthUnlinkController.php +++ b/src/applications/auth/controller/unlink/PhabricatorOAuthUnlinkController.php @@ -1,68 +1,68 @@ provider = PhabricatorOAuthProvider::newProvider($data['provider']); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $provider = $this->provider; if ($provider->isProviderLinkPermanent()) { throw new Exception( "You may not unlink accounts from this OAuth provider."); } $provider_key = $provider->getProviderKey(); $oauth_info = id(new PhabricatorUserOAuthInfo())->loadOneWhere( 'userID = %d AND oauthProvider = %s', $user->getID(), $provider_key); if (!$oauth_info) { return new Aphront400Response(); } if (!$request->isDialogFormPost()) { $dialog = new AphrontDialogView(); $dialog->setUser($user); $dialog->setTitle('Really unlink account?'); $dialog->appendChild( '

You will not be able to login using this account '. 'once you unlink it. Continue?

'); $dialog->addSubmitButton('Unlink Account'); $dialog->addCancelButton('/settings/page/'.$provider_key.'/'); return id(new AphrontDialogResponse())->setDialog($dialog); } $oauth_info->delete(); return id(new AphrontRedirectResponse()) ->setURI('/settings/page/'.$provider_key.'/'); } } diff --git a/src/applications/auth/controller/validate/PhabricatorLoginValidateController.php b/src/applications/auth/controller/validate/PhabricatorLoginValidateController.php index 838dbf96b6..e8b3de5045 100644 --- a/src/applications/auth/controller/validate/PhabricatorLoginValidateController.php +++ b/src/applications/auth/controller/validate/PhabricatorLoginValidateController.php @@ -1,91 +1,92 @@ getRequest(); $failures = array(); if (!$request->getStr('phusr')) { throw new Exception( "Login validation is missing expected parameters!"); } $expect_phusr = $request->getStr('phusr'); $actual_phusr = $request->getCookie('phusr'); if ($actual_phusr != $expect_phusr) { if ($actual_phusr) { $cookie_info = "sent back a cookie with the value '{$actual_phusr}'."; } else { $cookie_info = "did not accept the cookie."; } $failures[] = "Attempted to set 'phusr' cookie to '{$expect_phusr}', but your ". "browser {$cookie_info}"; } if (!$failures) { if (!$request->getUser()->getPHID()) { $failures[] = "Cookies were set correctly, but your session ". "isn't valid."; } } if ($failures) { $list = array(); foreach ($failures as $failure) { $list[] = '
  • '.phutil_escape_html($failure).'
  • '; } $list = '
      '.implode("\n", $list).'
    '; $view = new AphrontRequestFailureView(); $view->setHeader('Login Failed'); $view->appendChild( '

    Login failed:

    '. $list. '

    Clear your cookies and try again.

    '); $view->appendChild( '
    '. 'Try Again'. '
    '); return $this->buildStandardPageResponse( $view, array( 'title' => 'Login Failed', )); } $next = nonempty($request->getStr('next'), $request->getCookie('next_uri')); $request->clearCookie('next_uri'); if (!PhabricatorEnv::isValidLocalWebResource($next)) { $next = '/'; } return id(new AphrontRedirectResponse())->setURI($next); } } diff --git a/src/applications/base/controller/404/Phabricator404Controller.php b/src/applications/base/controller/404/Phabricator404Controller.php index 591e7a3c3e..643ffdcfd6 100644 --- a/src/applications/base/controller/404/Phabricator404Controller.php +++ b/src/applications/base/controller/404/Phabricator404Controller.php @@ -1,25 +1,25 @@ uri = $data['uri']; } public function processRequest() { return id(new AphrontRedirectResponse())->setURI($this->uri); } } diff --git a/src/applications/calendar/controller/browse/PhabricatorCalendarBrowseController.php b/src/applications/calendar/controller/browse/PhabricatorCalendarBrowseController.php index a57e53807e..dd8b97cccf 100644 --- a/src/applications/calendar/controller/browse/PhabricatorCalendarBrowseController.php +++ b/src/applications/calendar/controller/browse/PhabricatorCalendarBrowseController.php @@ -1,41 +1,41 @@ getRequest(); $user = $request->getUser(); $months = array(); for ($ii = 1; $ii <= 12; $ii++) { $month_view = new AphrontCalendarMonthView($ii, 2011); $month_view->setUser($user); $months[] = '
    '; $months[] = $month_view; $months[] = '
    '; } return $this->buildStandardPageResponse( $months, array( 'title' => 'Calendar', )); } } diff --git a/src/applications/conduit/controller/api/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/api/PhabricatorConduitAPIController.php index f86895fea9..abf90d7360 100644 --- a/src/applications/conduit/controller/api/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/api/PhabricatorConduitAPIController.php @@ -1,424 +1,424 @@ method = $data['method']; return $this; } public function processRequest() { $time_start = microtime(true); $request = $this->getRequest(); $method = $this->method; $method_class = ConduitAPIMethod::getClassNameFromAPIMethodName($method); $api_request = null; $log = new PhabricatorConduitMethodCallLog(); $log->setMethod($method); $metadata = array(); try { if (!class_exists($method_class)) { throw new Exception( "Unable to load the implementation class for method '{$method}'. ". "You may have misspelled the method, need to define ". "'{$method_class}', or need to run 'arc build'."); } $class_info = new ReflectionClass($method_class); if ($class_info->isAbstract()) { throw new Exception( "Method '{$method}' is not valid; the implementation is an abstract ". "base class."); } $method_handler = newv($method_class, array()); if (isset($_REQUEST['params']) && is_array($_REQUEST['params'])) { $params_post = $request->getArr('params'); foreach ($params_post as $key => $value) { if ($value == '') { // Interpret empty string null (e.g., the user didn't type anything // into the box). $value = 'null'; } $decoded_value = json_decode($value, true); if ($decoded_value === null && strtolower($value) != 'null') { // When json_decode() fails, it returns null. This almost certainly // indicates that a user was using the web UI and didn't put quotes // around a string value. We can either do what we think they meant // (treat it as a string) or fail. For now, err on the side of // caution and fail. In the future, if we make the Conduit API // actually do type checking, it might be reasonable to treat it as // a string if the parameter type is string. throw new Exception( "The value for parameter '{$key}' is not valid JSON. All ". "parameters must be encoded as JSON values, including strings ". "(which means you need to surround them in double quotes). ". "Check your syntax. Value was: {$value}"); } $params_post[$key] = $decoded_value; } $params = $params_post; } else { $params_json = $request->getStr('params'); if (!strlen($params_json)) { $params = array(); } else { $params = json_decode($params_json, true); if (!is_array($params)) { throw new Exception( "Invalid parameter information was passed to method ". "'{$method}', could not decode JSON serialization."); } } } $metadata = idx($params, '__conduit__', array()); unset($params['__conduit__']); $result = null; $api_request = new ConduitAPIRequest($params); $allow_unguarded_writes = false; $auth_error = null; if ($method_handler->shouldRequireAuthentication()) { $metadata['scope'] = $method_handler->getRequiredScope(); $auth_error = $this->authenticateUser($api_request, $metadata); // If we've explicitly authenticated the user here and either done // CSRF validation or are using a non-web authentication mechanism. $allow_unguarded_writes = true; if (isset($metadata['actAsUser'])) { $this->actAsUser($api_request, $metadata['actAsUser']); } } if ($method_handler->shouldAllowUnguardedWrites()) { $allow_unguarded_writes = true; } if ($auth_error === null) { if ($allow_unguarded_writes) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); } try { $result = $method_handler->executeMethod($api_request); $error_code = null; $error_info = null; } catch (ConduitException $ex) { $result = null; $error_code = $ex->getMessage(); if ($ex->getErrorDescription()) { $error_info = $ex->getErrorDescription(); } else { $error_info = $method_handler->getErrorDescription($error_code); } } if ($allow_unguarded_writes) { unset($unguarded); } } else { list($error_code, $error_info) = $auth_error; } } catch (Exception $ex) { phlog($ex); $result = null; $error_code = 'ERR-CONDUIT-CORE'; $error_info = $ex->getMessage(); } $time_end = microtime(true); $connection_id = null; if (idx($metadata, 'connectionID')) { $connection_id = $metadata['connectionID']; } else if (($method == 'conduit.connect') && $result) { $connection_id = idx($result, 'connectionID'); } $log->setConnectionID($connection_id); $log->setError((string)$error_code); $log->setDuration(1000000 * ($time_end - $time_start)); // TODO: This is a hack, but the insert is comparatively expensive and // we only really care about having these logs for real CLI clients, if // even that. if (empty($metadata['authToken'])) { $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $log->save(); unset($unguarded); } $response = id(new ConduitAPIResponse()) ->setResult($result) ->setErrorCode($error_code) ->setErrorInfo($error_info); switch ($request->getStr('output')) { case 'human': return $this->buildHumanReadableResponse( $method, $api_request, $response->toDictionary()); case 'json': default: return id(new AphrontJSONResponse()) ->setContent($response->toDictionary()); } } /** * Change the api request user to the user that we want to act as. * Only admins can use actAsUser * * @param ConduitAPIRequest Request being executed. * @param string The username of the user we want to act as */ private function actAsUser( ConduitAPIRequest $api_request, $user_name) { if (!$api_request->getUser()->getIsAdmin()) { throw new Exception("Only administrators can use actAsUser"); } $user = id(new PhabricatorUser())->loadOneWhere( 'userName = %s', $user_name); if (!$user) { throw new Exception( "The actAsUser username '{$user_name}' is not a valid user." ); } $api_request->setUser($user); } /** * Authenticate the client making the request to a Phabricator user account. * * @param ConduitAPIRequest Request being executed. * @param dict Request metadata. * @return null|pair Null to indicate successful authentication, or * an error code and error message pair. */ private function authenticateUser( ConduitAPIRequest $api_request, array $metadata) { $request = $this->getRequest(); if ($request->getUser()->getPHID()) { $request->validateCSRF(); $api_request->setUser($request->getUser()); return null; } // handle oauth // TODO - T897 (make error codes for OAuth more correct to spec) // and T891 (strip shield from Conduit response) $access_token = $request->getStr('access_token'); $method_scope = $metadata['scope']; if ($access_token && $method_scope != PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE) { $token = id(new PhabricatorOAuthServerAccessToken()) ->loadOneWhere('token = %s', $access_token); if (!$token) { return array( 'ERR-INVALID-AUTH', 'Access token does not exist.', ); } $oauth_server = new PhabricatorOAuthServer(); $valid = $oauth_server->validateAccessToken($token, $method_scope); if (!$valid) { return array( 'ERR-INVALID-AUTH', 'Access token is invalid.', ); } // valid token, so let's log in the user! $user_phid = $token->getUserPHID(); $user = id(new PhabricatorUser()) ->loadOneWhere('phid = %s', $user_phid); if (!$user) { return array( 'ERR-INVALID-AUTH', 'Access token is for invalid user.', ); } $api_request->setUser($user); return null; } // Handle sessionless auth. TOOD: This is super messy. if (isset($metadata['authUser'])) { $user = id(new PhabricatorUser())->loadOneWhere( 'userName = %s', $metadata['authUser']); if (!$user) { return array( 'ERR-INVALID-AUTH', 'Authentication is invalid.', ); } $token = idx($metadata, 'authToken'); $signature = idx($metadata, 'authSignature'); $certificate = $user->getConduitCertificate(); if (sha1($token.$certificate) !== $signature) { return array( 'ERR-INVALID-AUTH', 'Authentication is invalid.', ); } $api_request->setUser($user); return null; } $session_key = idx($metadata, 'sessionKey'); if (!$session_key) { return array( 'ERR-INVALID-SESSION', 'Session key is not present.' ); } $session = queryfx_one( id(new PhabricatorUser())->establishConnection('r'), 'SELECT * FROM %T WHERE sessionKey = %s', PhabricatorUser::SESSION_TABLE, $session_key); if (!$session) { return array( 'ERR-INVALID-SESSION', 'Session key is invalid.', ); } // TODO: Make sessions timeout. // TODO: When we pull a session, read connectionID from the session table. $user = id(new PhabricatorUser())->loadOneWhere( 'phid = %s', $session['userPHID']); if (!$user) { return array( 'ERR-INVALID-SESSION', 'Session is for nonexistent user.', ); } $api_request->setUser($user); return null; } private function buildHumanReadableResponse( $method, ConduitAPIRequest $request = null, $result = null) { $param_rows = array(); $param_rows[] = array('Method', $this->renderAPIValue($method)); if ($request) { foreach ($request->getAllParameters() as $key => $value) { $param_rows[] = array( phutil_escape_html($key), $this->renderAPIValue($value), ); } } $param_table = new AphrontTableView($param_rows); $param_table->setColumnClasses( array( 'header', 'wide', )); $result_rows = array(); foreach ($result as $key => $value) { $result_rows[] = array( phutil_escape_html($key), $this->renderAPIValue($value), ); } $result_table = new AphrontTableView($result_rows); $result_table->setColumnClasses( array( 'header', 'wide', )); $param_panel = new AphrontPanelView(); $param_panel->setHeader('Method Parameters'); $param_panel->appendChild($param_table); $result_panel = new AphrontPanelView(); $result_panel->setHeader('Method Result'); $result_panel->appendChild($result_table); return $this->buildStandardPageResponse( array( $param_panel, $result_panel, ), array( 'title' => 'Method Call Result', )); } private function renderAPIValue($value) { $json = new PhutilJSON(); if (is_array($value)) { $value = $json->encodeFormatted($value); $value = phutil_escape_html($value); } else { $value = phutil_escape_html($value); } $value = '
    '.$value.'
    '; return $value; } } diff --git a/src/applications/conduit/controller/console/PhabricatorConduitConsoleController.php b/src/applications/conduit/controller/console/PhabricatorConduitConsoleController.php index 47307a4e3b..010c03ba87 100644 --- a/src/applications/conduit/controller/console/PhabricatorConduitConsoleController.php +++ b/src/applications/conduit/controller/console/PhabricatorConduitConsoleController.php @@ -1,128 +1,128 @@ method = idx($data, 'method'); } public function processRequest() { $request = $this->getRequest(); $methods = $this->getAllMethods(); if (empty($methods[$this->method])) { $this->method = key($methods); } $this->setFilter('method/'.$this->method); $method_class = $methods[$this->method]; PhutilSymbolLoader::loadClass($method_class); $method_object = newv($method_class, array()); $error_description = array(); $error_types = $method_object->defineErrorTypes(); if ($error_types) { $error_description[] = '
      '; foreach ($error_types as $error => $meaning) { $error_description[] = '
    • '. ''.phutil_escape_html($error).': '. phutil_escape_html($meaning). '
    • '; } $error_description[] = '
    '; $error_description = implode("\n", $error_description); } else { $error_description = "This method does not raise any specific errors."; } $form = new AphrontFormView(); $form ->setUser($request->getUser()) ->setAction('/api/'.$this->method) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Description') ->setValue($method_object->getMethodDescription())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Returns') ->setValue($method_object->defineReturnType())) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel('Errors') ->setValue($error_description)) ->appendChild( '

    Enter parameters using '. 'JSON. For instance, to enter a list, type: '. '["apple", "banana", "cherry"]'); $params = $method_object->defineParamTypes(); foreach ($params as $param => $desc) { $form->appendChild( id(new AphrontFormTextControl()) ->setLabel($param) ->setName("params[{$param}]") ->setCaption(phutil_escape_html($desc))); } $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Output Format') ->setName('output') ->setOptions( array( 'human' => 'Human Readable', 'json' => 'JSON', ))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Call Method')); $panel = new AphrontPanelView(); $panel->setHeader('Conduit API: '.phutil_escape_html($this->method)); $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FULL); return $this->buildStandardPageResponse( array($panel), array( 'title' => 'Conduit Console', )); } private function getAllMethods() { $classes = $this->getAllMethodImplementationClasses(); $methods = array(); foreach ($classes as $class) { $name = ConduitAPIMethod::getAPIMethodNameFromClassName($class); $methods[$name] = $class; } return $methods; } } diff --git a/src/applications/conduit/controller/log/PhabricatorConduitLogController.php b/src/applications/conduit/controller/log/PhabricatorConduitLogController.php index 91902f1d00..009c372cdf 100644 --- a/src/applications/conduit/controller/log/PhabricatorConduitLogController.php +++ b/src/applications/conduit/controller/log/PhabricatorConduitLogController.php @@ -1,109 +1,110 @@ getRequest(); $conn_table = new PhabricatorConduitConnectionLog(); $call_table = new PhabricatorConduitMethodCallLog(); $conn_r = $call_table->establishConnection('r'); $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page')); $calls = $call_table->loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize() + 1); $calls = $pager->sliceResults($calls); $pager->setURI(new PhutilURI('/conduit/log/'), 'page'); $pager->setEnableKeyboardShortcuts(true); $min = $pager->getOffset() + 1; $max = ($min + count($calls) - 1); $conn_ids = array_filter(mpull($calls, 'getConnectionID')); $conns = array(); if ($conn_ids) { $conns = $conn_table->loadAllWhere( 'id IN (%Ld)', $conn_ids); } $table = $this->renderCallTable($calls, $conns); $panel = new AphrontPanelView(); $panel->setHeader('Conduit Method Calls ('.$min.'-'.$max.')'); $panel->appendChild($table); $panel->appendChild($pager); $this->setFilter('log'); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Conduit Logs', )); } private function renderCallTable(array $calls, array $conns) { $user = $this->getRequest()->getUser(); $rows = array(); foreach ($calls as $call) { $conn = idx($conns, $call->getConnectionID()); if (!$conn) { // If there's no connection, use an empty object. $conn = new PhabricatorConduitConnectionLog(); } $rows[] = array( $call->getConnectionID(), phutil_escape_html($conn->getUserName()), phutil_escape_html($call->getMethod()), phutil_escape_html($call->getError()), number_format($call->getDuration()).' us', phabricator_datetime($call->getDateCreated(), $user), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Connection', 'User', 'Method', 'Error', 'Duration', 'Date', )); $table->setColumnClasses( array( '', '', 'wide', '', 'n', 'right', )); return $table; } } diff --git a/src/applications/conduit/controller/token/PhabricatorConduitTokenController.php b/src/applications/conduit/controller/token/PhabricatorConduitTokenController.php index 7579e4192c..ad56c11ad5 100644 --- a/src/applications/conduit/controller/token/PhabricatorConduitTokenController.php +++ b/src/applications/conduit/controller/token/PhabricatorConduitTokenController.php @@ -1,68 +1,69 @@ getRequest()->getUser(); // Ideally we'd like to verify this, but it's fine to leave it unguarded // for now and verifying it would need some Ajax junk or for the user to // click a button or similar. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $old_token = id(new PhabricatorConduitCertificateToken()) ->loadOneWhere( 'userPHID = %s', $user->getPHID()); if ($old_token) { $old_token->delete(); } $token = id(new PhabricatorConduitCertificateToken()) ->setUserPHID($user->getPHID()) ->setToken(Filesystem::readRandomCharacters(40)) ->save(); $panel = new AphrontPanelView(); $panel->setHeader('Certificate Install Token'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild( '

    Copy and paste this token into '. 'the prompt given to you by "arc install-certificate":

    '. '

    '. ''.phutil_escape_html($token->getToken()).''. '

    '. '

    arc will then complete the '. 'install process for you.

    '); $this->setFilter('token'); $this->setShowSideNav(false); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Certificate Install Token', )); } } diff --git a/src/applications/countdown/controller/delete/PhabricatorCountdownDeleteController.php b/src/applications/countdown/controller/delete/PhabricatorCountdownDeleteController.php index 97e6963609..6798c4f819 100644 --- a/src/applications/countdown/controller/delete/PhabricatorCountdownDeleteController.php +++ b/src/applications/countdown/controller/delete/PhabricatorCountdownDeleteController.php @@ -1,60 +1,60 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $timer = id(new PhabricatorTimer())->load($this->id); if (!$timer) { return new Aphront404Response(); } if (($timer->getAuthorPHID() !== $user->getPHID()) && $user->getIsAdmin() === false) { return new Aphront403Response(); } if ($request->isFormPost()) { $timer->delete(); return id(new AphrontRedirectResponse()) ->setURI('/countdown/'); } $dialog = new AphrontDialogView(); $dialog->setUser($request->getUser()); $dialog->setTitle('Really delete this countdown?'); $dialog->appendChild( '

    Are you sure you want to delete the countdown "'. phutil_escape_html($timer->getTitle()).'"?

    '); $dialog->addSubmitButton('Delete'); $dialog->addCancelButton('/countdown/'); $dialog->setSubmitURI($request->getPath()); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/countdown/controller/edit/PhabricatorCountdownEditController.php b/src/applications/countdown/controller/edit/PhabricatorCountdownEditController.php index 854e395aae..5853b912be 100644 --- a/src/applications/countdown/controller/edit/PhabricatorCountdownEditController.php +++ b/src/applications/countdown/controller/edit/PhabricatorCountdownEditController.php @@ -1,140 +1,140 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $action_label = 'Create Timer'; if ($this->id) { $timer = id(new PhabricatorTimer())->load($this->id); // If no timer is found if (!$timer) { return new Aphront404Response(); } if (($timer->getAuthorPHID() != $user->getPHID()) && $user->getIsAdmin() == false) { return new Aphront403Response(); } $action_label = 'Update Timer'; } else { $timer = new PhabricatorTimer(); $timer->setDatePoint(time()); } $error_view = null; $e_text = null; if ($request->isFormPost()) { $errors = array(); $title = $request->getStr('title'); $datepoint = $request->getStr('datepoint'); $e_text = null; if (!strlen($title)) { $e_text = 'Required'; $errors[] = 'You must give it a name.'; } // If the user types something like "5 PM", convert it to a timestamp // using their local time, not the server time. $timezone = new DateTimeZone($user->getTimezoneIdentifier()); try { $date = new DateTime($datepoint, $timezone); $timestamp = $date->format('U'); } catch (Exception $e) { $errors[] = 'You entered an incorrect date. You can enter date like'. ' \'2011-06-26 13:33:37\' to create an event at'. ' 13:33:37 on the 26th of June 2011.'; $timestamp = null; } $timer->setTitle($title); $timer->setDatePoint($timestamp); if (!count($errors)) { $timer->setAuthorPHID($user->getPHID()); $timer->save(); return id(new AphrontRedirectResponse()) ->setURI('/countdown/'.$timer->getID().'/'); } else { $error_view = id(new AphrontErrorView()) ->setErrors($errors) ->setTitle('It\'s not The Final Countdown (du nu nuuu nun)' . ' until you fix these problem'); } } if ($timer->getDatePoint()) { $display_datepoint = phabricator_datetime( $timer->getDatePoint(), $user); } else { $display_datepoint = $request->getStr('datepoint'); } $form = id(new AphrontFormView()) ->setUser($user) ->setAction($request->getRequestURI()->getPath()) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Title') ->setValue($timer->getTitle()) ->setName('title')) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('End date') ->setValue($display_datepoint) ->setName('datepoint') ->setCaption( 'Examples: '. '2011-12-25 or '. '3 hours or '. 'June 8 2011, 5 PM.')) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/countdown/') ->setValue($action_label)); $panel = id(new AphrontPanelView()) ->setWidth(AphrontPanelView::WIDTH_FORM) ->setHeader($action_label) ->appendChild($form); return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Edit Countdown', )); } } diff --git a/src/applications/countdown/controller/list/PhabricatorCountdownListController.php b/src/applications/countdown/controller/list/PhabricatorCountdownListController.php index 1e23fe181c..2291f8f0db 100644 --- a/src/applications/countdown/controller/list/PhabricatorCountdownListController.php +++ b/src/applications/countdown/controller/list/PhabricatorCountdownListController.php @@ -1,111 +1,111 @@ getRequest(); $user = $request->getUser(); $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page')); $pager->setURI($request->getRequestURI(), 'page'); $timers = id(new PhabricatorTimer())->loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize() + 1); $timers = $pager->sliceResults($timers); $phids = mpull($timers, 'getAuthorPHID'); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $rows = array(); foreach ($timers as $timer) { $edit_button = null; $delete_button = null; if ($user->getIsAdmin() || ($user->getPHID() == $timer->getAuthorPHID())) { $edit_button = phutil_render_tag( 'a', array( 'class' => 'small button grey', 'href' => '/countdown/edit/'.$timer->getID().'/' ), 'Edit'); $delete_button = javelin_render_tag( 'a', array( 'class' => 'small button grey', 'href' => '/countdown/delete/'.$timer->getID().'/', 'sigil' => 'workflow' ), 'Delete'); } $rows[] = array( phutil_escape_html($timer->getID()), $handles[$timer->getAuthorPHID()]->renderLink(), phutil_render_tag( 'a', array( 'href' => '/countdown/'.$timer->getID().'/', ), phutil_escape_html($timer->getTitle())), phabricator_datetime($timer->getDatepoint(), $user), $edit_button, $delete_button, ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'ID', 'Author', 'Title', 'End Date', '', '' )); $table->setColumnClasses( array( null, null, 'wide pri', null, 'action', 'action', )); $panel = id(new AphrontPanelView()) ->appendChild($table) ->setHeader('Timers') ->setCreateButton('Create Timer', '/countdown/edit/') ->appendChild($pager); return $this->buildStandardPageResponse($panel, array( 'title' => 'Countdown', )); } } diff --git a/src/applications/countdown/controller/view/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/view/PhabricatorCountdownViewController.php index 30ce623cc2..74179be04b 100644 --- a/src/applications/countdown/controller/view/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/view/PhabricatorCountdownViewController.php @@ -1,88 +1,88 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $timer = id(new PhabricatorTimer())->load($this->id); if (!$timer) { return new Aphront404Response(); } require_celerity_resource('phabricator-countdown-css'); $chrome_visible = $request->getBool('chrome', true); $chrome_new = $chrome_visible ? false : null; $chrome_link = phutil_render_tag( 'a', array( 'href' => $request->getRequestURI()->alter('chrome', $chrome_new), 'class' => 'phabricator-timer-chrome-link', ), $chrome_visible ? 'Disable Chrome' : 'Enable Chrome'); $content = '

    '. phutil_escape_html($timer->getTitle()).' · '. phabricator_datetime($timer->getDatePoint(), $user). '

    Days Hours Minutes Seconds
    '. $chrome_link. '
    '; Javelin::initBehavior('countdown-timer', array( 'timestamp' => $timer->getDatepoint() )); $panel = $content; return $this->buildStandardPageResponse( $panel, array( 'title' => 'Countdown: '.$timer->getTitle(), 'chrome' => $chrome_visible )); } } diff --git a/src/applications/daemon/controller/combined/PhabricatorDaemonCombinedLogController.php b/src/applications/daemon/controller/combined/PhabricatorDaemonCombinedLogController.php index 6d0409ac25..17244da461 100644 --- a/src/applications/daemon/controller/combined/PhabricatorDaemonCombinedLogController.php +++ b/src/applications/daemon/controller/combined/PhabricatorDaemonCombinedLogController.php @@ -1,55 +1,55 @@ getRequest(); $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page')); $pager->setPageSize(1000); $events = id(new PhabricatorDaemonLogEvent())->loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize() + 1); $events = $pager->sliceResults($events); $pager->setURI($request->getRequestURI(), 'page'); $event_view = new PhabricatorDaemonLogEventsView(); $event_view->setEvents($events); $event_view->setUser($request->getUser()); $event_view->setCombinedLog(true); $log_panel = new AphrontPanelView(); $log_panel->setHeader('Combined Daemon Logs'); $log_panel->appendChild($event_view); $log_panel->appendChild($pager); return $this->buildStandardPageResponse( $log_panel, array( 'title' => 'Combined Daemon Log', )); } } diff --git a/src/applications/daemon/controller/console/PhabricatorDaemonConsoleController.php b/src/applications/daemon/controller/console/PhabricatorDaemonConsoleController.php index cedc0b2ba6..5b8c4ada4b 100644 --- a/src/applications/daemon/controller/console/PhabricatorDaemonConsoleController.php +++ b/src/applications/daemon/controller/console/PhabricatorDaemonConsoleController.php @@ -1,170 +1,171 @@ loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT 15'); $request = $this->getRequest(); $user = $request->getUser(); $daemon_table = new PhabricatorDaemonLogListView(); $daemon_table->setUser($user); $daemon_table->setDaemonLogs($logs); $daemon_panel = new AphrontPanelView(); $daemon_panel->setHeader( 'Recently Launched Daemons'. ' · '. phutil_render_tag( 'a', array( 'href' => '/daemon/log/', ), 'View All Daemons'). ' · '. phutil_render_tag( 'a', array( 'href' => '/daemon/log/combined/', ), 'View Combined Log')); $daemon_panel->appendChild($daemon_table); $tasks = id(new PhabricatorWorkerTask())->loadAllWhere( 'leaseOwner IS NOT NULL'); $rows = array(); foreach ($tasks as $task) { $rows[] = array( $task->getID(), $task->getTaskClass(), $task->getLeaseOwner(), $task->getLeaseExpires() - time(), $task->getFailureCount(), phutil_render_tag( 'a', array( 'href' => '/daemon/task/'.$task->getID().'/', 'class' => 'button small grey', ), 'View Task'), ); } $leased_table = new AphrontTableView($rows); $leased_table->setHeaders( array( 'ID', 'Class', 'Owner', 'Expires', 'Failures', '', )); $leased_table->setColumnClasses( array( 'n', 'wide', '', '', 'n', 'action', )); $leased_table->setNoDataString('No tasks are leased by workers.'); $leased_panel = new AphrontPanelView(); $leased_panel->setHeader('Leased Tasks'); $leased_panel->appendChild($leased_table); $task_table = new PhabricatorWorkerTask(); $queued = queryfx_all( $task_table->establishConnection('r'), 'SELECT taskClass, count(*) N FROM %T GROUP BY taskClass ORDER BY N DESC', $task_table->getTableName()); $rows = array(); foreach ($queued as $row) { $rows[] = array( phutil_escape_html($row['taskClass']), number_format($row['N']), ); } $queued_table = new AphrontTableView($rows); $queued_table->setHeaders( array( 'Class', 'Count', )); $queued_table->setColumnClasses( array( 'wide', 'n', )); $queued_table->setNoDataString('Task queue is empty.'); $queued_panel = new AphrontPanelView(); $queued_panel->setHeader('Queued Tasks'); $queued_panel->appendChild($queued_table); $cursors = id(new PhabricatorTimelineCursor()) ->loadAll(); $rows = array(); foreach ($cursors as $cursor) { $rows[] = array( phutil_escape_html($cursor->getName()), number_format($cursor->getPosition()), ); } $cursor_table = new AphrontTableView($rows); $cursor_table->setHeaders( array( 'Name', 'Position', )); $cursor_table->setColumnClasses( array( 'wide', 'n', )); $cursor_table->setNoDataString('No timeline cursors exist.'); $cursor_panel = new AphrontPanelView(); $cursor_panel->setHeader('Timeline Cursors'); $cursor_panel->appendChild($cursor_table); return $this->buildStandardPageResponse( array( $daemon_panel, $leased_panel, $queued_panel, $cursor_panel, ), array( 'title' => 'Console', 'tab' => 'console', )); } } diff --git a/src/applications/daemon/controller/loglist/PhabricatorDaemonLogListController.php b/src/applications/daemon/controller/loglist/PhabricatorDaemonLogListController.php index eb69979409..feeac6e023 100644 --- a/src/applications/daemon/controller/loglist/PhabricatorDaemonLogListController.php +++ b/src/applications/daemon/controller/loglist/PhabricatorDaemonLogListController.php @@ -1,51 +1,52 @@ getRequest(); $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page')); $logs = id(new PhabricatorDaemonLog())->loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize() + 1); $logs = $pager->sliceResults($logs); $pager->setURI($request->getRequestURI(), 'page'); $daemon_table = new PhabricatorDaemonLogListView(); $daemon_table->setUser($request->getUser()); $daemon_table->setDaemonLogs($logs); $daemon_panel = new AphrontPanelView(); $daemon_panel->setHeader('Launched Daemons'); $daemon_panel->appendChild($daemon_table); $daemon_panel->appendChild($pager); return $this->buildStandardPageResponse( $daemon_panel, array( 'title' => 'All Daemons', )); } } diff --git a/src/applications/daemon/controller/logview/PhabricatorDaemonLogViewController.php b/src/applications/daemon/controller/logview/PhabricatorDaemonLogViewController.php index ffb95ecd9e..4482d39811 100644 --- a/src/applications/daemon/controller/logview/PhabricatorDaemonLogViewController.php +++ b/src/applications/daemon/controller/logview/PhabricatorDaemonLogViewController.php @@ -1,93 +1,94 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $log = id(new PhabricatorDaemonLog())->load($this->id); if (!$log) { return new Aphront404Response(); } $events = id(new PhabricatorDaemonLogEvent())->loadAllWhere( 'logID = %d ORDER BY id DESC LIMIT 1000', $log->getID()); $content = array(); $argv = $log->getArgv(); $argv = implode("\n", $argv); $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Daemon') ->setValue($log->getDaemon())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Host') ->setValue($log->getHost())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('PID') ->setValue($log->getPID())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Started') ->setValue( phabricator_datetime($log->getDateCreated(), $user))) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Argv') ->setValue($argv)); $panel = new AphrontPanelView(); $panel->setHeader('Daemon Details'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); $content[] = $panel; $event_view = new PhabricatorDaemonLogEventsView(); $event_view->setUser($user); $event_view->setEvents($events); $log_panel = new AphrontPanelView(); $log_panel->setHeader('Daemon Logs'); $log_panel->appendChild($event_view); $content[] = $log_panel; return $this->buildStandardPageResponse( $content, array( 'title' => 'Daemon Log', )); } } diff --git a/src/applications/daemon/controller/timeline/PhabricatorDaemonTimelineConsoleController.php b/src/applications/daemon/controller/timeline/PhabricatorDaemonTimelineConsoleController.php index 36fed14a97..3705e3bda2 100644 --- a/src/applications/daemon/controller/timeline/PhabricatorDaemonTimelineConsoleController.php +++ b/src/applications/daemon/controller/timeline/PhabricatorDaemonTimelineConsoleController.php @@ -1,70 +1,70 @@ establishConnection('r'), 'SELECT id, type FROM %T ORDER BY id DESC LIMIT 100', $timeline_table->getTableName()); $rows = array(); foreach ($events as $event) { $rows[] = array( phutil_render_tag( 'a', array( 'href' => '/daemon/timeline/'.$event['id'].'/', ), $event['id']), phutil_escape_html($event['type']), ); } $event_table = new AphrontTableView($rows); $event_table->setHeaders( array( 'ID', 'Type', )); $event_table->setColumnClasses( array( null, 'wide', )); $event_panel = new AphrontPanelView(); $event_panel->setHeader('Timeline Events'); $event_panel->appendChild($event_table); return $this->buildStandardPageResponse( array( $event_panel, ), array( 'title' => 'Timeline', 'tab' => 'timeline', )); } } diff --git a/src/applications/daemon/controller/timelineevent/PhabricatorDaemonTimelineEventController.php b/src/applications/daemon/controller/timelineevent/PhabricatorDaemonTimelineEventController.php index a1499d141d..877525f8e3 100644 --- a/src/applications/daemon/controller/timelineevent/PhabricatorDaemonTimelineEventController.php +++ b/src/applications/daemon/controller/timelineevent/PhabricatorDaemonTimelineEventController.php @@ -1,80 +1,80 @@ id = $data['id']; } public function processRequest() { $event = id(new PhabricatorTimelineEvent('NULL'))->load($this->id); if (!$event) { return new Aphront404Response(); } $request = $this->getRequest(); $user = $request->getUser(); if ($event->getDataID()) { $data = id(new PhabricatorTimelineEventData())->load( $event->getDataID()); } if ($data) { $data = json_encode($data->getEventData()); } else { $data = 'null'; } $form = new AphrontFormView(); $form ->setUser($user) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('ID') ->setValue($event->getID())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Type') ->setValue($event->getType())) ->appendChild( id(new AphrontFormTextAreaControl()) ->setDisabled(true) ->setLabel('Data') ->setValue($data)) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/daemon/timeline/')); $panel = new AphrontPanelView(); $panel->setHeader('Event'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Timeline Event', )); } } diff --git a/src/applications/daemon/controller/workertaskdetail/PhabricatorWorkerTaskDetailController.php b/src/applications/daemon/controller/workertaskdetail/PhabricatorWorkerTaskDetailController.php index 02198e5e74..ca1d095caf 100644 --- a/src/applications/daemon/controller/workertaskdetail/PhabricatorWorkerTaskDetailController.php +++ b/src/applications/daemon/controller/workertaskdetail/PhabricatorWorkerTaskDetailController.php @@ -1,155 +1,155 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $task = id(new PhabricatorWorkerTask())->load($this->id); if (!$task) { $error_view = new AphrontErrorView(); $error_view->setTitle('No Such Task'); $error_view->appendChild( '

    This task may have recently completed.

    '); $error_view->setSeverity(AphrontErrorView::SEVERITY_WARNING); return $this->buildStandardPageResponse( $error_view, array( 'title' => 'Task Does Not Exist', )); } $data = id(new PhabricatorWorkerTaskData())->loadOneWhere( 'id = %d', $task->getDataID()); $extra = null; switch ($task->getTaskClass()) { case 'PhabricatorRepositorySvnCommitChangeParserWorker': case 'PhabricatorRepositoryGitCommitChangeParserWorker': $commit_id = idx($data->getData(), 'commitID'); if ($commit_id) { $commit = id(new PhabricatorRepositoryCommit())->load($commit_id); if ($commit) { $repository = id(new PhabricatorRepository())->load( $commit->getRepositoryID()); if ($repository) { $extra = "NOTE: ". "You can manually retry this task by running this script:". "
    ".
                       "phabricator/\$ ./scripts/repository/reparse.php ".
                       "r".
                       phutil_escape_html($repository->getCallsign()).
                       phutil_escape_html($commit->getCommitIdentifier()).
                       " ".
                       "--change".
                     "
    "; } } } break; default: break; } if ($data) { $data = json_encode($data->getData()); } $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('ID') ->setValue($task->getID())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Type') ->setValue($task->getTaskClass())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Lease Owner') ->setValue($task->getLeaseOwner())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Lease Expires') ->setValue($task->getLeaseExpires() - time())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Failure Count') ->setValue($task->getFailureCount())) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Data') ->setValue($data)); if ($extra) { $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel('More') ->setValue($extra)); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/daemon/', 'Back')); $panel = new AphrontPanelView(); $panel->setHeader('Task Detail'); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->appendChild($form); $panel->addButton( javelin_render_tag( 'a', array( 'href' => '/daemon/task/'.$task->getID().'/delete/', 'class' => 'button grey', 'sigil' => 'workflow', ), 'Delete Task')); $panel->addButton( javelin_render_tag( 'a', array( 'href' => '/daemon/task/'.$task->getID().'/release/', 'class' => 'button grey', 'sigil' => 'workflow', ), 'Free Lease')); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Task', )); } } diff --git a/src/applications/daemon/controller/workertaskupdate/PhabricatorWorkerTaskUpdateController.php b/src/applications/daemon/controller/workertaskupdate/PhabricatorWorkerTaskUpdateController.php index 34c570de91..5f6785de60 100644 --- a/src/applications/daemon/controller/workertaskupdate/PhabricatorWorkerTaskUpdateController.php +++ b/src/applications/daemon/controller/workertaskupdate/PhabricatorWorkerTaskUpdateController.php @@ -1,82 +1,82 @@ id = $data['id']; $this->action = $data['action']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $task = id(new PhabricatorWorkerTask())->load($this->id); if (!$task) { return new Aphront404Response(); } if ($request->isFormPost()) { switch ($this->action) { case 'delete': $task->delete(); break; case 'release': $task->setLeaseOwner(null); $task->setLeaseExpires(time()); $task->save(); break; } return id(new AphrontRedirectResponse())->setURI('/daemon/'); } $dialog = new AphrontDialogView(); $dialog->setUser($user); switch ($this->action) { case 'delete': $dialog->setTitle('Really delete task?'); $dialog->appendChild( '

    The work this task represents will never be performed if you '. 'delete it. Are you sure you want to delete it?

    '); $dialog->addSubmitButton('Delete Task'); break; case 'release': $dialog->setTitle('Really free task lease?'); $dialog->appendChild( '

    If the process which owns the task lease is still doing work '. 'on it, the work may be performed twice. Are you sure you '. 'want to free the lease?

    '); $dialog->addSubmitButton('Free Lease'); break; default: return new Aphront404Response(); } $dialog->addCancelButton('/daemon/'); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/differential/controller/changesetview/DifferentialChangesetViewController.php b/src/applications/differential/controller/changesetview/DifferentialChangesetViewController.php index 1e54d9a376..599efa12a2 100644 --- a/src/applications/differential/controller/changesetview/DifferentialChangesetViewController.php +++ b/src/applications/differential/controller/changesetview/DifferentialChangesetViewController.php @@ -1,338 +1,338 @@ allowsAnonymousAccess(); } public function processRequest() { $request = $this->getRequest(); $author_phid = $request->getUser()->getPHID(); $rendering_reference = $request->getStr('ref'); $parts = explode('/', $rendering_reference); if (count($parts) == 2) { list($id, $vs) = $parts; } else { $id = $parts[0]; $vs = 0; } $id = (int)$id; $vs = (int)$vs; $changeset = id(new DifferentialChangeset())->load($id); if (!$changeset) { return new Aphront404Response(); } $view = $request->getStr('view'); if ($view) { $changeset->attachHunks($changeset->loadHunks()); $phid = idx($changeset->getMetadata(), "$view:binary-phid"); if ($phid) { return id(new AphrontRedirectResponse())->setURI("/file/info/$phid/"); } switch ($view) { case 'new': return $this->buildRawFileResponse($changeset, $is_new = true); case 'old': return $this->buildRawFileResponse($changeset, $is_new = false); default: return new Aphront400Response(); } } if ($vs && ($vs != -1)) { $vs_changeset = id(new DifferentialChangeset())->load($vs); if (!$vs_changeset) { return new Aphront404Response(); } } if (!$vs) { $right = $changeset; $left = null; $right_source = $right->getID(); $right_new = true; $left_source = $right->getID(); $left_new = false; $render_cache_key = $right->getID(); } else if ($vs == -1) { $right = null; $left = $changeset; $right_source = $left->getID(); $right_new = false; $left_source = $left->getID(); $left_new = true; $render_cache_key = null; } else { $right = $changeset; $left = $vs_changeset; $right_source = $right->getID(); $right_new = true; $left_source = $left->getID(); $left_new = true; $render_cache_key = null; } if ($left) { $left->attachHunks($left->loadHunks()); } if ($right) { $right->attachHunks($right->loadHunks()); } if ($left) { $left_data = $left->makeNewFile(); if ($right) { $right_data = $right->makeNewFile(); } else { $right_data = $left->makeOldFile(); } $engine = new PhabricatorDifferenceEngine(); $synthetic = $engine->generateChangesetFromFileContent( $left_data, $right_data); $choice = nonempty($left, $right); $choice->attachHunks($synthetic->getHunks()); $changeset = $choice; } $coverage = null; if ($right && $right->getDiffID()) { $unit = id(new DifferentialDiffProperty())->loadOneWhere( 'diffID = %d AND name = %s', $right->getDiffID(), 'arc:unit'); if ($unit) { $coverage = array(); foreach ($unit->getData() as $result) { $result_coverage = idx($result, 'coverage'); if (!$result_coverage) { continue; } $file_coverage = idx($result_coverage, $right->getFileName()); if (!$file_coverage) { continue; } $coverage[] = $file_coverage; } $coverage = ArcanistUnitTestResult::mergeCoverage($coverage); } } $spec = $request->getStr('range'); list($range_s, $range_e, $mask) = DifferentialChangesetParser::parseRangeSpecification($spec); $parser = new DifferentialChangesetParser(); $parser->setCoverage($coverage); $parser->setChangeset($changeset); $parser->setRenderingReference($rendering_reference); $parser->setRenderCacheKey($render_cache_key); $parser->setRightSideCommentMapping($right_source, $right_new); $parser->setLeftSideCommentMapping($left_source, $left_new); $parser->setWhitespaceMode($request->getStr('whitespace')); // Load both left-side and right-side inline comments. $inlines = $this->loadInlineComments( array($left_source, $right_source), $author_phid); if ($left_new) { $inlines = array_merge( $inlines, $this->buildLintInlineComments($left)); } if ($right_new) { $inlines = array_merge( $inlines, $this->buildLintInlineComments($right)); } $phids = array(); foreach ($inlines as $inline) { $parser->parseInlineComment($inline); if ($inline->getAuthorPHID()) { $phids[$inline->getAuthorPHID()] = true; } } $phids = array_keys($phids); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $parser->setHandles($handles); $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); $parser->setMarkupEngine($engine); if ($request->isAjax()) { // TODO: This is sort of lazy, the effect is just to not render "Edit" // links on the "standalone view". $parser->setUser($request->getUser()); } $output = $parser->render($range_s, $range_e, $mask); if ($request->isAjax()) { return id(new AphrontAjaxResponse()) ->setContent($output); } Javelin::initBehavior('differential-show-more', array( 'uri' => '/differential/changeset/', 'whitespace' => $request->getStr('whitespace'), )); Javelin::initBehavior('differential-comment-jump', array()); $detail = new DifferentialChangesetDetailView(); $detail->setChangeset($changeset); $detail->appendChild($output); $detail->setRevisionID($request->getInt('revision_id')); $output = id(new DifferentialPrimaryPaneView()) ->setLineWidthFromChangesets(array($changeset)) ->appendChild( '
    '. $detail->render(). '
    '); return $this->buildStandardPageResponse( array( $output ), array( 'title' => 'Changeset View', )); } private function loadInlineComments(array $changeset_ids, $author_phid) { $changeset_ids = array_unique(array_filter($changeset_ids)); if (!$changeset_ids) { return; } return id(new DifferentialInlineComment())->loadAllWhere( 'changesetID IN (%Ld) AND (commentID IS NOT NULL OR authorPHID = %s)', $changeset_ids, $author_phid); } private function buildRawFileResponse( DifferentialChangeset $changeset, $is_new) { if ($is_new) { $key = 'raw:new:phid'; } else { $key = 'raw:old:phid'; } $metadata = $changeset->getMetadata(); $file = null; $phid = idx($metadata, $key); if ($phid) { $file = id(new PhabricatorFile())->loadOneWhere( 'phid = %s', $phid); } if (!$file) { // This is just building a cache of the changeset content in the file // tool, and is safe to run on a read pathway. $unguard = AphrontWriteGuard::beginScopedUnguardedWrites(); if ($is_new) { $data = $changeset->makeNewFile(); } else { $data = $changeset->makeOldFile(); } $file = PhabricatorFile::newFromFileData( $data, array( 'name' => $changeset->getFilename(), 'mime-type' => 'text/plain', )); $metadata[$key] = $file->getPHID(); $changeset->setMetadata($metadata); $changeset->save(); unset($unguard); } return id(new AphrontRedirectResponse()) ->setURI($file->getBestURI()); } private function buildLintInlineComments($changeset) { $lint = id(new DifferentialDiffProperty())->loadOneWhere( 'diffID = %d AND name = %s', $changeset->getDiffID(), 'arc:lint'); if (!$lint) { return array(); } $lint = $lint->getData(); $inlines = array(); foreach ($lint as $msg) { if ($msg['path'] != $changeset->getFilename()) { continue; } $inline = new DifferentialInlineComment(); $inline->setChangesetID($changeset->getID()); $inline->setIsNewFile(true); $inline->setSyntheticAuthor('Lint: '.$msg['name']); $inline->setLineNumber($msg['line']); $inline->setLineLength(0); $inline->setContent('%%%'.$msg['description'].'%%%'); $inlines[] = $inline; } return $inlines; } } diff --git a/src/applications/differential/controller/commentpreview/DifferentialCommentPreviewController.php b/src/applications/differential/controller/commentpreview/DifferentialCommentPreviewController.php index 4c611a2bdd..3c97ed0e45 100644 --- a/src/applications/differential/controller/commentpreview/DifferentialCommentPreviewController.php +++ b/src/applications/differential/controller/commentpreview/DifferentialCommentPreviewController.php @@ -1,83 +1,84 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $author_phid = $request->getUser()->getPHID(); $action = $request->getStr('action'); $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); $comment = new DifferentialComment(); $comment->setContent($request->getStr('content')); $comment->setAction($action); $comment->setAuthorPHID($author_phid); $handles = array($author_phid); $reviewers = $request->getStr('reviewers'); if (($action == DifferentialAction::ACTION_ADDREVIEWERS || $action == DifferentialAction::ACTION_REQUEST) && $reviewers) { $reviewers = explode(',', $reviewers); $comment->setMetadata(array( DifferentialComment::METADATA_ADDED_REVIEWERS => $reviewers)); $handles = array_merge($handles, $reviewers); } $ccs = $request->getStr('ccs'); if ($action == DifferentialAction::ACTION_ADDCCS && $ccs) { $ccs = explode(',', $ccs); $comment->setMetadata(array( DifferentialComment::METADATA_ADDED_CCS => $ccs)); $handles = array_merge($handles, $ccs); } $handles = id(new PhabricatorObjectHandleData($handles)) ->loadHandles(); $view = new DifferentialRevisionCommentView(); $view->setUser($request->getUser()); $view->setComment($comment); $view->setHandles($handles); $view->setMarkupEngine($engine); $view->setPreview(true); $view->setTargetDiff(null); $draft = new PhabricatorDraft(); $draft ->setAuthorPHID($author_phid) ->setDraftKey('differential-comment-'.$this->id) ->setDraft($comment->getContent()) ->replace(); return id(new AphrontAjaxResponse()) ->setContent($view->render()); } } diff --git a/src/applications/differential/controller/commentsave/DifferentialCommentSaveController.php b/src/applications/differential/controller/commentsave/DifferentialCommentSaveController.php index a26875d61d..cff04d2259 100644 --- a/src/applications/differential/controller/commentsave/DifferentialCommentSaveController.php +++ b/src/applications/differential/controller/commentsave/DifferentialCommentSaveController.php @@ -1,102 +1,102 @@ getRequest(); if (!$request->isFormPost()) { return new Aphront400Response(); } $revision_id = $request->getInt('revision_id'); $revision = id(new DifferentialRevision())->load($revision_id); if (!$revision) { return new Aphront400Response(); } $comment = $request->getStr('comment'); $action = $request->getStr('action'); $reviewers = $request->getArr('reviewers'); $ccs = $request->getArr('ccs'); $editor = new DifferentialCommentEditor( $revision, $request->getUser()->getPHID(), $action); $content_source = PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_WEB, array( 'ip' => $request->getRemoteAddr(), )); try { $editor ->setMessage($comment) ->setContentSource($content_source) ->setAttachInlineComments(true) ->setAddedReviewers($reviewers) ->setAddedCCs($ccs) ->save(); } catch (DifferentialActionHasNoEffectException $no_effect) { $has_inlines = id(new DifferentialInlineComment())->loadAllWhere( 'authorPHID = %s AND revisionID = %d AND commentID IS NULL', $request->getUser()->getPHID(), $revision->getID()); $dialog = new AphrontDialogView(); $dialog->setUser($request->getUser()); $dialog->addCancelButton('/D'.$revision_id); $dialog->addHiddenInput('revision_id', $revision_id); $dialog->addHiddenInput('action', 'none'); $dialog->addHiddenInput('reviewers', $reviewers); $dialog->addHiddenInput('ccs', $ccs); $dialog->addHiddenInput('comment', $comment); $dialog->setTitle('Action Has No Effect'); $dialog->appendChild( '

    '.phutil_escape_html($no_effect->getMessage()).'

    '); if (strlen($comment) || $has_inlines) { $dialog->addSubmitButton('Post as Comment'); $dialog->appendChild('
    '); $dialog->appendChild( '

    Do you want to post your feedback anyway, as a normal '. 'comment?

    '); } return id(new AphrontDialogResponse())->setDialog($dialog); } // TODO: Diff change detection? $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $request->getUser()->getPHID(), 'differential-comment-'.$revision->getID()); if ($draft) { $draft->delete(); } return id(new AphrontRedirectResponse()) ->setURI('/D'.$revision->getID()); } } diff --git a/src/applications/differential/controller/diffcreate/DifferentialDiffCreateController.php b/src/applications/differential/controller/diffcreate/DifferentialDiffCreateController.php index b7cd188dae..b3b91298c4 100644 --- a/src/applications/differential/controller/diffcreate/DifferentialDiffCreateController.php +++ b/src/applications/differential/controller/diffcreate/DifferentialDiffCreateController.php @@ -1,93 +1,93 @@ getRequest(); if ($request->isFormPost()) { $parser = new ArcanistDiffParser(); $diff = null; try { $diff = PhabricatorFile::readUploadedFileData($_FILES['diff-file']); } catch (Exception $ex) { $diff = $request->getStr('diff'); } $changes = $parser->parseDiff($diff); $diff = DifferentialDiff::newFromRawChanges($changes); $diff->setLintStatus(DifferentialLintStatus::LINT_SKIP); $diff->setUnitStatus(DifferentialLintStatus::LINT_SKIP); $diff->setAuthorPHID($request->getUser()->getPHID()); $diff->setCreationMethod('web'); $diff->save(); return id(new AphrontRedirectResponse()) ->setURI('/differential/diff/'.$diff->getID().'/'); } $form = new AphrontFormView(); $arcanist_href = PhabricatorEnv::getDoclink( 'article/Arcanist_User_Guide.html'); $arcanist_link = phutil_render_tag( 'a', array( 'href' => $arcanist_href, 'target' => '_blank', ), 'Arcanist'); $form ->setAction('/differential/diff/create/') ->setEncType('multipart/form-data') ->setUser($request->getUser()) ->appendChild( '

    The best way to create a '. "Differential diff is by using $arcanist_link, but you ". 'can also just paste a diff (e.g., from svn diff or '. 'git diff) into this box or upload it as a file if you '. 'really want.

    ') ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Raw Diff') ->setName('diff') ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)) ->appendChild( id(new AphrontFormFileControl()) ->setLabel('Raw Diff from file') ->setName('diff-file')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue("Create Diff \xC2\xBB")); $panel = new AphrontPanelView(); $panel->setHeader('Create New Diff'); $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Create Diff', 'tab' => 'create', )); } } diff --git a/src/applications/differential/controller/diffview/DifferentialDiffViewController.php b/src/applications/differential/controller/diffview/DifferentialDiffViewController.php index bcc78e6801..b8aa6e6ede 100644 --- a/src/applications/differential/controller/diffview/DifferentialDiffViewController.php +++ b/src/applications/differential/controller/diffview/DifferentialDiffViewController.php @@ -1,134 +1,134 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $diff = id(new DifferentialDiff())->load($this->id); if (!$diff) { return new Aphront404Response(); } if ($diff->getRevisionID()) { $top_panel = new AphrontPanelView(); $top_panel->setWidth(AphrontPanelView::WIDTH_WIDE); $link = phutil_render_tag( 'a', array( 'href' => PhabricatorEnv::getURI('/D'.$diff->getRevisionID()), ), phutil_escape_html('D'.$diff->getRevisionID())); $top_panel->appendChild("

    This diff belongs to revision {$link}

    "); } else { $action_panel = new AphrontPanelView(); $action_panel->setHeader('Preview Diff'); $action_panel->setWidth(AphrontPanelView::WIDTH_WIDE); $action_panel->appendChild( '

    Review the diff for '. 'correctness. When you are satisfied, either create a new '. 'revision or update an existing revision.'); // TODO: implmenent optgroup support in AphrontFormSelectControl? $select = array(); $select[] = ''; $select[] = ''; $select[] = ''; $revision_data = new DifferentialRevisionListData( DifferentialRevisionListData::QUERY_OPEN_OWNED, array($request->getUser()->getPHID())); $revisions = $revision_data->loadRevisions(); if ($revisions) { $select[] = ''; foreach ($revisions as $revision) { $select[] = phutil_render_tag( 'option', array( 'value' => $revision->getID(), ), phutil_escape_html($revision->getTitle())); } $select[] = ''; } $select = ''; $action_form = new AphrontFormView(); $action_form ->setUser($request->getUser()) ->setAction('/differential/revision/edit/') ->addHiddenInput('diffID', $diff->getID()) ->addHiddenInput('viaDiffView', 1) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel('Attach To') ->setValue($select)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Continue')); $action_panel->appendChild($action_form); $top_panel = $action_panel; } $changesets = $diff->loadChangesets(); $changesets = msort($changesets, 'getSortKey'); $table_of_contents = id(new DifferentialDiffTableOfContentsView()) ->setChangesets($changesets); $refs = array(); foreach ($changesets as $changeset) { $refs[$changeset->getID()] = $changeset->getID(); } $details = id(new DifferentialChangesetListView()) ->setChangesets($changesets) ->setRenderingReferences($refs) ->setUser($request->getUser()); return $this->buildStandardPageResponse( id(new DifferentialPrimaryPaneView()) ->setLineWidthFromChangesets($changesets) ->appendChild( array( $top_panel->render(), $table_of_contents->render(), $details->render(), )), array( 'title' => 'Diff View', )); } } diff --git a/src/applications/differential/controller/inlinecommentedit/DifferentialInlineCommentEditController.php b/src/applications/differential/controller/inlinecommentedit/DifferentialInlineCommentEditController.php index d36cfc25f0..5325950c02 100644 --- a/src/applications/differential/controller/inlinecommentedit/DifferentialInlineCommentEditController.php +++ b/src/applications/differential/controller/inlinecommentedit/DifferentialInlineCommentEditController.php @@ -1,248 +1,249 @@ revisionID = $data['id']; } public function processRequest() { $request = $this->getRequest(); $changeset = $request->getInt('changeset'); $is_new = $request->getBool('is_new'); $on_right = $request->getBool('on_right'); $number = $request->getInt('number'); $length = $request->getInt('length'); $text = $request->getStr('text'); $op = $request->getStr('op'); $inline_id = $request->getInt('id'); $user = $request->getUser(); $submit_uri = '/differential/comment/inline/edit/'.$this->revisionID.'/'; $edit_dialog = new DifferentialInlineCommentEditView(); $edit_dialog->setUser($user); $edit_dialog->setSubmitURI($submit_uri); $edit_dialog->setOnRight($on_right); switch ($op) { case 'delete': $inline = $this->loadInlineCommentForEditing($inline_id); if ($request->isFormPost()) { $inline->delete(); return $this->buildEmptyResponse(); } $dialog = new AphrontDialogView(); $dialog->setUser($user); $dialog->setSubmitURI($submit_uri); $dialog->setTitle('Really delete this comment?'); $dialog->addHiddenInput('id', $inline_id); $dialog->addHiddenInput('op', 'delete'); $dialog->appendChild('

    Delete this inline comment?

    '); $dialog->addCancelButton('#'); $dialog->addSubmitButton(); return id(new AphrontDialogResponse())->setDialog($dialog); case 'edit': $inline = $this->loadInlineCommentForEditing($inline_id); if ($request->isFormPost()) { if (strlen($text)) { $inline->setContent($text); $inline->setCache(null); $inline->save(); return $this->buildRenderedCommentResponse( $inline, $on_right); } else { $inline->delete(); return $this->buildEmptyResponse(); } } $edit_dialog->setTitle('Edit Inline Comment'); $edit_dialog->addHiddenInput('id', $inline_id); $edit_dialog->addHiddenInput('op', 'edit'); $edit_dialog->appendChild( $this->renderTextArea( nonempty($text, $inline->getContent()))); return id(new AphrontAjaxResponse()) ->setContent($edit_dialog->render()); case 'create': if (!$request->isFormPost() || !strlen($text)) { return $this->buildEmptyResponse(); } // Verify revision and changeset correspond to actual objects. $revision_obj = id(new DifferentialRevision())->load($this->revisionID); $changeset_obj = id(new DifferentialChangeset())->load($changeset); if (!$revision_obj || !$changeset_obj) { throw new Exception("Invalid revision ID or changeset ID!"); } $inline = id(new DifferentialInlineComment()) ->setRevisionID($this->revisionID) ->setChangesetID($changeset) ->setCommentID(null) ->setAuthorPHID($user->getPHID()) ->setLineNumber($number) ->setLineLength($length) ->setIsNewFile($is_new) ->setContent($text) ->save(); return $this->buildRenderedCommentResponse($inline, $on_right); case 'reply': default: if ($op == 'reply') { $inline = $this->loadInlineComment($inline_id); // Override defaults. $changeset = $inline->getChangesetID(); $is_new = $inline->getIsNewFile(); $number = $inline->getLineNumber(); $length = $inline->getLineLength(); $edit_dialog->setTitle('Reply to Inline Comment'); } else { $edit_dialog->setTitle('New Inline Comment'); } $edit_dialog->addHiddenInput('op', 'create'); $edit_dialog->addHiddenInput('changeset', $changeset); $edit_dialog->addHiddenInput('is_new', $is_new); $edit_dialog->addHiddenInput('number', $number); $edit_dialog->addHiddenInput('length', $length); $edit_dialog->appendChild($this->renderTextArea($text)); return id(new AphrontAjaxResponse()) ->setContent($edit_dialog->render()); } } private function buildRenderedCommentResponse( DifferentialInlineComment $inline, $on_right) { $request = $this->getRequest(); $user = $request->getUser(); $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); $phids = array($user->getPHID()); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $view = new DifferentialInlineCommentView(); $view->setInlineComment($inline); $view->setOnRight($on_right); $view->setBuildScaffolding(true); $view->setMarkupEngine($engine); $view->setHandles($handles); $view->setEditable(true); return id(new AphrontAjaxResponse()) ->setContent( array( 'inlineCommentID' => $inline->getID(), 'markup' => $view->render(), )); } private function buildEmptyResponse() { return id(new AphrontAjaxResponse()) ->setContent( array( 'markup' => '', )); } private function renderTextArea($text) { return javelin_render_tag( 'textarea', array( 'class' => 'differential-inline-comment-edit-textarea', 'sigil' => 'differential-inline-comment-edit-textarea', 'name' => 'text', ), phutil_escape_html($text)); } private function loadInlineComment($id) { $inline = null; if ($id) { $inline = id(new DifferentialInlineComment())->load($id); } if (!$inline) { throw new Exception("No such inline comment!"); } return $inline; } private function loadInlineCommentForEditing($id) { $inline = $this->loadInlineComment($id); $user = $this->getRequest()->getUser(); if (!$this->canEditInlineComment($user, $inline, $this->revisionID)) { throw new Exception("That comment is not editable!"); } return $inline; } private function canEditInlineComment( PhabricatorUser $user, DifferentialInlineComment $inline, $revision_id) { // Only the author may edit a comment. if ($inline->getAuthorPHID() != $user->getPHID()) { return false; } // Saved comments may not be edited. if ($inline->getCommentID()) { return false; } // Inline must be attached to the active revision. if ($inline->getRevisionID() != $revision_id) { return false; } return true; } } diff --git a/src/applications/differential/controller/inlinecommentpreview/DifferentialInlineCommentPreviewController.php b/src/applications/differential/controller/inlinecommentpreview/DifferentialInlineCommentPreviewController.php index 056b7e3c68..03bb1ea64d 100644 --- a/src/applications/differential/controller/inlinecommentpreview/DifferentialInlineCommentPreviewController.php +++ b/src/applications/differential/controller/inlinecommentpreview/DifferentialInlineCommentPreviewController.php @@ -1,65 +1,65 @@ revisionID = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); // TODO: This is a reasonable approximation of the feature as it exists // in Facebook trunk but we should probably pull filename data, sort these, // figure out next/prev/edit/delete, deal with out-of-date inlines, etc. $inlines = id(new DifferentialInlineComment())->loadAllWhere( 'authorPHID = %s AND revisionID = %d AND commentID IS NULL', $user->getPHID(), $this->revisionID); $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); $phids = array($user->getPHID()); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $views = array(); foreach ($inlines as $inline) { $view = new DifferentialInlineCommentView(); $view->setInlineComment($inline); $view->setMarkupEngine($engine); $view->setHandles($handles); $view->setEditable(false); $view->setPreview(true); $views[] = $view->render(); } $views = implode("\n", $views); return id(new AphrontAjaxResponse()) ->setContent($views); } } diff --git a/src/applications/differential/controller/revisionedit/DifferentialRevisionEditController.php b/src/applications/differential/controller/revisionedit/DifferentialRevisionEditController.php index a0c6c6662c..d7b8594406 100644 --- a/src/applications/differential/controller/revisionedit/DifferentialRevisionEditController.php +++ b/src/applications/differential/controller/revisionedit/DifferentialRevisionEditController.php @@ -1,193 +1,193 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); if (!$this->id) { $this->id = $request->getInt('revisionID'); } if ($this->id) { $revision = id(new DifferentialRevision())->load($this->id); if (!$revision) { return new Aphront404Response(); } } else { $revision = new DifferentialRevision(); } $revision->loadRelationships(); $aux_fields = $this->loadAuxiliaryFields($revision); $diff_id = $request->getInt('diffID'); if ($diff_id) { $diff = id(new DifferentialDiff())->load($diff_id); if (!$diff) { return new Aphront404Response(); } if ($diff->getRevisionID()) { // TODO: Redirect? throw new Exception("This diff is already attached to a revision!"); } } else { $diff = null; } $errors = array(); if ($request->isFormPost() && !$request->getStr('viaDiffView')) { $user_phid = $request->getUser()->getPHID(); foreach ($aux_fields as $aux_field) { $aux_field->setValueFromRequest($request); try { $aux_field->validateField(); } catch (DifferentialFieldValidationException $ex) { $errors[] = $ex->getMessage(); } } if (!$errors) { $editor = new DifferentialRevisionEditor($revision, $user_phid); if ($diff) { $editor->addDiff($diff, $request->getStr('comments')); } $editor->setAuxiliaryFields($aux_fields); $editor->save(); return id(new AphrontRedirectResponse()) ->setURI('/D'.$revision->getID()); } } $aux_phids = array(); foreach ($aux_fields as $key => $aux_field) { $aux_phids[$key] = $aux_field->getRequiredHandlePHIDsForRevisionEdit(); } $phids = array_mergev($aux_phids); $phids = array_unique($phids); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); foreach ($aux_fields as $key => $aux_field) { $aux_field->setHandles(array_select_keys($handles, $aux_phids[$key])); } $form = new AphrontFormView(); $form->setUser($request->getUser()); if ($diff) { $form->addHiddenInput('diffID', $diff->getID()); } if ($revision->getID()) { $form->setAction('/differential/revision/edit/'.$revision->getID().'/'); } else { $form->setAction('/differential/revision/edit/'); } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle('Form Errors') ->setErrors($errors); } if ($diff && $revision->getID()) { $form ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Comments') ->setName('comments') ->setCaption("Explain what's new in this diff.") ->setValue($request->getStr('comments'))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save')) ->appendChild( id(new AphrontFormDividerControl())); } foreach ($aux_fields as $aux_field) { $control = $aux_field->renderEditControl(); if ($control) { $form->appendChild($control); } } $submit = id(new AphrontFormSubmitControl()) ->setValue('Save'); if ($diff) { $submit->addCancelButton('/differential/diff/'.$diff->getID().'/'); } else { $submit->addCancelButton('/D'.$revision->getID()); } $form->appendChild($submit); $panel = new AphrontPanelView(); if ($revision->getID()) { if ($diff) { $panel->setHeader('Update Differential Revision'); } else { $panel->setHeader('Edit Differential Revision'); } } else { $panel->setHeader('Create New Differential Revision'); } $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); return $this->buildStandardPageResponse( array($error_view, $panel), array( 'title' => 'Edit Differential Revision', )); } private function loadAuxiliaryFields(DifferentialRevision $revision) { $user = $this->getRequest()->getUser(); $aux_fields = DifferentialFieldSelector::newSelector() ->getFieldSpecifications(); foreach ($aux_fields as $key => $aux_field) { $aux_field->setRevision($revision); if (!$aux_field->shouldAppearOnEdit()) { unset($aux_fields[$key]); } else { $aux_field->setUser($user); } } return DifferentialAuxiliaryField::loadFromStorage( $revision, $aux_fields); } } diff --git a/src/applications/differential/controller/revisionlist/DifferentialRevisionListController.php b/src/applications/differential/controller/revisionlist/DifferentialRevisionListController.php index c49d16e6b7..38b85fbbc8 100644 --- a/src/applications/differential/controller/revisionlist/DifferentialRevisionListController.php +++ b/src/applications/differential/controller/revisionlist/DifferentialRevisionListController.php @@ -1,441 +1,441 @@ allowsAnonymousAccess(); } public function willProcessRequest(array $data) { $this->filter = idx($data, 'filter'); $this->username = idx($data, 'username'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $viewer_is_anonymous = !$user->isLoggedIn(); $params = array_filter( array( 'status' => $request->getStr('status'), 'order' => $request->getStr('order'), )); $default_filter = ($viewer_is_anonymous ? 'all' : 'active'); $filters = $this->getFilters(); $this->filter = $this->selectFilter( $filters, $this->filter, $default_filter); // Redirect from search to canonical URL. $phid_arr = $request->getArr('view_user'); if ($phid_arr) { $view_user = id(new PhabricatorUser()) ->loadOneWhere('phid = %s', head($phid_arr)); if (!$view_user) { return new Aphront404Response(); } $uri = id(new PhutilURI('/differential/filter/'.$this->filter.'/'. phutil_escape_uri($view_user->getUserName()).'/')) ->setQueryParams($params); return id(new AphrontRedirectResponse())->setURI($uri); } $uri = new PhutilURI('/differential/filter/'.$this->filter.'/'); $uri->setQueryParams($params); $username = ''; if ($this->username) { $view_user = id(new PhabricatorUser()) ->loadOneWhere('userName = %s', $this->username); if (!$view_user) { return new Aphront404Response(); } $username = phutil_escape_uri($this->username).'/'; $uri->setPath('/differential/filter/'.$this->filter.'/'.$username); $params['phid'] = $view_user->getPHID(); } // Fill in the defaults we'll actually use for calculations if any // parameters are missing. $params += array( 'phid' => $user->getPHID(), 'status' => 'all', 'order' => 'modified', ); $side_nav = new AphrontSideNavView(); foreach ($filters as $filter) { list($filter_name, $display_name) = $filter; if ($filter_name) { $href = clone $uri; $href->setPath('/differential/filter/'.$filter_name.'/'.$username); if ($filter_name == $this->filter) { $class = 'aphront-side-nav-selected'; } else { $class = null; } $item = phutil_render_tag( 'a', array( 'href' => (string)$href, 'class' => $class, ), phutil_escape_html($display_name)); } else { $item = phutil_render_tag( 'span', array(), phutil_escape_html($display_name)); } $side_nav->addNavItem($item); } $panels = array(); $handles = array(); $controls = $this->getFilterControls($this->filter); if ($this->getFilterRequiresUser($this->filter) && !$params['phid']) { // In the anonymous case, we still want to let you see some user's // list, but we don't have a default PHID to provide (normally, we use // the viewing user's). Show a warning instead. $warning = new AphrontErrorView(); $warning->setSeverity(AphrontErrorView::SEVERITY_WARNING); $warning->setTitle('User Required'); $warning->appendChild( 'This filter requires that a user be specified above.'); $panels[] = $warning; } else { $query = $this->buildQuery($this->filter, $params['phid']); $pager = null; if ($this->getFilterAllowsPaging($this->filter)) { $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page')); $pager->setPageSize(1000); $pager->setURI($uri, 'page'); $query->setOffset($pager->getOffset()); $query->setLimit($pager->getPageSize() + 1); } foreach ($controls as $control) { $this->applyControlToQuery($control, $query, $params); } $revisions = $query->execute(); if ($pager) { $revisions = $pager->sliceResults($revisions); } $views = $this->buildViews($this->filter, $params['phid'], $revisions); $view_objects = ipull($views, 'view'); $phids = array_mergev(mpull($view_objects, 'getRequiredHandlePHIDs')); $phids[] = $params['phid']; $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); foreach ($views as $view) { $view['view']->setHandles($handles); $panel = new AphrontPanelView(); $panel->setHeader($view['title']); $panel->appendChild($view['view']); if ($pager) { $panel->appendChild($pager); } $panels[] = $panel; } } $filter_form = id(new AphrontFormView()) ->setMethod('GET') ->setAction('/differential/filter/'.$this->filter.'/') ->setUser($user); foreach ($controls as $control) { $control_view = $this->renderControl($control, $handles, $uri, $params); $filter_form->appendChild($control_view); } $filter_form ->addHiddenInput('status', $params['status']) ->addHiddenInput('order', $params['order']) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Filter Revisions')); $filter_view = new AphrontListFilterView(); $filter_view->appendChild($filter_form); if (!$viewer_is_anonymous) { $create_uri = new PhutilURI('/differential/diff/create/'); $filter_view->addButton( phutil_render_tag( 'a', array( 'href' => (string)$create_uri, 'class' => 'green button', ), 'Create Revision')); } $side_nav->appendChild($filter_view); foreach ($panels as $panel) { $side_nav->appendChild($panel); } return $this->buildStandardPageResponse( $side_nav, array( 'title' => 'Differential Home', 'tab' => 'revisions', )); } private function getFilters() { return array( array(null, 'User Revisions'), array('active', 'Active'), array('revisions', 'Revisions'), array('reviews', 'Reviews'), array('subscribed', 'Subscribed'), array(null, 'All Revisions'), array('all', 'All'), ); } private function selectFilter( array $filters, $requested_filter, $default_filter) { // If the user requested a filter, make sure it actually exists. if ($requested_filter) { foreach ($filters as $filter) { if ($filter[0] === $requested_filter) { return $requested_filter; } } } // If not, return the default filter. return $default_filter; } private function getFilterRequiresUser($filter) { static $requires = array( 'active' => true, 'revisions' => true, 'reviews' => true, 'subscribed' => true, 'all' => false, ); if (!isset($requires[$filter])) { throw new Exception("Unknown filter '{$filter}'!"); } return $requires[$filter]; } private function getFilterAllowsPaging($filter) { static $allows = array( 'active' => false, 'revisions' => true, 'reviews' => true, 'subscribed' => true, 'all' => true, ); if (!isset($allows[$filter])) { throw new Exception("Unknown filter '{$filter}'!"); } return $allows[$filter]; } private function getFilterControls($filter) { static $controls = array( 'active' => array('phid'), 'revisions' => array('phid', 'status', 'order'), 'reviews' => array('phid', 'status', 'order'), 'subscribed' => array('phid', 'status', 'order'), 'all' => array('status', 'order'), ); if (!isset($controls[$filter])) { throw new Exception("Unknown filter '{$filter}'!"); } return $controls[$filter]; } private function buildQuery($filter, $user_phid) { $query = new DifferentialRevisionQuery(); $query->needRelationships(true); switch ($filter) { case 'active': $query->withResponsibleUsers(array($user_phid)); $query->withStatus(DifferentialRevisionQuery::STATUS_OPEN); $query->setLimit(null); break; case 'revisions': $query->withAuthors(array($user_phid)); break; case 'reviews': $query->withReviewers(array($user_phid)); break; case 'subscribed': $query->withSubscribers(array($user_phid)); break; case 'all': break; default: throw new Exception("Unknown filter '{$filter}'!"); } return $query; } private function renderControl( $control, array $handles, PhutilURI $uri, array $params) { switch ($control) { case 'phid': $view_phid = $params['phid']; $value = array(); if ($view_phid) { $value = array( $view_phid => $handles[$view_phid]->getFullName(), ); } return id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setLabel('View User') ->setName('view_user') ->setValue($value) ->setLimit(1); case 'status': return id(new AphrontFormToggleButtonsControl()) ->setLabel('Status') ->setValue($params['status']) ->setBaseURI($uri, 'status') ->setButtons( array( 'all' => 'All', 'open' => 'Open', 'committed' => 'Committed', 'abandoned' => 'Abandoned', )); case 'order': return id(new AphrontFormToggleButtonsControl()) ->setLabel('Order') ->setValue($params['order']) ->setBaseURI($uri, 'order') ->setButtons( array( 'modified' => 'Updated', 'created' => 'Created', )); default: throw new Exception("Unknown control '{$control}'!"); } } private function applyControlToQuery($control, $query, array $params) { switch ($control) { case 'phid': // Already applied by query construction. break; case 'status': if ($params['status'] == 'open') { $query->withStatus(DifferentialRevisionQuery::STATUS_OPEN); } elseif ($params['status'] == 'committed') { $query->withStatus(DifferentialRevisionQuery::STATUS_COMMITTED); } elseif ($params['status'] == 'abandoned') { $query->withStatus(DifferentialRevisionQuery::STATUS_ABANDONED); } break; case 'order': if ($params['order'] == 'created') { $query->setOrder(DifferentialRevisionQuery::ORDER_CREATED); } break; default: throw new Exception("Unknown control '{$control}'!"); } } private function buildViews($filter, $user_phid, array $revisions) { $user = $this->getRequest()->getUser(); $template = id(new DifferentialRevisionListView()) ->setUser($user) ->setFields(DifferentialRevisionListView::getDefaultFields()); $views = array(); switch ($filter) { case 'active': list($active, $waiting) = DifferentialRevisionQuery::splitResponsible( $revisions, $user_phid); $view = id(clone $template) ->setRevisions($active) ->setNoDataString("You have no active revisions requiring action."); $views[] = array( 'title' => 'Action Required', 'view' => $view, ); $view = id(clone $template) ->setRevisions($waiting) ->setNoDataString("You have no active revisions waiting on others."); $views[] = array( 'title' => 'Waiting On Others', 'view' => $view, ); break; case 'revisions': case 'reviews': case 'subscribed': case 'all': $titles = array( 'revisions' => 'Revisions by Author', 'reviews' => 'Revisions by Reviewer', 'subscribed' => 'Revisions by Subscriber', 'all' => 'Revisions', ); $view = id(clone $template) ->setRevisions($revisions); $views[] = array( 'title' => idx($titles, $filter), 'view' => $view, ); break; default: throw new Exception("Unknown filter '{$filter}'!"); } return $views; } } diff --git a/src/applications/differential/controller/revisionstats/DifferentialRevisionStatsController.php b/src/applications/differential/controller/revisionstats/DifferentialRevisionStatsController.php index d3661512a5..07580a0872 100644 --- a/src/applications/differential/controller/revisionstats/DifferentialRevisionStatsController.php +++ b/src/applications/differential/controller/revisionstats/DifferentialRevisionStatsController.php @@ -1,170 +1,170 @@ establishConnection('r'); $rows = queryfx_all( $conn_r, 'SELECT revisions.* FROM %T revisions ' . 'JOIN %T comments ON comments.revisionID = revisions.id ' . 'JOIN (' . ' SELECT revisionID FROM %T WHERE objectPHID = %s ' . ' UNION ALL ' . ' SELECT id from differential_revision WHERE authorPHID = %s) rel ' . 'ON (comments.revisionID = rel.revisionID)' . 'WHERE comments.action = %s' . 'AND comments.authorPHID = %s', $table->getTableName(), id(new DifferentialComment())->getTableName(), DifferentialRevision::RELATIONSHIP_TABLE, $phid, $phid, $this->filter, $phid ); return $table->loadAllFromArray($rows); } private function loadComments($phid) { $table = new DifferentialComment(); $conn_r = $table->establishConnection('r'); $rows = queryfx_all( $conn_r, 'SELECT comments.* FROM %T comments ' . 'JOIN (' . ' SELECT revisionID FROM %T WHERE objectPHID = %s ' . ' UNION ALL ' . ' SELECT id from differential_revision WHERE authorPHID = %s) rel ' . 'ON (comments.revisionID = rel.revisionID)' . 'WHERE comments.action = %s' . 'AND comments.authorPHID = %s', $table->getTableName(), DifferentialRevision::RELATIONSHIP_TABLE, $phid, $phid, $this->filter, $phid ); return $table->loadAllFromArray($rows); } public function willProcessRequest(array $data) { $this->filter = idx($data, 'filter'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if ($request->isFormPost()) { $phid_arr = $request->getArr('view_user'); $view_target = head($phid_arr); return id(new AphrontRedirectResponse()) ->setURI($request->getRequestURI()->alter('phid', $view_target)); } $params = array_filter( array( 'phid' => $request->getStr('phid'), )); // Fill in the defaults we'll actually use for calculations if any // parameters are missing. $params += array( 'phid' => $user->getPHID(), ); $side_nav = new AphrontSideNavFilterView(); $side_nav->setBaseURI(id(new PhutilURI('/differential/stats/')) ->alter('phid', $params['phid'])); foreach (array( DifferentialAction::ACTION_COMMIT, DifferentialAction::ACTION_ACCEPT, DifferentialAction::ACTION_REJECT, DifferentialAction::ACTION_UPDATE, DifferentialAction::ACTION_COMMENT, ) as $action) { $verb = ucfirst(DifferentialAction::getActionPastTenseVerb($action)); $side_nav->addFilter($action, $verb); } $this->filter = $side_nav->selectFilter($this->filter, DifferentialAction::ACTION_COMMIT); $panels = array(); $handles = id(new PhabricatorObjectHandleData(array($params['phid']))) ->loadHandles(); $filter_form = id(new AphrontFormView()) ->setAction('/differential/stats/'.$this->filter.'/') ->setUser($user); $filter_form->appendChild( $this->renderControl($params['phid'], $handles)); $filter_form->appendChild(id(new AphrontFormSubmitControl()) ->setValue('Filter Revisions')); $side_nav->appendChild($filter_form); $comments = $this->loadComments($params['phid']); $revisions = $this->loadRevisions($params['phid']); $panel = new AphrontPanelView(); $panel->setHeader('Differential rate analysis'); $panel->appendChild( id(new DifferentialRevisionStatsView()) ->setComments($comments) ->setRevisions($revisions) ->setUser($user)); $panels[] = $panel; foreach ($panels as $panel) { $side_nav->appendChild($panel); } return $this->buildStandardPageResponse( $side_nav, array( 'title' => 'Differential statistics', )); } private function renderControl($view_phid, $handles) { $value = array(); if ($view_phid) { $value = array( $view_phid => $handles[$view_phid]->getFullName(), ); } return id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setLabel('View User') ->setName('view_user') ->setValue($value) ->setLimit(1); } } diff --git a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php index e35c7370b7..c36569fe1e 100644 --- a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php @@ -1,678 +1,678 @@ allowsAnonymousAccess(); } public function willProcessRequest(array $data) { $this->revisionID = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $viewer_is_anonymous = !$user->isLoggedIn(); $revision = id(new DifferentialRevision())->load($this->revisionID); if (!$revision) { return new Aphront404Response(); } $revision->loadRelationships(); $diffs = $revision->loadDiffs(); if (!$diffs) { throw new Exception( "This revision has no diffs. Something has gone quite wrong."); } $diff_vs = $request->getInt('vs'); $target = end($diffs); $target_id = $request->getInt('id'); if ($target_id) { if (isset($diffs[$target_id])) { $target = $diffs[$target_id]; } } $diffs = mpull($diffs, null, 'getID'); if (empty($diffs[$diff_vs])) { $diff_vs = null; } list($aux_fields, $props) = $this->loadAuxiliaryFieldsAndProperties( $revision, $target, array( 'local:commits', )); list($changesets, $vs_map, $rendering_references) = $this->loadChangesetsAndVsMap($diffs, $diff_vs, $target); $comments = $revision->loadComments(); $comments = array_merge( $this->getImplicitComments($revision), $comments); $all_changesets = $changesets; $inlines = $this->loadInlineComments($comments, $all_changesets); $object_phids = array_merge( $revision->getReviewers(), $revision->getCCPHIDs(), $revision->loadCommitPHIDs(), array( $revision->getAuthorPHID(), $user->getPHID(), ), mpull($comments, 'getAuthorPHID')); foreach ($comments as $comment) { $metadata = $comment->getMetadata(); $added_reviewers = idx( $metadata, DifferentialComment::METADATA_ADDED_REVIEWERS); if ($added_reviewers) { foreach ($added_reviewers as $phid) { $object_phids[] = $phid; } } $added_ccs = idx( $metadata, DifferentialComment::METADATA_ADDED_CCS); if ($added_ccs) { foreach ($added_ccs as $phid) { $object_phids[] = $phid; } } } foreach ($revision->getAttached() as $type => $phids) { foreach ($phids as $phid => $info) { $object_phids[] = $phid; } } $aux_phids = array(); foreach ($aux_fields as $key => $aux_field) { $aux_phids[$key] = $aux_field->getRequiredHandlePHIDsForRevisionView(); } $object_phids = array_merge($object_phids, array_mergev($aux_phids)); $object_phids = array_unique($object_phids); $handles = id(new PhabricatorObjectHandleData($object_phids)) ->loadHandles(); foreach ($aux_fields as $key => $aux_field) { // Make sure each field only has access to handles it specifically // requested, not all handles. Otherwise you can get a field which works // only in the presence of other fields. $aux_field->setHandles(array_select_keys($handles, $aux_phids[$key])); } $reviewer_warning = null; $has_live_reviewer = false; foreach ($revision->getReviewers() as $reviewer) { if (!$handles[$reviewer]->isDisabled()) { $has_live_reviewer = true; } } if (!$has_live_reviewer) { $reviewer_warning = new AphrontErrorView(); $reviewer_warning->setSeverity(AphrontErrorView::SEVERITY_WARNING); $reviewer_warning->setTitle('No Active Reviewers'); if ($revision->getReviewers()) { $reviewer_warning->appendChild( '

    All specified reviewers are disabled. You may want to add '. 'some new reviewers.

    '); } else { $reviewer_warning->appendChild( '

    This revision has no specified reviewers. You may want to '. 'add some.

    '); } } $request_uri = $request->getRequestURI(); $limit = 100; $large = $request->getStr('large'); if (count($changesets) > $limit && !$large) { $count = number_format(count($changesets)); $warning = new AphrontErrorView(); $warning->setTitle('Very Large Diff'); $warning->setSeverity(AphrontErrorView::SEVERITY_WARNING); $warning->setWidth(AphrontErrorView::WIDTH_WIDE); $warning->appendChild( "

    This diff is very large and affects {$count} files. Use ". "Table of Contents to open files in a standalone view. ". "". phutil_render_tag( 'a', array( 'href' => $request_uri->alter('large', 'true'), ), 'Show All Files Inline'). ""); $warning = $warning->render(); $visible_changesets = array(); } else { $warning = null; $visible_changesets = $changesets; } $revision_detail = new DifferentialRevisionDetailView(); $revision_detail->setRevision($revision); $revision_detail->setAuxiliaryFields($aux_fields); $actions = $this->getRevisionActions($revision); $custom_renderer_class = PhabricatorEnv::getEnvConfig( 'differential.revision-custom-detail-renderer'); if ($custom_renderer_class) { // TODO: build a better version of the action links and deprecate the // whole DifferentialRevisionDetailRenderer class. PhutilSymbolLoader::loadClass($custom_renderer_class); $custom_renderer = newv($custom_renderer_class, array()); $actions = array_merge( $actions, $custom_renderer->generateActionLinks($revision, $target)); } $whitespace = $request->getStr( 'whitespace', DifferentialChangesetParser::WHITESPACE_IGNORE_ALL); $arc_project = $target->loadArcanistProject(); if ($arc_project) { $symbol_indexes = $this->buildSymbolIndexes( $target, $arc_project, $visible_changesets); $repository = $arc_project->loadRepository(); } else { $symbol_indexes = array(); $repository = null; } $revision_detail->setActions($actions); $revision_detail->setUser($user); $comment_view = new DifferentialRevisionCommentListView(); $comment_view->setComments($comments); $comment_view->setHandles($handles); $comment_view->setInlineComments($inlines); $comment_view->setChangesets($all_changesets); $comment_view->setUser($user); $comment_view->setTargetDiff($target); $comment_view->setVersusDiffID($diff_vs); $changeset_view = new DifferentialChangesetListView(); $changeset_view->setChangesets($visible_changesets); $changeset_view->setEditable(!$viewer_is_anonymous); $changeset_view->setStandaloneViews(true); $changeset_view->setUser($user); $changeset_view->setRevision($revision); $changeset_view->setDiff($target); $changeset_view->setRenderingReferences($rendering_references); $changeset_view->setVsMap($vs_map); $changeset_view->setWhitespace($whitespace); if ($repository) { $changeset_view->setRepository($repository, $target); } $changeset_view->setSymbolIndexes($symbol_indexes); $diff_history = new DifferentialRevisionUpdateHistoryView(); $diff_history->setDiffs($diffs); $diff_history->setSelectedVersusDiffID($diff_vs); $diff_history->setSelectedDiffID($target->getID()); $diff_history->setSelectedWhitespace($whitespace); $diff_history->setUser($user); $local_view = new DifferentialLocalCommitsView(); $local_view->setUser($user); $local_view->setLocalCommits(idx($props, 'local:commits')); $toc_view = new DifferentialDiffTableOfContentsView(); $toc_view->setChangesets($changesets); if ($repository) { $toc_view->setRepository($repository); } $toc_view->setDiff($target); $toc_view->setUser($user); $toc_view->setStandaloneViewLink(empty($visible_changesets)); $toc_view->setVsMap($vs_map); $toc_view->setRevisionID($revision->getID()); $toc_view->setWhitespace($whitespace); if (!$viewer_is_anonymous) { $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $user->getPHID(), 'differential-comment-'.$revision->getID()); if ($draft) { $draft = $draft->getDraft(); } else { $draft = null; } $comment_form = new DifferentialAddCommentView(); $comment_form->setRevision($revision); $comment_form->setActions($this->getRevisionCommentActions($revision)); $comment_form->setActionURI('/differential/comment/save/'); $comment_form->setUser($user); $comment_form->setDraft($draft); } $pane_id = celerity_generate_unique_node_id(); Javelin::initBehavior( 'differential-keyboard-navigation', array( 'haunt' => $pane_id, )); $page_pane = id(new DifferentialPrimaryPaneView()) ->setLineWidthFromChangesets($changesets) ->setID($pane_id) ->appendChild($reviewer_warning) ->appendChild( $revision_detail->render(). $comment_view->render(). $diff_history->render(). $warning. $local_view->render(). $toc_view->render(). $changeset_view->render()); if ($comment_form) { $page_pane->appendChild($comment_form->render()); } return $this->buildStandardPageResponse( $page_pane, array( 'title' => 'D'.$revision->getID().' '.$revision->getTitle(), )); } private function getImplicitComments(DifferentialRevision $revision) { $template = new DifferentialComment(); $template->setAuthorPHID($revision->getAuthorPHID()); $template->setRevisionID($revision->getID()); $template->setDateCreated($revision->getDateCreated()); $comments = array(); if (strlen($revision->getSummary())) { $summary_comment = clone $template; $summary_comment->setContent($revision->getSummary()); $summary_comment->setAction(DifferentialAction::ACTION_SUMMARIZE); $comments[] = $summary_comment; } if (strlen($revision->getTestPlan())) { $testplan_comment = clone $template; $testplan_comment->setContent($revision->getTestPlan()); $testplan_comment->setAction(DifferentialAction::ACTION_TESTPLAN); $comments[] = $testplan_comment; } return $comments; } private function getRevisionActions(DifferentialRevision $revision) { $viewer_phid = $this->getRequest()->getUser()->getPHID(); $viewer_is_owner = ($revision->getAuthorPHID() == $viewer_phid); $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers()); $viewer_is_cc = in_array($viewer_phid, $revision->getCCPHIDs()); $viewer_is_anonymous = !$this->getRequest()->getUser()->isLoggedIn(); $status = $revision->getStatus(); $revision_id = $revision->getID(); $revision_phid = $revision->getPHID(); $links = array(); if ($viewer_is_owner) { $links[] = array( 'class' => 'revision-edit', 'href' => "/differential/revision/edit/{$revision_id}/", 'name' => 'Edit Revision', ); } if (!$viewer_is_anonymous) { if (!$viewer_is_owner && !$viewer_is_reviewer) { $action = $viewer_is_cc ? 'rem' : 'add'; $links[] = array( 'class' => $viewer_is_cc ? 'subscribe-rem' : 'subscribe-add', 'href' => "/differential/subscribe/{$action}/{$revision_id}/", 'name' => $viewer_is_cc ? 'Unsubscribe' : 'Subscribe', 'instant' => true, ); } else { $links[] = array( 'class' => 'subscribe-rem unavailable', 'name' => 'Automatically Subscribed', ); } require_celerity_resource('phabricator-object-selector-css'); require_celerity_resource('javelin-behavior-phabricator-object-selector'); $links[] = array( 'class' => 'action-dependencies', 'name' => 'Edit Dependencies', 'href' => "/search/attach/{$revision_phid}/DREV/dependencies/", 'sigil' => 'workflow', ); if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) { $links[] = array( 'class' => 'attach-maniphest', 'name' => 'Edit Maniphest Tasks', 'href' => "/search/attach/{$revision_phid}/TASK/", 'sigil' => 'workflow', ); } $links[] = array( 'class' => 'transcripts-metamta', 'name' => 'MetaMTA Transcripts', 'href' => "/mail/?phid={$revision_phid}", ); $links[] = array( 'class' => 'transcripts-herald', 'name' => 'Herald Transcripts', 'href' => "/herald/transcript/?phid={$revision_phid}", ); } return $links; } private function getRevisionCommentActions(DifferentialRevision $revision) { $actions = array( DifferentialAction::ACTION_COMMENT => true, ); $admin_actions = array(); $viewer = $this->getRequest()->getUser(); $viewer_phid = $viewer->getPHID(); $viewer_is_admin = $viewer->getIsAdmin(); $viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID()); $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers()); $viewer_did_accept = ($viewer_phid === $revision->loadReviewedBy()); if ($viewer_is_owner) { switch ($revision->getStatus()) { case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: $actions[DifferentialAction::ACTION_ABANDON] = true; $actions[DifferentialAction::ACTION_RETHINK] = true; break; case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: $actions[DifferentialAction::ACTION_ABANDON] = true; $actions[DifferentialAction::ACTION_REQUEST] = true; break; case ArcanistDifferentialRevisionStatus::ACCEPTED: $actions[DifferentialAction::ACTION_ABANDON] = true; $actions[DifferentialAction::ACTION_REQUEST] = true; $actions[DifferentialAction::ACTION_RETHINK] = true; $actions[DifferentialAction::ACTION_COMMIT] = true; break; case ArcanistDifferentialRevisionStatus::COMMITTED: break; case ArcanistDifferentialRevisionStatus::ABANDONED: $actions[DifferentialAction::ACTION_RECLAIM] = true; break; } } else { switch ($revision->getStatus()) { case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: $admin_actions[DifferentialAction::ACTION_ABANDON] = $viewer_is_admin; $actions[DifferentialAction::ACTION_ACCEPT] = true; $actions[DifferentialAction::ACTION_REJECT] = true; $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer; break; case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: $admin_actions[DifferentialAction::ACTION_ABANDON] = $viewer_is_admin; $actions[DifferentialAction::ACTION_ACCEPT] = true; $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer; break; case ArcanistDifferentialRevisionStatus::ACCEPTED: $admin_actions[DifferentialAction::ACTION_ABANDON] = $viewer_is_admin; $actions[DifferentialAction::ACTION_REJECT] = true; $actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer && !$viewer_did_accept; break; case ArcanistDifferentialRevisionStatus::COMMITTED: case ArcanistDifferentialRevisionStatus::ABANDONED: break; } } $actions[DifferentialAction::ACTION_ADDREVIEWERS] = true; $actions[DifferentialAction::ACTION_ADDCCS] = true; $actions = array_keys(array_filter($actions)); $admin_actions = array_keys(array_filter($admin_actions)); $actions_dict = array(); foreach ($actions as $action) { $actions_dict[$action] = DifferentialAction::getActionVerb($action); } foreach ($admin_actions as $action) { $actions_dict[$action] = '(Admin) ' . DifferentialAction::getActionVerb($action); } return $actions_dict; } private function loadInlineComments(array $comments, array &$changesets) { $inline_comments = array(); $comment_ids = array_filter(mpull($comments, 'getID')); if (!$comment_ids) { return $inline_comments; } $inline_comments = id(new DifferentialInlineComment()) ->loadAllWhere( 'commentID in (%Ld)', $comment_ids); $load_changesets = array(); foreach ($inline_comments as $inline) { $changeset_id = $inline->getChangesetID(); if (isset($changesets[$changeset_id])) { continue; } $load_changesets[$changeset_id] = true; } $more_changesets = array(); if ($load_changesets) { $changeset_ids = array_keys($load_changesets); $more_changesets += id(new DifferentialChangeset()) ->loadAllWhere( 'id IN (%Ld)', $changeset_ids); } if ($more_changesets) { $changesets += $more_changesets; $changesets = msort($changesets, 'getSortKey'); } return $inline_comments; } private function loadChangesetsAndVsMap(array $diffs, $diff_vs, $target) { $load_ids = array(); if ($diff_vs) { $load_ids[] = $diff_vs; } $load_ids[] = $target->getID(); $raw_changesets = id(new DifferentialChangeset()) ->loadAllWhere( 'diffID IN (%Ld)', $load_ids); $changeset_groups = mgroup($raw_changesets, 'getDiffID'); $changesets = idx($changeset_groups, $target->getID(), array()); $changesets = mpull($changesets, null, 'getID'); $refs = array(); foreach ($changesets as $changeset) { $refs[$changeset->getID()] = $changeset->getID(); } $vs_map = array(); if ($diff_vs) { $vs_changesets = idx($changeset_groups, $diff_vs, array()); $vs_changesets = mpull($vs_changesets, null, 'getFilename'); foreach ($changesets as $key => $changeset) { $file = $changeset->getFilename(); if (isset($vs_changesets[$file])) { $vs_map[$changeset->getID()] = $vs_changesets[$file]->getID(); $refs[$changeset->getID()] = $changeset->getID().'/'.$vs_changesets[$file]->getID(); unset($vs_changesets[$file]); } else { $refs[$changeset->getID()] = $changeset->getID(); } } foreach ($vs_changesets as $changeset) { $changesets[$changeset->getID()] = $changeset; $vs_map[$changeset->getID()] = -1; $refs[$changeset->getID()] = $changeset->getID().'/-1'; } } $changesets = msort($changesets, 'getSortKey'); return array($changesets, $vs_map, $refs); } private function loadAuxiliaryFieldsAndProperties( DifferentialRevision $revision, DifferentialDiff $diff, array $special_properties) { $aux_fields = DifferentialFieldSelector::newSelector() ->getFieldSpecifications(); foreach ($aux_fields as $key => $aux_field) { if (!$aux_field->shouldAppearOnRevisionView()) { unset($aux_fields[$key]); } } $aux_fields = DifferentialAuxiliaryField::loadFromStorage( $revision, $aux_fields); $aux_props = array(); foreach ($aux_fields as $key => $aux_field) { $aux_field->setDiff($diff); $aux_props[$key] = $aux_field->getRequiredDiffProperties(); } $required_properties = array_mergev($aux_props); $required_properties = array_merge( $required_properties, $special_properties); $property_map = array(); if ($required_properties) { $properties = id(new DifferentialDiffProperty())->loadAllWhere( 'diffID = %d AND name IN (%Ls)', $diff->getID(), $required_properties); $property_map = mpull($properties, 'getData', 'getName'); } foreach ($aux_fields as $key => $aux_field) { // Give each field only the properties it specifically required, and // set 'null' for each requested key which we didn't actually load a // value for (otherwise, getDiffProperty() will throw). if ($aux_props[$key]) { $props = array_select_keys($property_map, $aux_props[$key]) + array_fill_keys($aux_props[$key], null); } else { $props = array(); } $aux_field->setDiffProperties($props); } return array( $aux_fields, array_select_keys( $property_map, $special_properties)); } private function buildSymbolIndexes( DifferentialDiff $target, PhabricatorRepositoryArcanistProject $arc_project, array $visible_changesets) { $engine = PhabricatorSyntaxHighlighter::newEngine(); $langs = $arc_project->getSymbolIndexLanguages(); if (!$langs) { return array(); } $symbol_indexes = array(); $project_phids = array_merge( array($arc_project->getPHID()), nonempty($arc_project->getSymbolIndexProjects(), array())); $indexed_langs = array_fill_keys($langs, true); foreach ($visible_changesets as $key => $changeset) { $lang = $engine->getLanguageFromFilename($changeset->getFilename()); if (isset($indexed_langs[$lang])) { $symbol_indexes[$key] = array( 'lang' => $lang, 'projects' => $project_phids, ); } } return $symbol_indexes; } } diff --git a/src/applications/differential/controller/subscribe/DifferentialSubscribeController.php b/src/applications/differential/controller/subscribe/DifferentialSubscribeController.php index cd39209b78..bbd22df5ac 100644 --- a/src/applications/differential/controller/subscribe/DifferentialSubscribeController.php +++ b/src/applications/differential/controller/subscribe/DifferentialSubscribeController.php @@ -1,92 +1,92 @@ id = $data['id']; $this->action = $data['action']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $revision = id(new DifferentialRevision())->load($this->id); if (!$revision) { return new Aphront404Response(); } if (!$request->isFormPost()) { $dialog = new AphrontDialogView(); switch ($this->action) { case 'add': $button = 'Subscribe'; $title = 'Subscribe to Revision'; $prompt = 'Really subscribe to this revision?'; break; case 'rem': $button = 'Unsubscribe'; $title = 'Unsubscribe from Revision'; // TODO: Once herald is in, add a notice about not getting any more // herald notifications. $prompt = 'Really unsubscribe from this revision?'; break; default: return new Aphront400Response(); } $dialog ->setUser($user) ->setTitle($title) ->appendChild('

    '.$prompt.'

    ') ->setSubmitURI($request->getRequestURI()) ->addSubmitButton($button) ->addCancelButton('/D'.$revision->getID()); return id(new AphrontDialogResponse())->setDialog($dialog); } $revision->loadRelationships(); $phid = $user->getPHID(); switch ($this->action) { case 'add': DifferentialRevisionEditor::addCCAndUpdateRevision( $revision, $phid, $phid); break; case 'rem': DifferentialRevisionEditor::removeCCAndUpdateRevision( $revision, $phid, $phid); break; default: return new Aphront400Response(); } return id(new AphrontRedirectResponse())->setURI('/D'.$revision->getID()); } } diff --git a/src/applications/diffusion/controller/browse/DiffusionBrowseController.php b/src/applications/diffusion/controller/browse/DiffusionBrowseController.php index a9001f9ae1..2b067458f2 100644 --- a/src/applications/diffusion/controller/browse/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/browse/DiffusionBrowseController.php @@ -1,88 +1,88 @@ diffusionRequest; $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); $results = $browse_query->loadPaths(); $content = array(); $content[] = $this->buildCrumbs( array( 'branch' => true, 'path' => true, 'view' => 'browse', )); if (!$results) { if ($browse_query->getReasonForEmptyResultSet() == DiffusionBrowseQuery::REASON_IS_FILE) { $controller = new DiffusionBrowseFileController($this->getRequest()); $controller->setDiffusionRequest($drequest); return $this->delegateToController($controller); } $empty_result = new DiffusionEmptyResultView(); $empty_result->setDiffusionRequest($drequest); $empty_result->setBrowseQuery($browse_query); $content[] = $empty_result; } else { $phids = array(); foreach ($results as $result) { $data = $result->getLastCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { $phids[$data->getCommitDetail('authorPHID')] = true; } } } $phids = array_keys($phids); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $browse_table = new DiffusionBrowseTableView(); $browse_table->setDiffusionRequest($drequest); $browse_table->setHandles($handles); $browse_table->setPaths($results); $browse_panel = new AphrontPanelView(); $browse_panel->appendChild($browse_table); $content[] = $browse_panel; } $content[] = $this->buildOpenRevisions(); $nav = $this->buildSideNav('browse', false); $nav->appendChild($content); return $this->buildStandardPageResponse( $nav, array( 'title' => basename($drequest->getPath()), )); } } diff --git a/src/applications/diffusion/controller/change/DiffusionChangeController.php b/src/applications/diffusion/controller/change/DiffusionChangeController.php index 79b01e8183..862492783f 100644 --- a/src/applications/diffusion/controller/change/DiffusionChangeController.php +++ b/src/applications/diffusion/controller/change/DiffusionChangeController.php @@ -1,74 +1,74 @@ diffusionRequest; $content = array(); $diff_query = DiffusionDiffQuery::newFromDiffusionRequest($drequest); $changeset = $diff_query->loadChangeset(); if (!$changeset) { // TODO: Refine this. return new Aphront404Response(); } $changeset_view = new DifferentialChangesetListView(); $changeset_view->setChangesets( array( 0 => $changeset, )); $changeset_view->setRenderingReferences( array( 0 => $diff_query->getRenderingReference(), )); $changeset_view->setRenderURI( '/diffusion/'.$drequest->getRepository()->getCallsign().'/diff/'); $changeset_view->setWhitespace( DifferentialChangesetParser::WHITESPACE_SHOW_ALL); $changeset_view->setUser($this->getRequest()->getUser()); $content[] = $this->buildCrumbs( array( 'branch' => true, 'path' => true, 'view' => 'change', )); // TODO: This is pretty awkward, unify the CSS between Diffusion and // Differential better. require_celerity_resource('differential-core-view-css'); $content[] = '
    '. $changeset_view->render(). '
    '; $nav = $this->buildSideNav('change', true); $nav->appendChild($content); return $this->buildStandardPageResponse( $nav, array( 'title' => 'Change', )); } } diff --git a/src/applications/diffusion/controller/commit/DiffusionCommitController.php b/src/applications/diffusion/controller/commit/DiffusionCommitController.php index 858a52f51a..4b524e1674 100644 --- a/src/applications/diffusion/controller/commit/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/commit/DiffusionCommitController.php @@ -1,415 +1,415 @@ getDiffusionRequest(); $request = $this->getRequest(); $user = $request->getUser(); $callsign = $drequest->getRepository()->getCallsign(); $content = array(); $content[] = $this->buildCrumbs(array( 'commit' => true, )); $detail_panel = new AphrontPanelView(); $repository = $drequest->getRepository(); $commit = $drequest->loadCommit(); if (!$commit) { // TODO: Make more user-friendly. throw new Exception('This commit has not parsed yet.'); } $commit_data = $drequest->loadCommitData(); $is_foreign = $commit_data->getCommitDetail('foreign-svn-stub'); if ($is_foreign) { $subpath = $commit_data->getCommitDetail('svn-subpath'); $error_panel = new AphrontErrorView(); $error_panel->setWidth(AphrontErrorView::WIDTH_WIDE); $error_panel->setTitle('Commit Not Tracked'); $error_panel->setSeverity(AphrontErrorView::SEVERITY_WARNING); $error_panel->appendChild( "This Diffusion repository is configured to track only one ". "subdirectory of the entire Subversion repository, and this commit ". "didn't affect the tracked subdirectory ('". phutil_escape_html($subpath)."'), so no information is available."); $content[] = $error_panel; } else { $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); require_celerity_resource('diffusion-commit-view-css'); require_celerity_resource('phabricator-remarkup-css'); $property_table = $this->renderPropertyTable($commit, $commit_data); $detail_panel->appendChild( '
    '. ''. '

    Revision Detail

    '. '
    '. $property_table. '
    '. '
    '. $engine->markupText($commit_data->getCommitMessage()). '
    '. '
    '. '
    '); $content[] = $detail_panel; } $content[] = $this->buildAuditTable($commit); $content[] = $this->buildComments($commit); $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest( $drequest); $changes = $change_query->loadChanges(); $original_changes_count = count($changes); if ($request->getStr('show_all') !== 'true' && $original_changes_count > self::CHANGES_LIMIT) { $changes = array_slice($changes, 0, self::CHANGES_LIMIT); } $change_table = new DiffusionCommitChangeTableView(); $change_table->setDiffusionRequest($drequest); $change_table->setPathChanges($changes); $count = count($changes); $bad_commit = null; if ($count == 0) { $bad_commit = queryfx_one( id(new PhabricatorRepository())->establishConnection('r'), 'SELECT * FROM %T WHERE fullCommitName = %s', PhabricatorRepository::TABLE_BADCOMMIT, 'r'.$callsign.$commit->getCommitIdentifier()); } if ($bad_commit) { $error_panel = new AphrontErrorView(); $error_panel->setWidth(AphrontErrorView::WIDTH_WIDE); $error_panel->setTitle('Bad Commit'); $error_panel->appendChild( phutil_escape_html($bad_commit['description'])); $content[] = $error_panel; } else if ($is_foreign) { // Don't render anything else. } else if (!count($changes)) { $no_changes = new AphrontErrorView(); $no_changes->setWidth(AphrontErrorView::WIDTH_WIDE); $no_changes->setSeverity(AphrontErrorView::SEVERITY_WARNING); $no_changes->setTitle('Not Yet Parsed'); // TODO: This can also happen with weird SVN changes that don't do // anything (or only alter properties?), although the real no-changes case // is extremely rare and might be impossible to produce organically. We // should probably write some kind of "Nothing Happened!" change into the // DB once we parse these changes so we can distinguish between // "not parsed yet" and "no changes". $no_changes->appendChild( "This commit hasn't been fully parsed yet (or doesn't affect any ". "paths)."); $content[] = $no_changes; } else { $change_panel = new AphrontPanelView(); $change_panel->setHeader("Changes (".number_format($count).")"); if ($count !== $original_changes_count) { $show_all_button = phutil_render_tag( 'a', array( 'class' => 'button green', 'href' => '?show_all=true', ), phutil_escape_html('Show All Changes')); $warning_view = id(new AphrontErrorView()) ->setSeverity(AphrontErrorView::SEVERITY_WARNING) ->setTitle(sprintf( "Showing only the first %d changes out of %s!", self::CHANGES_LIMIT, number_format($original_changes_count))); $change_panel->appendChild($warning_view); $change_panel->addButton($show_all_button); } $change_panel->appendChild($change_table); $content[] = $change_panel; $changesets = DiffusionPathChange::convertToDifferentialChangesets( $changes); $vcs = $repository->getVersionControlSystem(); switch ($vcs) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $vcs_supports_directory_changes = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $vcs_supports_directory_changes = false; break; default: throw new Exception("Unknown VCS."); } $references = array(); foreach ($changesets as $key => $changeset) { $file_type = $changeset->getFileType(); if ($file_type == DifferentialChangeType::FILE_DIRECTORY) { if (!$vcs_supports_directory_changes) { unset($changesets[$key]); continue; } } $branch = $drequest->getBranchURIComponent( $drequest->getBranch()); $filename = $changeset->getFilename(); $reference = "{$branch}{$filename};".$drequest->getCommit(); $references[$key] = $reference; } $change_list = new DifferentialChangesetListView(); $change_list->setChangesets($changesets); $change_list->setRenderingReferences($references); $change_list->setRenderURI('/diffusion/'.$callsign.'/diff/'); $change_list->setUser($user); // TODO: This is pretty awkward, unify the CSS between Diffusion and // Differential better. require_celerity_resource('differential-core-view-css'); $change_list = '
    '. $change_list->render(). '
    '; $content[] = $change_list; } $content[] = $this->buildAddCommentView($commit); return $this->buildStandardPageResponse( $content, array( 'title' => 'r'.$callsign.$commit->getCommitIdentifier(), )); } private function renderPropertyTable( PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data) { $phids = array(); if ($data->getCommitDetail('authorPHID')) { $phids[] = $data->getCommitDetail('authorPHID'); } if ($data->getCommitDetail('reviewerPHID')) { $phids[] = $data->getCommitDetail('reviewerPHID'); } if ($data->getCommitDetail('differential.revisionPHID')) { $phids[] = $data->getCommitDetail('differential.revisionPHID'); } $handles = array(); if ($phids) { $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); } $props = array(); $author_phid = $data->getCommitDetail('authorPHID'); if ($data->getCommitDetail('authorPHID')) { $props['Author'] = $handles[$author_phid]->renderLink(); } else { $props['Author'] = phutil_escape_html($data->getAuthorName()); } $reviewer_phid = $data->getCommitDetail('reviewerPHID'); $reviewer_name = $data->getCommitDetail('reviewerName'); if ($reviewer_phid) { $props['Reviewer'] = $handles[$reviewer_phid]->renderLink(); } else if ($reviewer_name) { $props['Reviewer'] = phutil_escape_html($reviewer_name); } $revision_phid = $data->getCommitDetail('differential.revisionPHID'); if ($revision_phid) { $props['Differential Revision'] = $handles[$revision_phid]->renderLink(); } if ($commit->getAuditStatus()) { $props['Audit'] = PhabricatorAuditCommitStatusConstants::getStatusName( $commit->getAuditStatus()); } $request = $this->getDiffusionRequest(); $contains = DiffusionContainsQuery::newFromDiffusionRequest($request); $branches = $contains->loadContainingBranches(); if ($branches) { // TODO: Separate these into 'tracked' and other; link tracked branches. $branches = implode(', ', array_keys($branches)); $branches = phutil_escape_html($branches); $props['Branches'] = $branches; } $rows = array(); foreach ($props as $key => $value) { $rows[] = ''. ''.$key.':'. ''.$value.''. ''; } return ''. implode("\n", $rows). '
    '; } private function buildAuditTable($commit) { $user = $this->getRequest()->getUser(); $query = new PhabricatorAuditQuery(); $query->withCommitPHIDs(array($commit->getPHID())); $audits = $query->execute(); $view = new PhabricatorAuditListView(); $view->setAudits($audits); $phids = $view->getRequiredHandlePHIDs(); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $view->setHandles($handles); $view->setAuthorityPHIDs( PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user)); $panel = new AphrontPanelView(); $panel->setHeader('Audits'); $panel->appendChild($view); return $panel; } private function buildComments($commit) { $user = $this->getRequest()->getUser(); $comments = id(new PhabricatorAuditComment())->loadAllWhere( 'targetPHID = %s ORDER BY dateCreated ASC', $commit->getPHID()); $view = new DiffusionCommentListView(); $view->setUser($user); $view->setComments($comments); $phids = $view->getRequiredHandlePHIDs(); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $view->setHandles($handles); return $view; } private function buildAddCommentView($commit) { $user = $this->getRequest()->getUser(); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $user->getPHID(), 'diffusion-audit-'.$commit->getID()); if ($draft) { $draft = $draft->getDraft(); } else { $draft = null; } $form = id(new AphrontFormView()) ->setUser($user) ->setAction('/audit/addcomment/') ->addHiddenInput('commit', $commit->getPHID()) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Action') ->setName('action') ->setID('audit-action') ->setOptions(PhabricatorAuditActionConstants::getActionNameMap())) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Comments') ->setName('content') ->setValue($draft) ->setID('audit-content') ->setCaption(phutil_render_tag( 'a', array( 'href' => PhabricatorEnv::getDoclink( 'article/Remarkup_Reference.html'), 'tabindex' => '-1', 'target' => '_blank', ), 'Formatting Reference'))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue($is_serious ? 'Submit' : 'Cook the Books')); $panel = new AphrontPanelView(); $panel->setHeader($is_serious ? 'Audit Commit' : 'Creative Accounting'); $panel->appendChild($form); require_celerity_resource('phabricator-transaction-view-css'); Javelin::initBehavior('audit-preview', array( 'uri' => '/audit/preview/'.$commit->getID().'/', 'preview' => 'audit-preview', 'content' => 'audit-content', 'action' => 'audit-action', )); $preview_panel = '
    Loading preview...
    '; $view = new AphrontNullView(); $view->appendChild($panel); $view->appendChild($preview_panel); return $view; } } diff --git a/src/applications/diffusion/controller/diff/DiffusionDiffController.php b/src/applications/diffusion/controller/diff/DiffusionDiffController.php index ba5acb80ba..33eac0dd36 100644 --- a/src/applications/diffusion/controller/diff/DiffusionDiffController.php +++ b/src/applications/diffusion/controller/diff/DiffusionDiffController.php @@ -1,58 +1,58 @@ getRequest(); if ($request->getStr('ref')) { $parts = explode(';', $request->getStr('ref')); $data['path'] = idx($parts, 0); $data['commit'] = idx($parts, 1); } $this->diffusionRequest = DiffusionRequest::newFromAphrontRequestDictionary( $data); } public function processRequest() { $drequest = $this->getDiffusionRequest(); $request = $this->getRequest(); $diff_query = DiffusionDiffQuery::newFromDiffusionRequest($drequest); $changeset = $diff_query->loadChangeset(); if (!$changeset) { return new Aphront404Response(); } $parser = new DifferentialChangesetParser(); $parser->setChangeset($changeset); $parser->setRenderingReference($diff_query->getRenderingReference()); $parser->setWhitespaceMode( DifferentialChangesetParser::WHITESPACE_SHOW_ALL); $spec = $request->getStr('range'); list($range_s, $range_e, $mask) = DifferentialChangesetParser::parseRangeSpecification($spec); $output = $parser->render($range_s, $range_e, $mask); return id(new AphrontAjaxResponse()) ->setContent($output); } } diff --git a/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php index 18cd2b47b4..2b16b36683 100644 --- a/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php +++ b/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php @@ -1,481 +1,481 @@ tags protected $imageTypes = array( 'png' => 'image/png', 'gif' => 'image/gif', 'ico' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg'=> 'image/jpeg' ); // Document types that should trigger link to ?view=raw protected $documentTypes = array( 'pdf'=> 'application/pdf', 'ps' => 'application/postscript', ); public function processRequest() { // Build the view selection form. $select_map = array( 'highlighted' => 'View as Highlighted Text', 'blame' => 'View as Highlighted Text with Blame', 'plain' => 'View as Plain Text', 'plainblame' => 'View as Plain Text with Blame', 'raw' => 'View as raw document', ); $request = $this->getRequest(); $drequest = $this->getDiffusionRequest(); $path = $drequest->getPath(); $selected = $request->getStr('view'); $needs_blame = ($selected == 'blame' || $selected == 'plainblame'); $file_query = DiffusionFileContentQuery::newFromDiffusionRequest( $this->diffusionRequest); $file_query->setNeedsBlame($needs_blame); $file_query->loadFileContent(); $data = $file_query->getRawData(); if ($selected === 'raw') { $response = new AphrontFileResponse(); $response->setContent($data); $mime_type = $this->getDocumentType($path); if ($mime_type) { $response->setMimeType($mime_type); } else { $as_filename = idx(pathinfo($path), 'basename'); $response->setDownload($as_filename); } return $response; } $select = ''; require_celerity_resource('diffusion-source-css'); $view_select_panel = new AphrontPanelView(); $view_select_form = phutil_render_tag( 'form', array( 'action' => $request->getRequestURI(), 'method' => 'get', 'class' => 'diffusion-browse-type-form', ), $select. ''); $view_select_panel->appendChild($view_select_form); $user = $request->getUser(); if ($user) { $line = 1; $repository = $this->getDiffusionRequest()->getRepository(); $editor_link = $user->loadEditorLink($path, $line, $repository); if ($editor_link) { $view_select_panel->addButton( phutil_render_tag( 'a', array( 'href' => $editor_link, 'class' => 'button', ), 'Edit' )); } } $view_select_panel->appendChild('
    '); // Build the content of the file. $corpus = $this->buildCorpus( $selected, $file_query, $needs_blame, $drequest, $path, $data ); // Render the page. $content = array(); $content[] = $this->buildCrumbs( array( 'branch' => true, 'path' => true, 'view' => 'browse', )); $content[] = $view_select_panel; $content[] = $corpus; $content[] = $this->buildOpenRevisions(); $nav = $this->buildSideNav('browse', true); $nav->appendChild($content); $basename = basename($this->getDiffusionRequest()->getPath()); return $this->buildStandardPageResponse( $nav, array( 'title' => $basename, )); } /** * Returns a content-type corrsponding to an image file extension * * @param string $path File path * @return mixed A content-type string or NULL if path doesn't end with a * recognized image extension */ public function getImageType($path) { $ext = pathinfo($path); $ext = idx($ext, 'extension'); return idx($this->imageTypes, $ext); } /** * Returns a content-type corresponding to an document file extension * * @param string $path File path * @return mixed A content-type string or NULL if path doesn't end with a * recognized document extension */ public function getDocumentType($path) { $ext = pathinfo($path); $ext = idx($ext, 'extension'); return idx($this->documentTypes, $ext); } private function buildCorpus($selected, $file_query, $needs_blame, $drequest, $path, $data) { $image_type = $this->getImageType($path); if ($image_type && !$selected) { $corpus = phutil_render_tag( 'img', array( 'style' => 'padding-bottom: 10px', 'src' => 'data:'.$image_type.';base64,'.base64_encode($data), ) ); return $corpus; } $document_type = $this->getDocumentType($path); if (($document_type && !$selected) || !phutil_is_utf8($data)) { $data = $file_query->getRawData(); $document_type_description = $document_type ? $document_type : 'binary'; $corpus = phutil_render_tag( 'p', array( 'style' => 'text-align: center;' ), phutil_render_tag( 'a', array( 'href' => '?view=raw', 'class' => 'button' ), "View $document_type_description" ) ); return $corpus; } // TODO: blame of blame. switch ($selected) { case 'plain': $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace"; $corpus = phutil_render_tag( 'textarea', array( 'style' => $style, ), phutil_escape_html($file_query->getRawData())); break; case 'plainblame': $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace"; list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData(); $rows = array(); foreach ($text_list as $k => $line) { $rev = $rev_list[$k]; if (isset($blame_dict[$rev]['handle'])) { $author = $blame_dict[$rev]['handle']->getName(); } else { $author = $blame_dict[$rev]['author']; } $rows[] = sprintf("%-10s %-20s %s", substr($rev, 0, 7), $author, $line); } $corpus = phutil_render_tag( 'textarea', array( 'style' => $style, ), phutil_escape_html(implode("\n", $rows))); break; case 'highlighted': case 'blame': default: require_celerity_resource('syntax-highlighting-css'); list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData(); $text_list = implode("\n", $text_list); $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename( $path, $text_list); $text_list = explode("\n", $text_list); $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, $needs_blame, $drequest, $file_query, $selected); $corpus_table = phutil_render_tag( 'table', array( 'class' => "diffusion-source remarkup-code PhabricatorMonospaced", ), implode("\n", $rows)); $corpus = phutil_render_tag( 'div', array( 'style' => 'padding: 0pt 2em;', ), $corpus_table); break; } return $corpus; } private function buildDisplayRows($text_list, $rev_list, $blame_dict, $needs_blame, DiffusionRequest $drequest, $file_query, $selected) { $last_rev = null; $color = '#eeeeee'; $rows = array(); $n = 1; $view = $this->getRequest()->getStr('view'); if ($blame_dict) { $epoch_list = ipull($blame_dict, 'epoch'); $epoch_max = max($epoch_list); $epoch_min = min($epoch_list); $epoch_range = $epoch_max - $epoch_min + 1; } $targ = ''; $min_line = 0; $line = $drequest->getLine(); if (strpos($line, '-') !== false) { list($min, $max) = explode('-', $line, 2); $min_line = min($min, $max); $max_line = max($min, $max); } else if (strlen($line)) { $min_line = $line; $max_line = $line; } foreach ($text_list as $k => $line) { if ($needs_blame) { // If the line's rev is same as the line above, show empty content // with same color; otherwise generate blame info. The newer a change // is, the darker the color. $rev = $rev_list[$k]; if ($last_rev == $rev) { $blame_info = ($file_query->getSupportsBlameOnBlame() ? '' : ''). ''. ''; } else { if ($blame_dict) { $color_number = (int)(0xEE - 0xEE * ($blame_dict[$rev]['epoch'] - $epoch_min) / $epoch_range); $color = sprintf('#%02xee%02x', $color_number, $color_number); } $revision_link = self::renderRevision( $drequest, substr($rev, 0, 7)); if (!$file_query->getSupportsBlameOnBlame()) { $prev_link = ''; } else { $prev_rev = $file_query->getPrevRev($rev); $path = $drequest->getPath(); $prev_link = self::renderBrowse( $drequest, $path, "\xC2\xAB", $prev_rev, $n, $selected, 'Blame previous revision'); $prev_link = phutil_render_tag( 'th', array( 'class' => 'diffusion-wide-link', 'style' => 'background: '.$color.'; width: 2em;', ), $prev_link); } if (isset($blame_dict[$rev]['handle'])) { $author_link = $blame_dict[$rev]['handle']->renderLink(); } else { $author_link = phutil_escape_html($blame_dict[$rev]['author']); } $blame_info = $prev_link . ''.$revision_link.''. ''.$author_link.''; $last_rev = $rev; } } else { $blame_info = null; } // Highlight the line of interest if needed. if ($min_line > 0 && ($n >= $min_line && $n <= $max_line)) { $tr = ''; if ($targ == '') { $targ = ''; Javelin::initBehavior('diffusion-jump-to', array('target' => 'scroll_target')); } } else { $tr = ''; $targ = null; } // Create the row display. $uri_path = $drequest->getUriPath(); $uri_rev = $drequest->getStableCommitName(); $uri_view = $view ? '?view='.$view : null; $l = phutil_render_tag( 'a', array( 'href' => $uri_path.';'.$uri_rev.'$'.$n.$uri_view, ), $n); $rows[] = $tr.$blame_info. ''.$l.''. ''.$targ.$line.''; ++$n; } return $rows; } private static function renderRevision(DiffusionRequest $drequest, $revision) { $callsign = $drequest->getCallsign(); $name = 'r'.$callsign.$revision; return phutil_render_tag( 'a', array( 'href' => '/'.$name, ), $name ); } private static function renderBrowse( DiffusionRequest $drequest, $path, $name = null, $rev = null, $line = null, $view = null, $title = null) { $callsign = $drequest->getCallsign(); if ($name === null) { $name = $path; } $at = null; if ($rev) { $at = ';'.$rev; } if ($view) { $view = '?view='.$view; } if ($line) { $line = '$'.$line; } return phutil_render_tag( 'a', array( 'href' => "/diffusion/{$callsign}/browse/{$path}{$at}{$line}{$view}", 'title' => $title, ), $name ); } } diff --git a/src/applications/diffusion/controller/history/DiffusionHistoryController.php b/src/applications/diffusion/controller/history/DiffusionHistoryController.php index 499955d03d..5cd766e4c0 100644 --- a/src/applications/diffusion/controller/history/DiffusionHistoryController.php +++ b/src/applications/diffusion/controller/history/DiffusionHistoryController.php @@ -1,114 +1,114 @@ diffusionRequest; $request = $this->getRequest(); $page_size = $request->getInt('pagesize', 100); $offset = $request->getInt('page', 0); $history_query = DiffusionHistoryQuery::newFromDiffusionRequest( $drequest); $history_query->setOffset($offset); $history_query->setLimit($page_size + 1); if (!$request->getBool('copies')) { $history_query->needDirectChanges(true); $history_query->needChildChanges(true); } $history = $history_query->loadHistory(); $phids = array(); foreach ($history as $item) { $data = $item->getCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { $phids[$data->getCommitDetail('authorPHID')] = true; } } } $phids = array_keys($phids); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $pager = new AphrontPagerView(); $pager->setPageSize($page_size); $pager->setOffset($offset); if (count($history) == $page_size + 1) { array_pop($history); $pager->setHasMorePages(true); } else { $pager->setHasMorePages(false); } $pager->setURI($request->getRequestURI(), 'page'); $content = array(); $content[] = $this->buildCrumbs( array( 'branch' => true, 'path' => true, 'view' => 'history', )); if ($request->getBool('copies')) { $button_title = 'Hide Copies/Branches'; $copies_new = null; } else { $button_title = 'Show Copies/Branches'; $copies_new = true; } $button = phutil_render_tag( 'a', array( 'class' => 'button small grey', 'href' => $request->getRequestURI()->alter('copies', $copies_new), ), phutil_escape_html($button_title)); $history_table = new DiffusionHistoryTableView(); $history_table->setDiffusionRequest($drequest); $history_table->setHandles($handles); $history_table->setHistory($history); $history_panel = new AphrontPanelView(); $history_panel->setHeader('History'); $history_panel->addButton($button); $history_panel->appendChild($history_table); $history_panel->appendChild($pager); $content[] = $history_panel; // TODO: Sometimes we do have a change view, we need to look at the most // recent history entry to figure it out. $nav = $this->buildSideNav('history', false); $nav->appendChild($content); return $this->buildStandardPageResponse( $nav, array( 'title' => 'history', )); } } diff --git a/src/applications/diffusion/controller/home/DiffusionHomeController.php b/src/applications/diffusion/controller/home/DiffusionHomeController.php index a5fdc4e7b7..52f3a323e2 100644 --- a/src/applications/diffusion/controller/home/DiffusionHomeController.php +++ b/src/applications/diffusion/controller/home/DiffusionHomeController.php @@ -1,163 +1,163 @@ getRequest(); $user = $request->getUser(); $shortcuts = id(new PhabricatorRepositoryShortcut())->loadAll(); if ($shortcuts) { $shortcuts = msort($shortcuts, 'getSequence'); $rows = array(); foreach ($shortcuts as $shortcut) { $rows[] = array( phutil_render_tag( 'a', array( 'href' => $shortcut->getHref(), ), phutil_escape_html($shortcut->getName())), phutil_escape_html($shortcut->getDescription()), ); } $shortcut_table = new AphrontTableView($rows); $shortcut_table->setHeaders( array( 'Link', '', )); $shortcut_table->setColumnClasses( array( 'pri', 'wide', )); $shortcut_panel = new AphrontPanelView(); $shortcut_panel->setHeader('Shortcuts'); $shortcut_panel->appendChild($shortcut_table); } else { $shortcut_panel = null; } $repository = new PhabricatorRepository(); $repositories = $repository->loadAll(); foreach ($repositories as $key => $repository) { if (!$repository->isTracked()) { unset($repositories[$key]); } } $repository_ids = mpull($repositories, 'getID'); $summaries = array(); $commits = array(); if ($repository_ids) { $summaries = queryfx_all( $repository->establishConnection('r'), 'SELECT * FROM %T WHERE repositoryID IN (%Ld)', PhabricatorRepository::TABLE_SUMMARY, $repository_ids); $summaries = ipull($summaries, null, 'repositoryID'); $commit_ids = array_filter(ipull($summaries, 'lastCommitID')); if ($commit_ids) { $commit = new PhabricatorRepositoryCommit(); $commits = $commit->loadAllWhere('id IN (%Ld)', $commit_ids); $commits = mpull($commits, null, 'getRepositoryID'); } } $rows = array(); foreach ($repositories as $repository) { $id = $repository->getID(); $commit = idx($commits, $id); $size = idx(idx($summaries, $id, array()), 'size', 0); $date = '-'; $time = '-'; if ($commit) { $date = phabricator_date($commit->getEpoch(), $user); $time = phabricator_time($commit->getEpoch(), $user); } $rows[] = array( phutil_render_tag( 'a', array( 'href' => '/diffusion/'.$repository->getCallsign().'/', ), phutil_escape_html($repository->getName())), phutil_escape_html($repository->getDetail('description')), PhabricatorRepositoryType::getNameForRepositoryType( $repository->getVersionControlSystem()), $size ? number_format($size) : '-', $commit ? DiffusionView::linkCommit( $repository, $commit->getCommitIdentifier()) : '-', $date, $time, ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Repository', 'Description', 'VCS', 'Size', 'Last', 'Date', 'Time', )); $table->setColumnClasses( array( 'pri', 'wide', '', 'n', 'n', '', 'right', )); $panel = new AphrontPanelView(); $panel->setHeader('Browse Repositories'); $panel->appendChild($table); $crumbs = $this->buildCrumbs(); return $this->buildStandardPageResponse( array( $crumbs, $shortcut_panel, $panel, ), array( 'title' => 'Diffusion', )); } } diff --git a/src/applications/diffusion/controller/lastmodified/DiffusionLastModifiedController.php b/src/applications/diffusion/controller/lastmodified/DiffusionLastModifiedController.php index 1e64fd66eb..26380b6848 100644 --- a/src/applications/diffusion/controller/lastmodified/DiffusionLastModifiedController.php +++ b/src/applications/diffusion/controller/lastmodified/DiffusionLastModifiedController.php @@ -1,45 +1,45 @@ getDiffusionRequest(); $request = $this->getRequest(); $modified_query = DiffusionLastModifiedQuery::newFromDiffusionRequest( $drequest); list($commit, $commit_data) = $modified_query->loadLastModification(); $phids = array(); if ($commit_data && $commit_data->getCommitDetail('authorPHID')) { $phids = array($commit_data->getCommitDetail('authorPHID')); } $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $output = DiffusionBrowseTableView::renderLastModifiedColumns( $drequest->getRepository(), $handles, $commit, $commit_data); return id(new AphrontAjaxResponse()) ->setContent($output); } } diff --git a/src/applications/diffusion/controller/pathcomplete/DiffusionPathCompleteController.php b/src/applications/diffusion/controller/pathcomplete/DiffusionPathCompleteController.php index 4536a643cd..2efcbdd60f 100644 --- a/src/applications/diffusion/controller/pathcomplete/DiffusionPathCompleteController.php +++ b/src/applications/diffusion/controller/pathcomplete/DiffusionPathCompleteController.php @@ -1,67 +1,67 @@ getRequest(); $repository_phid = $request->getStr('repositoryPHID'); $repository = id(new PhabricatorRepository())->loadOneWhere( 'phid = %s', $repository_phid); if (!$repository) { return new Aphront400Response(); } $query_path = $request->getStr('q'); $query_path = ltrim($query_path, '/'); if (preg_match('@/$@', $query_path)) { $query_dir = $query_path; } else { $query_dir = dirname($query_path); if ($query_dir == '.') { $query_dir = ''; } } $drequest = DiffusionRequest::newFromAphrontRequestDictionary( array( 'callsign' => $repository->getCallsign(), 'path' => ':/'.$query_dir, )); $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); $paths = $browse_query->loadPaths(); $output = array(); foreach ($paths as $path) { $full_path = $query_dir.$path->getPath(); if ($path->getFileType() == DifferentialChangeType::FILE_DIRECTORY) { $full_path .= '/'; } $output[] = array('/'.$full_path, null, substr(md5($full_path), 0, 7)); } return id(new AphrontAjaxResponse())->setContent($output); } } diff --git a/src/applications/diffusion/controller/pathvalidate/DiffusionPathValidateController.php b/src/applications/diffusion/controller/pathvalidate/DiffusionPathValidateController.php index 6199cdca2b..d539c4db39 100644 --- a/src/applications/diffusion/controller/pathvalidate/DiffusionPathValidateController.php +++ b/src/applications/diffusion/controller/pathvalidate/DiffusionPathValidateController.php @@ -1,79 +1,79 @@ getRequest(); $repository_phid = $request->getStr('repositoryPHID'); $repository = id(new PhabricatorRepository())->loadOneWhere( 'phid = %s', $repository_phid); if (!$repository) { return new Aphront400Response(); } $path = $request->getStr('path'); $path = ltrim($path, '/'); $drequest = DiffusionRequest::newFromAphrontRequestDictionary( array( 'callsign' => $repository->getCallsign(), 'path' => ':/'.$path, )); $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); $browse_query->needValidityOnly(true); $valid = $browse_query->loadPaths(); if (!$valid) { switch ($browse_query->getReasonForEmptyResultSet()) { case DiffusionBrowseQuery::REASON_IS_FILE: $valid = true; break; case DiffusionBrowseQuery::REASON_IS_EMPTY: $valid = true; break; } } $output = array( 'valid' => (bool)$valid, ); if (!$valid) { $branch = $drequest->getBranch(); if ($branch) { $message = 'Not found in '.$branch; } else { $message = 'Not found at HEAD'; } } else { $message = 'OK'; } $output['message'] = $message; return id(new AphrontAjaxResponse())->setContent($output); } } diff --git a/src/applications/diffusion/controller/repository/DiffusionRepositoryController.php b/src/applications/diffusion/controller/repository/DiffusionRepositoryController.php index 7c61184522..4e8271dd34 100644 --- a/src/applications/diffusion/controller/repository/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/repository/DiffusionRepositoryController.php @@ -1,152 +1,152 @@ diffusionRequest; $content = array(); $crumbs = $this->buildCrumbs(); $content[] = $crumbs; $content[] = $this->buildPropertiesTable($drequest->getRepository()); $history_query = DiffusionHistoryQuery::newFromDiffusionRequest( $drequest); $history_query->setLimit(15); $history = $history_query->loadHistory(); $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); $browse_results = $browse_query->loadPaths(); $phids = array(); foreach ($history as $item) { $data = $item->getCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { $phids[$data->getCommitDetail('authorPHID')] = true; } } } foreach ($browse_results as $item) { $data = $item->getLastCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { $phids[$data->getCommitDetail('authorPHID')] = true; } } } $phids = array_keys($phids); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $history_table = new DiffusionHistoryTableView(); $history_table->setDiffusionRequest($drequest); $history_table->setHandles($handles); $history_table->setHistory($history); $callsign = $drequest->getRepository()->getCallsign(); $all = phutil_render_tag( 'a', array( 'href' => "/diffusion/{$callsign}/history/", ), 'View Full Commit History'); $panel = new AphrontPanelView(); $panel->setHeader("Recent Commits · {$all}"); $panel->appendChild($history_table); $content[] = $panel; $browse_table = new DiffusionBrowseTableView(); $browse_table->setDiffusionRequest($drequest); $browse_table->setHandles($handles); $browse_table->setPaths($browse_results); $browse_panel = new AphrontPanelView(); $browse_panel->setHeader('Browse Repository'); $browse_panel->appendChild($browse_table); $content[] = $browse_panel; if ($drequest->getBranch() !== null) { $branch_query = DiffusionBranchQuery::newFromDiffusionRequest($drequest); $branches = $branch_query->loadBranches(); $branch_table = new DiffusionBranchTableView(); $branch_table->setDiffusionRequest($drequest); $branch_table->setBranches($branches); $branch_panel = new AphrontPanelView(); $branch_panel->setHeader('Branches'); $branch_panel->appendChild($branch_table); $content[] = $branch_panel; } return $this->buildStandardPageResponse( $content, array( 'title' => $drequest->getRepository()->getName(), )); } private function buildPropertiesTable(PhabricatorRepository $repository) { $properties = array(); $properties['Name'] = $repository->getName(); $properties['Callsign'] = $repository->getCallsign(); $properties['Description'] = $repository->getDetail('description'); switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $properties['Clone URI'] = $repository->getPublicRemoteURI(); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $properties['Repository Root'] = $repository->getPublicRemoteURI(); break; } $rows = array(); foreach ($properties as $key => $value) { $rows[] = array( phutil_escape_html($key), phutil_escape_html($value)); } $table = new AphrontTableView($rows); $table->setColumnClasses( array( 'header', 'wide', )); $panel = new AphrontPanelView(); $panel->setHeader('Repository Properties'); $panel->appendChild($table); return $panel; } } diff --git a/src/applications/diffusion/controller/symbol/DiffusionSymbolController.php b/src/applications/diffusion/controller/symbol/DiffusionSymbolController.php index d042488cdd..c11e5965ad 100644 --- a/src/applications/diffusion/controller/symbol/DiffusionSymbolController.php +++ b/src/applications/diffusion/controller/symbol/DiffusionSymbolController.php @@ -1,154 +1,154 @@ name = $data['name']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $query = new DiffusionSymbolQuery(); $query->setName($this->name); if ($request->getStr('type')) { $query->setType($request->getStr('type')); } if ($request->getStr('lang')) { $query->setLanguage($request->getStr('lang')); } if ($request->getStr('projects')) { $phids = $request->getStr('projects'); $phids = explode(',', $phids); $phids = array_filter($phids); if ($phids) { $projects = id(new PhabricatorRepositoryArcanistProject()) ->loadAllWhere( 'phid IN (%Ls)', $phids); $projects = mpull($projects, 'getID'); if ($projects) { $query->setProjectIDs($projects); } } } $query->needPaths(true); $query->needArcanistProjects(true); $query->needRepositories(true); $symbols = $query->execute(); // For PHP builtins, jump to php.net documentation. if ($request->getBool('jump') && count($symbols) == 0) { if ($request->getStr('lang') == 'php') { if ($request->getStr('type') == 'function') { if (in_array($this->name, idx(get_defined_functions(), 'internal'))) { return id(new AphrontRedirectResponse()) ->setURI('http://www.php.net/'.$this->name); } } } } $rows = array(); foreach ($symbols as $symbol) { $project = $symbol->getArcanistProject(); if ($project) { $project_name = $project->getName(); } else { $project_name = '-'; } $file = phutil_escape_html($symbol->getPath()); $line = phutil_escape_html($symbol->getLineNumber()); $repo = $symbol->getRepository(); if ($repo) { $href = $symbol->getURI(); if ($request->getBool('jump') && count($symbols) == 1) { // If this is a clickthrough from Differential, just jump them // straight to the target if we got a single hit. return id(new AphrontRedirectResponse())->setURI($href); } $location = phutil_render_tag( 'a', array( 'href' => $href, ), phutil_escape_html($file.':'.$line)); } else if ($file) { $location = phutil_escape_html($file.':'.$line); } else { $location = '?'; } $rows[] = array( phutil_escape_html($symbol->getSymbolType()), phutil_escape_html($symbol->getSymbolName()), phutil_escape_html($symbol->getSymbolLanguage()), phutil_escape_html($project_name), $location, ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Type', 'Name', 'Language', 'Project', 'File', )); $table->setColumnClasses( array( '', 'pri', '', '', '', 'n' )); $table->setNoDataString( "No matching symbol could be found in any indexed project."); $panel = new AphrontPanelView(); $panel->setHeader('Similar Symbols'); $panel->appendChild($table); return $this->buildStandardPageResponse( array( $panel, ), array( 'title' => 'Find Symbol', )); } } diff --git a/src/applications/directory/controller/categorydelete/PhabricatorDirectoryCategoryDeleteController.php b/src/applications/directory/controller/categorydelete/PhabricatorDirectoryCategoryDeleteController.php index a8035f31f0..f4e2be1cac 100644 --- a/src/applications/directory/controller/categorydelete/PhabricatorDirectoryCategoryDeleteController.php +++ b/src/applications/directory/controller/categorydelete/PhabricatorDirectoryCategoryDeleteController.php @@ -1,52 +1,52 @@ id = $data['id']; } public function processRequest() { $category = id(new PhabricatorDirectoryCategory())->load($this->id); if (!$category) { return new Aphront404Response(); } $request = $this->getRequest(); if ($request->isFormPost()) { $category->delete(); return id(new AphrontRedirectResponse()) ->setURI('/directory/edit/'); } $dialog = new AphrontDialogView(); $dialog->setUser($request->getUser()); $dialog->setTitle('Really delete this category?'); $dialog->appendChild("Are you sure you want to delete this category?"); $dialog->addSubmitButton('Delete'); $dialog->addCancelButton('/directory/edit/'); $dialog->setSubmitURI($request->getPath()); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/directory/controller/categoryedit/PhabricatorDirectoryCategoryEditController.php b/src/applications/directory/controller/categoryedit/PhabricatorDirectoryCategoryEditController.php index 5317e3fe4d..ab75056161 100644 --- a/src/applications/directory/controller/categoryedit/PhabricatorDirectoryCategoryEditController.php +++ b/src/applications/directory/controller/categoryedit/PhabricatorDirectoryCategoryEditController.php @@ -1,111 +1,111 @@ id = idx($data, 'id'); } public function processRequest() { if ($this->id) { $category = id(new PhabricatorDirectoryCategory())->load($this->id); if (!$category) { return new Aphront404Response(); } } else { $category = new PhabricatorDirectoryCategory(); } $e_name = true; $errors = array(); $request = $this->getRequest(); if ($request->isFormPost()) { $category->setName($request->getStr('name')); $category->setSequence($request->getStr('sequence')); if (!strlen($category->getName())) { $errors[] = 'Category name is required.'; $e_name = 'Required'; } if (!$errors) { $category->save(); return id(new AphrontRedirectResponse()) ->setURI('/directory/edit/'); } } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle('Form Errors') ->setErrors($errors); } $form = new AphrontFormView(); $form->setUser($request->getUser()); if ($category->getID()) { $form->setAction('/directory/category/edit/'.$category->getID().'/'); } else { $form->setAction('/directory/category/edit/'); } $categories = id(new PhabricatorDirectoryCategory())->loadAll(); $category_map = mpull($categories, 'getName', 'getID'); $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Name') ->setName('name') ->setValue($category->getName()) ->setError($e_name)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Order') ->setName('sequence') ->setValue((int)$category->getSequence())) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save') ->addCancelButton('/directory/edit/')); $panel = new AphrontPanelView(); if ($category->getID()) { $panel->setHeader('Edit Directory Category'); } else { $panel->setHeader('Create New Directory Category'); } $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); return $this->buildStandardPageResponse( array($error_view, $panel), array( 'title' => 'Edit Directory Category', )); } } diff --git a/src/applications/directory/controller/itemdelete/PhabricatorDirectoryItemDeleteController.php b/src/applications/directory/controller/itemdelete/PhabricatorDirectoryItemDeleteController.php index aac5f9da3e..75090932bb 100644 --- a/src/applications/directory/controller/itemdelete/PhabricatorDirectoryItemDeleteController.php +++ b/src/applications/directory/controller/itemdelete/PhabricatorDirectoryItemDeleteController.php @@ -1,52 +1,52 @@ id = $data['id']; } public function processRequest() { $item = id(new PhabricatorDirectoryItem())->load($this->id); if (!$item) { return new Aphront404Response(); } $request = $this->getRequest(); if ($request->isFormPost()) { $item->delete(); return id(new AphrontRedirectResponse()) ->setURI('/directory/edit/'); } $dialog = new AphrontDialogView(); $dialog->setUser($request->getUser()); $dialog->setTitle('Really delete this item?'); $dialog->appendChild("Are you sure you want to delete this item?"); $dialog->addSubmitButton('Delete'); $dialog->addCancelButton('/directory/edit/'); $dialog->setSubmitURI($request->getPath()); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/directory/controller/itemedit/PhabricatorDirectoryItemEditController.php b/src/applications/directory/controller/itemedit/PhabricatorDirectoryItemEditController.php index 164f10c744..1ebdb0cddf 100644 --- a/src/applications/directory/controller/itemedit/PhabricatorDirectoryItemEditController.php +++ b/src/applications/directory/controller/itemedit/PhabricatorDirectoryItemEditController.php @@ -1,146 +1,146 @@ id = idx($data, 'id'); } public function processRequest() { if ($this->id) { $item = id(new PhabricatorDirectoryItem())->load($this->id); if (!$item) { return new Aphront404Response(); } } else { $item = new PhabricatorDirectoryItem(); } $e_name = true; $e_href = true; $errors = array(); $request = $this->getRequest(); if ($request->isFormPost()) { $item->setName($request->getStr('name')); $item->setHref($request->getStr('href')); $item->setDescription($request->getStr('description')); $item->setCategoryID($request->getStr('categoryID')); $item->setSequence($request->getStr('sequence')); if (!strlen($item->getName())) { $errors[] = 'Item name is required.'; $e_name = 'Required'; } if (!strlen($item->getHref())) { $errors[] = 'Item link is required.'; $e_href = 'Required'; } else { $href = $item->getHref(); if (!PhabricatorEnv::isValidWebResource($href)) { $e_href = 'Invalid'; $errors[] = 'Item link must point to a valid web page.'; } } if (!$errors) { $item->save(); return id(new AphrontRedirectResponse()) ->setURI('/directory/edit/'); } } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle('Form Errors') ->setErrors($errors); } $form = new AphrontFormView(); $form->setUser($request->getUser()); if ($item->getID()) { $form->setAction('/directory/item/edit/'.$item->getID().'/'); } else { $form->setAction('/directory/item/edit/'); } $categories = id(new PhabricatorDirectoryCategory())->loadAll(); $category_map = mpull($categories, 'getName', 'getID'); $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Name') ->setName('name') ->setValue($item->getName()) ->setError($e_name)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Category') ->setName('categoryID') ->setOptions($category_map) ->setValue($item->getCategoryID())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Link') ->setName('href') ->setValue($item->getHref()) ->setError($e_href)) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Description') ->setName('description') ->setValue($item->getDescription())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Order') ->setName('sequence') ->setCaption( 'Items in a category are sorted by "order", then by name.') ->setValue((int)$item->getSequence())) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save') ->addCancelButton('/directory/edit/')); $panel = new AphrontPanelView(); if ($item->getID()) { $panel->setHeader('Edit Directory Item'); } else { $panel->setHeader('Create New Directory Item'); } $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); return $this->buildStandardPageResponse( array($error_view, $panel), array( 'title' => 'Edit Directory Item', )); } } diff --git a/src/applications/directory/controller/main/PhabricatorDirectoryMainController.php b/src/applications/directory/controller/main/PhabricatorDirectoryMainController.php index 8ad99543c4..f4fed2b268 100644 --- a/src/applications/directory/controller/main/PhabricatorDirectoryMainController.php +++ b/src/applications/directory/controller/main/PhabricatorDirectoryMainController.php @@ -1,656 +1,656 @@ filter = idx($data, 'filter'); $this->subfilter = idx($data, 'subfilter'); } public function shouldRequireAdmin() { // These controllers are admin-only by default, but this one is public, // so allow non-admin users to view it. return false; } public function processRequest() { $user = $this->getRequest()->getUser(); $nav = $this->buildNav(); $this->filter = $nav->selectFilter($this->filter, 'home'); switch ($this->filter) { case 'jump': break; case 'home': case 'feed': $project_query = new PhabricatorProjectQuery(); $project_query->setMembers(array($user->getPHID())); $projects = $project_query->execute(); break; default: throw new Exception("Unknown filter '{$this->filter}'!"); } switch ($this->filter) { case 'feed': return $this->buildFeedResponse($nav, $projects); case 'jump': return $this->buildJumpResponse($nav); default: return $this->buildMainResponse($nav, $projects); } } private function buildMainResponse($nav, $projects) { if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) { $unbreak_panel = $this->buildUnbreakNowPanel(); $triage_panel = $this->buildNeedsTriagePanel($projects); $tasks_panel = $this->buildTasksPanel(); } else { $unbreak_panel = null; $triage_panel = null; $tasks_panel = null; } $jump_panel = $this->buildJumpPanel(); $revision_panel = $this->buildRevisionPanel(); $app_panel = $this->buildAppPanel(); $audit_panel = $this->buildAuditPanel(); $commit_panel = $this->buildCommitPanel(); $content = array( $app_panel, $jump_panel, $unbreak_panel, $triage_panel, $revision_panel, $tasks_panel, $audit_panel, $commit_panel, ); $nav->appendChild($content); return $this->buildStandardPageResponse( $nav, array( 'title' => 'Phabricator', )); } private function buildJumpResponse($nav) { $request = $this->getRequest(); if ($request->isFormPost()) { $jump = $request->getStr('jump'); $response = PhabricatorJumpNavHandler::jumpPostResponse($jump); if ($response) { return $response; } else { $query = new PhabricatorSearchQuery(); $query->setQuery($jump); $query->save(); return id(new AphrontRedirectResponse()) ->setURI('/search/'.$query->getQueryKey().'/'); } } $nav->appendChild($this->buildJumpPanel()); return $this->buildStandardPageResponse( $nav, array( 'title' => 'Jump Nav', )); } private function buildFeedResponse($nav, $projects) { $subnav = new AphrontSideNavFilterView(); $subnav->setBaseURI(new PhutilURI('/feed/')); $subnav->addFilter('all', 'All Activity', '/feed/'); $subnav->addFilter('projects', 'My Projects'); $filter = $subnav->selectFilter($this->subfilter, 'all'); switch ($filter) { case 'all': $phids = array(); break; case 'projects': $phids = mpull($projects, 'getPHID'); break; } $view = $this->buildFeedView($phids); $subnav->appendChild($view); $nav->appendChild($subnav); return $this->buildStandardPageResponse( $nav, array( 'title' => 'Feed', )); } private function buildUnbreakNowPanel() { $user = $this->getRequest()->getUser(); $user_phid = $user->getPHID(); $task_query = new ManiphestTaskQuery(); $task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN); $task_query->withPriority(ManiphestTaskPriority::PRIORITY_UNBREAK_NOW); $task_query->setLimit(10); $tasks = $task_query->execute(); if (!$tasks) { return $this->renderMiniPanel( 'No "Unbreak Now!" Tasks', 'Nothing appears to be critically broken right now.'); } $panel = new AphrontPanelView(); $panel->setHeader('Unbreak Now!'); $panel->setCaption('Open tasks with "Unbreak Now!" priority.'); $panel->addButton( phutil_render_tag( 'a', array( 'href' => '/maniphest/view/all/', 'class' => 'grey button', ), "View All Unbreak Now \xC2\xBB")); $panel->appendChild($this->buildTaskListView($tasks)); return $panel; } private function buildNeedsTriagePanel(array $projects) { $user = $this->getRequest()->getUser(); $user_phid = $user->getPHID(); if ($projects) { $task_query = new ManiphestTaskQuery(); $task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN); $task_query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE); $task_query->withProjects(mpull($projects, 'getPHID')); $task_query->withAnyProject(true); $task_query->setLimit(10); $tasks = $task_query->execute(); } else { $tasks = array(); } if (!$tasks) { return $this->renderMiniPanel( 'No "Needs Triage" Tasks', 'No tasks in projects you are a member of '. 'need triage.

    '); } $panel = new AphrontPanelView(); $panel->setHeader('Needs Triage'); $panel->setCaption( 'Open tasks with "Needs Triage" priority in '. 'projects you are a member of.'); $panel->addButton( phutil_render_tag( 'a', array( // TODO: This should filter to just your projects' need-triage // tasks? 'href' => '/maniphest/view/alltriage/', 'class' => 'grey button', ), "View All Triage \xC2\xBB")); $panel->appendChild($this->buildTaskListView($tasks)); return $panel; } private function buildRevisionPanel() { $user = $this->getRequest()->getUser(); $user_phid = $user->getPHID(); $revision_query = new DifferentialRevisionQuery(); $revision_query->withStatus(DifferentialRevisionQuery::STATUS_OPEN); $revision_query->withResponsibleUsers(array($user_phid)); $revision_query->needRelationships(true); // NOTE: We need to unlimit this query to hit the responsible user // fast-path. $revision_query->setLimit(null); $revisions = $revision_query->execute(); list($active, $waiting) = DifferentialRevisionQuery::splitResponsible( $revisions, $user_phid); if (!$active) { return $this->renderMiniPanel( 'No Waiting Revisions', 'No revisions are waiting on you.'); } $panel = new AphrontPanelView(); $panel->setHeader('Revisions Waiting on You'); $panel->setCaption('Revisions waiting for you for review or commit.'); $panel->addButton( phutil_render_tag( 'a', array( 'href' => '/differential/', 'class' => 'button grey', ), "View Active Revisions \xC2\xBB")); $fields = $revision_view = id(new DifferentialRevisionListView()) ->setRevisions($active) ->setFields(DifferentialRevisionListView::getDefaultFields()) ->setUser($user); $phids = array_merge( array($user_phid), $revision_view->getRequiredHandlePHIDs()); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $revision_view->setHandles($handles); $panel->appendChild($revision_view); return $panel; } private function buildTasksPanel() { $user = $this->getRequest()->getUser(); $user_phid = $user->getPHID(); $task_query = new ManiphestTaskQuery(); $task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN); $task_query->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY); $task_query->withOwners(array($user_phid)); $task_query->setLimit(10); $tasks = $task_query->execute(); if (!$tasks) { return $this->renderMiniPanel( 'No Assigned Tasks', 'You have no assigned tasks.'); } $panel = new AphrontPanelView(); $panel->setHeader('Assigned Tasks'); $panel->setCaption('Tasks assigned to you.'); $panel->addButton( phutil_render_tag( 'a', array( 'href' => '/maniphest/', 'class' => 'button grey', ), "View Active Tasks \xC2\xBB")); $panel->appendChild($this->buildTaskListView($tasks)); return $panel; } private function buildTaskListView(array $tasks) { $user = $this->getRequest()->getUser(); $phids = array_filter(mpull($tasks, 'getOwnerPHID')); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $view = new ManiphestTaskListView(); $view->setTasks($tasks); $view->setUser($user); $view->setHandles($handles); return $view; } private function buildFeedView(array $phids) { $request = $this->getRequest(); $user = $request->getUser(); $user_phid = $user->getPHID(); $feed_query = new PhabricatorFeedQuery(); if ($phids) { $feed_query->setFilterPHIDs($phids); } // TODO: All this limit stuff should probably be consolidated into the // feed query? $old_link = null; $new_link = null; $feed_query->setAfter($request->getStr('after')); $feed_query->setBefore($request->getStr('before')); $limit = 500; // Grab one more story than we intend to display so we can figure out // if we need to render an "Older Posts" link or not (with reasonable // accuracy, at least). $feed_query->setLimit($limit + 1); $feed = $feed_query->execute(); $extra_row = (count($feed) == $limit + 1); $have_new = ($request->getStr('before')) || ($request->getStr('after') && $extra_row); $have_old = ($request->getStr('after')) || ($request->getStr('before') && $extra_row) || (!$request->getStr('before') && !$request->getStr('after') && $extra_row); $feed = array_slice($feed, 0, $limit, $preserve_keys = true); if ($have_old) { $old_link = phutil_render_tag( 'a', array( 'href' => '?before='.end($feed)->getChronologicalKey(), 'class' => 'phabricator-feed-older-link', ), "Older Stories \xC2\xBB"); } if ($have_new) { $new_link = phutil_render_tag( 'a', array( 'href' => '?after='.reset($feed)->getChronologicalKey(), 'class' => 'phabricator-feed-newer-link', ), "\xC2\xAB Newer Stories"); } $builder = new PhabricatorFeedBuilder($feed); $builder->setUser($user); $feed_view = $builder->buildView(); return '
    '. '
    '. '

    Feed

    '. '
    '. $feed_view->render(). '
    '. $new_link. $old_link. '
    '. '
    '; } private function buildJumpPanel() { $request = $this->getRequest(); $user = $request->getUser(); $uniq_id = celerity_generate_unique_node_id(); Javelin::initBehavior( 'phabricator-autofocus', array( 'id' => $uniq_id, )); require_celerity_resource('phabricator-jump-nav'); $doc_href = PhabricatorEnv::getDocLink('article/Jump_Nav_User_Guide.html'); $doc_link = phutil_render_tag( 'a', array( 'href' => $doc_href, ), 'Jump Nav User Guide'); $jump_input = phutil_render_tag( 'input', array( 'type' => 'text', 'class' => 'phabricator-jump-nav', 'name' => 'jump', 'id' => $uniq_id, )); $jump_caption = phutil_render_tag( 'p', array( 'class' => 'phabricator-jump-nav-caption', ), 'Enter the name of an object like D123 to quickly jump to '. 'it. See '.$doc_link.' or type help.'); $panel = new AphrontPanelView(); $panel->addClass('aphront-unpadded-panel-view'); $panel->appendChild( phabricator_render_form( $user, array( 'action' => '/jump/', 'method' => 'POST', 'class' => 'phabricator-jump-nav-form', ), $jump_input. $jump_caption)); return $panel; } private function buildAppPanel() { require_celerity_resource('phabricator-app-buttons-css'); $nav_buttons = array(); $nav_buttons[] = array( 'Differential', '/differential/', 'differential'); if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) { $nav_buttons[] = array( 'Maniphest', '/maniphest/', 'maniphest'); $nav_buttons[] = array( 'Create Task', '/maniphest/task/create/', 'create-task'); } $nav_buttons[] = array( 'Upload File', '/file/', 'upload-file'); $nav_buttons[] = array( 'Create Paste', '/paste/', 'create-paste'); if (PhabricatorEnv::getEnvConfig('phriction.enabled')) { $nav_buttons[] = array( 'Browse Wiki', '/w/', 'phriction'); } $nav_buttons[] = array( 'Browse Code', '/diffusion/', 'diffusion'); $nav_buttons[] = array( 'Audit Code', '/audit/', 'audit'); $view = new AphrontNullView(); $view->appendChild('
    '); foreach ($nav_buttons as $info) { list($name, $uri, $icon) = $info; $button = phutil_render_tag( 'a', array( 'href' => $uri, 'class' => 'app-button icon-'.$icon, ), phutil_render_tag( 'div', array( 'class' => 'app-icon icon-'.$icon, ), '')); $caption = phutil_render_tag( 'a', array( 'href' => $uri, 'class' => 'phabricator-button-caption', ), phutil_escape_html($name)); $view->appendChild( '
    '. $button. $caption. '
    '); } $view->appendChild('
    '); return $view; } private function renderMiniPanel($title, $body) { $panel = new AphrontMiniPanelView(); $panel->appendChild( phutil_render_tag( 'p', array( ), ''.$title.': '.$body)); return $panel; } public function buildAuditPanel() { $request = $this->getRequest(); $user = $request->getUser(); $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user); $query = new PhabricatorAuditQuery(); $query->withAuditorPHIDs($phids); $query->withStatus(PhabricatorAuditQuery::STATUS_OPEN); $query->setLimit(10); $audits = $query->execute(); if (!$audits) { return $this->renderMinipanel( 'No Audits', 'No commits are waiting for you to audit them.'); } $view = new PhabricatorAuditListView(); $view->setAudits($audits); $phids = $view->getRequiredHandlePHIDs(); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $view->setHandles($handles); $panel = new AphrontPanelView(); $panel->setHeader('Audits'); $panel->setCaption('Commits awaiting your audit.'); $panel->appendChild($view); $panel->addButton( phutil_render_tag( 'a', array( 'href' => '/audit/', 'class' => 'button grey', ), "View Active Audits \xC2\xBB")); return $panel; } public function buildCommitPanel() { $request = $this->getRequest(); $user = $request->getUser(); $phids = array($user->getPHID()); $query = new PhabricatorAuditCommitQuery(); $query->withAuthorPHIDs($phids); $query->withStatus(PhabricatorAuditQuery::STATUS_OPEN); $query->needCommitData(true); $query->setLimit(10); $commits = $query->execute(); if (!$commits) { return $this->renderMinipanel( 'No Problem Commits', 'No one has raised concerns with your commits.'); } $view = new PhabricatorAuditCommitListView(); $view->setCommits($commits); $view->setUser($user); $phids = $view->getRequiredHandlePHIDs(); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $view->setHandles($handles); $panel = new AphrontPanelView(); $panel->setHeader('Problem Commits'); $panel->setCaption('Commits which auditors have raised concerns about.'); $panel->appendChild($view); $panel->addButton( phutil_render_tag( 'a', array( 'href' => '/audit/', 'class' => 'button grey', ), "View Problem Commits \xC2\xBB")); return $panel; } } diff --git a/src/applications/files/controller/delete/PhabricatorFileDeleteController.php b/src/applications/files/controller/delete/PhabricatorFileDeleteController.php index 11ac72147e..600a9382ab 100644 --- a/src/applications/files/controller/delete/PhabricatorFileDeleteController.php +++ b/src/applications/files/controller/delete/PhabricatorFileDeleteController.php @@ -1,60 +1,60 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $file = id(new PhabricatorFile())->loadOneWhere( 'id = %d', $this->id); if (!$file) { return new Aphront404Response(); } if (($user->getPHID() != $file->getAuthorPHID()) && (!$user->getIsAdmin())) { return new Aphront403Response(); } if ($request->isFormPost()) { $file->delete(); return id(new AphrontRedirectResponse())->setURI('/file/'); } $dialog = new AphrontDialogView(); $dialog->setUser($user); $dialog->setTitle('Really delete file?'); $dialog->appendChild( "

    Permanently delete '".phutil_escape_html($file->getName())."'? This ". "action can not be undone."); $dialog->addSubmitButton('Delete'); $dialog->addCancelButton($file->getInfoURI()); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/files/controller/dropupload/PhabricatorFileDropUploadController.php b/src/applications/files/controller/dropupload/PhabricatorFileDropUploadController.php index 4e8d48f2cf..ca16749bd8 100644 --- a/src/applications/files/controller/dropupload/PhabricatorFileDropUploadController.php +++ b/src/applications/files/controller/dropupload/PhabricatorFileDropUploadController.php @@ -1,50 +1,51 @@ getRequest(); $user = $request->getUser(); // NOTE: Throws if valid CSRF token is not present in the request. $request->validateCSRF(); $data = file_get_contents('php://input'); $name = $request->getStr('name'); $file = PhabricatorFile::newFromFileData( $data, array( 'name' => $request->getStr('name'), 'authorPHID' => $user->getPHID(), )); $view = new AphrontAttachedFileView(); $view->setFile($file); return id(new AphrontAjaxResponse())->setContent( array( 'id' => $file->getID(), 'phid' => $file->getPHID(), 'html' => $view->render(), 'uri' => $file->getBestURI(), )); } } diff --git a/src/applications/files/controller/list/PhabricatorFileListController.php b/src/applications/files/controller/list/PhabricatorFileListController.php index 7aa7c24e10..00db73770f 100644 --- a/src/applications/files/controller/list/PhabricatorFileListController.php +++ b/src/applications/files/controller/list/PhabricatorFileListController.php @@ -1,352 +1,352 @@ filter = $filter; return $this; } private function getFilter() { return $this->filter; } private function showUploader() { return $this->getShowUploader(); } private function getShowUploader() { return $this->showUploader; } private function setShowUploader($show_uploader) { $this->showUploader = $show_uploader; return $this; } private function useBasicUploader() { return $this->getUseBasicUploader(); } private function getUseBasicUploader() { return $this->useBasicUploader; } private function setUseBasicUploader($use_basic_uploader) { $this->useBasicUploader = $use_basic_uploader; return $this; } private function setListAuthor(PhabricatorUser $list_author) { $this->listAuthor = $list_author; return $this; } private function getListAuthor() { return $this->listAuthor; } private function getListRows() { return $this->listRows; } private function setListRows($list_rows) { $this->listRows = $list_rows; return $this; } private function getListRowClasses() { return $this->listRowClasses; } private function setListRowClasses($list_row_classes) { $this->listRowClasses = $list_row_classes; return $this; } private function getListHeader() { return $this->listHeader; } private function setListHeader($list_header) { $this->listHeader = $list_header; return $this; } private function showListPager() { return $this->getShowListPager(); } private function getShowListPager() { return $this->showListPager; } private function setShowListPager($show_list_pager) { $this->showListPager = $show_list_pager; return $this; } private function getListPager() { return $this->listPager; } private function setListPager($list_pager) { $this->listPager = $list_pager; return $this; } private function setPagerOffset($pager_offset) { $this->pagerOffset = $pager_offset; return $this; } private function getPagerOffset() { return $this->pagerOffset; } private function setPagerPageSize($pager_page_size) { $this->pagerPageSize = $pager_page_size; return $this; } private function getPagerPageSize() { return $this->pagerPageSize; } public function willProcessRequest(array $data) { $this->setFilter(idx($data, 'filter', 'upload')); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); switch ($this->getFilter()) { case 'upload': default: $this->setShowUploader(true); $this->setUseBasicUploader($request->getExists('basic_uploader')); $see_all = phutil_render_tag( 'a', array( 'href' => '/file/filter/all', ), 'See all Files'); $this->setListHeader("Recently Uploaded Files · {$see_all}"); $this->setShowListPager(false); $this->setPagerOffset(0); $this->setPagerPageSize(10); break; case 'my': $this->setShowUploader(false); $this->setListHeader('Files You Uploaded'); $this->setListAuthor($user); $this->setPagerOffset($request->getInt('page', 0)); break; case 'all': $this->setShowUploader(false); $this->setListHeader('All Files'); $this->setPagerOffset($request->getInt('page', 0)); break; } $this->loadListData(); $side_nav = new PhabricatorFileSideNavView(); $side_nav->setSelectedFilter($this->getFilter()); if ($this->showUploader()) { $side_nav->appendChild($this->renderUploadPanel()); } $side_nav->appendChild($this->renderList()); return $this->buildStandardPageResponse( $side_nav, array( 'title' => 'Files', 'tab' => 'files', )); } private function loadListData() { $request = $this->getRequest(); $user = $request->getUser(); $pager = new AphrontPagerView(); $pager->setOffset($this->getPagerOffset()); if ($this->getPagerPageSize()) { $pager->setPageSize($this->getPagerPageSize()); } $author = $this->getListAuthor(); if ($author) { $files = id(new PhabricatorFile())->loadAllWhere( 'authorPHID = %s ORDER BY id DESC LIMIT %d, %d', $author->getPHID(), $pager->getOffset(), $pager->getPageSize() + 1); } else { $files = id(new PhabricatorFile())->loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize() + 1); } $files = $pager->sliceResults($files); $pager->setURI($request->getRequestURI(), 'page'); $this->setListPager($pager); $phids = mpull($files, 'getAuthorPHID'); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $highlighted = $request->getStr('h'); $highlighted = explode('-', $highlighted); $highlighted = array_fill_keys($highlighted, true); $rows = array(); $rowc = array(); foreach ($files as $file) { if ($file->isViewableInBrowser()) { $view_button = phutil_render_tag( 'a', array( 'class' => 'small button grey', 'href' => $file->getViewURI(), ), 'View'); } else { $view_button = null; } if (isset($highlighted[$file->getID()])) { $rowc[] = 'highlighted'; } else { $rowc[] = ''; } $name = $file->getName(); $rows[] = array( phutil_escape_html('F'.$file->getID()), $file->getAuthorPHID() ? $handles[$file->getAuthorPHID()]->renderLink() : null, phutil_render_tag( 'a', array( 'href' => $file->getBestURI(), ), ($name != '' ? phutil_escape_html($name) : 'no name')), phutil_escape_html(number_format($file->getByteSize()).' bytes'), phutil_render_tag( 'a', array( 'class' => 'small button grey', 'href' => '/file/info/'.$file->getPHID().'/', ), 'Info'), $view_button, phabricator_date($file->getDateCreated(), $user), phabricator_time($file->getDateCreated(), $user), ); } $this->setListRows($rows); $this->setListRowClasses($rowc); } private function renderList() { $table = new AphrontTableView($this->getListRows()); $table->setRowClasses($this->getListRowClasses()); $table->setHeaders( array( 'File ID', 'Author', 'Name', 'Size', '', '', 'Created', '', )); $table->setColumnClasses( array( null, '', 'wide pri', 'right', 'action', 'action', '', 'right', )); $panel = new AphrontPanelView(); $panel->appendChild($table); $panel->setHeader($this->getListHeader()); if ($this->showListPager()) { $panel->appendChild($this->getListPager()); } return $panel; } private function renderUploadPanel() { $request = $this->getRequest(); $user = $request->getUser(); if ($this->useBasicUploader()) { $upload_panel = new PhabricatorFileUploadView(); $upload_panel->setUser($user); } else { require_celerity_resource('files-css'); $upload_id = celerity_generate_unique_node_id(); $panel_id = celerity_generate_unique_node_id(); $upload_panel = new AphrontPanelView(); $upload_panel->setHeader('Upload Files'); $upload_panel->setCreateButton('Basic Uploader', $request->getRequestURI()->setQueryParam('basic_uploader', true) ); $upload_panel->setWidth(AphrontPanelView::WIDTH_FULL); $upload_panel->setID($panel_id); $upload_panel->appendChild( phutil_render_tag( 'div', array( 'id' => $upload_id, 'style' => 'display: none;', 'class' => 'files-drag-and-drop', ), '')); Javelin::initBehavior( 'files-drag-and-drop', array( 'uri' => '/file/dropupload/', 'browseURI' => '/file/filter/my/', 'control' => $upload_id, 'target' => $panel_id, 'activatedClass' => 'aphront-panel-view-drag-and-drop', )); } return $upload_panel; } } diff --git a/src/applications/files/controller/macrodelete/PhabricatorFileMacroDeleteController.php b/src/applications/files/controller/macrodelete/PhabricatorFileMacroDeleteController.php index d8d8187f89..b5f8c2e819 100644 --- a/src/applications/files/controller/macrodelete/PhabricatorFileMacroDeleteController.php +++ b/src/applications/files/controller/macrodelete/PhabricatorFileMacroDeleteController.php @@ -1,56 +1,57 @@ id = $data['id']; } public function processRequest() { $macro = id(new PhabricatorFileImageMacro())->load($this->id); if (!$macro) { return new Aphront404Response(); } $request = $this->getRequest(); if ($request->isDialogFormPost()) { $macro->delete(); return id(new AphrontRedirectResponse())->setURI('/file/macro/'); } $dialog = new AphrontDialogView(); $dialog ->setUser($request->getUser()) ->setTitle('Really delete macro?') ->appendChild( '

    Really delete the much-beloved image macro "'. phutil_escape_html($macro->getName()).'"? It will be sorely missed.'. '

    ') ->setSubmitURI('/file/macro/delete/'.$this->id.'/') ->addSubmitButton('Delete') ->addCancelButton('/file/macro/'); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/files/controller/macroedit/PhabricatorFileMacroEditController.php b/src/applications/files/controller/macroedit/PhabricatorFileMacroEditController.php index 6d73ce401b..4c01321905 100644 --- a/src/applications/files/controller/macroedit/PhabricatorFileMacroEditController.php +++ b/src/applications/files/controller/macroedit/PhabricatorFileMacroEditController.php @@ -1,130 +1,131 @@ id = idx($data, 'id'); } public function processRequest() { if ($this->id) { $macro = id(new PhabricatorFileImageMacro())->load($this->id); if (!$macro) { return new Aphront404Response(); } } else { $macro = new PhabricatorFileImageMacro(); } $errors = array(); $e_name = true; $request = $this->getRequest(); $user = $request->getUser(); if ($request->isFormPost()) { $macro->setName($request->getStr('name')); if (!strlen($macro->getName())) { $errors[] = 'Macro name is required.'; $e_name = 'Required'; } else if (!preg_match('/^[a-z0-9_-]{3,}$/', $macro->getName())) { $errors[] = 'Macro must be at least three characters long and contain '. 'only lowercase letters, digits, hyphen and underscore.'; $e_name = 'Invalid'; } else { $e_name = null; } if (!$errors) { $file = PhabricatorFile::newFromPHPUpload( idx($_FILES, 'file'), array( 'name' => $request->getStr('name'), 'authorPHID' => $user->getPHID(), )); $macro->setFilePHID($file->getPHID()); try { $macro->save(); return id(new AphrontRedirectResponse())->setURI('/file/macro/'); } catch (AphrontQueryDuplicateKeyException $ex) { $errors[] = 'Macro name is not unique!'; $e_name = 'Duplicate'; } } } if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Form Errors'); $error_view->setErrors($errors); } else { $error_view = null; } $form = new AphrontFormView(); $form->setAction('/file/macro/edit/'); $form->setUser($request->getUser()); $form ->setEncType('multipart/form-data') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Name') ->setName('name') ->setValue($macro->getName()) ->setCaption('This word or phrase will be replaced with the image.') ->setError($e_name)) ->appendChild( id(new AphrontFormFileControl()) ->setLabel('File') ->setName('file') ->setError(true)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save Image Macro') ->addCancelButton('/file/macro/')); $panel = new AphrontPanelView(); if ($macro->getID()) { $title = 'Edit Image Macro'; } else { $title = 'Create Image Macro'; } $panel->setHeader($title); $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FULL); $side_nav = new PhabricatorFileSideNavView(); $side_nav->setSelectedFilter('create_macro'); $side_nav->appendChild($error_view); $side_nav->appendChild($panel); return $this->buildStandardPageResponse( $side_nav, array( 'title' => $title, )); } } diff --git a/src/applications/files/controller/macrolist/PhabricatorFileMacroListController.php b/src/applications/files/controller/macrolist/PhabricatorFileMacroListController.php index f5c5c6ccb3..b5128d4e8d 100644 --- a/src/applications/files/controller/macrolist/PhabricatorFileMacroListController.php +++ b/src/applications/files/controller/macrolist/PhabricatorFileMacroListController.php @@ -1,130 +1,132 @@ getRequest(); $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page')); $macro_table = new PhabricatorFileImageMacro(); $macros = $macro_table->loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize()); // Get an exact count since the size here is reasonably going to be a few // thousand at most in any reasonable case. $count = queryfx_one( $macro_table->establishConnection('r'), 'SELECT COUNT(*) N FROM %T', $macro_table->getTableName()); $count = $count['N']; $pager->setCount($count); $pager->setURI($request->getRequestURI(), 'page'); $file_phids = mpull($macros, 'getFilePHID'); $files = array(); if ($file_phids) { $files = id(new PhabricatorFile())->loadAllWhere( "phid IN (%Ls)", $file_phids); $author_phids = mpull($files, 'getAuthorPHID', 'getPHID'); $handles = id(new PhabricatorObjectHandleData($author_phids)) ->loadHandles(); } $files_map = mpull($files, null, 'getPHID'); $rows = array(); foreach ($macros as $macro) { $file_phid = $macro->getFilePHID(); $file = $files_map[$file_phid]; $author_link = isset($author_phids[$file_phid]) ? $handles[$author_phids[$file_phid]]->renderLink() : null; $rows[] = array( phutil_render_tag( 'a', array( 'href' => '/file/macro/edit/'.$macro->getID().'/', ), phutil_escape_html($macro->getName())), $author_link, phutil_render_tag( 'a', array( 'href' => $file->getBestURI(), 'target' => '_blank', ), phutil_render_tag( 'img', array( 'src' => $file->getBestURI(), ))), javelin_render_tag( 'a', array( 'href' => '/file/macro/delete/'.$macro->getID().'/', 'sigil' => 'workflow', 'class' => 'grey small button', ), 'Delete'), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Name', 'Author', 'Image', '', )); $table->setColumnClasses( array( 'pri', '', 'wide thumb', 'action', )); $panel = new AphrontPanelView(); $panel->appendChild($table); $panel->setHeader('Image Macros'); $panel->setCreateButton('New Image Macro', '/file/macro/edit/'); $panel->appendChild($pager); $side_nav = new PhabricatorFileSideNavView(); $side_nav->setSelectedFilter('all_macros'); $side_nav->appendChild($panel); return $this->buildStandardPageResponse( $side_nav, array( 'title' => 'Image Macros', 'tab' => 'macros', )); } } diff --git a/src/applications/files/controller/proxy/PhabricatorFileProxyController.php b/src/applications/files/controller/proxy/PhabricatorFileProxyController.php index 1b29b874ff..8c449fcdc8 100644 --- a/src/applications/files/controller/proxy/PhabricatorFileProxyController.php +++ b/src/applications/files/controller/proxy/PhabricatorFileProxyController.php @@ -1,70 +1,70 @@ getRequest(); $uri = $request->getStr('uri'); $proxy = id(new PhabricatorFileProxyImage())->loadOneWhere( 'uri = %s', $uri); if (!$proxy) { // This write is fine to skip CSRF checks for, we're just building a // cache of some remote image. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $file = PhabricatorFile::newFromFileDownload( $uri, nonempty(basename($uri), 'proxied-file')); if ($file) { $proxy = new PhabricatorFileProxyImage(); $proxy->setURI($uri); $proxy->setFilePHID($file->getPHID()); $proxy->save(); } unset($unguarded); } if ($proxy) { $file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $proxy->getFilePHID()); if ($file) { $view_uri = $file->getBestURI(); } else { $bad_phid = $proxy->getFilePHID(); throw new Exception( "Unable to load file with phid {$bad_phid}." ); } return id(new AphrontRedirectResponse())->setURI($view_uri); } return new Aphront400Response(); } } diff --git a/src/applications/files/controller/transform/PhabricatorFileTransformController.php b/src/applications/files/controller/transform/PhabricatorFileTransformController.php index dca8e11598..06475cb8c0 100644 --- a/src/applications/files/controller/transform/PhabricatorFileTransformController.php +++ b/src/applications/files/controller/transform/PhabricatorFileTransformController.php @@ -1,138 +1,139 @@ transform = $data['transform']; $this->phid = $data['phid']; } public function processRequest() { $xform = id(new PhabricatorTransformedFile()) ->loadOneWhere( 'originalPHID = %s AND transform = %s', $this->phid, $this->transform); if ($xform) { return $this->buildTransformedFileResponse($xform); } $file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $this->phid); if (!$file) { return new Aphront404Response(); } $type = $file->getMimeType(); if (!$file->isViewableInBrowser() || !$file->isTransformableImage()) { return $this->buildDefaultTransformation($file); } // We're essentially just building a cache here and don't need CSRF // protection. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); switch ($this->transform) { case 'thumb-160x120': $xformed_file = $this->executeThumbTransform($file, 160, 120); break; case 'thumb-60x45': $xformed_file = $this->executeThumbTransform($file, 60, 45); break; default: return new Aphront400Response(); } if (!$xformed_file) { return new Aphront400Response(); } $xform = new PhabricatorTransformedFile(); $xform->setOriginalPHID($this->phid); $xform->setTransform($this->transform); $xform->setTransformedPHID($xformed_file->getPHID()); $xform->save(); return $this->buildTransformedFileResponse($xform); } private function buildDefaultTransformation(PhabricatorFile $file) { static $regexps = array( '@application/zip@' => 'zip', '@image/@' => 'image', '@application/pdf@' => 'pdf', '@.*@' => 'default', ); $type = $file->getMimeType(); $prefix = 'default'; foreach ($regexps as $regexp => $implied_prefix) { if (preg_match($regexp, $type)) { $prefix = $implied_prefix; break; } } switch ($this->transform) { case 'thumb-160x120': $suffix = '160x120'; break; case 'thumb-60x45': $suffix = '60x45'; break; default: throw new Exception("Unsupported transformation type!"); } $path = "/rsrc/image/icon/fatcow/thumbnails/{$prefix}{$suffix}.png"; return id(new AphrontRedirectResponse()) ->setURI($path); } private function buildTransformedFileResponse( PhabricatorTransformedFile $xform) { $file = id(new PhabricatorFile())->loadOneWhere( 'phid = %s', $xform->getTransformedPHID()); if ($file) { $uri = $file->getBestURI(); } else { $bad_phid = $xform->getTransformedPHID(); throw new Exception( "Unable to load file with phid {$bad_phid}." ); } // TODO: We could just delegate to the file view controller instead, // which would save the client a roundtrip, but is slightly more complex. return id(new AphrontRedirectResponse())->setURI($uri); } private function executeThumbTransform(PhabricatorFile $file, $x, $y) { $xformer = new PhabricatorImageTransformer(); return $xformer->executeThumbTransform($file, $x, $y); } } diff --git a/src/applications/files/controller/upload/PhabricatorFileUploadController.php b/src/applications/files/controller/upload/PhabricatorFileUploadController.php index fed5387517..fd367f9bd0 100644 --- a/src/applications/files/controller/upload/PhabricatorFileUploadController.php +++ b/src/applications/files/controller/upload/PhabricatorFileUploadController.php @@ -1,46 +1,46 @@ getRequest(); $user = $request->getUser(); if ($request->isFormPost()) { $file = PhabricatorFile::newFromPHPUpload( idx($_FILES, 'file'), array( 'name' => $request->getStr('name'), 'authorPHID' => $user->getPHID(), )); return id(new AphrontRedirectResponse())->setURI($file->getBestURI()); } $panel = new PhabricatorFileUploadView(); $panel->setUser($user); return $this->buildStandardPageResponse( array($panel), array( 'title' => 'Upload File', )); } } diff --git a/src/applications/help/controller/keyboardshortcut/PhabricatorHelpKeyboardShortcutController.php b/src/applications/help/controller/keyboardshortcut/PhabricatorHelpKeyboardShortcutController.php index 951dfdb518..e71dfe1390 100644 --- a/src/applications/help/controller/keyboardshortcut/PhabricatorHelpKeyboardShortcutController.php +++ b/src/applications/help/controller/keyboardshortcut/PhabricatorHelpKeyboardShortcutController.php @@ -1,69 +1,69 @@ getRequest(); $user = $request->getUser(); $keys = $request->getStr('keys'); $keys = json_decode($keys, true); if (!is_array($keys)) { return new Aphront400Response(); } // There have been at least two users asking for a keyboard shortcut to // close the dialog, so be explicit that escape works since it isn't // terribly discoverable. $keys[] = array( 'keys' => array('esc'), 'description' => 'Close any dialog, including this one.', ); $rows = array(); foreach ($keys as $shortcut) { $keystrokes = array(); foreach ($shortcut['keys'] as $stroke) { $keystrokes[] = ''.phutil_escape_html($stroke).''; } $keystrokes = implode(' or ', $keystrokes); $rows[] = ''. ''.$keystrokes.''. ''.phutil_escape_html($shortcut['description']).''. ''; } $table = ''. implode('', $rows). '
    '; $dialog = id(new AphrontDialogView()) ->setUser($user) ->setTitle('Keyboard Shortcuts') ->appendChild($table) ->addCancelButton('#', 'Close'); return id(new AphrontDialogResponse()) ->setDialog($dialog); } } diff --git a/src/applications/herald/controller/all/HeraldAllRulesController.php b/src/applications/herald/controller/all/HeraldAllRulesController.php index f6097494ef..8a651efcd6 100644 --- a/src/applications/herald/controller/all/HeraldAllRulesController.php +++ b/src/applications/herald/controller/all/HeraldAllRulesController.php @@ -1,173 +1,173 @@ filter; } public function setFilter($filter) { $this->filter = 'all/view/'.$filter; return $this; } public function shouldRequireAdmin() { return true; } public function willProcessRequest(array $data) { $this->view = idx($data, 'view'); $this->setFilter($this->view); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $this->viewPHID = nonempty($request->getStr('phid'), null); if ($request->isFormPost()) { $phid_arr = $request->getArr('view_user'); $view_target = head($phid_arr); return id(new AphrontRedirectResponse()) ->setURI($request->getRequestURI()->alter('phid', $view_target)); } $map = HeraldContentTypeConfig::getContentTypeMap(); if (empty($map[$this->view])) { reset($map); $this->view = key($map); } $offset = $request->getInt('offset', 0); $pager = new AphrontPagerView(); $pager->setPageSize(50); $pager->setOffset($offset); $pager->setURI($request->getRequestURI(), 'offset'); list($rules, $handles) = $this->queryRules($pager); if (!$this->viewPHID) { $view_users = array(); } else { $view_users = array( $this->viewPHID => $handles[$this->viewPHID]->getFullName(), ); } $filter_form = id(new AphrontFormView()) ->setUser($user) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setLabel('View User') ->setName('view_user') ->setValue($view_users) ->setLimit(1)); $filter_view = new AphrontListFilterView(); $filter_view->appendChild($filter_form); $list_view = id(new HeraldRuleListView()) ->setRules($rules) ->setHandles($handles) ->setMap($map) ->setShowType(true) ->setAllowCreation(false) ->setUser($user) ->setView($this->view); $panel = $list_view->render(); $panel->appendChild($pager); $sidenav = new AphrontSideNavView(); $sidenav->appendChild($filter_view); $sidenav->appendChild($panel); $query = ''; if ($this->viewPHID) { $query = '?phid='.$this->viewPHID; } foreach ($map as $key => $value) { $sidenav->addNavItem( phutil_render_tag( 'a', array( 'href' => '/herald/all/view/'.$key.'/'.$query, 'class' => ($key == $this->view) ? 'aphront-side-nav-selected' : null, ), phutil_escape_html($value))); } return $this->buildStandardPageResponse( array( $filter_view, $panel ), array( 'title' => 'Herald', 'tab' => 'all', )); } private function queryRules(AphrontPagerView $pager) { $rule = new HeraldRule(); $conn_r = $rule->establishConnection('r'); $where_clause = qsprintf( $conn_r, 'WHERE contentType = %s', $this->view); if ($this->viewPHID) { $where_clause .= qsprintf( $conn_r, ' AND authorPHID = %s', $this->viewPHID); } $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q ORDER BY id DESC LIMIT %d, %d', $rule->getTableName(), $where_clause, $pager->getOffset(), $pager->getPageSize() + 1); $data = $pager->sliceResults($data); $rules = $rule->loadAllFromArray($data); $need_phids = mpull($rules, 'getAuthorPHID'); if ($this->viewPHID) { $need_phids[] = $this->viewPHID; } $handles = id(new PhabricatorObjectHandleData($need_phids)) ->loadHandles(); return array($rules, $handles); } } diff --git a/src/applications/herald/controller/delete/HeraldDeleteController.php b/src/applications/herald/controller/delete/HeraldDeleteController.php index fb5157fd47..baf7f52b49 100644 --- a/src/applications/herald/controller/delete/HeraldDeleteController.php +++ b/src/applications/herald/controller/delete/HeraldDeleteController.php @@ -1,70 +1,70 @@ id = $data['id']; } public function processRequest() { $rule = id(new HeraldRule())->load($this->id); if (!$rule) { return new Aphront404Response(); } $request = $this->getRequest(); $user = $request->getUser(); if ($user->getPHID() != $rule->getAuthorPHID() && !$user->getIsAdmin()) { return new Aphront400Response(); } if ($request->isFormPost()) { $rule->delete(); if ($request->isAjax()) { return new AphrontRedirectResponse(); } else { return id(new AphrontRedirectResponse())->setURI('/herald/'); } } $dialog = new AphrontDialogView(); $dialog->setUser($request->getUser()); $dialog->setTitle('Really delete this rule?'); $dialog->appendChild( "Are you sure you want to delete the rule ". "'".phutil_escape_html($rule->getName())."'?"); $dialog->addSubmitButton('Delete'); $dialog->addCancelButton('/herald/'); $dialog->setSubmitURI($request->getPath()); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/herald/controller/edithistory/HeraldRuleEditHistoryController.php b/src/applications/herald/controller/edithistory/HeraldRuleEditHistoryController.php index 457c55130a..69ce352c80 100644 --- a/src/applications/herald/controller/edithistory/HeraldRuleEditHistoryController.php +++ b/src/applications/herald/controller/edithistory/HeraldRuleEditHistoryController.php @@ -1,55 +1,55 @@ id = $data['id']; } public function processRequest() { $rule = id(new HeraldRule())->load($this->id); if ($rule === null) { return new Aphront404Response(); } $edits = $rule->loadEdits(); $rule->attachEdits($edits); $need_phids = mpull($edits, 'getEditorPHID'); $handles = id(new PhabricatorObjectHandleData($need_phids)) ->loadHandles(); $list_view = id(new HeraldRuleEditHistoryView()) ->setRule($rule) ->setHandles($handles) ->setUser($this->getRequest()->getUser()); return $this->buildStandardPageResponse( $list_view->render(), array( 'title' => 'Rule Edit History', )); } public function getFilter() { return; } } diff --git a/src/applications/herald/controller/home/HeraldHomeController.php b/src/applications/herald/controller/home/HeraldHomeController.php index d0fb2c38ff..39ea74383c 100644 --- a/src/applications/herald/controller/home/HeraldHomeController.php +++ b/src/applications/herald/controller/home/HeraldHomeController.php @@ -1,93 +1,93 @@ view = idx($data, 'view'); $this->global = idx($data, 'global'); if ($this->global) { $this->setFilter($this->view.'/global'); } else { $this->setFilter($this->view); } } public function getFilter() { return $this->filter; } public function setFilter($filter) { $this->filter = 'view/'.$filter; return $this; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $map = HeraldContentTypeConfig::getContentTypeMap(); if (empty($map[$this->view])) { reset($map); $this->view = key($map); } if ($this->global) { $rules = id(new HeraldRule())->loadAllWhere( 'contentType = %s AND ruleType = %s', $this->view, HeraldRuleTypeConfig::RULE_TYPE_GLOBAL); } else { $rules = id(new HeraldRule())->loadAllWhere( 'contentType = %s AND authorPHID = %s AND ruleType = %s', $this->view, $user->getPHID(), HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); } foreach ($rules as $rule) { $edits = $rule->loadEdits(); $rule->attachEdits($edits); } $need_phids = mpull($rules, 'getAuthorPHID'); $handles = id(new PhabricatorObjectHandleData($need_phids)) ->loadHandles(); $list_view = id(new HeraldRuleListView()) ->setRules($rules) ->setShowOwner(!$this->global) ->setHandles($handles) ->setMap($map) ->setAllowCreation(true) ->setView($this->view) ->setUser($user); $panel = $list_view->render(); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Herald', )); } } diff --git a/src/applications/herald/controller/new/HeraldNewController.php b/src/applications/herald/controller/new/HeraldNewController.php index fea5d0a494..2dc868f96c 100644 --- a/src/applications/herald/controller/new/HeraldNewController.php +++ b/src/applications/herald/controller/new/HeraldNewController.php @@ -1,100 +1,100 @@ contentType = idx($data, 'type'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $content_type_map = HeraldContentTypeConfig::getContentTypeMap(); if (empty($content_type_map[$this->contentType])) { reset($content_type_map); $this->contentType = key($content_type_map); } $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); // Reorder array to put "personal" first. $rule_type_map = array_select_keys( $rule_type_map, array( HeraldRuleTypeConfig::RULE_TYPE_PERSONAL, )) + $rule_type_map; $captions = array( HeraldRuleTypeConfig::RULE_TYPE_PERSONAL => 'Personal rules notify you about events. You own them, but they can '. 'only affect you.', HeraldRuleTypeConfig::RULE_TYPE_GLOBAL => 'Global rules notify anyone about events. No one owns them, and '. 'anyone can edit them. Usually, Global rules are used to notify '. 'mailing lists.', ); $radio = id(new AphrontFormRadioButtonControl()) ->setLabel('Type') ->setName('rule_type') ->setValue(HeraldRuleTypeConfig::RULE_TYPE_PERSONAL); foreach ($rule_type_map as $value => $name) { $radio->addButton( $value, $name, idx($captions, $value)); } $form = id(new AphrontFormView()) ->setUser($user) ->setAction('/herald/rule/') ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('New rule for') ->setName('content_type') ->setValue($this->contentType) ->setOptions($content_type_map)) ->appendChild($radio) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Create Rule') ->addCancelButton('/herald/view/'.$this->contentType.'/')); $panel = new AphrontPanelView(); $panel->setHeader('Create New Herald Rule'); $panel->setWidth(AphrontPanelView::WIDTH_FULL); $panel->appendChild($form); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Create Herald Rule', )); } } diff --git a/src/applications/herald/controller/rule/HeraldRuleController.php b/src/applications/herald/controller/rule/HeraldRuleController.php index 907d3d45ea..43fe41aa2e 100644 --- a/src/applications/herald/controller/rule/HeraldRuleController.php +++ b/src/applications/herald/controller/rule/HeraldRuleController.php @@ -1,590 +1,590 @@ filter; } public function setFilter($filter) { $this->filter = 'view/'.$filter; } public function willProcessRequest(array $data) { $this->id = (int)idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $content_type_map = HeraldContentTypeConfig::getContentTypeMap(); $rule_type_map = HeraldRuleTypeConfig::getRuleTypeMap(); if ($this->id) { $rule = id(new HeraldRule())->load($this->id); if (!$rule) { return new Aphront404Response(); } if (!$this->canEditRule($rule, $user)) { throw new Exception("You don't own this rule and can't edit it."); } } else { $rule = new HeraldRule(); $rule->setAuthorPHID($user->getPHID()); $rule->setMustMatchAll(true); $content_type = $request->getStr('content_type'); if (!isset($content_type_map[$content_type])) { $content_type = HeraldContentTypeConfig::CONTENT_TYPE_DIFFERENTIAL; } $rule->setContentType($content_type); $rule_type = $request->getStr('rule_type'); if (!isset($rule_type_map[$rule_type])) { $rule_type = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL; } $rule->setRuleType($rule_type); } $this->setFilter($rule->getContentType()); $local_version = id(new HeraldRule())->getConfigVersion(); if ($rule->getConfigVersion() > $local_version) { throw new Exception( "This rule was created with a newer version of Herald. You can not ". "view or edit it in this older version. Try dev or wait for a push."); } // 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($rule, $request); if (!$errors) { $uri = '/herald/view/'.$rule->getContentType().'/'; if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL) { $uri .= 'global/'; } return id(new AphrontRedirectResponse()) ->setURI($uri); } } if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Form Errors'); $error_view->setErrors($errors); } else { $error_view = null; } $must_match_selector = $this->getMustMatchSelector($rule); $repetition_selector = $this->getRepetitionSelector($rule); $handles = $this->loadHandles($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($user) ->setID('herald-rule-edit-form') ->addHiddenInput('content_type', $rule->getContentType()) ->addHiddenInput('rule_type', $rule->getRuleType()) ->addHiddenInput('save', 1) ->appendChild( // Build this explicitly so we can add a sigil to it. javelin_render_tag( 'input', array( 'type' => 'hidden', 'name' => 'rule', 'sigil' => 'rule', ))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Rule Name') ->setName('name') ->setError($e_name) ->setValue($rule->getName())); if ($rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL) { $form ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel('Owner') ->setValue('
    ')) ->appendChild( // Build this explicitly so we can add a sigil to it. javelin_render_tag( 'input', array( 'type' => 'hidden', 'name' => 'author', 'sigil' => 'author', ))); } $form ->appendChild( id(new AphrontFormMarkupControl()) ->setValue( "This ${rule_type_name} rule triggers for " . "${content_type_name}.")) ->appendChild( '

    Conditions

    '. '
    '. '
    '. javelin_render_tag( 'a', array( 'href' => '#', 'class' => 'button green', 'sigil' => 'create-condition', 'mustcapture' => true, ), 'Create New Condition'). '
    '. '

    When '.$must_match_selector.' these conditions are met:

    '. '
    '. javelin_render_tag( 'table', array( 'sigil' => 'rule-conditions', 'class' => 'herald-condition-table', ), ''). '
    ') ->appendChild( '

    Action

    '. '
    '. '
    '. javelin_render_tag( 'a', array( 'href' => '#', 'class' => 'button green', 'sigil' => 'create-action', 'mustcapture' => true, ), 'Create New Action'). '
    '. '

    '. 'Take these actions '.$repetition_selector.' this rule matches:'. '

    '. '
    '. javelin_render_tag( 'table', array( 'sigil' => 'rule-actions', 'class' => 'herald-action-table', ), ''). '
    ') ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save Rule') ->addCancelButton('/herald/view/'.$rule->getContentType().'/')); $this->setupEditorBehavior($rule, $handles); $panel = new AphrontPanelView(); $panel->setHeader('Edit Herald Rule'); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->appendChild($form); return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Edit Rule', )); } private function canEditRule($rule, $user) { return $user->getIsAdmin() || $rule->getRuleType() == HeraldRuleTypeConfig::RULE_TYPE_GLOBAL || $rule->getAuthorPHID() == $user->getPHID(); } private function saveRule($rule, $request) { $rule->setName($request->getStr('name')); $rule->setMustMatchAll(($request->getStr('must_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 = "Required"; $errors[] = "Rule must have a name."; } $data = json_decode($request->getStr('rule'), true); if (!is_array($data) || !$data['conditions'] || !$data['actions']) { throw new Exception("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]); } $cond_type = $obj->getFieldCondition(); if ($cond_type == HeraldConditionConfig::CONDITION_REGEXP) { if (@preg_match($obj->getValue(), '') === false) { $errors[] = 'The regular expression "'.$obj->getValue().'" is not valid. '. 'Regular expressions must have enclosing characters (e.g. '. '"@/path/to/file@", not "/path/to/file") and be syntactically '. 'correct.'; } } if ($cond_type == HeraldConditionConfig::CONDITION_REGEXP_PAIR) { $json = json_decode($obj->getValue(), true); if (!is_array($json)) { $errors[] = 'The regular expression pair "'.$obj->getValue().'" is not '. 'valid JSON. Enter a valid JSON array with two elements.'; } else { if (count($json) != 2) { $errors[] = 'The regular expression pair "'.$obj->getValue().'" must have '. 'exactly two elements.'; } else { $key_regexp = array_shift($json); $val_regexp = array_shift($json); if (@preg_match($key_regexp, '') === false) { $errors[] = 'The first regexp, "'.$key_regexp.'" in the regexp pair '. 'is not a valid regexp.'; } if (@preg_match($val_regexp, '') === false) { $errors[] = 'The second regexp, "'.$val_regexp.'" in the regexp pair '. 'is not a valid regexp.'; } } } } $conditions[] = $obj; } $author = $request->getStr('author'); if ($author) { $rule->setAuthorPHID($author); } $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; } $actions[] = HeraldActionConfig::willSaveAction($rule->getRuleType(), $rule->getAuthorPHID(), $action); } $rule->attachConditions($conditions); $rule->attachActions($actions); if (!$errors) { try { // TODO // $rule->openTransaction(); $rule->save(); $rule->saveConditions($conditions); $rule->saveActions($actions); $rule->saveEdit($request->getUser()->getPHID()); // $rule->saveTransaction(); } catch (AphrontQueryDuplicateKeyException $ex) { $e_name = "Not Unique"; $errors[] = "Rule name is not unique. Choose a unique name."; } } return array($e_name, $errors); } private function setupEditorBehavior($rule, $handles) { $serial_conditions = array( array('default', 'default', ''), ); if ($rule->getConditions()) { $serial_conditions = array(); foreach ($rule->getConditions() as $condition) { $value = $condition->getValue(); if (is_array($value)) { $value_map = array(); foreach ($value as $k => $fbid) { $value_map[$fbid] = $handles[$fbid]->getName(); } $value = $value_map; } $serial_conditions[] = array( $condition->getFieldName(), $condition->getFieldCondition(), $value, ); } } $serial_actions = array( array('default', ''), ); if ($rule->getActions()) { $serial_actions = array(); foreach ($rule->getActions() as $action) { $target_map = array(); foreach ((array)$action->getTarget() as $fbid) { $target_map[$fbid] = $handles[$fbid]->getName(); } $serial_actions[] = array( $action->getAction(), $target_map, ); } } $all_rules = id(new HeraldRule())->loadAllWhere( 'authorPHID = %s AND contentType = %s', $rule->getAuthorPHID(), $rule->getContentType()); $all_rules = mpull($all_rules, 'getName', 'getID'); asort($all_rules); unset($all_rules[$rule->getID()]); $config_info = array(); $config_info['fields'] = HeraldFieldConfig::getFieldMapForContentType($rule->getContentType()); $config_info['conditions'] = HeraldConditionConfig::getConditionMap(); foreach ($config_info['fields'] as $field => $name) { $config_info['conditionMap'][$field] = array_keys( HeraldConditionConfig::getConditionMapForField($field)); } foreach ($config_info['fields'] as $field => $fname) { foreach ($config_info['conditions'] as $condition => $cname) { $config_info['values'][$field][$condition] = HeraldValueTypeConfig::getValueTypeForFieldAndCondition( $field, $condition); } } $config_info['actions'] = HeraldActionConfig::getActionMessageMap($rule->getContentType(), $rule->getRuleType()); $config_info['rule_type'] = $rule->getRuleType(); foreach ($config_info['actions'] as $action => $name) { $config_info['targets'][$action] = HeraldValueTypeConfig::getValueTypeForAction($action, $rule->getRuleType()); } 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, ), 'author' => array($rule->getAuthorPHID() => $handles[$rule->getAuthorPHID()]->getName()), 'info' => $config_info, )); } private function loadHandles($rule) { $phids = array(); $phids[] = $rule->getAuthorPHID(); 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 += array($rule->getAuthorPHID()); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); return $handles; } private function getMustMatchSelector($rule) { $options = array( 'all' => 'all of', 'any' => 'any of', ); $selected = $rule->getMustMatchAll() ? 'all' : 'any'; $must_match = array(); foreach ($options as $key => $option) { $must_match[] = phutil_render_tag( 'option', array( 'selected' => ($selected == $key) ? 'selected' : null, 'value' => $key, ), phutil_escape_html($option)); } $must_match = ''; return $must_match; } private function getRepetitionSelector($rule) { // Make the selector for choosing how often this rule should be repeated $repetition_policy = HeraldRepetitionPolicyConfig::toString( $rule->getRepetitionPolicy() ); $repetition_options = HeraldRepetitionPolicyConfig::getMapForContentType( $rule->getContentType() ); if (empty($repetition_options)) { // default option is 'every time' $repetition_selector = idx( HeraldRepetitionPolicyConfig::getMap(), HeraldRepetitionPolicyConfig::EVERY ); return $repetition_selector; } else if (count($repetition_options) == 1) { // if there's only 1 option, just pick it for the user $repetition_selector = reset($repetition_options); return $repetition_selector; } else { // give the user all the options for this rule type $tags = array(); foreach ($repetition_options as $name => $option) { $tags[] = phutil_render_tag( 'option', array( 'selected' => ($repetition_policy == $name) ? 'selected' : null, 'value' => $name, ), phutil_escape_html($option) ); } $repetition_selector = ''; return $repetition_selector; } } protected function buildTokenizerTemplates() { $template = new AphrontTokenizerTemplateView(); $template = $template->render(); return array( 'source' => array( 'email' => '/typeahead/common/mailable/', 'user' => '/typeahead/common/users/', 'repository' => '/typeahead/common/repositories/', 'package' => '/typeahead/common/packages/', 'project' => '/typeahead/common/projects/', ), 'markup' => $template, ); } } diff --git a/src/applications/herald/controller/test/HeraldTestConsoleController.php b/src/applications/herald/controller/test/HeraldTestConsoleController.php index fa909d796e..339e0bb614 100644 --- a/src/applications/herald/controller/test/HeraldTestConsoleController.php +++ b/src/applications/herald/controller/test/HeraldTestConsoleController.php @@ -1,150 +1,150 @@ getRequest(); $user = $request->getUser(); $request = $this->getRequest(); $object_name = trim($request->getStr('object_name')); $e_name = true; $errors = array(); if ($request->isFormPost()) { if (!$object_name) { $e_name = 'Required'; $errors[] = 'An object name is required.'; } if (!$errors) { $matches = null; $object = null; if (preg_match('/^D(\d+)$/', $object_name, $matches)) { $object = id(new DifferentialRevision())->load($matches[1]); if (!$object) { $e_name = 'Invalid'; $errors[] = 'No Differential Revision with that ID exists.'; } } else if (preg_match('/^r([A-Z]+)(\w+)$/', $object_name, $matches)) { $repo = id(new PhabricatorRepository())->loadOneWhere( 'callsign = %s', $matches[1]); if (!$repo) { $e_name = 'Invalid'; $errors[] = 'There is no repository with the callsign '. $matches[1].'.'; } $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere( 'repositoryID = %d AND commitIdentifier = %s', $repo->getID(), $matches[2]); if (!$commit) { $e_name = 'Invalid'; $errors[] = 'There is no commit with that identifier.'; } $object = $commit; } else { $e_name = 'Invalid'; $errors[] = 'This object name is not recognized.'; } if (!$errors) { if ($object instanceof DifferentialRevision) { $adapter = new HeraldDifferentialRevisionAdapter( $object, $object->loadActiveDiff()); } else if ($object instanceof PhabricatorRepositoryCommit) { $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere( 'commitID = %d', $object->getID()); $adapter = new HeraldCommitAdapter( $repo, $object, $data); } else { throw new Exception("Can not build adapter for object!"); } $rules = HeraldRule::loadAllByContentTypeWithFullData( $adapter->getHeraldTypeName(), $object->getPHID()); $engine = new HeraldEngine(); $effects = $engine->applyRules($rules, $adapter); $dry_run = new HeraldDryRunAdapter(); $engine->applyEffects($effects, $dry_run, $rules); $xscript = $engine->getTranscript(); return id(new AphrontRedirectResponse()) ->setURI('/herald/transcript/'.$xscript->getID().'/'); } } } if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Form Errors'); $error_view->setErrors($errors); } else { $error_view = null; } $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( '

    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('Object Name') ->setName('object_name') ->setError($e_name) ->setValue($object_name)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Test Rules')); $panel = new AphrontPanelView(); $panel->setHeader('Test Herald Rules'); $panel->setWidth(AphrontPanelView::WIDTH_FULL); $panel->appendChild($form); return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Test Console', 'tab' => 'test', )); } } diff --git a/src/applications/herald/controller/transcript/HeraldTranscriptController.php b/src/applications/herald/controller/transcript/HeraldTranscriptController.php index a507e89103..27b6ee0761 100644 --- a/src/applications/herald/controller/transcript/HeraldTranscriptController.php +++ b/src/applications/herald/controller/transcript/HeraldTranscriptController.php @@ -1,535 +1,535 @@ id = $data['id']; $map = $this->getFilterMap(); $this->filter = idx($data, 'filter'); if (empty($map[$this->filter])) { $this->filter = self::FILTER_AFFECTED; } } public function processRequest() { $xscript = id(new HeraldTranscript())->load($this->id); if (!$xscript) { throw new Exception('Uknown transcript!'); } require_celerity_resource('herald-test-css'); $nav = $this->buildSideNav(); $object_xscript = $xscript->getObjectTranscript(); if (!$object_xscript) { $notice = id(new AphrontErrorView()) ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) ->setTitle('Old Transcript') ->appendChild( '

    Details of this transcript have been garbage collected.

    '); $nav->appendChild($notice); } else { $filter = $this->getFilterPHIDs(); $this->filterTranscript($xscript, $filter); $phids = array_merge($filter, $this->getTranscriptPHIDs($xscript)); $phids = array_unique($phids); $phids = array_filter($phids); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $this->handles = $handles; $apply_xscript_panel = $this->buildApplyTranscriptPanel( $xscript); $nav->appendChild($apply_xscript_panel); $action_xscript_panel = $this->buildActionTranscriptPanel( $xscript); $nav->appendChild($action_xscript_panel); $object_xscript_panel = $this->buildObjectTranscriptPanel( $xscript); $nav->appendChild($object_xscript_panel); } /* TODO $notice = null; if ($xscript->getDryRun()) { $notice = This was a dry run to test Herald rules, no actions were executed. ; } */ return $this->buildStandardPageResponse( $nav, array( 'title' => 'Transcript', )); } protected function renderConditionTestValue($condition, $handles) { $value = $condition->getTestValue(); if (!is_scalar($value) && $value !== null) { foreach ($value as $key => $phid) { $handle = idx($handles, $phid); if ($handle) { $value[$key] = $handle->getName(); } else { // This shouldn't ever really happen as we are supposed to have // grabbed handles for everything, but be super liberal in what // we accept here since we expect all sorts of weird issues as we // version the system. $value[$key] = 'Unknown Object #'.$phid; } } sort($value); $value = implode(', ', $value); } return ''. phutil_escape_html($value). ''; } private function buildSideNav() { $nav = new AphrontSideNavView(); $items = array(); $filters = $this->getFilterMap(); foreach ($filters as $key => $name) { $nav->addNavItem( phutil_render_tag( 'a', array( 'href' => '/herald/transcript/'.$this->id.'/'.$key.'/', 'class' => ($key == $this->filter) ? 'aphront-side-nav-selected' : null, ), phutil_escape_html($name))); } return $nav; } protected function getFilterMap() { return array( self::FILTER_AFFECTED => 'Rules that Affected Me', self::FILTER_OWNED => 'Rules I Own', self::FILTER_ALL => 'All Rules', ); } protected function getFilterPHIDs() { return array($this->getRequest()->getUser()->getPHID()); /* TODO $viewer_id = $this->getRequest()->getUser()->getPHID(); $fbids = array(); if ($this->filter == self::FILTER_AFFECTED) { $fbids[] = $viewer_id; require_module_lazy('intern/subscriptions'); $datastore = new SubscriberDatabaseStore(); $lists = $datastore->getUserMailmanLists($viewer_id); foreach ($lists as $list) { $fbids[] = $list; } } return $fbids; */ } 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) { $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; } } } } return $phids; } protected function filterTranscript($xscript, $filter_phids) { $filter_owned = ($this->filter == self::FILTER_OWNED); $filter_affected = ($this->filter == self::FILTER_AFFECTED); if (!$filter_owned && !$filter_affected) { // No filtering to be done. return; } if (!$xscript->getObjectTranscript()) { return; } $user_phid = $this->getRequest()->getUser()->getPHID(); $keep_apply_xscripts = array(); $keep_rule_xscripts = array(); $filter_phids = array_fill_keys($filter_phids, true); $rule_xscripts = $xscript->getRuleTranscripts(); foreach ($xscript->getApplyTranscripts() as $id => $apply_xscript) { $rule_id = $apply_xscript->getRuleID(); if ($filter_owned) { if (empty($rule_xscripts[$rule_id])) { // No associated rule so you can't own this effect. continue; } if ($rule_xscripts[$rule_id]->getRuleOwner() != $user_phid) { continue; } } else if ($filter_affected) { $targets = (array)$apply_xscript->getTarget(); if (!array_select_keys($filter_phids, $targets)) { continue; } } $keep_apply_xscripts[$id] = true; if ($rule_id) { $keep_rule_xscripts[$rule_id] = true; } } foreach ($rule_xscripts as $rule_id => $rule_xscript) { if ($filter_owned && $rule_xscript->getRuleOwner() == $user_phid) { $keep_rule_xscripts[$rule_id] = true; } } $xscript->setRuleTranscripts( array_intersect_key( $xscript->getRuleTranscripts(), $keep_rule_xscripts)); $xscript->setApplyTranscripts( array_intersect_key( $xscript->getApplyTranscripts(), $keep_apply_xscripts)); $xscript->setConditionTranscripts( array_intersect_key( $xscript->getConditionTranscripts(), $keep_rule_xscripts)); } private function buildApplyTranscriptPanel($xscript) { $handles = $this->handles; $action_names = HeraldActionConfig::getActionMessageMapForRuleType( HeraldRuleTypeConfig::RULE_TYPE_GLOBAL); $rows = array(); foreach ($xscript->getApplyTranscripts() as $apply_xscript) { // TODO: Hacks, this is an approximate guess at the target type. $target = (array)$apply_xscript->getTarget(); if (!$target) { if ($apply_xscript->getAction() == HeraldActionConfig::ACTION_NOTHING) { $target = ''; } else { $target = ''; } } else { foreach ($target as $k => $phid) { $target[$k] = $handles[$phid]->getName(); } $target = implode("\n", $target); } $target = phutil_escape_html($target); if ($apply_xscript->getApplied()) { $outcome = 'SUCCESS'; } else { $outcome = 'FAILURE'; } $outcome .= ' '.phutil_escape_html($apply_xscript->getAppliedReason()); $rows[] = array( phutil_escape_html($action_names[$apply_xscript->getAction()]), $target, 'Taken because: '. phutil_escape_html($apply_xscript->getReason()). '
    '. 'Outcome: '.$outcome, ); } $table = new AphrontTableView($rows); $table->setNoDataString('No actions were taken.'); $table->setHeaders( array( 'Action', 'Target', 'Details', )); $table->setColumnClasses( array( '', '', 'wide', )); $panel = new AphrontPanelView(); $panel->setHeader('Actions Taken'); $panel->appendChild($table); return $panel; } private function buildActionTranscriptPanel($xscript) { $action_xscript = mgroup($xscript->getApplyTranscripts(), 'getRuleID'); $field_names = HeraldFieldConfig::getFieldMap(); $condition_names = HeraldConditionConfig::getConditionMap(); $handles = $this->handles; $rule_markup = array(); foreach ($xscript->getRuleTranscripts() as $rule_id => $rule) { $cond_markup = array(); foreach ($xscript->getConditionTranscriptsForRule($rule_id) as $cond) { if ($cond->getNote()) { $note = '
    '. phutil_escape_html($cond->getNote()). '
    '; } else { $note = null; } if ($cond->getResult()) { $result = ''. "\xE2\x9C\x93". ''; } else { $result = ''. "\xE2\x9C\x98". ''; } $cond_markup[] = '
  • '. $result.' Condition: '. phutil_escape_html($field_names[$cond->getFieldName()]). ' '. phutil_escape_html($condition_names[$cond->getCondition()]). ' '. $this->renderConditionTestValue($cond, $handles). $note. '
  • '; } if ($rule->getResult()) { $result = 'PASS'; $class = 'herald-rule-pass'; } else { $result = 'FAIL'; $class = 'herald-rule-fail'; } $cond_markup[] = '
  • '.$result.' '.phutil_escape_html($rule->getReason()).'
  • '; /* if ($rule->getResult()) { $actions = idx($action_xscript, $rule_id, array()); if ($actions) { $cond_markup[] =
  • Actions
  • ; foreach ($actions as $action) { $target = $action->getTarget(); if ($target) { foreach ((array)$target as $k => $phid) { $target[$k] = $handles[$phid]->getName(); } $target = : {implode(', ', $target)}; } $cond_markup[] =
  • {$action_names[$action->getAction()]} {$target}
  • ; } } } */ $user_phid = $this->getRequest()->getUser()->getPHID(); $name = $rule->getRuleName(); if ($rule->getRuleOwner() == $user_phid) { // $name = getRuleID()."/"}>{$name}; } $rule_markup[] = phutil_render_tag( 'li', array( 'class' => $class, ), '
    '. ''.phutil_escape_html($name).' '. phutil_escape_html($handles[$rule->getRuleOwner()]->getName()). '
    '. '
      '.implode("\n", $cond_markup).'
    '); } $panel = new AphrontPanelView(); $panel->setHeader('Rule Details'); $panel->appendChild( '
      '. implode("\n", $rule_markup). '
    '); return $panel; } private function buildObjectTranscriptPanel($xscript) { $field_names = HeraldFieldConfig::getFieldMap(); $object_xscript = $xscript->getObjectTranscript(); $data = array(); if ($object_xscript) { $phid = $object_xscript->getPHID(); $handles = id(new PhabricatorObjectHandleData(array($phid))) ->loadHandles(); $data += array( 'Object Name' => $object_xscript->getName(), 'Object Type' => $object_xscript->getType(), 'Object PHID' => $phid, '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 (!is_scalar($value) && !is_null($value)) { $value = implode("\n", $value); } if (strlen($value) > 256) { $value = phutil_render_tag( 'textarea', array( 'class' => 'herald-field-value-transcript', ), phutil_escape_html($value)); } else if ($name === 'Object Link') { // The link cannot be escaped } else { $value = phutil_escape_html($value); } $rows[] = array( phutil_escape_html($name), $value, ); } $table = new AphrontTableView($rows); $table->setColumnClasses( array( 'header', 'wide', )); $panel = new AphrontPanelView(); $panel->setHeader('Object Transcript'); $panel->appendChild($table); return $panel; } } diff --git a/src/applications/herald/controller/transcriptlist/HeraldTranscriptListController.php b/src/applications/herald/controller/transcriptlist/HeraldTranscriptListController.php index 560c7ad8dc..950ef83ee9 100644 --- a/src/applications/herald/controller/transcriptlist/HeraldTranscriptListController.php +++ b/src/applications/herald/controller/transcriptlist/HeraldTranscriptListController.php @@ -1,155 +1,155 @@ getRequest(); $user = $request->getUser(); // Get one page of data together with the pager. // Pull these objects manually since the serialized fields are gigantic. $transcript = new HeraldTranscript(); $conn_r = $transcript->establishConnection('r'); $phid = $request->getStr('phid'); $where_clause = ''; if ($phid) { $where_clause = qsprintf( $conn_r, 'WHERE objectPHID = %s', $phid); } $offset = $request->getInt('offset', 0); $page_size = 100; $limit_clause = qsprintf( $conn_r, 'LIMIT %d, %d', $offset, $page_size + 1); $data = queryfx_all( $conn_r, 'SELECT id, objectPHID, time, duration, dryRun FROM %T %Q ORDER BY id DESC %Q', $transcript->getTableName(), $where_clause, $limit_clause); $pager = new AphrontPagerView(); $pager->getPageSize($page_size); $pager->setHasMorePages(count($data) == $page_size + 1); $pager->setOffset($offset); $pager->setURI($request->getRequestURI(), 'offset'); /* $conn_r = smc_get_db('cdb.herald', 'r'); $page_size = 100; $pager = new SimplePager(); $pager->setPageSize($page_size); $pager->setOffset((((int)$request->getInt('page')) - 1) * $page_size); $pager->order('id', array('id')); $fbid = $request->getInt('fbid'); if ($fbid) { $filter = qsprintf( $conn_r, 'WHERE objectID = %d', $fbid); } else { $filter = ''; } $data = $pager->select( $conn_r, 'id, objectID, time, duration, dryRun FROM transcript %Q', $filter); */ // Render the table. $handles = array(); if ($data) { $phids = ipull($data, 'objectPHID', 'objectPHID'); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); } $rows = array(); foreach ($data as $xscript) { $rows[] = array( phabricator_date($xscript['time'],$user), phabricator_time($xscript['time'],$user), $handles[$xscript['objectPHID']]->renderLink(), $xscript['dryRun'] ? 'Yes' : '', number_format((int)(1000 * $xscript['duration'])).' ms', phutil_render_tag( 'a', array( 'href' => '/herald/transcript/'.$xscript['id'].'/', 'class' => 'button small grey', ), 'View Transcript'), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Date', 'Time', 'Object', 'Dry Run', 'Duration', 'View', )); $table->setColumnClasses( array( '', 'right', 'wide wrap', '', '', 'action', )); // Render the whole page. $panel = new AphrontPanelView(); $panel->setHeader('Herald Transcripts'); $panel->appendChild($table); $panel->appendChild($pager); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Herald Transcripts', 'tab' => 'transcripts', )); } } diff --git a/src/applications/maniphest/controller/descriptionchange/ManiphestTaskDescriptionChangeController.php b/src/applications/maniphest/controller/descriptionchange/ManiphestTaskDescriptionChangeController.php index 8f9a81945e..0532df3469 100644 --- a/src/applications/maniphest/controller/descriptionchange/ManiphestTaskDescriptionChangeController.php +++ b/src/applications/maniphest/controller/descriptionchange/ManiphestTaskDescriptionChangeController.php @@ -1,73 +1,78 @@ transactionID = $transaction_id; return $this; } public function getTransactionID() { return $this->transactionID; } public function willProcessRequest(array $data) { - $this->setTransactionID($data['id']); + $this->setTransactionID(idx($data, 'id')); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); + if (!$this->getTransactionID()) { + $this->setTransactionID($this->getRequest()->getStr('ref')); + } + $transaction_id = $this->getTransactionID(); $transaction = id(new ManiphestTransaction())->load($transaction_id); if (!$transaction) { return new Aphront404Response(); } $transactions = array($transaction); $phids = array(); foreach ($transactions as $transaction) { foreach ($transaction->extractPHIDs() as $phid) { $phids[$phid] = $phid; } } $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $engine = PhabricatorMarkupEngine::newManiphestMarkupEngine(); $view = new ManiphestTransactionDetailView(); $view->setTransactionGroup($transactions); $view->setHandles($handles); $view->setUser($user); $view->setMarkupEngine($engine); $view->setRenderSummaryOnly(true); $view->setRenderFullSummary(true); $view->setRangeSpecification($request->getStr('range')); return id(new AphrontAjaxResponse())->setContent($view->render()); } } diff --git a/src/applications/maniphest/controller/descriptiondiff/ManiphestTaskDescriptionDiffController.php b/src/applications/maniphest/controller/descriptiondiff/ManiphestTaskDescriptionDiffController.php deleted file mode 100644 index e9e00eddd4..0000000000 --- a/src/applications/maniphest/controller/descriptiondiff/ManiphestTaskDescriptionDiffController.php +++ /dev/null @@ -1,29 +0,0 @@ -getRequest()->getStr('ref'); - $this->setTransactionID($ref); - } -} diff --git a/src/applications/maniphest/controller/descriptiondiff/__init__.php b/src/applications/maniphest/controller/descriptiondiff/__init__.php deleted file mode 100644 index 7e1ba1bbda..0000000000 --- a/src/applications/maniphest/controller/descriptiondiff/__init__.php +++ /dev/null @@ -1,12 +0,0 @@ -getRequest(); $description = $request->getStr('description'); $engine = PhabricatorMarkupEngine::newManiphestMarkupEngine(); $content = '
    '. '
    '. $engine->markupText($description). '
    '. '
    '; return id(new AphrontAjaxResponse()) ->setContent($content); } } diff --git a/src/applications/maniphest/controller/taskdetail/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/taskdetail/ManiphestTaskDetailController.php index f0b237be66..7341064208 100644 --- a/src/applications/maniphest/controller/taskdetail/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/taskdetail/ManiphestTaskDetailController.php @@ -1,512 +1,512 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $e_title = null; $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); $task = id(new ManiphestTask())->load($this->id); if (!$task) { return new Aphront404Response(); } $workflow = $request->getStr('workflow'); $parent_task = null; if ($workflow && is_numeric($workflow)) { $parent_task = id(new ManiphestTask())->load($workflow); } $transactions = id(new ManiphestTransaction())->loadAllWhere( 'taskID = %d ORDER BY id ASC', $task->getID()); $phids = array(); foreach ($transactions as $transaction) { foreach ($transaction->extractPHIDs() as $phid) { $phids[$phid] = true; } } foreach ($task->getCCPHIDs() as $phid) { $phids[$phid] = true; } foreach ($task->getProjectPHIDs() as $phid) { $phids[$phid] = 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 = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $engine = PhabricatorMarkupEngine::newManiphestMarkupEngine(); $dict = array(); $dict['Status'] = ''. ManiphestTaskStatus::getTaskStatusFullName($task->getStatus()). ''; $dict['Assigned To'] = $task->getOwnerPHID() ? $handles[$task->getOwnerPHID()]->renderLink() : 'None'; $dict['Priority'] = ManiphestTaskPriority::getTaskPriorityName( $task->getPriority()); $cc = $task->getCCPHIDs(); if ($cc) { $cc_links = array(); foreach ($cc as $phid) { $cc_links[] = $handles[$phid]->renderLink(); } $dict['CC'] = implode(', ', $cc_links); } else { $dict['CC'] = 'None'; } $dict['Author'] = $handles[$task->getAuthorPHID()]->renderLink(); $source = $task->getOriginalEmailSource(); if ($source) { $subject = '[T'.$task->getID().'] '.$task->getTitle(); $dict['From Email'] = phutil_render_tag( 'a', array( 'href' => 'mailto:'.$source.'?subject='.$subject ), phutil_escape_html($source)); } $projects = $task->getProjectPHIDs(); if ($projects) { $project_links = array(); foreach ($projects as $phid) { $project_links[] = $handles[$phid]->renderLink(); } $dict['Projects'] = implode(', ', $project_links); } else { $dict['Projects'] = 'None'; } $extensions = ManiphestTaskExtensions::newExtensions(); $aux_fields = $extensions->getAuxiliaryFieldSpecifications(); if ($aux_fields) { $task->loadAndAttachAuxiliaryAttributes(); foreach ($aux_fields as $aux_field) { $aux_key = $aux_field->getAuxiliaryKey(); $aux_field->setValue($task->getAuxiliaryAttribute($aux_key)); $value = $aux_field->renderForDetailView(); if (strlen($value)) { $dict[$aux_field->getLabel()] = $value; } } } $dtasks = idx($attached, PhabricatorPHIDConstants::PHID_TYPE_TASK); if ($dtasks) { $dtask_links = array(); foreach ($dtasks as $dtask => $info) { $dtask_links[] = $handles[$dtask]->renderLink(); } $dtask_links = implode('
    ', $dtask_links); $dict['Depends On'] = $dtask_links; } $revs = idx($attached, PhabricatorPHIDConstants::PHID_TYPE_DREV); if ($revs) { $rev_links = array(); foreach ($revs as $rev => $info) { $rev_links[] = $handles[$rev]->renderLink(); } $rev_links = implode('
    ', $rev_links); $dict['Revisions'] = $rev_links; } $file_infos = idx($attached, PhabricatorPHIDConstants::PHID_TYPE_FILE); if ($file_infos) { $file_phids = array_keys($file_infos); $files = id(new PhabricatorFile())->loadAllWhere( 'phid IN (%Ls)', $file_phids); $views = array(); foreach ($files as $file) { $view = new AphrontFilePreviewView(); $view->setFile($file); $views[] = $view->render(); } $dict['Files'] = implode('', $views); } $dict['Description'] = '
    '. '
    '. $engine->markupText($task->getDescription()). '
    '. '
    '; require_celerity_resource('maniphest-task-detail-css'); $table = array(); foreach ($dict as $key => $value) { $table[] = ''. ''.phutil_escape_html($key).':'. ''.$value.''. ''; } $table = ''. implode("\n", $table). '
    '; $context_bar = null; if ($parent_task) { $context_bar = new AphrontContextBarView(); $context_bar->addButton( phutil_render_tag( 'a', array( 'href' => '/maniphest/task/create/?parent='.$parent_task->getID(), 'class' => 'green button', ), 'Create Another Subtask')); $context_bar->appendChild( 'Created a subtask of '. $handles[$parent_task->getPHID()]->renderLink(). ''); } else if ($workflow == 'create') { $context_bar = new AphrontContextBarView(); $context_bar->addButton(''); $context_bar->addButton( phutil_render_tag( 'a', array( 'href' => '/maniphest/task/create/?template='.$task->getID(), 'class' => 'green button', ), 'Similar Task')); $context_bar->addButton( phutil_render_tag( 'a', array( 'href' => '/maniphest/task/create/', 'class' => 'green button', ), 'Empty Task')); $context_bar->appendChild('New task created.'); } $actions = array(); $action = new AphrontHeadsupActionView(); $action->setName('Edit Task'); $action->setURI('/maniphest/task/edit/'.$task->getID().'/'); $action->setClass('action-edit'); $actions[] = $action; require_celerity_resource('phabricator-object-selector-css'); require_celerity_resource('javelin-behavior-phabricator-object-selector'); $action = new AphrontHeadsupActionView(); $action->setName('Merge Duplicates'); $action->setURI('/search/attach/'.$task->getPHID().'/TASK/merge/'); $action->setWorkflow(true); $action->setClass('action-merge'); $actions[] = $action; $action = new AphrontHeadsupActionView(); $action->setName('Create Subtask'); $action->setURI('/maniphest/task/create/?parent='.$task->getID()); $action->setClass('action-branch'); $actions[] = $action; $action = new AphrontHeadsupActionView(); $action->setName('Edit Dependencies'); $action->setURI('/search/attach/'.$task->getPHID().'/TASK/dependencies/'); $action->setWorkflow(true); $action->setClass('action-dependencies'); $actions[] = $action; $action = new AphrontHeadsupActionView(); $action->setName('Edit Differential Revisions'); $action->setURI('/search/attach/'.$task->getPHID().'/DREV/'); $action->setWorkflow(true); $action->setClass('action-attach'); $actions[] = $action; $action_list = new AphrontHeadsupActionListView(); $action_list->setActions($actions); $panel = '
    '. $action_list->render(). '
    '. '

    '. ''. phutil_escape_html('T'.$task->getID()). ''. ' '. phutil_escape_html($task->getTitle()). '

    '. $table. '
    '. '
    '; $transaction_types = ManiphestTransactionType::getTransactionTypeMap(); $resolution_types = ManiphestTaskStatus::getTaskStatusMap(); if ($task->getStatus() == ManiphestTaskStatus::STATUS_OPEN) { $resolution_types = array_select_keys( $resolution_types, array( ManiphestTaskStatus::STATUS_CLOSED_RESOLVED, ManiphestTaskStatus::STATUS_CLOSED_WONTFIX, ManiphestTaskStatus::STATUS_CLOSED_INVALID, ManiphestTaskStatus::STATUS_CLOSED_SPITE, )); } else { $resolution_types = array( ManiphestTaskStatus::STATUS_OPEN => 'Reopened', ); $transaction_types[ManiphestTransactionType::TYPE_STATUS] = 'Reopen Task'; unset($transaction_types[ManiphestTransactionType::TYPE_PRIORITY]); unset($transaction_types[ManiphestTransactionType::TYPE_OWNER]); } $default_claim = array( $user->getPHID() => $user->getUsername().' ('.$user->getRealName().')', ); $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $user->getPHID(), $task->getPHID()); if ($draft) { $draft_text = $draft->getDraft(); } else { $draft_text = null; } $panel_id = celerity_generate_unique_node_id(); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); if ($is_serious) { // Prevent tasks from being closed "out of spite" in serious business // installs. unset($resolution_types[ManiphestTaskStatus::STATUS_CLOSED_SPITE]); } $remarkup_href = PhabricatorEnv::getDoclink( 'article/Remarkup_Reference.html'); $comment_form = new AphrontFormView(); $comment_form ->setUser($user) ->setAction('/maniphest/transaction/save/') ->setEncType('multipart/form-data') ->addHiddenInput('taskID', $task->getID()) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Action') ->setName('action') ->setOptions($transaction_types) ->setID('transaction-action')) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Resolution') ->setName('resolution') ->setControlID('resolution') ->setControlStyle('display: none') ->setOptions($resolution_types)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('Assign To') ->setName('assign_to') ->setControlID('assign_to') ->setControlStyle('display: none') ->setID('assign-tokenizer') ->setDisableBehavior(true)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('CCs') ->setName('ccs') ->setControlID('ccs') ->setControlStyle('display: none') ->setID('cc-tokenizer') ->setDisableBehavior(true)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Priority') ->setName('priority') ->setOptions($priority_map) ->setControlID('priority') ->setControlStyle('display: none') ->setValue($task->getPriority())) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('Projects') ->setName('projects') ->setControlID('projects') ->setControlStyle('display: none') ->setID('projects-tokenizer') ->setDisableBehavior(true)) ->appendChild( id(new AphrontFormFileControl()) ->setLabel('File') ->setName('file') ->setControlID('file') ->setControlStyle('display: none')) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Comments') ->setName('comments') ->setValue($draft_text) ->setCaption( phutil_render_tag( 'a', array( 'href' => $remarkup_href, 'tabindex' => '-1', 'target' => '_blank', ), 'Formatting Reference')) ->setID('transaction-comments')) ->appendChild( id(new AphrontFormDragAndDropUploadControl()) ->setLabel('Attached Files') ->setName('files') ->setDragAndDropTarget($panel_id) ->setActivatedClass('aphront-panel-view-drag-and-drop')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue($is_serious ? 'Submit' : 'Avast!')); $control_map = array( ManiphestTransactionType::TYPE_STATUS => 'resolution', ManiphestTransactionType::TYPE_OWNER => 'assign_to', ManiphestTransactionType::TYPE_CCS => 'ccs', ManiphestTransactionType::TYPE_PRIORITY => 'priority', ManiphestTransactionType::TYPE_PROJECTS => 'projects', ManiphestTransactionType::TYPE_ATTACH => 'file', ); Javelin::initBehavior('maniphest-transaction-controls', array( 'select' => 'transaction-action', 'controlMap' => $control_map, 'tokenizers' => array( ManiphestTransactionType::TYPE_PROJECTS => array( 'id' => 'projects-tokenizer', 'src' => '/typeahead/common/projects/', 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), ), ManiphestTransactionType::TYPE_OWNER => array( 'id' => 'assign-tokenizer', 'src' => '/typeahead/common/users/', 'value' => $default_claim, 'limit' => 1, 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), ), ManiphestTransactionType::TYPE_CCS => array( 'id' => 'cc-tokenizer', 'src' => '/typeahead/common/mailable/', 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), ), ), )); Javelin::initBehavior('maniphest-transaction-preview', array( 'uri' => '/maniphest/transaction/preview/'.$task->getID().'/', 'preview' => 'transaction-preview', 'comments' => 'transaction-comments', 'action' => 'transaction-action', 'map' => $control_map, )); $comment_panel = new AphrontPanelView(); $comment_panel->appendChild($comment_form); $comment_panel->setID($panel_id); $comment_panel->addClass('aphront-panel-accent'); $comment_panel->setHeader($is_serious ? 'Add Comment' : 'Weigh In'); $preview_panel = '
    Loading preview...
    '; $transaction_view = new ManiphestTransactionListView(); $transaction_view->setTransactions($transactions); $transaction_view->setHandles($handles); $transaction_view->setUser($user); $transaction_view->setAuxiliaryFields($aux_fields); $transaction_view->setMarkupEngine($engine); return $this->buildStandardPageResponse( array( $context_bar, $panel, $transaction_view, $comment_panel, $preview_panel, ), array( 'title' => 'T'.$task->getID().' '.$task->getTitle(), )); } } diff --git a/src/applications/maniphest/controller/taskedit/ManiphestTaskEditController.php b/src/applications/maniphest/controller/taskedit/ManiphestTaskEditController.php index 38dfdaf757..efe62ce180 100644 --- a/src/applications/maniphest/controller/taskedit/ManiphestTaskEditController.php +++ b/src/applications/maniphest/controller/taskedit/ManiphestTaskEditController.php @@ -1,541 +1,541 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $files = array(); $parent_task = null; $template_id = null; if ($this->id) { $task = id(new ManiphestTask())->load($this->id); if (!$task) { return new Aphront404Response(); } } else { $task = new ManiphestTask(); $task->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE); $task->setAuthorPHID($user->getPHID()); // These allow task creation with defaults. if (!$request->isFormPost()) { $task->setTitle($request->getStr('title')); $default_projects = $request->getStr('projects'); if ($default_projects) { $task->setProjectPHIDs(explode(';', $default_projects)); } } $file_phids = $request->getArr('files', array()); if (!$file_phids) { // Allow a single 'file' key instead, mostly since Mac OS X urlencodes // square brackets in URLs when passed to 'open', so you can't 'open' // a URL like '?files[]=xyz' and have PHP interpret it correctly. $phid = $request->getStr('file'); if ($phid) { $file_phids = array($phid); } } if ($file_phids) { $files = id(new PhabricatorFile())->loadAllWhere( 'phid IN (%Ls)', $file_phids); } $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 ($parent_id) { $parent_task = id(new ManiphestTask())->load($parent_id); } } $errors = array(); $e_title = true; $extensions = ManiphestTaskExtensions::newExtensions(); $aux_fields = $extensions->getAuxiliaryFieldSpecifications(); if ($request->isFormPost()) { $changes = array(); $new_title = $request->getStr('title'); $new_desc = $request->getStr('description'); $new_status = $request->getStr('status'); $workflow = ''; if ($task->getID()) { if ($new_title != $task->getTitle()) { $changes[ManiphestTransactionType::TYPE_TITLE] = $new_title; } if ($new_desc != $task->getDescription()) { $changes[ManiphestTransactionType::TYPE_DESCRIPTION] = $new_desc; } if ($new_status != $task->getStatus()) { $changes[ManiphestTransactionType::TYPE_STATUS] = $new_status; } } else { $task->setTitle($new_title); $task->setDescription($new_desc); $changes[ManiphestTransactionType::TYPE_STATUS] = ManiphestTaskStatus::STATUS_OPEN; $workflow = 'create'; } $owner_tokenizer = $request->getArr('assigned_to'); $owner_phid = reset($owner_tokenizer); if (!strlen($new_title)) { $e_title = 'Required'; $errors[] = 'Title is required.'; } foreach ($aux_fields as $aux_field) { $aux_field->setValueFromRequest($request); if ($aux_field->isRequired() && !strlen($aux_field->getValue())) { $errors[] = $aux_field->getLabel() . ' is required.'; $aux_field->setError('Required'); } if (strlen($aux_field->getValue())) { try { $aux_field->validate(); } catch (Exception $e) { $errors[] = $e->getMessage(); $aux_field->setError('Invalid'); } } } if ($errors) { $task->setPriority($request->getInt('priority')); $task->setOwnerPHID($owner_phid); $task->setCCPHIDs($request->getArr('cc')); $task->setProjectPHIDs($request->getArr('projects')); } else { if ($request->getInt('priority') != $task->getPriority()) { $changes[ManiphestTransactionType::TYPE_PRIORITY] = $request->getInt('priority'); } if ($owner_phid != $task->getOwnerPHID()) { $changes[ManiphestTransactionType::TYPE_OWNER] = $owner_phid; } if ($request->getArr('cc') != $task->getCCPHIDs()) { $changes[ManiphestTransactionType::TYPE_CCS] = $request->getArr('cc'); } $new_proj_arr = $request->getArr('projects'); $new_proj_arr = array_values($new_proj_arr); sort($new_proj_arr); $cur_proj_arr = $task->getProjectPHIDs(); $cur_proj_arr = array_values($cur_proj_arr); sort($cur_proj_arr); if ($new_proj_arr != $cur_proj_arr) { $changes[ManiphestTransactionType::TYPE_PROJECTS] = $new_proj_arr; } if ($files) { $file_map = mpull($files, 'getPHID'); $file_map = array_fill_keys($file_map, array()); $changes[ManiphestTransactionType::TYPE_ATTACH] = array( PhabricatorPHIDConstants::PHID_TYPE_FILE => $file_map, ); } $content_source = PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_WEB, array( 'ip' => $request->getRemoteAddr(), )); $template = new ManiphestTransaction(); $template->setAuthorPHID($user->getPHID()); $template->setContentSource($content_source); $transactions = array(); foreach ($changes as $type => $value) { $transaction = clone $template; $transaction->setTransactionType($type); $transaction->setNewValue($value); $transactions[] = $transaction; } if ($aux_fields) { $task->loadAndAttachAuxiliaryAttributes(); foreach ($aux_fields as $aux_field) { $transaction = clone $template; $transaction->setTransactionType( ManiphestTransactionType::TYPE_AUXILIARY); $aux_key = $aux_field->getAuxiliaryKey(); $transaction->setMetadataValue('aux:key', $aux_key); $transaction->setNewValue($aux_field->getValueForStorage()); $transactions[] = $transaction; } } if ($transactions) { $event = new PhabricatorEvent( PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK, array( 'task' => $task, 'new' => !$task->getID(), 'transactions' => $transactions, )); $event->setUser($user); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); $task = $event->getValue('task'); $transactions = $event->getValue('transactions'); $editor = new ManiphestTransactionEditor(); $editor->setAuxiliaryFields($aux_fields); $editor->applyTransactions($task, $transactions); } if ($parent_task) { $type_task = PhabricatorPHIDConstants::PHID_TYPE_TASK; // NOTE: It's safe to simply apply this transaction without doing // cycle detection because we know the new task has no children. $new_value = $parent_task->getAttached(); $new_value[$type_task][$task->getPHID()] = array(); $parent_xaction = clone $template; $attach_type = ManiphestTransactionType::TYPE_ATTACH; $parent_xaction->setTransactionType($attach_type); $parent_xaction->setNewValue($new_value); $editor = new ManiphestTransactionEditor(); $editor->setAuxiliaryFields($aux_fields); $editor->applyTransactions($parent_task, array($parent_xaction)); $workflow = $parent_task->getID(); } $redirect_uri = '/T'.$task->getID(); if ($workflow) { $redirect_uri .= '?workflow='.$workflow; } return id(new AphrontRedirectResponse()) ->setURI($redirect_uri); } } else { if (!$task->getID()) { $task->setCCPHIDs(array( $user->getPHID(), )); if ($template_id) { $template_task = id(new ManiphestTask())->load($template_id); if ($template_task) { $task->setCCPHIDs($template_task->getCCPHIDs()); $task->setProjectPHIDs($template_task->getProjectPHIDs()); $task->setOwnerPHID($template_task->getOwnerPHID()); } } } } $phids = array_merge( array($task->getOwnerPHID()), $task->getCCPHIDs(), $task->getProjectPHIDs()); if ($parent_task) { $phids[] = $parent_task->getPHID(); } $phids = array_filter($phids); $phids = array_unique($phids); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles($phids); $tvalues = mpull($handles, 'getFullName', 'getPHID'); $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setErrors($errors); $error_view->setTitle('Form Errors'); } $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); if ($task->getOwnerPHID()) { $assigned_value = array( $task->getOwnerPHID() => $handles[$task->getOwnerPHID()]->getFullName(), ); } else { $assigned_value = array(); } if ($task->getCCPHIDs()) { $cc_value = array_select_keys($tvalues, $task->getCCPHIDs()); } else { $cc_value = array(); } if ($task->getProjectPHIDs()) { $projects_value = array_select_keys($tvalues, $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 = 'Save Task'; $header_name = 'Edit Task'; } else if ($parent_task) { $cancel_uri = '/T'.$parent_task->getID(); $button_name = 'Create Task'; $header_name = 'Create New Subtask'; } else { $button_name = 'Create Task'; $header_name = 'Create New Task'; } require_celerity_resource('maniphest-task-edit-css'); $project_tokenizer_id = celerity_generate_unique_node_id(); $form = new AphrontFormView(); $form ->setUser($user) ->setAction($request->getRequestURI()->getPath()) ->addHiddenInput('template', $template_id); if ($parent_task) { $form ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Parent Task') ->setValue($handles[$parent_task->getPHID()]->getFullName())) ->addHiddenInput('parent', $parent_task->getID()); } $form ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Title') ->setName('title') ->setError($e_title) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) ->setValue($task->getTitle())); if ($task->getID()) { // Only show this in "edit" mode, not "create" mode, since creating a // non-open task is kind of silly and it would just clutter up the // "create" interface. $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Status') ->setName('status') ->setValue($task->getStatus()) ->setOptions(ManiphestTaskStatus::getTaskStatusMap())); } $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('Assigned To') ->setName('assigned_to') ->setValue($assigned_value) ->setDatasource('/typeahead/common/users/') ->setLimit(1)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('CC') ->setName('cc') ->setValue($cc_value) ->setDatasource('/typeahead/common/mailable/')) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Priority') ->setName('priority') ->setOptions($priority_map) ->setValue($task->getPriority())) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('Projects') ->setName('projects') ->setValue($projects_value) ->setID($project_tokenizer_id) ->setCaption( javelin_render_tag( 'a', array( 'href' => '/project/create/', 'mustcapture' => true, 'sigil' => 'project-create', ), 'Create New Project')) ->setDatasource('/typeahead/common/projects/')); if ($aux_fields) { if (!$request->isFormPost()) { $task->loadAndAttachAuxiliaryAttributes(); foreach ($aux_fields as $aux_field) { $aux_key = $aux_field->getAuxiliaryKey(); $value = $task->getAuxiliaryAttribute($aux_key); $aux_field->setValueFromStorage($value); } } foreach ($aux_fields as $aux_field) { if ($aux_field->isRequired() && !$aux_field->getError() && !$aux_field->getValue()) { $aux_field->setError(true); } $aux_control = $aux_field->renderControl(); $form->appendChild($aux_control); } } require_celerity_resource('aphront-error-view-css'); Javelin::initBehavior('maniphest-project-create', array( 'tokenizerID' => $project_tokenizer_id, )); if ($files) { $file_display = array(); foreach ($files as $file) { $file_display[] = phutil_escape_html($file->getName()); } $file_display = implode('
    ', $file_display); $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel('Files') ->setValue($file_display)); foreach ($files as $ii => $file) { $form->addHiddenInput('files['.$ii.']', $file->getPHID()); } } $email_create = PhabricatorEnv::getEnvConfig( 'metamta.maniphest.public-create-email'); $email_hint = null; if (!$task->getID() && $email_create) { $email_hint = 'You can also create tasks by sending an email to: '. ''.phutil_escape_html($email_create).''; } $panel_id = celerity_generate_unique_node_id(); $form ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Description') ->setName('description') ->setID('description-textarea') ->setCaption($email_hint) ->setValue($task->getDescription())); if (!$task->getID()) { $form ->appendChild( id(new AphrontFormDragAndDropUploadControl()) ->setLabel('Attached Files') ->setName('files') ->setDragAndDropTarget($panel_id) ->setActivatedClass('aphront-panel-view-drag-and-drop')); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($button_name)); $panel = new AphrontPanelView(); $panel->setWidth(AphrontPanelView::WIDTH_FULL); $panel->setHeader($header_name); $panel->setID($panel_id); $panel->appendChild($form); $description_preview_panel = '
    Description Preview
    Loading preview...
    '; Javelin::initBehavior( 'maniphest-description-preview', array( 'preview' => 'description-preview', 'textarea' => 'description-textarea', 'uri' => '/maniphest/task/descriptionpreview/', )); return $this->buildStandardPageResponse( array( $error_view, $panel, $description_preview_panel ), array( 'title' => $header_name, )); } } diff --git a/src/applications/maniphest/controller/tasklist/ManiphestTaskListController.php b/src/applications/maniphest/controller/tasklist/ManiphestTaskListController.php index a5cab5a05b..326c2904c2 100644 --- a/src/applications/maniphest/controller/tasklist/ManiphestTaskListController.php +++ b/src/applications/maniphest/controller/tasklist/ManiphestTaskListController.php @@ -1,602 +1,602 @@ view = idx($data, 'view'); } private function getArrToStrList($key) { $arr = $this->getRequest()->getArr($key); $arr = implode(',', $arr); return nonempty($arr, null); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if ($request->isFormPost()) { // Redirect to GET so URIs can be copy/pasted. $task_ids = $request->getStr('set_tasks'); $task_ids = nonempty($task_ids, null); $uri = $request->getRequestURI() ->alter('users', $this->getArrToStrList('set_users')) ->alter('projects', $this->getArrToStrList('set_projects')) ->alter('xprojects', $this->getArrToStrList('set_xprojects')) ->alter('owners', $this->getArrToStrList('set_owners')) ->alter('authors', $this->getArrToStrList('set_authors')) ->alter('tasks', $task_ids); return id(new AphrontRedirectResponse())->setURI($uri); } $nav = $this->buildBaseSideNav(); $this->view = $nav->selectFilter($this->view, 'action'); $has_filter = array( 'action' => true, 'created' => true, 'subscribed' => true, 'triage' => true, ); list($status_map, $status_control) = $this->renderStatusLinks(); list($grouping, $group_control) = $this->renderGroupLinks(); list($order, $order_control) = $this->renderOrderLinks(); $user_phids = $request->getStrList( 'users', array($user->getPHID())); $project_phids = $request->getStrList('projects'); $exclude_project_phids = $request->getStrList('xprojects'); $task_ids = $request->getStrList('tasks'); $owner_phids = $request->getStrList('owners'); $author_phids = $request->getStrList('authors'); $page = $request->getInt('page'); $page_size = self::DEFAULT_PAGE_SIZE; $query = new PhabricatorSearchQuery(); $query->setQuery('<>'); $query->setParameters( array( 'view' => $this->view, 'userPHIDs' => $user_phids, 'projectPHIDs' => $project_phids, 'excludeProjectPHIDs' => $exclude_project_phids, 'ownerPHIDs' => $owner_phids, 'authorPHIDs' => $author_phids, 'taskIDs' => $task_ids, 'group' => $grouping, 'order' => $order, 'offset' => $page, 'limit' => $page_size, 'status' => $status_map, )); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $query->save(); unset($unguarded); list($tasks, $handles, $total_count) = self::loadTasks($query); $form = id(new AphrontFormView()) ->setUser($user) ->setAction($request->getRequestURI()); if (isset($has_filter[$this->view])) { $tokens = array(); foreach ($user_phids as $phid) { $tokens[$phid] = $handles[$phid]->getFullName(); } $form->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/searchowner/') ->setName('set_users') ->setLabel('Users') ->setValue($tokens)); } if ($this->view == 'custom') { $form->appendChild( id(new AphrontFormTextControl()) ->setName('set_tasks') ->setLabel('Task IDs') ->setValue(join(',', $task_ids)) ); $tokens = array(); foreach ($owner_phids as $phid) { $tokens[$phid] = $handles[$phid]->getFullName(); } $form->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/searchowner/') ->setName('set_owners') ->setLabel('Owners') ->setValue($tokens)); $tokens = array(); foreach ($author_phids as $phid) { $tokens[$phid] = $handles[$phid]->getFullName(); } $form->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('set_authors') ->setLabel('Authors') ->setValue($tokens)); } $tokens = array(); foreach ($project_phids as $phid) { $tokens[$phid] = $handles[$phid]->getFullName(); } $form->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/searchproject/') ->setName('set_projects') ->setLabel('Projects') ->setValue($tokens)); if ($this->view == 'custom') { $tokens = array(); foreach ($exclude_project_phids as $phid) { $tokens[$phid] = $handles[$phid]->getFullName(); } $form->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/projects/') ->setName('set_xprojects') ->setLabel('Exclude Projects') ->setValue($tokens)); } $form ->appendChild($status_control) ->appendChild($group_control) ->appendChild($order_control); $form->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Filter Tasks')); $create_uri = new PhutilURI('/maniphest/task/create/'); if ($project_phids) { // If we have project filters selected, use them as defaults for task // creation. $create_uri->setQueryParam('projects', implode(';', $project_phids)); } $filter = new AphrontListFilterView(); $filter->addButton( phutil_render_tag( 'a', array( 'href' => (string)$create_uri, 'class' => 'green button', ), 'Create New Task')); $filter->appendChild($form); $nav->appendChild($filter); $have_tasks = false; foreach ($tasks as $group => $list) { if (count($list)) { $have_tasks = true; break; } } require_celerity_resource('maniphest-task-summary-css'); $list_container = new AphrontNullView(); $list_container->appendChild('
    '); if (!$have_tasks) { $list_container->appendChild( '

    '. 'No matching tasks.'. '

    '); } else { $pager = new AphrontPagerView(); $pager->setURI($request->getRequestURI(), 'page'); $pager->setPageSize($page_size); $pager->setOffset($page); $pager->setCount($total_count); $cur = ($pager->getOffset() + 1); $max = min($pager->getOffset() + $page_size, $total_count); $tot = $total_count; $cur = number_format($cur); $max = number_format($max); $tot = number_format($tot); $list_container->appendChild( '
    '. "Displaying tasks {$cur} - {$max} of {$tot}.". '
    '); $selector = new AphrontNullView(); foreach ($tasks as $group => $list) { $task_list = new ManiphestTaskListView(); $task_list->setShowBatchControls(true); $task_list->setUser($user); $task_list->setTasks($list); $task_list->setHandles($handles); $count = number_format(count($list)); $selector->appendChild( '

    '. phutil_escape_html($group).' ('.$count.')'. '

    '); $selector->appendChild($task_list); } $selector->appendChild($this->renderBatchEditor($query)); $selector = phabricator_render_form( $user, array( 'method' => 'POST', 'action' => '/maniphest/batch/', ), $selector->render()); $list_container->appendChild($selector); $list_container->appendChild($pager); } $list_container->appendChild('
    '); $nav->appendChild($list_container); return $this->buildStandardPageResponse( $nav, array( 'title' => 'Task List', )); } public static function loadTasks(PhabricatorSearchQuery $search_query) { $user_phids = $search_query->getParameter('userPHIDs', array()); $project_phids = $search_query->getParameter('projectPHIDs', array()); $task_ids = $search_query->getParameter('taskIDs', array()); $xproject_phids = $search_query->getParameter( 'excludeProjectPHIDs', array()); $owner_phids = $search_query->getParameter('ownerPHIDs', array()); $author_phids = $search_query->getParameter('authorPHIDs', array()); $query = new ManiphestTaskQuery(); $query->withProjects($project_phids); $query->withTaskIDs($task_ids); if ($xproject_phids) { $query->withoutProjects($xproject_phids); } if ($owner_phids) { $query->withOwners($owner_phids); } if ($author_phids) { $query->withAuthors($author_phids); } $status = $search_query->getParameter('status', 'all'); if (!empty($status['open']) && !empty($status['closed'])) { $query->withStatus(ManiphestTaskQuery::STATUS_ANY); } else if (!empty($status['open'])) { $query->withStatus(ManiphestTaskQuery::STATUS_OPEN); } else { $query->withStatus(ManiphestTaskQuery::STATUS_CLOSED); } switch ($search_query->getParameter('view')) { case 'action': $query->withOwners($user_phids); break; case 'created': $query->withAuthors($user_phids); break; case 'subscribed': $query->withSubscribers($user_phids); break; case 'triage': $query->withOwners($user_phids); $query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE); break; case 'alltriage': $query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE); break; case 'all': break; } $order_map = array( 'priority' => ManiphestTaskQuery::ORDER_PRIORITY, 'created' => ManiphestTaskQuery::ORDER_CREATED, ); $query->setOrderBy( idx( $order_map, $search_query->getParameter('order'), ManiphestTaskQuery::ORDER_MODIFIED)); $group_map = array( 'priority' => ManiphestTaskQuery::GROUP_PRIORITY, 'owner' => ManiphestTaskQuery::GROUP_OWNER, 'status' => ManiphestTaskQuery::GROUP_STATUS, ); $query->setGroupBy( idx( $group_map, $search_query->getParameter('group'), ManiphestTaskQuery::GROUP_NONE)); $query->setCalculateRows(true); $query->setLimit($search_query->getParameter('limit')); $query->setOffset($search_query->getParameter('offset')); $data = $query->execute(); $total_row_count = $query->getRowCount(); $handle_phids = mpull($data, 'getOwnerPHID'); $handle_phids = array_merge( $handle_phids, $project_phids, $user_phids, $xproject_phids, $owner_phids, $author_phids); $handles = id(new PhabricatorObjectHandleData($handle_phids)) ->loadHandles(); switch ($search_query->getParameter('group')) { case 'priority': $data = mgroup($data, 'getPriority'); krsort($data); // If we have invalid priorities, they'll all map to "???". Merge // arrays to prevent them from overwriting each other. $out = array(); foreach ($data as $pri => $tasks) { $out[ManiphestTaskPriority::getTaskPriorityName($pri)][] = $tasks; } foreach ($out as $pri => $tasks) { $out[$pri] = array_mergev($tasks); } $data = $out; break; case 'status': $data = mgroup($data, 'getStatus'); ksort($data); $out = array(); foreach ($data as $status => $tasks) { $out[ManiphestTaskStatus::getTaskStatusFullName($status)] = $tasks; } $data = $out; break; case 'owner': $data = mgroup($data, 'getOwnerPHID'); $out = array(); foreach ($data as $phid => $tasks) { if ($phid) { $out[$handles[$phid]->getFullName()] = $tasks; } else { $out['Unassigned'] = $tasks; } } if (isset($out['Unassigned'])) { // If any tasks are unassigned, move them to the front of the list. $data = array('Unassigned' => $out['Unassigned']) + $out; } else { $data = $out; } ksort($data); break; default: $data = array( 'Tasks' => $data, ); break; } return array($data, $handles, $total_row_count); } public function renderStatusLinks() { $request = $this->getRequest(); $statuses = array( 'o' => array('open' => true), 'c' => array('closed' => true), 'oc' => array('open' => true, 'closed' => true), ); $status = $request->getStr('s'); if (empty($statuses[$status])) { $status = 'o'; } $status_control = id(new AphrontFormToggleButtonsControl()) ->setLabel('Status') ->setValue($status) ->setBaseURI($request->getRequestURI(), 's') ->setButtons( array( 'o' => 'Open', 'c' => 'Closed', 'oc' => 'All', )); return array($statuses[$status], $status_control); } public function renderOrderLinks() { $request = $this->getRequest(); $order = $request->getStr('o'); $orders = array( 'u' => 'updated', 'c' => 'created', 'p' => 'priority', ); if (empty($orders[$order])) { $order = 'p'; } $order_by = $orders[$order]; $order_control = id(new AphrontFormToggleButtonsControl()) ->setLabel('Order') ->setValue($order) ->setBaseURI($request->getRequestURI(), 'o') ->setButtons( array( 'p' => 'Priority', 'u' => 'Updated', 'c' => 'Created', )); return array($order_by, $order_control); } public function renderGroupLinks() { $request = $this->getRequest(); $group = $request->getStr('g'); $groups = array( 'n' => 'none', 'p' => 'priority', 's' => 'status', 'o' => 'owner', ); if (empty($groups[$group])) { $group = 'p'; } $group_by = $groups[$group]; $group_control = id(new AphrontFormToggleButtonsControl()) ->setLabel('Group') ->setValue($group) ->setBaseURI($request->getRequestURI(), 'g') ->setButtons( array( 'p' => 'Priority', 'o' => 'Owner', 's' => 'Status', 'n' => 'None', )); return array($group_by, $group_control); } private function renderBatchEditor(PhabricatorSearchQuery $search_query) { Javelin::initBehavior( 'maniphest-batch-selector', array( 'selectAll' => 'batch-select-all', 'selectNone' => 'batch-select-none', 'submit' => 'batch-select-submit', 'status' => 'batch-select-status-cell', )); $select_all = javelin_render_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'class' => 'grey button', 'id' => 'batch-select-all', ), 'Select All'); $select_none = javelin_render_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'class' => 'grey button', 'id' => 'batch-select-none', ), 'Clear Selection'); $submit = phutil_render_tag( 'button', array( 'id' => 'batch-select-submit', 'disabled' => 'disabled', 'class' => 'disabled', ), 'Batch Edit Selected Tasks »'); $export = javelin_render_tag( 'a', array( 'href' => '/maniphest/export/'.$search_query->getQueryKey().'/', 'class' => 'grey button', ), 'Export Tasks to Excel...'); return '
    '. '
    Batch Task Editor
    '. ''. ''. ''. ''. ''. ''. ''. '
    '. $select_all. $select_none. ''. $export. ''. '0 Selected Tasks'. ''.$submit.'
    '. ''; } } diff --git a/src/applications/maniphest/controller/transactionpreview/ManiphestTransactionPreviewController.php b/src/applications/maniphest/controller/transactionpreview/ManiphestTransactionPreviewController.php index 190892bbf3..f270f1db33 100644 --- a/src/applications/maniphest/controller/transactionpreview/ManiphestTransactionPreviewController.php +++ b/src/applications/maniphest/controller/transactionpreview/ManiphestTransactionPreviewController.php @@ -1,96 +1,96 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $comments = $request->getStr('comments'); $task = id(new ManiphestTask())->load($this->id); if (!$task) { return new Aphront404Response(); } $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $user->getPHID(), $task->getPHID()); if (!$draft) { $draft = new PhabricatorDraft(); $draft->setAuthorPHID($user->getPHID()); $draft->setDraftKey($task->getPHID()); } $draft->setDraft($comments); $draft->save(); $phids = array($user->getPHID()); $action = $request->getStr('action'); $transaction = new ManiphestTransaction(); $transaction->setAuthorPHID($user->getPHID()); $transaction->setComments($comments); $transaction->setTransactionType($action); $value = $request->getStr('value'); switch ($action) { case ManiphestTransactionType::TYPE_OWNER: if (!$value) { $value = $user->getPHID(); } $phids[] = $value; break; case ManiphestTransactionType::TYPE_PRIORITY: $transaction->setOldValue($task->getPriority()); break; } $transaction->setNewValue($value); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $transactions = array(); $transactions[] = $transaction; $engine = PhabricatorMarkupEngine::newManiphestMarkupEngine(); $transaction_view = new ManiphestTransactionListView(); $transaction_view->setTransactions($transactions); $transaction_view->setHandles($handles); $transaction_view->setUser($user); $transaction_view->setMarkupEngine($engine); $transaction_view->setPreview(true); return id(new AphrontAjaxResponse()) ->setContent($transaction_view->render()); } } diff --git a/src/applications/maniphest/controller/transactionsave/ManiphestTransactionSaveController.php b/src/applications/maniphest/controller/transactionsave/ManiphestTransactionSaveController.php index fde6d26eb9..eb90e74470 100644 --- a/src/applications/maniphest/controller/transactionsave/ManiphestTransactionSaveController.php +++ b/src/applications/maniphest/controller/transactionsave/ManiphestTransactionSaveController.php @@ -1,261 +1,261 @@ getRequest(); $user = $request->getUser(); $task = id(new ManiphestTask())->load($request->getStr('taskID')); if (!$task) { return new Aphront404Response(); } $transactions = array(); $action = $request->getStr('action'); // If we have drag-and-dropped files, attach them first in a separate // transaction. These can come in on any transaction type, which is why we // handle them separately. $files = array(); // Look for drag-and-drop uploads first. $file_phids = $request->getArr('files'); if ($file_phids) { $files = id(new PhabricatorFile())->loadAllWhere( 'phid in (%Ls)', $file_phids); } // This means "attach a file" even though we store other types of data // as 'attached'. if ($action == ManiphestTransactionType::TYPE_ATTACH) { if (!empty($_FILES['file'])) { $err = idx($_FILES['file'], 'error'); if ($err != UPLOAD_ERR_NO_FILE) { $file = PhabricatorFile::newFromPHPUpload( $_FILES['file'], array( 'authorPHID' => $user->getPHID(), )); $files[] = $file; } } } // If we had explicit or drag-and-drop files, create a transaction // for those before we deal with whatever else might have happened. $file_transaction = null; if ($files) { $files = mpull($files, 'getPHID', 'getPHID'); $new = $task->getAttached(); foreach ($files as $phid) { if (empty($new[PhabricatorPHIDConstants::PHID_TYPE_FILE])) { $new[PhabricatorPHIDConstants::PHID_TYPE_FILE] = array(); } $new[PhabricatorPHIDConstants::PHID_TYPE_FILE][$phid] = array(); } $transaction = new ManiphestTransaction(); $transaction ->setAuthorPHID($user->getPHID()) ->setTransactionType(ManiphestTransactionType::TYPE_ATTACH); $transaction->setNewValue($new); $transactions[] = $transaction; $file_transaction = $transaction; } // Compute new CCs added by @mentions. Several things can cause CCs to // be added as side effects: mentions, explicit CCs, users who aren't // CC'd interacting with the task, and ownership changes. We build up a // list of all the CCs and then construct a transaction for them at the // end if necessary. $added_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions( array( $request->getStr('comments'), )); $cc_transaction = new ManiphestTransaction(); $cc_transaction ->setAuthorPHID($user->getPHID()) ->setTransactionType(ManiphestTransactionType::TYPE_CCS); $force_cc_transaction = false; $transaction = new ManiphestTransaction(); $transaction ->setAuthorPHID($user->getPHID()) ->setComments($request->getStr('comments')) ->setTransactionType($action); switch ($action) { case ManiphestTransactionType::TYPE_STATUS: $transaction->setNewValue($request->getStr('resolution')); break; case ManiphestTransactionType::TYPE_OWNER: $assign_to = $request->getArr('assign_to'); $assign_to = reset($assign_to); $transaction->setNewValue($assign_to); break; case ManiphestTransactionType::TYPE_PROJECTS: $projects = $request->getArr('projects'); $projects = array_merge($projects, $task->getProjectPHIDs()); $projects = array_filter($projects); $projects = array_unique($projects); $transaction->setNewValue($projects); break; case ManiphestTransactionType::TYPE_CCS: // Accumulate the new explicit CCs into the array that we'll add in // the CC transaction later. $added_ccs = array_merge($added_ccs, $request->getArr('ccs')); // Transfer any comments over to the CC transaction. $cc_transaction->setComments($transaction->getComments()); // Make sure we include this transaction, even if the user didn't // actually add any CC's, because we'll discard their comment otherwise. $force_cc_transaction = true; // Throw away the primary transaction. $transaction = null; break; case ManiphestTransactionType::TYPE_PRIORITY: $transaction->setNewValue($request->getInt('priority')); break; case ManiphestTransactionType::TYPE_NONE: case ManiphestTransactionType::TYPE_ATTACH: // If we have a file transaction, just get rid of this secondary // transaction and put the comments on it instead. if ($file_transaction) { $file_transaction->setComments($transaction->getComments()); $transaction = null; } break; default: throw new Exception('unknown action'); } if ($transaction) { $transactions[] = $transaction; } // When you interact with a task, we add you to the CC list so you get // further updates, and possibly assign the task to you if you took an // ownership action (closing it) but it's currently unowned. We also move // previous owners to CC if ownership changes. Detect all these conditions // and create side-effect transactions for them. $implicitly_claimed = false; switch ($action) { case ManiphestTransactionType::TYPE_OWNER: if ($task->getOwnerPHID() == $transaction->getNewValue()) { // If this is actually no-op, don't generate the side effect. break; } // Otherwise, when a task is reassigned, move the previous owner to CC. $added_ccs[] = $task->getOwnerPHID(); break; case ManiphestTransactionType::TYPE_STATUS: if (!$task->getOwnerPHID() && $request->getStr('resolution') != ManiphestTaskStatus::STATUS_OPEN) { // Closing an unassigned task. Assign the user as the owner of // this task. $assign = new ManiphestTransaction(); $assign->setAuthorPHID($user->getPHID()); $assign->setTransactionType(ManiphestTransactionType::TYPE_OWNER); $assign->setNewValue($user->getPHID()); $transactions[] = $assign; $implicitly_claimed = true; } break; } $user_owns_task = false; if ($implicitly_claimed) { $user_owns_task = true; } else { if ($action == ManiphestTransactionType::TYPE_OWNER) { if ($transaction->getNewValue() == $user->getPHID()) { $user_owns_task = true; } } else if ($task->getOwnerPHID() == $user->getPHID()) { $user_owns_task = true; } } if (!$user_owns_task) { // If we aren't making the user the new task owner and they aren't the // existing task owner, add them to CC. $added_ccs[] = $user->getPHID(); } if ($added_ccs || $force_cc_transaction) { // We've added CCs, so include a CC transaction. It's safe to do this even // if we're just "adding" CCs which already exist, because the // ManiphestTransactionEditor is smart enough to ignore them. $all_ccs = array_merge($task->getCCPHIDs(), $added_ccs); $cc_transaction->setNewValue($all_ccs); $transactions[] = $cc_transaction; } $content_source = PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_WEB, array( 'ip' => $request->getRemoteAddr(), )); foreach ($transactions as $transaction) { $transaction->setContentSource($content_source); } $event = new PhabricatorEvent( PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK, array( 'task' => $task, 'new' => false, 'transactions' => $transactions, )); $event->setUser($user); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); $task = $event->getValue('task'); $transactions = $event->getValue('transactions'); $editor = new ManiphestTransactionEditor(); $editor->applyTransactions($task, $transactions); $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $user->getPHID(), $task->getPHID()); if ($draft) { $draft->delete(); } return id(new AphrontRedirectResponse()) ->setURI('/T'.$task->getID()); } } diff --git a/src/applications/maniphest/view/transactionlist/ManiphestTransactionListView.php b/src/applications/maniphest/view/transactionlist/ManiphestTransactionListView.php index 66a912d131..417d94f3a6 100644 --- a/src/applications/maniphest/view/transactionlist/ManiphestTransactionListView.php +++ b/src/applications/maniphest/view/transactionlist/ManiphestTransactionListView.php @@ -1,123 +1,123 @@ transactions = $transactions; return $this; } public function setHandles(array $handles) { $this->handles = $handles; return $this; } public function setUser(PhabricatorUser $user) { $this->user = $user; return $this; } public function setMarkupEngine(PhutilMarkupEngine $engine) { $this->markupEngine = $engine; return $this; } public function setPreview($preview) { $this->preview = $preview; return $this; } public function setAuxiliaryFields(array $fields) { $this->auxiliaryFields = $fields; return $this; } public function render() { $views = array(); $last = null; $group = array(); $groups = array(); $has_description_transaction = false; foreach ($this->transactions as $transaction) { if ($transaction->getTransactionType() == ManiphestTransactionType::TYPE_DESCRIPTION) { $has_description_transaction = true; } if ($last === null) { $last = $transaction; $group[] = $transaction; continue; } else if ($last->canGroupWith($transaction)) { $group[] = $transaction; if ($transaction->hasComments()) { $last = $transaction; } } else { $groups[] = $group; $last = $transaction; $group = array($transaction); } } if ($group) { $groups[] = $group; } if ($has_description_transaction) { require_celerity_resource('differential-changeset-view-css'); require_celerity_resource('syntax-highlighting-css'); $whitespace_mode = DifferentialChangesetParser::WHITESPACE_SHOW_ALL; Javelin::initBehavior('differential-show-more', array( - 'uri' => '/maniphest/task/descriptiondiff/', + 'uri' => '/maniphest/task/descriptionchange/', 'whitespace' => $whitespace_mode, )); } $sequence = 1; foreach ($groups as $group) { $view = new ManiphestTransactionDetailView(); $view->setUser($this->user); $view->setAuxiliaryFields($this->auxiliaryFields); $view->setTransactionGroup($group); $view->setHandles($this->handles); $view->setMarkupEngine($this->markupEngine); $view->setPreview($this->preview); $view->setCommentNumber($sequence++); $views[] = $view->render(); } return '
    '. implode("\n", $views). '
    '; } } diff --git a/src/applications/metamta/controller/list/PhabricatorMetaMTAListController.php b/src/applications/metamta/controller/list/PhabricatorMetaMTAListController.php index 325077d5f1..459ebc08db 100644 --- a/src/applications/metamta/controller/list/PhabricatorMetaMTAListController.php +++ b/src/applications/metamta/controller/list/PhabricatorMetaMTAListController.php @@ -1,126 +1,127 @@ getRequest(); $user = $request->getUser(); $offset = $request->getInt('offset', 0); $related_phid = $request->getStr('phid'); $status = $request->getStr('status'); $pager = new AphrontPagerView(); $pager->setOffset($offset); $pager->setURI($request->getRequestURI(), 'offset'); $mail = new PhabricatorMetaMTAMail(); $conn_r = $mail->establishConnection('r'); $wheres = array(); if ($status) { $wheres[] = qsprintf( $conn_r, 'status = %s', $status); } if ($related_phid) { $wheres[] = qsprintf( $conn_r, 'relatedPHID = %s', $related_phid); } if (count($wheres)) { $where_clause = 'WHERE '.implode($wheres, ' AND '); } else { $where_clause = 'WHERE 1 = 1'; } $data = queryfx_all( $conn_r, 'SELECT * FROM %T %Q ORDER BY id DESC LIMIT %d, %d', $mail->getTableName(), $where_clause, $pager->getOffset(), $pager->getPageSize() + 1); $data = $pager->sliceResults($data); $mails = $mail->loadAllFromArray($data); // Render the details table. $rows = array(); foreach ($mails as $mail) { $rows[] = array( PhabricatorMetaMTAMail::getReadableStatus($mail->getStatus()), $mail->getRetryCount(), ($mail->getNextRetry() - time()).' s', phabricator_datetime($mail->getDateCreated(), $user), (time() - $mail->getDateModified()).' s', phutil_escape_html($mail->getSubject()), phutil_render_tag( 'a', array( 'class' => 'button small grey', 'href' => '/mail/view/'.$mail->getID().'/', ), 'View'), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Status', 'Retry', 'Next', 'Created', 'Updated', 'Subject', '', )); $table->setColumnClasses( array( null, null, null, null, null, 'wide', 'action', )); // Render the whole page. $panel = new AphrontPanelView(); $panel->appendChild($table); $panel->setHeader('MetaMTA Messages'); if ($user->getIsAdmin()) { $panel->setCreateButton('Send New Test Message', '/mail/send/'); } $panel->appendChild($pager); return $this->buildStandardPageResponse( $panel, array( 'title' => 'MetaMTA', 'tab' => 'queue', )); } } diff --git a/src/applications/metamta/controller/mailinglistedit/PhabricatorMetaMTAMailingListEditController.php b/src/applications/metamta/controller/mailinglistedit/PhabricatorMetaMTAMailingListEditController.php index 3d40143bf5..b90dc60a20 100644 --- a/src/applications/metamta/controller/mailinglistedit/PhabricatorMetaMTAMailingListEditController.php +++ b/src/applications/metamta/controller/mailinglistedit/PhabricatorMetaMTAMailingListEditController.php @@ -1,152 +1,152 @@ id = idx($data, 'id'); } public function processRequest() { if ($this->id) { $list = id(new PhabricatorMetaMTAMailingList())->load($this->id); if (!$list) { return new Aphront404Response(); } } else { $list = new PhabricatorMetaMTAMailingList(); } $e_email = true; $e_uri = null; $e_name = true; $errors = array(); $request = $this->getRequest(); if ($request->isFormPost()) { $list->setName($request->getStr('name')); $list->setEmail($request->getStr('email')); $list->setURI($request->getStr('uri')); $e_email = null; $e_name = null; if (!strlen($list->getEmail())) { $e_email = 'Required'; $errors[] = 'Email is required.'; } if (!strlen($list->getName())) { $e_name = 'Required'; $errors[] = 'Name is required.'; } else if (preg_match('/[ ,]/', $list->getName())) { $e_name = 'Invalid'; $errors[] = 'Name must not contain spaces or commas.'; } if ($list->getURI()) { if (!PhabricatorEnv::isValidWebResource($list->getURI())) { $e_uri = 'Invalid'; $errors[] = 'Mailing list URI must point to a valid web page.'; } } if (!$errors) { try { $list->save(); return id(new AphrontRedirectResponse()) ->setURI('/mail/lists/'); } catch (AphrontQueryDuplicateKeyException $ex) { $e_email = 'Duplicate'; $errors[] = 'Another mailing list already uses that address.'; } } } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle('Form Errors') ->setErrors($errors); } $form = new AphrontFormView(); $form->setUser($request->getUser()); if ($list->getID()) { $form->setAction('/mail/lists/edit/'.$list->getID().'/'); } else { $form->setAction('/mail/lists/edit/'); } $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Email') ->setName('email') ->setValue($list->getEmail()) ->setCaption('Email will be delivered to this address.') ->setError($e_email)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Name') ->setName('name') ->setError($e_name) ->setCaption('Human-readable display and autocomplete name.') ->setValue($list->getName())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('URI') ->setName('uri') ->setError($e_uri) ->setCaption('Optional link to mailing list archives or info.') ->setValue($list->getURI())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('ID') ->setValue(nonempty($list->getID(), '-'))) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('PHID') ->setValue(nonempty($list->getPHID(), '-'))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save') ->addCancelButton('/mail/lists/')); $panel = new AphrontPanelView(); if ($list->getID()) { $panel->setHeader('Edit Mailing List'); } else { $panel->setHeader('Create New Mailing List'); } $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); return $this->buildStandardPageResponse( array($error_view, $panel), array( 'title' => 'Edit Mailing List', )); } } diff --git a/src/applications/metamta/controller/mailinglists/PhabricatorMetaMTAMailingListsController.php b/src/applications/metamta/controller/mailinglists/PhabricatorMetaMTAMailingListsController.php index 858d6fb38b..6dbb57117b 100644 --- a/src/applications/metamta/controller/mailinglists/PhabricatorMetaMTAMailingListsController.php +++ b/src/applications/metamta/controller/mailinglists/PhabricatorMetaMTAMailingListsController.php @@ -1,90 +1,90 @@ getRequest(); $user = $request->getUser(); $offset = $request->getInt('offset', 0); $pager = new AphrontPagerView(); $pager->setPageSize(1000); $pager->setOffset($offset); $pager->setURI($request->getRequestURI(), 'offset'); $list = new PhabricatorMetaMTAMailingList(); $conn_r = $list->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T ORDER BY name ASC LIMIT %d, %d', $list->getTableName(), $pager->getOffset(), $pager->getPageSize() + 1); $data = $pager->sliceResults($data); $lists = $list->loadAllFromArray($data); $rows = array(); foreach ($lists as $list) { $rows[] = array( phutil_escape_html($list->getPHID()), phutil_escape_html($list->getEmail()), phutil_escape_html($list->getName()), phutil_render_tag( 'a', array( 'class' => 'button grey small', 'href' => '/mail/lists/edit/'.$list->getID().'/', ), 'Edit'), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'PHID', 'Email', 'Name', '', )); $table->setColumnClasses( array( null, null, 'wide', 'action', )); $panel = new AphrontPanelView(); $panel->appendChild($table); $panel->setHeader('Mailing Lists'); $panel->setCreateButton('Add New List', '/mail/lists/edit/'); $panel->appendChild($pager); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Mailing Lists', 'tab' => 'lists', )); } } diff --git a/src/applications/metamta/controller/receive/PhabricatorMetaMTAReceiveController.php b/src/applications/metamta/controller/receive/PhabricatorMetaMTAReceiveController.php index 7fef2f7907..13e26ca51d 100644 --- a/src/applications/metamta/controller/receive/PhabricatorMetaMTAReceiveController.php +++ b/src/applications/metamta/controller/receive/PhabricatorMetaMTAReceiveController.php @@ -1,91 +1,91 @@ getRequest(); $user = $request->getUser(); if ($request->isFormPost()) { $receiver = PhabricatorMetaMTAReceivedMail::loadReceiverObject( $request->getStr('obj')); if (!$receiver) { throw new Exception("No such task or revision!"); } $hash = PhabricatorMetaMTAReceivedMail::computeMailHash( $receiver->getMailKey(), $user->getPHID()); $received = new PhabricatorMetaMTAReceivedMail(); $received->setHeaders( array( 'to' => $request->getStr('obj').'+'.$user->getID().'+'.$hash.'@', )); $received->setBodies( array( 'text' => $request->getStr('body'), )); $received->save(); $received->processReceivedMail(); $phid = $receiver->getPHID(); $handles = id(new PhabricatorObjectHandleData(array($phid))) ->loadHandles(); $uri = $handles[$phid]->getURI(); return id(new AphrontRedirectResponse())->setURI($uri); } $form = new AphrontFormView(); $form->setUser($request->getUser()); $form->setAction('/mail/receive/'); $form ->appendChild( '

    This form will simulate '. 'sending mail to an object.

    ') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('To') ->setName('obj') ->setCaption('e.g. D1234 or T1234')) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Body') ->setName('body')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Receive Mail')); $panel = new AphrontPanelView(); $panel->setHeader('Receive Email'); $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Receive Mail', )); } } diff --git a/src/applications/metamta/controller/receivedlist/PhabricatorMetaMTAReceivedListController.php b/src/applications/metamta/controller/receivedlist/PhabricatorMetaMTAReceivedListController.php index 5ff2093ba5..f5e8197b2f 100644 --- a/src/applications/metamta/controller/receivedlist/PhabricatorMetaMTAReceivedListController.php +++ b/src/applications/metamta/controller/receivedlist/PhabricatorMetaMTAReceivedListController.php @@ -1,94 +1,94 @@ getRequest(); $user = $request->getUser(); $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page')); $pager->setURI($request->getRequestURI(), 'page'); $mails = id(new PhabricatorMetaMTAReceivedMail())->loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize() + 1); $mails = $pager->sliceResults($mails); $phids = array_merge( mpull($mails, 'getAuthorPHID'), mpull($mails, 'getRelatedPHID') ); $phids = array_unique(array_filter($phids)); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $rows = array(); foreach ($mails as $mail) { $rows[] = array( $mail->getID(), phabricator_date($mail->getDateCreated(), $user), phabricator_time($mail->getDateCreated(), $user), $mail->getAuthorPHID() ? $handles[$mail->getAuthorPHID()]->renderLink() : '-', $mail->getRelatedPHID() ? $handles[$mail->getRelatedPHID()]->renderLink() : '-', phutil_escape_html($mail->getMessage()), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'ID', 'Date', 'Time', 'Author', 'Object', 'Message', )); $table->setColumnClasses( array( null, null, 'right', null, null, 'wide', )); $panel = new AphrontPanelView(); $panel->setHeader('Received Mail'); $panel->setCreateButton('Test Receiver', '/mail/receive/'); $panel->appendChild($table); $panel->appendChild($pager); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Received Mail', 'tab' => 'received', )); } } diff --git a/src/applications/metamta/controller/send/PhabricatorMetaMTASendController.php b/src/applications/metamta/controller/send/PhabricatorMetaMTASendController.php index 81a476a582..a39b6adecf 100644 --- a/src/applications/metamta/controller/send/PhabricatorMetaMTASendController.php +++ b/src/applications/metamta/controller/send/PhabricatorMetaMTASendController.php @@ -1,173 +1,174 @@ getRequest(); if ($request->isFormPost()) { $mail = new PhabricatorMetaMTAMail(); $mail->addTos($request->getArr('to')); $mail->addCCs($request->getArr('cc')); $mail->setSubject($request->getStr('subject')); $mail->setBody($request->getStr('body')); $files = $request->getArr('files'); if ($files) { foreach ($files as $phid) { $file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $phid); $mail->addAttachment(new PhabricatorMetaMTAAttachment( $file->loadFileData(), $file->getName(), $file->getMimeType() )); } } $mail->setFrom($request->getUser()->getPHID()); $mail->setSimulatedFailureCount($request->getInt('failures')); $mail->setIsHTML($request->getInt('html')); $mail->setIsBulk($request->getInt('bulk')); $mail->setMailTags($request->getStrList('mailtags')); $mail->save(); if ($request->getInt('immediately')) { $mail->sendNow(); } return id(new AphrontRedirectResponse()) ->setURI('/mail/view/'.$mail->getID().'/'); } $failure_caption = "Enter a number to simulate that many consecutive send failures before ". "really attempting to deliver via the underlying MTA."; $doclink_href = PhabricatorEnv::getDoclink( 'article/Configuring_Outbound_Email.html'); $doclink = phutil_render_tag( 'a', array( 'href' => $doclink_href, 'target' => '_blank', ), 'Configuring Outbound Email'); $instructions = '

    This form will send a normal '. 'email using the settings you have configured for Phabricator. For more '. 'information, see '.$doclink.'.

    '; $adapter = PhabricatorEnv::getEnvConfig('metamta.mail-adapter'); $warning = null; if ($adapter == 'PhabricatorMailImplementationTestAdapter') { $warning = new AphrontErrorView(); $warning->setTitle('Email is Disabled'); $warning->setSeverity(AphrontErrorView::SEVERITY_WARNING); $warning->appendChild( '

    This installation of Phabricator is currently set to use '. 'PhabricatorMailImplementationTestAdapter to deliver '. 'outbound email. This completely disables outbound email! All '. 'outbound email will be thrown in a deep, dark hole until you '. 'configure a real adapter.

    '); } $panel_id = celerity_generate_unique_node_id(); $form = new AphrontFormView(); $form->setUser($request->getUser()); $form->setAction('/mail/send/'); $form ->appendChild($instructions) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Configured Adapter') ->setValue($adapter)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('To') ->setName('to') ->setDatasource('/typeahead/common/mailable/')) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('CC') ->setName('cc') ->setDatasource('/typeahead/common/mailable/')) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Subject') ->setName('subject')) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Body') ->setName('body')) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Mail Tags') ->setName('mailtags') ->setCaption( 'Example: differential-cc, differential-comment')) ->appendChild( id(new AphrontFormDragAndDropUploadControl()) ->setLabel('Attach Files') ->setName('files') ->setDragAndDropTarget($panel_id) ->setActivatedClass('aphront-panel-view-drag-and-drop')) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Simulate Failures') ->setName('failures') ->setCaption($failure_caption)) ->appendChild( id(new AphrontFormCheckboxControl()) ->setLabel('HTML') ->addCheckbox('html', '1', 'Send as HTML email.')) ->appendChild( id(new AphrontFormCheckboxControl()) ->setLabel('Bulk') ->addCheckbox('bulk', '1', 'Send with bulk email headers.')) ->appendChild( id(new AphrontFormCheckboxControl()) ->setLabel('Send Now') ->addCheckbox( 'immediately', '1', 'Send immediately, not via MetaMTA background script.')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Send Mail')); $panel = new AphrontPanelView(); $panel->setHeader('Send Email'); $panel->appendChild($form); $panel->setID($panel_id); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); return $this->buildStandardPageResponse( array( $warning, $panel, ), array( 'title' => 'Send Mail', )); } } diff --git a/src/applications/metamta/controller/sendgridreceive/PhabricatorMetaMTASendGridReceiveController.php b/src/applications/metamta/controller/sendgridreceive/PhabricatorMetaMTASendGridReceiveController.php index 51c8113777..da6be312a7 100644 --- a/src/applications/metamta/controller/sendgridreceive/PhabricatorMetaMTASendGridReceiveController.php +++ b/src/applications/metamta/controller/sendgridreceive/PhabricatorMetaMTASendGridReceiveController.php @@ -1,82 +1,82 @@ getRequest(); $user = $request->getUser(); $raw_headers = $request->getStr('headers'); $raw_headers = explode("\n", rtrim($raw_headers)); $raw_dict = array(); foreach (array_filter($raw_headers) as $header) { list($name, $value) = explode(':', $header, 2); $raw_dict[$name] = ltrim($value); } $headers = array( 'to' => $request->getStr('to'), 'from' => $request->getStr('from'), 'subject' => $request->getStr('subject'), ) + $raw_dict; $received = new PhabricatorMetaMTAReceivedMail(); $received->setHeaders($headers); $received->setBodies(array( 'text' => $request->getStr('text'), 'html' => $request->getStr('from'), )); $file_phids = array(); foreach ($_FILES as $file_raw) { try { $file = PhabricatorFile::newFromPHPUpload( $file_raw, array( 'authorPHID' => $user->getPHID(), )); $file_phids[] = $file->getPHID(); } catch (Exception $ex) { phlog($ex); } } $received->setAttachments($file_phids); $received->save(); $received->processReceivedMail(); $response = new AphrontWebpageResponse(); $response->setContent("Got it! Thanks, SendGrid!\n"); return $response; } } diff --git a/src/applications/metamta/controller/view/PhabricatorMetaMTAViewController.php b/src/applications/metamta/controller/view/PhabricatorMetaMTAViewController.php index 0bf71a4d09..b1b7653fe6 100644 --- a/src/applications/metamta/controller/view/PhabricatorMetaMTAViewController.php +++ b/src/applications/metamta/controller/view/PhabricatorMetaMTAViewController.php @@ -1,83 +1,84 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $mail = id(new PhabricatorMetaMTAMail())->load($this->id); if (!$mail) { return new Aphront404Response(); } $status = PhabricatorMetaMTAMail::getReadableStatus($mail->getStatus()); $form = new AphrontFormView(); $form->setUser($request->getUser()); $form->setAction('/mail/send/'); $form ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Subject') ->setValue($mail->getSubject())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Created') ->setValue(phabricator_datetime($mail->getDateCreated(), $user))) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Status') ->setValue($status)) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Retry Count') ->setValue($mail->getRetryCount())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Message') ->setValue($mail->getMessage())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Related PHID') ->setValue($mail->getRelatedPHID())) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/mail/', 'Done')); $panel = new AphrontPanelView(); $panel->setHeader('View Email'); $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); return $this->buildStandardPageResponse( $panel, array( 'title' => 'View Mail', )); } } diff --git a/src/applications/owners/controller/delete/PhabricatorOwnersDeleteController.php b/src/applications/owners/controller/delete/PhabricatorOwnersDeleteController.php index 2e4ad2e8d1..0349d9b1d5 100644 --- a/src/applications/owners/controller/delete/PhabricatorOwnersDeleteController.php +++ b/src/applications/owners/controller/delete/PhabricatorOwnersDeleteController.php @@ -1,55 +1,56 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $package = id(new PhabricatorOwnersPackage())->load($this->id); if (!$package) { return new Aphront404Response(); } if ($request->isDialogFormPost()) { $package->delete(); return id(new AphrontRedirectResponse())->setURI('/owners/'); } $dialog = id(new AphrontDialogView()) ->setUser($user) ->setTitle('Really delete this package?') ->appendChild( '

    Are you sure you want to delete the "'. phutil_escape_html($package->getName()).'" package? This operation '. 'can not be undone.

    ') ->addSubmitButton('Delete') ->addCancelButton('/owners/package/'.$package->getID().'/') ->setSubmitURI($request->getRequestURI()); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/owners/controller/detail/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/detail/PhabricatorOwnersDetailController.php index e4149b92f1..6555cd9448 100644 --- a/src/applications/owners/controller/detail/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/detail/PhabricatorOwnersDetailController.php @@ -1,228 +1,229 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $package = id(new PhabricatorOwnersPackage())->load($this->id); if (!$package) { return new Aphront404Response(); } $this->package = $package; $paths = $package->loadPaths(); $owners = $package->loadOwners(); $phids = array(); foreach ($paths as $path) { $phids[$path->getRepositoryPHID()] = true; } foreach ($owners as $owner) { $phids[$owner->getUserPHID()] = true; } $phids = array_keys($phids); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $rows = array(); $rows[] = array( 'Name', phutil_escape_html($package->getName())); $rows[] = array( 'Description', phutil_escape_html($package->getDescription())); $primary_owner = null; $primary_phid = $package->getPrimaryOwnerPHID(); if ($primary_phid && isset($handles[$primary_phid])) { $primary_owner = ''.$handles[$primary_phid]->renderLink().''; } $rows[] = array( 'Primary Owner', $primary_owner, ); $owner_links = array(); foreach ($owners as $owner) { $owner_links[] = $handles[$owner->getUserPHID()]->renderLink(); } $owner_links = implode('
    ', $owner_links); $rows[] = array( 'Owners', $owner_links); $rows[] = array( 'Auditing', $package->getAuditingEnabled() ? 'Enabled' : 'Disabled', ); $path_links = array(); foreach ($paths as $path) { $callsign = $handles[$path->getRepositoryPHID()]->getName(); $repo = ''.phutil_escape_html($callsign).''; $path_link = phutil_render_tag( 'a', array( 'href' => '/diffusion/'.$callsign.'/browse/:'.$path->getPath(), ), phutil_escape_html($path->getPath())); $path_links[] = $repo.' '.$path_link; } $path_links = implode('
    ', $path_links); $rows[] = array( 'Paths', $path_links); $table = new AphrontTableView($rows); $table->setColumnClasses( array( 'header', 'wide', )); $panel = new AphrontPanelView(); $panel->setHeader( 'Package Details for "'.phutil_escape_html($package->getName()).'"'); $panel->addButton( javelin_render_tag( 'a', array( 'href' => '/owners/delete/'.$package->getID().'/', 'class' => 'button grey', 'sigil' => 'workflow', ), 'Delete Package')); $panel->addButton( phutil_render_tag( 'a', array( 'href' => '/owners/edit/'.$package->getID().'/', 'class' => 'button', ), 'Edit Package')); $panel->appendChild($table); $key = 'package/'.$package->getID(); $this->setSideNavFilter($key); $commit_views = array(); $commit_uri = id(new PhutilURI('/audit/view/packagecommits/')) ->setQueryParams( array( 'phid' => $package->getPHID(), )); $attention_query = id(new PhabricatorAuditCommitQuery()) ->withPackagePHIDs(array($package->getPHID())) ->withStatus(PhabricatorAuditCommitQuery::STATUS_OPEN) ->needCommitData(true) ->setLimit(10); $attention_commits = $attention_query->execute(); if ($attention_commits) { $view = new PhabricatorAuditCommitListView(); $view->setUser($user); $view->setCommits($attention_commits); $commit_views[] = array( 'view' => $view, 'header' => 'Commits in this Package that Need Attention', 'button' => phutil_render_tag( 'a', array( 'href' => $commit_uri->alter('status', 'open'), 'class' => 'button grey', ), 'View All Problem Commits'), ); } $all_query = id(new PhabricatorAuditCommitQuery()) ->withPackagePHIDs(array($package->getPHID())) ->needCommitData(true) ->setLimit(100); $all_commits = $all_query->execute(); $view = new PhabricatorAuditCommitListView(); $view->setUser($user); $view->setCommits($all_commits); $view->setNoDataString('No commits in this package.'); $commit_views[] = array( 'view' => $view, 'header' => 'Recent Commits in Package', 'button' => phutil_render_tag( 'a', array( 'href' => $commit_uri, 'class' => 'button grey', ), 'View All Package Commits'), ); $phids = array(); foreach ($commit_views as $commit_view) { $phids[] = $commit_view['view']->getRequiredHandlePHIDs(); } $phids = array_mergev($phids); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $commit_panels = array(); foreach ($commit_views as $commit_view) { $commit_panel = new AphrontPanelView(); $commit_panel->setHeader(phutil_escape_html($commit_view['header'])); if (isset($commit_view['button'])) { $commit_panel->addButton($commit_view['button']); } $commit_view['view']->setHandles($handles); $commit_panel->appendChild($commit_view['view']); $commit_panels[] = $commit_panel; } return $this->buildStandardPageResponse( array( $panel, $commit_panels, ), array( 'title' => "Package '".$package->getName()."'", )); } protected function getExtraPackageViews() { $package = $this->package; return array( array('name' => 'Details', 'key' => 'package/'.$package->getID(), )); } } diff --git a/src/applications/owners/controller/edit/PhabricatorOwnersEditController.php b/src/applications/owners/controller/edit/PhabricatorOwnersEditController.php index 9f6d487a8b..baf702ac6b 100644 --- a/src/applications/owners/controller/edit/PhabricatorOwnersEditController.php +++ b/src/applications/owners/controller/edit/PhabricatorOwnersEditController.php @@ -1,293 +1,294 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if ($this->id) { $package = id(new PhabricatorOwnersPackage())->load($this->id); if (!$package) { return new Aphront404Response(); } } else { $package = new PhabricatorOwnersPackage(); $package->setPrimaryOwnerPHID($user->getPHID()); } $e_name = true; $e_primary = true; $e_owners = true; $errors = array(); if ($request->isFormPost()) { $package->setName($request->getStr('name')); $package->setDescription($request->getStr('description')); $package->setAuditingEnabled($request->getStr('auditing') === 'enabled'); $primary = $request->getArr('primary'); $primary = reset($primary); $package->setPrimaryOwnerPHID($primary); $owners = $request->getArr('owners'); if ($primary) { array_unshift($owners, $primary); } $owners = array_unique($owners); $paths = $request->getArr('path'); $repos = $request->getArr('repo'); $path_refs = array(); for ($ii = 0; $ii < count($paths); $ii++) { if (empty($paths[$ii]) || empty($repos[$ii])) { continue; } $path_refs[] = array( 'repositoryPHID' => $repos[$ii], 'path' => $paths[$ii], ); } if (!strlen($package->getName())) { $e_name = 'Required'; $errors[] = 'Package name is required.'; } else { $e_name = null; } if (!$package->getPrimaryOwnerPHID()) { $e_primary = 'Required'; $errors[] = 'Package must have a primary owner.'; } else { $e_primary = null; } if (!$owners) { $e_owners = 'Required'; $errors[] = 'Package must have at least one owner.'; } else { $e_owners = null; } if (!$path_refs) { $errors[] = 'Package must include at least one path.'; } if (!$errors) { $package->attachUnsavedOwners($owners); $package->attachUnsavedPaths($path_refs); try { $package->save(); return id(new AphrontRedirectResponse()) ->setURI('/owners/package/'.$package->getID().'/'); } catch (AphrontQueryDuplicateKeyException $ex) { $e_name = 'Duplicate'; $errors[] = 'Package name must be unique.'; } } } else { $owners = $package->loadOwners(); $owners = mpull($owners, 'getUserPHID'); $paths = $package->loadPaths(); $path_refs = array(); foreach ($paths as $path) { $path_refs[] = array( 'repositoryPHID' => $path->getRepositoryPHID(), 'path' => $path->getPath(), ); } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Package Errors'); $error_view->setErrors($errors); } $handles = id(new PhabricatorObjectHandleData($owners)) ->loadHandles(); $primary = $package->getPrimaryOwnerPHID(); if ($primary && isset($handles[$primary])) { $token_primary_owner = array( $primary => $handles[$primary]->getFullName(), ); } else { $token_primary_owner = array(); } $token_all_owners = array_select_keys($handles, $owners); $token_all_owners = mpull($token_all_owners, 'getFullName'); if ($package->getID()) { $title = 'Edit Package'; $side_nav_filter = 'edit/'.$this->id; } else { $title = 'New Package'; $side_nav_filter = 'new'; } $this->setSideNavFilter($side_nav_filter); $repos = id(new PhabricatorRepository())->loadAll(); $default_paths = array(); foreach ($repos as $repo) { $default_path = $repo->getDetail('default-owners-path'); if ($default_path) { $default_paths[$repo->getPHID()] = $default_path; } } $repos = mpull($repos, 'getCallsign', 'getPHID'); $template = new AphrontTypeaheadTemplateView(); $template = $template->render(); Javelin::initBehavior( 'owners-path-editor', array( 'root' => 'path-editor', 'table' => 'paths', 'add_button' => 'addpath', 'repositories' => $repos, 'input_template' => $template, 'pathRefs' => $path_refs, 'completeURI' => '/diffusion/services/path/complete/', 'validateURI' => '/diffusion/services/path/validate/', 'repositoryDefaultPaths' => $default_paths, )); require_celerity_resource('owners-path-editor-css'); $cancel_uri = $package->getID() ? '/owners/package/'.$package->getID().'/' : '/owners/'; $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Name') ->setName('name') ->setValue($package->getName()) ->setError($e_name)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setLabel('Primary Owner') ->setName('primary') ->setLimit(1) ->setValue($token_primary_owner) ->setError($e_primary)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setLabel('Owners') ->setName('owners') ->setValue($token_all_owners) ->setError($e_owners)) ->appendChild( id(new AphrontFormSelectControl()) ->setName('auditing') ->setLabel('Auditing') ->setCaption('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' => 'Disabled', 'enabled' => 'Enabled', )) ->setValue( $package->getAuditingEnabled() ? 'enabled' : 'disabled')) ->appendChild( '

    Paths

    '. '
    '. '
    '. javelin_render_tag( 'a', array( 'href' => '#', 'class' => 'button green', 'sigil' => 'addpath', 'mustcapture' => true, ), 'Add New Path'). '
    '. '

    Specify the files and directories which comprise this '. 'package.

    '. '
    '. javelin_render_tag( 'table', array( 'class' => 'owners-path-editor-table', 'sigil' => 'paths', ), ''). '
    ') ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Description') ->setName('description') ->setValue($package->getDescription())) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue('Save Package')); $panel = new AphrontPanelView(); $panel->setHeader($title); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->appendChild($error_view); $panel->appendChild($form); return $this->buildStandardPageResponse( $panel, array( 'title' => $title, )); } protected function getExtraPackageViews() { if ($this->id) { $extra = array(array('name' => 'Edit', 'key' => 'edit/'.$this->id)); } else { $extra = array(array('name' => 'New', 'key' => 'new')); } return $extra; } } diff --git a/src/applications/owners/controller/list/PhabricatorOwnersListController.php b/src/applications/owners/controller/list/PhabricatorOwnersListController.php index 883502e0c6..0e82598a82 100644 --- a/src/applications/owners/controller/list/PhabricatorOwnersListController.php +++ b/src/applications/owners/controller/list/PhabricatorOwnersListController.php @@ -1,317 +1,318 @@ view = idx($data, 'view', 'owned'); $this->setSideNavFilter('view/'.$this->view); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $package = new PhabricatorOwnersPackage(); $owner = new PhabricatorOwnersOwner(); $path = new PhabricatorOwnersPath(); $repository_phid = ''; if ($request->getStr('repository') != '') { $repository_phid = id(new PhabricatorRepository()) ->loadOneWhere('callsign = %s', $request->getStr('repository')) ->getPHID(); } switch ($this->view) { case 'search': $packages = array(); $conn_r = $package->establishConnection('r'); $where = array('1 = 1'); $join = array(); if ($request->getStr('name')) { $where[] = qsprintf( $conn_r, 'p.name LIKE %~', $request->getStr('name')); } if ($repository_phid || $request->getStr('path')) { $join[] = qsprintf( $conn_r, 'JOIN %T path ON path.packageID = p.id', $path->getTableName()); if ($repository_phid) { $where[] = qsprintf( $conn_r, 'path.repositoryPHID = %s', $repository_phid); } if ($request->getStr('path')) { $where[] = qsprintf( $conn_r, 'path.path LIKE %~ OR %s LIKE CONCAT(path.path, %s)', $request->getStr('path'), $request->getStr('path'), '%'); } } if ($request->getArr('owner')) { $join[] = qsprintf( $conn_r, 'JOIN %T o ON o.packageID = p.id', $owner->getTableName()); $where[] = qsprintf( $conn_r, 'o.userPHID IN (%Ls)', $request->getArr('owner')); } $data = queryfx_all( $conn_r, 'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id', $package->getTableName(), implode(' ', $join), '('.implode(') AND (', $where).')'); $packages = $package->loadAllFromArray($data); $header = 'Search Results'; $nodata = 'No packages match your query.'; break; case 'owned': $data = queryfx_all( $package->establishConnection('r'), 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID WHERE o.userPHID = %s GROUP BY p.id', $package->getTableName(), $owner->getTableName(), $user->getPHID()); $packages = $package->loadAllFromArray($data); $header = 'Owned Packages'; $nodata = 'No owned packages'; break; case 'all': $packages = $package->loadAll(); $header = 'All Packages'; $nodata = 'There are no defined packages.'; break; } $content = $this->renderPackageTable( $packages, $header, $nodata); $filter = new AphrontListFilterView(); $filter->addButton( phutil_render_tag( 'a', array( 'href' => '/owners/new/', 'class' => 'green button', ), 'Create New Package')); $owners_search_value = array(); if ($request->getArr('owner')) { $phids = $request->getArr('owner'); $phid = reset($phids); $handles = id(new PhabricatorObjectHandleData(array($phid))) ->loadHandles(); $owners_search_value = array( $phid => $handles[$phid]->getFullName(), ); } $callsigns = array('' => '(Any Repository)'); $repositories = id(new PhabricatorRepository()) ->loadAllWhere('1 = 1 ORDER BY callsign'); foreach ($repositories as $repository) { $callsigns[$repository->getCallsign()] = $repository->getCallsign().': '.$repository->getName(); } $form = id(new AphrontFormView()) ->setUser($user) ->setAction('/owners/view/search/') ->setMethod('GET') ->appendChild( id(new AphrontFormTextControl()) ->setName('name') ->setLabel('Name') ->setValue($request->getStr('name'))) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setLimit(1) ->setName('owner') ->setLabel('Owner') ->setValue($owners_search_value)) ->appendChild( id(new AphrontFormSelectControl()) ->setName('repository') ->setLabel('Repository') ->setOptions($callsigns) ->setValue($request->getStr('repository'))) ->appendChild( id(new AphrontFormTextControl()) ->setName('path') ->setLabel('Path') ->setValue($request->getStr('path'))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Search for Packages')); $filter->appendChild($form); return $this->buildStandardPageResponse( array( $filter, $content, ), array( 'title' => 'Package Index', )); } private function renderPackageTable(array $packages, $header, $nodata) { if ($packages) { $package_ids = mpull($packages, 'getID'); $owners = id(new PhabricatorOwnersOwner())->loadAllWhere( 'packageID IN (%Ld)', $package_ids); $paths = id(new PhabricatorOwnersPath())->loadAllWhere( 'packageID in (%Ld)', $package_ids); $phids = array(); foreach ($owners as $owner) { $phids[$owner->getUserPHID()] = true; } foreach ($paths as $path) { $phids[$path->getRepositoryPHID()] = true; } $phids = array_keys($phids); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $owners = mgroup($owners, 'getPackageID'); $paths = mgroup($paths, 'getPackageID'); } else { $handles = array(); $owners = array(); $paths = array(); } $rows = array(); foreach ($packages as $package) { $pkg_owners = idx($owners, $package->getID(), array()); foreach ($pkg_owners as $key => $owner) { $pkg_owners[$key] = $handles[$owner->getUserPHID()]->renderLink(); if ($owner->getUserPHID() == $package->getPrimaryOwnerPHID()) { $pkg_owners[$key] = ''.$pkg_owners[$key].''; } } $pkg_owners = implode('
    ', $pkg_owners); $pkg_paths = idx($paths, $package->getID(), array()); foreach ($pkg_paths as $key => $path) { $repo = $handles[$path->getRepositoryPHID()]->getName(); $pkg_paths[$key] = ''.phutil_escape_html($repo).' '. phutil_render_tag( 'a', array( 'href' => '/diffusion/'.$repo.'/browse/:'.$path->getPath(), ), phutil_escape_html($path->getPath())); } $pkg_paths = implode('
    ', $pkg_paths); $rows[] = array( phutil_render_tag( 'a', array( 'href' => '/owners/package/'.$package->getID().'/', ), phutil_escape_html($package->getName())), $pkg_owners, $pkg_paths, phutil_render_tag( 'a', array( 'href' => '/owners/related/package/?phid='.$package->getPHID(), ), phutil_escape_html('Related Commits')) ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Name', 'Owners', 'Paths', 'Related Commits', )); $table->setColumnClasses( array( 'pri', '', 'wide wrap', 'narrow', )); $panel = new AphrontPanelView(); $panel->setHeader($header); $panel->appendChild($table); return $panel; } protected function getExtraPackageViews() { switch ($this->view) { case 'search': $extra = array(array('name' => 'Search Results', 'key' => 'view/search')); break; default: $extra = array(); break; } return $extra; } } diff --git a/src/applications/paste/controller/list/PhabricatorPasteListController.php b/src/applications/paste/controller/list/PhabricatorPasteListController.php index 615360d01e..5ef111e61b 100644 --- a/src/applications/paste/controller/list/PhabricatorPasteListController.php +++ b/src/applications/paste/controller/list/PhabricatorPasteListController.php @@ -1,448 +1,448 @@ filter = $filter; return $this; } private function getFilter() { return $this->filter; } private function setErrorView($error_view) { $this->errorView = $error_view; return $this; } private function getErrorView() { return $this->errorView; } private function setErrorText($error_text) { $this->errorText = $error_text; return $this; } private function getErrorText() { return $this->errorText; } private function setPaste(PhabricatorPaste $paste) { $this->paste = $paste; return $this; } private function getPaste() { return $this->paste; } private function setPasteText($paste_text) { $this->pasteText = $paste_text; return $this; } private function getPasteText() { return $this->pasteText; } private function setOffset($offset) { $this->offset = $offset; return $this; } private function getOffset() { return $this->offset; } private function setPageSize($page_size) { $this->pageSize = $page_size; return $this; } private function getPageSize() { return $this->pageSize; } private function setAuthor($author) { $this->author = $author; return $this; } private function getAuthor() { return $this->author; } public function willProcessRequest(array $data) { $this->setFilter(idx($data, 'filter', 'create')); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $paste_list = array(); $pager = null; switch ($this->getFilter()) { case 'create': default: // if we successfully create a paste, we redirect to view it $created_paste_redirect = $this->processCreateRequest(); if ($created_paste_redirect) { return $created_paste_redirect; } // if we didn't succeed or we weren't trying, load just a few // recent pastes with NO pagination $this->setOffset(0); $this->setPageSize(10); list($paste_list, $pager) = $this->loadPasteList(); break; case 'my': $this->setAuthor($user->getPHID()); $this->setOffset($request->getInt('page', 0)); list($paste_list, $pager) = $this->loadPasteList(); break; case 'all': $this->setOffset($request->getInt('page', 0)); list($paste_list, $pager) = $this->loadPasteList(); break; } $filters = array( 'create' => array( 'name' => 'Create Paste', ), 'my' => array( 'name' => 'My Pastes', ), 'all' => array( 'name' => 'All Pastes', ), ); $side_nav = new AphrontSideNavView(); foreach ($filters as $filter_key => $filter) { $selected = $filter_key == $this->getFilter(); $side_nav->addNavItem( phutil_render_tag( 'a', array( 'href' => '/paste/filter/'.$filter_key.'/', 'class' => $selected ? 'aphront-side-nav-selected': null, ), $filter['name']) ); } if ($this->getErrorView()) { $side_nav->appendChild($this->getErrorView()); } switch ($this->getFilter()) { case 'create': default: $side_nav->appendChild($this->renderCreatePaste()); $see_all = phutil_render_tag( 'a', array( 'href' => '/paste/filter/all', ), 'See all Pastes'); $header = "Recent Pastes · {$see_all}"; $side_nav->appendChild($this->renderPasteList($paste_list, $header, $pager = null)); break; case 'my': $header = 'Your Pastes'; $side_nav->appendChild($this->renderPasteList($paste_list, $header, $pager)); break; case 'all': $header = 'All Pastes'; $side_nav->appendChild($this->renderPasteList($paste_list, $header, $pager)); break; } return $this->buildStandardPageResponse( $side_nav, array( 'title' => 'Paste', ) ); } private function processCreateRequest() { $request = $this->getRequest(); $user = $request->getUser(); $fork = $request->getInt('fork'); $error_view = null; $e_text = true; $new_paste = new PhabricatorPaste(); $new_paste_text = null; $new_paste_language = PhabricatorEnv::getEnvConfig( 'pygments.dropdown-default'); if ($request->isFormPost()) { $errors = array(); $text = $request->getStr('text'); if (!strlen($text)) { $e_text = 'Required'; $errors[] = 'The paste may not be blank.'; } else { $e_text = null; } $parent_phid = $request->getStr('parent'); if ($parent_phid) { $parent = id(new PhabricatorPaste())->loadOneWhere('phid = %s', $parent_phid); if ($parent) { $new_paste->setParentPHID($parent->getPHID()); } } $title = $request->getStr('title'); $new_paste->setTitle($title); $new_paste_language = $request->getStr('language'); if (!$errors) { if ($new_paste_language == 'infer') { // If it's infer, store an empty string. Otherwise, store the // language name. We do this so we can refer to 'infer' elsewhere // in the code (such as default value) while retaining backwards // compatibility with old posts with no language stored. $new_paste_language = ''; } $new_paste->setLanguage($new_paste_language); $new_paste_file = PhabricatorFile::newFromFileData( $text, array( 'name' => $title, 'mime-type' => 'text/plain; charset=utf-8', 'authorPHID' => $user->getPHID(), )); $new_paste->setFilePHID($new_paste_file->getPHID()); $new_paste->setAuthorPHID($user->getPHID()); $new_paste->save(); return id(new AphrontRedirectResponse()) ->setURI('/P'.$new_paste->getID()); } else { $error_view = new AphrontErrorView(); $error_view->setErrors($errors); $error_view->setTitle('A problem has occurred!'); } } else if ($fork) { $fork_paste = id(new PhabricatorPaste())->load($fork); if ($fork_paste) { $new_paste->setTitle('Fork of '.$fork_paste->getID().': '. $fork_paste->getTitle()); $fork_file = id(new PhabricatorFile())->loadOneWhere( 'phid = %s', $fork_paste->getFilePHID()); $new_paste_text = $fork_file->loadFileData(); $new_paste_language = nonempty($fork_paste->getLanguage(), 'infer'); $new_paste->setParentPHID($fork_paste->getPHID()); } } $this->setErrorView($error_view); $this->setErrorText($e_text); $this->setPasteText($new_paste_text); $new_paste->setLanguage($new_paste_language); $this->setPaste($new_paste); } private function loadPasteList() { $request = $this->getRequest(); $pager = new AphrontPagerView(); $pager->setOffset($this->getOffset()); if ($this->getPageSize()) { $pager->setPageSize($this->getPageSize()); } if ($this->getAuthor()) { $pastes = id(new PhabricatorPaste())->loadAllWhere( 'authorPHID = %s ORDER BY id DESC LIMIT %d, %d', $this->getAuthor(), $pager->getOffset(), $pager->getPageSize() + 1); } else { $pastes = id(new PhabricatorPaste())->loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize() + 1); } $pastes = $pager->sliceResults($pastes); $pager->setURI($request->getRequestURI(), 'page'); $phids = mpull($pastes, 'getAuthorPHID'); $handles = array(); if ($phids) { $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); } $phids = mpull($pastes, 'getFilePHID'); $file_uris = array(); if ($phids) { $files = id(new PhabricatorFile())->loadAllWhere( 'phid in (%Ls)', $phids ); if ($files) { $file_uris = mpull($files, 'getBestURI', 'getPHID'); } } $paste_list_rows = array(); foreach ($pastes as $paste) { $handle = $handles[$paste->getAuthorPHID()]; $file_uri = $file_uris[$paste->getFilePHID()]; $paste_list_rows[] = array( phutil_escape_html('P'.$paste->getID()), // TODO: Make this filter by user instead of going to their profile. phutil_render_tag( 'a', array( 'href' => '/p/'.$handle->getName().'/', ), phutil_escape_html($handle->getName())), phutil_escape_html($paste->getLanguage()), phutil_render_tag( 'a', array( 'href' => '/P'.$paste->getID(), ), phutil_escape_html( nonempty( $paste->getTitle(), 'Untitled Masterwork P'.$paste->getID()))), phutil_render_tag( 'a', array( 'href' => $file_uri, ), phutil_escape_html($paste->getFilePHID())), ); } return array($paste_list_rows, $pager); } private function renderCreatePaste() { $request = $this->getRequest(); $user = $request->getUser(); $new_paste = $this->getPaste(); $form = new AphrontFormView(); $available_languages = PhabricatorEnv::getEnvConfig( 'pygments.dropdown-choices'); asort($available_languages); $language_select = id(new AphrontFormSelectControl()) ->setLabel('Language') ->setName('language') ->setValue($new_paste->getLanguage()) ->setOptions($available_languages); $form ->setUser($user) ->setAction($request->getRequestURI()->getPath()) ->addHiddenInput('parent', $new_paste->getParentPHID()) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Title') ->setValue($new_paste->getTitle()) ->setName('title')) ->appendChild($language_select) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Text') ->setError($this->getErrorText()) ->setValue($this->getPasteText()) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setName('text')) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/paste/') ->setValue('Create Paste')); $create_panel = new AphrontPanelView(); $create_panel->setWidth(AphrontPanelView::WIDTH_FULL); $create_panel->setHeader('Create a Paste'); $create_panel->appendChild($form); return $create_panel; } private function renderPasteList($paste_list_rows, $header, $pager = null) { $table = new AphrontTableView($paste_list_rows); $table->setHeaders( array( 'Paste ID', 'Author', 'Language', 'Title', 'File', )); $table->setColumnClasses( array( null, null, null, 'wide pri', null, )); $panel = new AphrontPanelView(); $panel->setWidth(AphrontPanelView::WIDTH_FULL); $panel->setHeader($header); $panel->appendChild($table); if ($pager) { $panel->appendChild($pager); } return $panel; } } diff --git a/src/applications/paste/controller/view/PhabricatorPasteViewController.php b/src/applications/paste/controller/view/PhabricatorPasteViewController.php index 3ae8afbc6e..48ecbea65f 100644 --- a/src/applications/paste/controller/view/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/view/PhabricatorPasteViewController.php @@ -1,189 +1,189 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $paste = id(new PhabricatorPaste())->load($this->id); if (!$paste) { return new Aphront404Response(); } $file = id(new PhabricatorFile())->loadOneWhere( 'phid = %s', $paste->getFilePHID()); if (!$file) { return new Aphront400Response(); } $corpus = $this->buildCorpus($paste, $file); $paste_panel = new AphrontPanelView(); if (strlen($paste->getTitle())) { $paste_panel->setHeader( 'Viewing Paste '.$paste->getID().' - '. phutil_escape_html($paste->getTitle())); } else { $paste_panel->setHeader('Viewing Paste '.$paste->getID()); } $paste_panel->setWidth(AphrontPanelView::WIDTH_FULL); $paste_panel->addButton( phutil_render_tag( 'a', array( 'href' => '/paste/?fork='.$paste->getID(), 'class' => 'green button', ), 'Fork This')); $raw_uri = $file->getBestURI(); $paste_panel->addButton( phutil_render_tag( 'a', array( 'href' => $raw_uri, 'class' => 'button', ), 'View Raw Text')); $paste_panel->appendChild($corpus); $forks_panel = null; $forks_of_this_paste = id(new PhabricatorPaste())->loadAllWhere( 'parentPHID = %s', $paste->getPHID()); if ($forks_of_this_paste) { $forks_panel = new AphrontPanelView(); $forks_panel->setHeader("Forks of this paste"); $forks = array(); foreach ($forks_of_this_paste as $fork) { $forks[] = array( $fork->getID(), phutil_render_tag( 'a', array( 'href' => '/P'.$fork->getID(), ), phutil_escape_html($fork->getTitle()) ) ); } $forks_table = new AphrontTableView($forks); $forks_table->setHeaders( array( 'Paste ID', 'Title', ) ); $forks_table->setColumnClasses( array( null, 'wide pri', ) ); $forks_panel->appendChild($forks_table); } return $this->buildStandardPageResponse( array( $paste_panel, $forks_panel, ), array( 'title' => 'Paste: '.nonempty($paste->getTitle(), 'P'.$paste->getID()), 'tab' => 'view', )); } private function buildCorpus($paste, $file) { // Blantently copied from DiffusionBrowseFileController require_celerity_resource('diffusion-source-css'); require_celerity_resource('syntax-highlighting-css'); $language = $paste->getLanguage(); $source = $file->loadFileData(); if (empty($language)) { $source = PhabricatorSyntaxHighlighter::highlightWithFilename( $paste->getTitle(), $source); } else { $source = PhabricatorSyntaxHighlighter::highlightWithLanguage( $language, $source); } $text_list = explode("\n", $source); $rows = $this->buildDisplayRows($text_list); $corpus_table = phutil_render_tag( 'table', array( 'class' => 'diffusion-source remarkup-code PhabricatorMonospaced', ), implode("\n", $rows)); $corpus = phutil_render_tag( 'div', array( 'style' => 'padding: 0pt 2em;', ), $corpus_table); return $corpus; } private function buildDisplayRows($text_list) { $rows = array(); $n = 1; foreach ($text_list as $k => $line) { // Pardon the ugly for the time being. // And eventually this will highlight a line that you click // like diffusion does. Or maybe allow for line comments // like differential. Either way it will be better than it is now. $anchor = 'L'.$n; $link = phutil_render_tag( 'a', array( 'name' => $anchor, 'href' => '#'.$anchor, ), $n); $rows[] = ''.$link.''. ''.$line.''; ++$n; } return $rows; } } diff --git a/src/applications/people/controller/edit/PhabricatorPeopleEditController.php b/src/applications/people/controller/edit/PhabricatorPeopleEditController.php index 9a1adad629..1dc6b87dc4 100644 --- a/src/applications/people/controller/edit/PhabricatorPeopleEditController.php +++ b/src/applications/people/controller/edit/PhabricatorPeopleEditController.php @@ -1,442 +1,443 @@ id = idx($data, 'id'); $this->view = idx($data, 'view'); } public function processRequest() { $request = $this->getRequest(); $admin = $request->getUser(); if ($this->id) { $user = id(new PhabricatorUser())->load($this->id); if (!$user) { return new Aphront404Response(); } } else { $user = new PhabricatorUser(); } $views = array( 'basic' => 'Basic Information', 'role' => 'Edit Role', 'cert' => 'Conduit Certificate', ); if (!$user->getID()) { $view = 'basic'; } else if (isset($views[$this->view])) { $view = $this->view; } else { $view = 'basic'; } $content = array(); if ($request->getStr('saved')) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle('Changes Saved'); $notice->appendChild('

    Your changes were saved.

    '); $content[] = $notice; } switch ($view) { case 'basic': $response = $this->processBasicRequest($user); break; case 'role': $response = $this->processRoleRequest($user); break; case 'cert': $response = $this->processCertificateRequest($user); break; } if ($response instanceof AphrontResponse) { return $response; } $content[] = $response; if ($user->getID()) { $side_nav = new AphrontSideNavView(); $side_nav->appendChild($content); foreach ($views as $key => $name) { $side_nav->addNavItem( phutil_render_tag( 'a', array( 'href' => '/people/edit/'.$user->getID().'/'.$key.'/', 'class' => ($key == $view) ? 'aphront-side-nav-selected' : null, ), phutil_escape_html($name))); } $content = $side_nav; } return $this->buildStandardPageResponse( $content, array( 'title' => 'Edit User', )); } private function processBasicRequest(PhabricatorUser $user) { $request = $this->getRequest(); $admin = $request->getUser(); $e_username = true; $e_realname = true; $e_email = true; $errors = array(); $welcome_checked = true; $request = $this->getRequest(); if ($request->isFormPost()) { $welcome_checked = $request->getInt('welcome'); if (!$user->getID()) { $user->setUsername($request->getStr('username')); $user->setEmail($request->getStr('email')); if ($request->getStr('role') == 'agent') { $user->setIsSystemAgent(true); } } $user->setRealName($request->getStr('realname')); if (!strlen($user->getUsername())) { $errors[] = "Username is required."; $e_username = 'Required'; } else if (!PhabricatorUser::validateUsername($user->getUsername())) { $errors[] = "Username must consist of only numbers and letters."; $e_username = 'Invalid'; } else { $e_username = null; } if (!strlen($user->getRealName())) { $errors[] = 'Real name is required.'; $e_realname = 'Required'; } else { $e_realname = null; } if (!strlen($user->getEmail())) { $errors[] = 'Email is required.'; $e_email = 'Required'; } else { $e_email = null; } if (!$errors) { try { $is_new = !$user->getID(); $user->save(); if ($is_new) { $log = PhabricatorUserLog::newLog( $admin, $user, PhabricatorUserLog::ACTION_CREATE); $log->save(); if ($welcome_checked) { $user->sendWelcomeEmail($admin); } } $response = id(new AphrontRedirectResponse()) ->setURI('/people/edit/'.$user->getID().'/?saved=true'); return $response; } catch (AphrontQueryDuplicateKeyException $ex) { $errors[] = 'Username and email must be unique.'; $same_username = id(new PhabricatorUser()) ->loadOneWhere('username = %s', $user->getUsername()); $same_email = id(new PhabricatorUser()) ->loadOneWhere('email = %s', $user->getEmail()); if ($same_username) { $e_username = 'Duplicate'; } if ($same_email) { $e_email = 'Duplicate'; } } } } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle('Form Errors') ->setErrors($errors); } $form = new AphrontFormView(); $form->setUser($admin); if ($user->getID()) { $form->setAction('/people/edit/'.$user->getID().'/'); } else { $form->setAction('/people/edit/'); } if ($user->getID()) { $is_immutable = true; } else { $is_immutable = false; } $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Username') ->setName('username') ->setValue($user->getUsername()) ->setError($e_username) ->setDisabled($is_immutable) ->setCaption('Usernames are permanent and can not be changed later!')) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Real Name') ->setName('realname') ->setValue($user->getRealName()) ->setError($e_realname)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Email') ->setName('email') ->setDisabled($is_immutable) ->setValue($user->getEmail()) ->setError($e_email)); if (!$user->getID()) { $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Role') ->setName('role') ->setValue('user') ->setOptions( array( 'user' => 'Normal User', 'agent' => 'System Agent', )) ->setCaption( 'You can create a "system agent" account for bots, scripts, '. 'etc.')) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'welcome', 1, 'Send "Welcome to Phabricator" email.', $welcome_checked)); } else { $form->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Role') ->setValue( $user->getIsSystemAgent() ? 'System Agent' : 'Normal User')); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save')); $panel = new AphrontPanelView(); if ($user->getID()) { $panel->setHeader('Edit User'); } else { $panel->setHeader('Create New User'); } $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); return array($error_view, $panel); } private function processRoleRequest(PhabricatorUser $user) { $request = $this->getRequest(); $admin = $request->getUser(); $is_self = ($user->getID() == $admin->getID()); $errors = array(); if ($request->isFormPost()) { $log_template = PhabricatorUserLog::newLog( $admin, $user, null); $logs = array(); if ($is_self) { $errors[] = "You can not edit your own role."; } else { $new_admin = (bool)$request->getBool('is_admin'); $old_admin = (bool)$user->getIsAdmin(); if ($new_admin != $old_admin) { $log = clone $log_template; $log->setAction(PhabricatorUserLog::ACTION_ADMIN); $log->setOldValue($old_admin); $log->setNewValue($new_admin); $user->setIsAdmin($new_admin); $logs[] = $log; } $new_disabled = (bool)$request->getBool('is_disabled'); $old_disabled = (bool)$user->getIsDisabled(); if ($new_disabled != $old_disabled) { $log = clone $log_template; $log->setAction(PhabricatorUserLog::ACTION_DISABLE); $log->setOldValue($old_disabled); $log->setNewValue($new_disabled); $user->setIsDisabled($new_disabled); $logs[] = $log; } } if (!$errors) { $user->save(); foreach ($logs as $log) { $log->save(); } return id(new AphrontRedirectResponse()) ->setURI($request->getRequestURI()->alter('saved', 'true')); } } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle('Form Errors') ->setErrors($errors); } $form = id(new AphrontFormView()) ->setUser($admin) ->setAction($request->getRequestURI()->alter('saved', null)); if ($is_self) { $form->appendChild( '

    NOTE: You can not edit your own '. 'role.

    '); } $form ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'is_admin', 1, 'Admin: wields absolute power.', $user->getIsAdmin()) ->setDisabled($is_self)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'is_disabled', 1, 'Disabled: can not login.', $user->getIsDisabled()) ->setDisabled($is_self)); if (!$is_self) { $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Edit Role')); } $panel = new AphrontPanelView(); $panel->setHeader('Edit Role'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return array($error_view, $panel); } private function processCertificateRequest($user) { $request = $this->getRequest(); $admin = $request->getUser(); $form = new AphrontFormView(); $form ->setUser($admin) ->setAction($request->getRequestURI()) ->appendChild( '

    You can use this certificate '. 'to write scripts or bots which interface with Phabricator over '. 'Conduit.

    '); if ($user->getIsSystemAgent()) { $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Username') ->setValue($user->getUsername())) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Certificate') ->setValue($user->getConduitCertificate())); } else { $form->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Certificate') ->setValue( 'You may only view the certificates of System Agents.')); } $panel = new AphrontPanelView(); $panel->setHeader('Conduit Certificate'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return array($panel); } } diff --git a/src/applications/people/controller/list/PhabricatorPeopleListController.php b/src/applications/people/controller/list/PhabricatorPeopleListController.php index 991b5fff40..1fa7a56b9d 100644 --- a/src/applications/people/controller/list/PhabricatorPeopleListController.php +++ b/src/applications/people/controller/list/PhabricatorPeopleListController.php @@ -1,127 +1,128 @@ getRequest(); $viewer = $request->getUser(); $is_admin = $viewer->getIsAdmin(); $user = new PhabricatorUser(); $count = queryfx_one( $user->establishConnection('r'), 'SELECT COUNT(*) N FROM %T', $user->getTableName()); $count = idx($count, 'N', 0); $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page', 0)); $pager->setCount($count); $pager->setURI($request->getRequestURI(), 'page'); $users = id(new PhabricatorUser())->loadAllWhere( '1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize()); $rows = array(); foreach ($users as $user) { $status = ''; if ($user->getIsDisabled()) { $status = 'Disabled'; } else if ($user->getIsAdmin()) { $status = 'Admin'; } else { $status = '-'; } $rows[] = array( phabricator_date($user->getDateCreated(), $viewer), phabricator_time($user->getDateCreated(), $viewer), phutil_render_tag( 'a', array( 'href' => '/p/'.$user->getUsername().'/', ), phutil_escape_html($user->getUserName())), phutil_escape_html($user->getRealName()), $status, phutil_render_tag( 'a', array( 'class' => 'button grey small', 'href' => '/people/edit/'.$user->getID().'/', ), 'Administrate User'), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Join Date', 'Time', 'Username', 'Real Name', 'Status', '', )); $table->setColumnClasses( array( null, 'right', 'pri', 'wide', null, 'action', )); $table->setColumnVisibility( array( true, true, true, true, $is_admin, $is_admin, )); $panel = new AphrontPanelView(); $panel->setHeader('People ('.number_format($count).')'); $panel->appendChild($table); $panel->appendChild($pager); if ($is_admin) { $panel->addButton( phutil_render_tag( 'a', array( 'href' => '/people/edit/', 'class' => 'button green', ), 'Create New Account')); } return $this->buildStandardPageResponse($panel, array( 'title' => 'People', 'tab' => 'directory', )); } } diff --git a/src/applications/people/controller/logs/PhabricatorPeopleLogsController.php b/src/applications/people/controller/logs/PhabricatorPeopleLogsController.php index 545a2ee27c..178b32fb04 100644 --- a/src/applications/people/controller/logs/PhabricatorPeopleLogsController.php +++ b/src/applications/people/controller/logs/PhabricatorPeopleLogsController.php @@ -1,244 +1,245 @@ getRequest(); $user = $request->getUser(); $filter_activity = $request->getStr('activity'); $filter_ip = $request->getStr('ip'); $filter_session = $request->getStr('session'); $filter_user = $request->getArr('user', array()); $filter_actor = $request->getArr('actor', array()); $user_value = array(); $actor_value = array(); $phids = array_merge($filter_user, $filter_actor); if ($phids) { $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); if ($filter_user) { $filter_user = reset($filter_user); $user_value = array( $filter_user => $handles[$filter_user]->getFullName(), ); } if ($filter_actor) { $filter_actor = reset($filter_actor); $actor_value = array( $filter_actor => $handles[$filter_actor]->getFullName(), ); } } $form = new AphrontFormView(); $form ->setUser($user) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('Filter Actor') ->setName('actor') ->setLimit(1) ->setValue($actor_value) ->setDatasource('/typeahead/common/accounts/')) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('Filter User') ->setName('user') ->setLimit(1) ->setValue($user_value) ->setDatasource('/typeahead/common/accounts/')) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Show Activity') ->setName('activity') ->setValue($filter_activity) ->setOptions( array( '' => 'All Activity', 'admin' => 'Admin Activity', ))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Filter IP') ->setName('ip') ->setValue($filter_ip) ->setCaption( 'Enter an IP (or IP prefix) to show only activity by that remote '. 'address.')) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Filter Session') ->setName('session') ->setValue($filter_session)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Filter Logs')); $log_table = new PhabricatorUserLog(); $conn_r = $log_table->establishConnection('r'); $where_clause = array(); $where_clause[] = '1 = 1'; if ($filter_user) { $where_clause[] = qsprintf( $conn_r, 'userPHID = %s', $filter_user); } if ($filter_actor) { $where_clause[] = qsprintf( $conn_r, 'actorPHID = %s', $filter_actor); } if ($filter_activity == 'admin') { $where_clause[] = qsprintf( $conn_r, 'action NOT IN (%Ls)', array( PhabricatorUserLog::ACTION_LOGIN, PhabricatorUserLog::ACTION_LOGOUT, PhabricatorUserLog::ACTION_LOGIN_FAILURE, )); } if ($filter_ip) { $where_clause[] = qsprintf( $conn_r, 'remoteAddr LIKE %>', $filter_ip); } if ($filter_session) { $where_clause[] = qsprintf( $conn_r, 'session = %s', $filter_session); } $where_clause = '('.implode(') AND (', $where_clause).')'; $pager = new AphrontPagerView(); $pager->setURI($request->getRequestURI(), 'page'); $pager->setOffset($request->getInt('page')); $pager->setPageSize(500); $logs = $log_table->loadAllWhere( '(%Q) ORDER BY dateCreated DESC LIMIT %d, %d', $where_clause, $pager->getOffset(), $pager->getPageSize() + 1); $logs = $pager->sliceResults($logs); $phids = array(); foreach ($logs as $log) { $phids[$log->getActorPHID()] = true; $phids[$log->getUserPHID()] = true; } $phids = array_keys($phids); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $rows = array(); foreach ($logs as $log) { $rows[] = array( phabricator_date($log->getDateCreated(),$user), phabricator_time($log->getDateCreated(),$user), $log->getAction(), $log->getActorPHID() ? phutil_escape_html($handles[$log->getActorPHID()]->getName()) : null, phutil_escape_html($handles[$log->getUserPHID()]->getName()), json_encode($log->getOldValue(), true), json_encode($log->getNewValue(), true), phutil_render_tag( 'a', array( 'href' => $request ->getRequestURI() ->alter('ip', $log->getRemoteAddr()), ), phutil_escape_html($log->getRemoteAddr())), phutil_render_tag( 'a', array( 'href' => $request ->getRequestURI() ->alter('session', $log->getSession()), ), phutil_escape_html($log->getSession())), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Date', 'Time', 'Action', 'Actor', 'User', 'Old', 'New', 'IP', 'Session', )); $table->setColumnClasses( array( '', 'right', '', '', '', 'wrap', 'wrap', '', 'wide', )); $panel = new AphrontPanelView(); $panel->setHeader('Activity Logs'); $panel->appendChild($table); $panel->appendChild($pager); $filter = new AphrontListFilterView(); $filter->appendChild($form); return $this->buildStandardPageResponse( array( $filter, $panel, ), array( 'title' => 'Activity Logs', 'tab' => 'logs', )); } } diff --git a/src/applications/people/controller/profile/PhabricatorPeopleProfileController.php b/src/applications/people/controller/profile/PhabricatorPeopleProfileController.php index 066c9448bc..111795f2df 100644 --- a/src/applications/people/controller/profile/PhabricatorPeopleProfileController.php +++ b/src/applications/people/controller/profile/PhabricatorPeopleProfileController.php @@ -1,221 +1,222 @@ username = idx($data, 'username'); $this->page = idx($data, 'page'); } public function processRequest() { $viewer = $this->getRequest()->getUser(); $user = id(new PhabricatorUser())->loadOneWhere( 'userName = %s', $this->username); if (!$user) { return new Aphront404Response(); } require_celerity_resource('phabricator-profile-css'); $profile = id(new PhabricatorUserProfile())->loadOneWhere( 'userPHID = %s', $user->getPHID()); if (!$profile) { $profile = new PhabricatorUserProfile(); } $username = phutil_escape_uri($user->getUserName()); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI('/p/'.$username.'/')); $nav->addFilter('feed', 'Feed'); $nav->addFilter('about', 'About'); $nav->addSpacer(); $nav->addLabel('Activity'); $external_arrow = "\xE2\x86\x97"; $nav->addFilter( null, "Revisions {$external_arrow}", '/differential/filter/revisions/'.$username.'/'); $nav->addFilter( null, "Tasks {$external_arrow}", '/maniphest/view/action/?users='.$user->getPHID()); $nav->addFilter( null, "Commits {$external_arrow}", '/audit/view/author/'.$username.'/'); $oauths = id(new PhabricatorUserOAuthInfo())->loadAllWhere( 'userID = %d', $user->getID()); $oauths = mpull($oauths, null, 'getOAuthProvider'); $providers = PhabricatorOAuthProvider::getAllProviders(); $added_spacer = false; foreach ($providers as $provider) { if (!$provider->isProviderEnabled()) { continue; } $provider_key = $provider->getProviderKey(); if (!isset($oauths[$provider_key])) { continue; } $name = $provider->getProviderName().' Profile'; $href = $oauths[$provider_key]->getAccountURI(); if ($href) { if (!$added_spacer) { $nav->addSpacer(); $nav->addLabel('Linked Accounts'); $added_spacer = true; } $nav->addFilter(null, $name.' '.$external_arrow, $href); } } $this->page = $nav->selectFilter($this->page, 'feed'); switch ($this->page) { case 'feed': $content = $this->renderUserFeed($user); break; case 'about': $content = $this->renderBasicInformation($user, $profile); break; default: throw new Exception("Unknown page '{$this->page}'!"); } $src_phid = $user->getProfileImagePHID(); $file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $src_phid); if ($file) { $picture = $file->getBestURI(); } else { $picture = null; } $header = new PhabricatorProfileHeaderView(); $header ->setProfilePicture($picture) ->setName($user->getUserName().' ('.$user->getRealName().')') ->setDescription($profile->getTitle()); $header->appendChild($nav); $nav->appendChild( '
    '.$content.'
    '); if ($user->getPHID() == $viewer->getPHID()) { $nav->addSpacer(); $nav->addFilter(null, 'Edit Profile...', '/settings/page/profile/'); } if ($viewer->getIsAdmin()) { $nav->addSpacer(); $nav->addFilter( null, 'Administrate User...', '/people/edit/'.$user->getID().'/'); } return $this->buildStandardPageResponse( $header, array( 'title' => $user->getUsername(), )); } private function renderBasicInformation($user, $profile) { $blurb = nonempty( $profile->getBlurb(), '//Nothing is known about this rare specimen.//'); $engine = PhabricatorMarkupEngine::newProfileMarkupEngine(); $blurb = $engine->markupText($blurb); $viewer = $this->getRequest()->getUser(); $content = '

    Basic Information

    PHID '.phutil_escape_html($user->getPHID()).'
    User Since '.phabricator_datetime($user->getDateCreated(), $viewer). '
    '; $content .= '

    Flavor Text

    Blurb '.$blurb.'
    '; return $content; } private function renderUserFeed(PhabricatorUser $user) { $query = new PhabricatorFeedQuery(); $query->setFilterPHIDs( array( $user->getPHID(), )); $stories = $query->execute(); $builder = new PhabricatorFeedBuilder($stories); $builder->setUser($this->getRequest()->getUser()); $view = $builder->buildView(); return '

    Activity Feed

    '.$view->render().'
    '; } } diff --git a/src/applications/people/controller/settings/PhabricatorUserSettingsController.php b/src/applications/people/controller/settings/PhabricatorUserSettingsController.php index 96b7aa710b..c56502ddcc 100644 --- a/src/applications/people/controller/settings/PhabricatorUserSettingsController.php +++ b/src/applications/people/controller/settings/PhabricatorUserSettingsController.php @@ -1,133 +1,134 @@ page = idx($data, 'page'); } public function processRequest() { $request = $this->getRequest(); $oauth_providers = PhabricatorOAuthProvider::getAllProviders(); $sidenav = $this->renderSideNav($oauth_providers); $this->page = $sidenav->selectFilter($this->page, 'account'); switch ($this->page) { case 'account': $delegate = new PhabricatorUserAccountSettingsPanelController($request); break; case 'profile': $delegate = new PhabricatorUserProfileSettingsPanelController($request); break; case 'email': $delegate = new PhabricatorUserEmailSettingsPanelController($request); break; case 'emailpref': $delegate = new PhabricatorUserEmailPreferenceSettingsPanelController( $request); break; case 'password': $delegate = new PhabricatorUserPasswordSettingsPanelController( $request); break; case 'conduit': $delegate = new PhabricatorUserConduitSettingsPanelController($request); break; case 'sshkeys': $delegate = new PhabricatorUserSSHKeysSettingsPanelController($request); break; case 'preferences': $delegate = new PhabricatorUserPreferenceSettingsPanelController( $request); break; default: $delegate = new PhabricatorUserOAuthSettingsPanelController($request); $delegate->setOAuthProvider($oauth_providers[$this->page]); break; } $response = $this->delegateToController($delegate); if ($response instanceof AphrontView) { $sidenav->appendChild($response); return $this->buildStandardPageResponse( $sidenav, array( 'title' => 'Account Settings', )); } else { return $response; } } private function renderSideNav($oauth_providers) { $sidenav = new AphrontSideNavFilterView(); $sidenav ->setBaseURI(new PhutilURI('/settings/page/')) ->addLabel('Account Information') ->addFilter('account', 'Account') ->addFilter('profile', 'Profile') ->addSpacer() ->addLabel('Email') ->addFilter('email', 'Email Address') ->addFilter('emailpref', 'Email Preferences') ->addSpacer() ->addLabel('Authentication'); if (PhabricatorEnv::getEnvConfig('account.editable') && PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) { $sidenav->addFilter('password', 'Password'); } $sidenav->addFilter('conduit', 'Conduit Certificate'); if (PhabricatorUserSSHKeysSettingsPanelController::isEnabled()) { $sidenav->addFilter('sshkeys', 'SSH Public Keys'); } $sidenav->addSpacer(); $sidenav->addLabel('Application Settings'); $sidenav->addFilter('preferences', 'Display Preferences'); $items = array(); foreach ($oauth_providers as $provider) { if (!$provider->isProviderEnabled()) { continue; } $key = $provider->getProviderKey(); $name = $provider->getProviderName(); $items[$key] = $name.' Account'; } if ($items) { $sidenav->addSpacer(); $sidenav->addLabel('Linked Accounts'); foreach ($items as $key => $name) { $sidenav->addFilter($key, $name); } } return $sidenav; } } diff --git a/src/applications/people/controller/settings/panels/account/PhabricatorUserAccountSettingsPanelController.php b/src/applications/people/controller/settings/panels/account/PhabricatorUserAccountSettingsPanelController.php index 5786e71358..de4c286b28 100644 --- a/src/applications/people/controller/settings/panels/account/PhabricatorUserAccountSettingsPanelController.php +++ b/src/applications/people/controller/settings/panels/account/PhabricatorUserAccountSettingsPanelController.php @@ -1,110 +1,110 @@ getRequest(); $user = $request->getUser(); $editable = $this->getAccountEditable(); $e_realname = $editable ? true : null; $errors = array(); if ($request->isFormPost()) { if ($editable) { $user->setRealName($request->getStr('realname')); if (!strlen($user->getRealName())) { $errors[] = 'Real name must be nonempty.'; $e_realname = 'Required'; } } $new_timezone = $request->getStr('timezone'); if (in_array($new_timezone, DateTimeZone::listIdentifiers(), true)) { $user->setTimezoneIdentifier($new_timezone); } else { $errors[] = 'The selected timezone is not a valid timezone.'; } if (!$errors) { $user->save(); return id(new AphrontRedirectResponse()) ->setURI('/settings/page/account/?saved=true'); } } $notice = null; if (!$errors) { if ($request->getStr('saved')) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle('Changes Saved'); $notice->appendChild('

    Your changes have been saved.

    '); $notice = $notice->render(); } } else { $notice = new AphrontErrorView(); $notice->setTitle('Form Errors'); $notice->setErrors($errors); $notice = $notice->render(); } $timezone_ids = DateTimeZone::listIdentifiers(); $timezone_id_map = array_combine($timezone_ids, $timezone_ids); $form = new AphrontFormView(); $form ->setUser($user) ->setEncType('multipart/form-data') ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Username') ->setValue($user->getUsername())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Real Name') ->setName('realname') ->setError($e_realname) ->setValue($user->getRealName()) ->setDisabled(!$editable)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Timezone') ->setName('timezone') ->setOptions($timezone_id_map) ->setValue($user->getTimezoneIdentifier())) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save')); $panel = new AphrontPanelView(); $panel->setHeader('Account Settings'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return id(new AphrontNullView()) ->appendChild( array( $notice, $panel, )); } } diff --git a/src/applications/people/controller/settings/panels/conduit/PhabricatorUserConduitSettingsPanelController.php b/src/applications/people/controller/settings/panels/conduit/PhabricatorUserConduitSettingsPanelController.php index 285987898d..2946374ff7 100644 --- a/src/applications/people/controller/settings/panels/conduit/PhabricatorUserConduitSettingsPanelController.php +++ b/src/applications/people/controller/settings/panels/conduit/PhabricatorUserConduitSettingsPanelController.php @@ -1,114 +1,114 @@ getRequest(); $user = $request->getUser(); if ($request->isFormPost()) { if (!$request->isDialogFormPost()) { $dialog = new AphrontDialogView(); $dialog->setUser($user); $dialog->setTitle('Really regenerate session?'); $dialog->setSubmitURI('/settings/page/conduit/'); $dialog->addSubmitButton('Regenerate'); $dialog->addCancelbutton('/settings/page/conduit/'); $dialog->appendChild( '

    Really destroy the old certificate? Any established '. 'sessions will be terminated.'); return id(new AphrontDialogResponse()) ->setDialog($dialog); } $conn = $user->establishConnection('w'); queryfx( $conn, 'DELETE FROM %T WHERE userPHID = %s AND type LIKE %>', PhabricatorUser::SESSION_TABLE, $user->getPHID(), 'conduit'); // This implicitly regenerates the certificate. $user->setConduitCertificate(null); $user->save(); return id(new AphrontRedirectResponse()) ->setURI('/settings/page/conduit/?regenerated=true'); } if ($request->getStr('regenerated')) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle('Certificate Regenerated'); $notice->appendChild( '

    Your old certificate has been destroyed and you have been issued '. 'a new certificate. Sessions established under the old certificate '. 'are no longer valid.

    '); $notice = $notice->render(); } else { $notice = null; } $cert_form = new AphrontFormView(); $cert_form ->setUser($user) ->appendChild( '

    This certificate allows you to '. 'authenticate over Conduit, the Phabricator API. Normally, you just '. 'run arc install-certificate to install it.') ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Certificate') ->setHeight(AphrontFormTextAreaControl::HEIGHT_SHORT) ->setValue($user->getConduitCertificate())); $cert = new AphrontPanelView(); $cert->setHeader('Arcanist Certificate'); $cert->appendChild($cert_form); $cert->setWidth(AphrontPanelView::WIDTH_FORM); $regen_form = new AphrontFormView(); $regen_form ->setUser($user) ->setAction('/settings/page/conduit/') ->appendChild( '

    You can regenerate this '. 'certificate, which will invalidate the old certificate and create '. 'a new one.

    ') ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Regenerate Certificate')); $regen = new AphrontPanelView(); $regen->setHeader('Regenerate Certificate'); $regen->appendChild($regen_form); $regen->setWidth(AphrontPanelView::WIDTH_FORM); return id(new AphrontNullView()) ->appendChild( array( $notice, $cert, $regen, )); } } diff --git a/src/applications/people/controller/settings/panels/email/PhabricatorUserEmailSettingsPanelController.php b/src/applications/people/controller/settings/panels/email/PhabricatorUserEmailSettingsPanelController.php index 2a29debae6..e5c1294818 100644 --- a/src/applications/people/controller/settings/panels/email/PhabricatorUserEmailSettingsPanelController.php +++ b/src/applications/people/controller/settings/panels/email/PhabricatorUserEmailSettingsPanelController.php @@ -1,96 +1,96 @@ getRequest(); $user = $request->getUser(); $editable = $this->getAccountEditable(); $e_email = true; $errors = array(); if ($request->isFormPost()) { if (!$editable) { return new Aphront400Response(); } $user->setEmail($request->getStr('email')); if (!strlen($user->getEmail())) { $errors[] = 'You must enter an e-mail address.'; $e_email = 'Required'; } if (!$errors) { $user->save(); return id(new AphrontRedirectResponse()) ->setURI('/settings/page/email/?saved=true'); } } $notice = null; if (!$errors) { if ($request->getStr('saved')) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle('Changes Saved'); $notice->appendChild('

    Your changes have been saved.

    '); } } else { $notice = new AphrontErrorView(); $notice->setTitle('Form Errors'); $notice->setErrors($errors); } $form = new AphrontFormView(); $form ->setUser($user) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Email') ->setName('email') ->setDisabled(!$editable) ->setCaption( 'Note: there is no email validation yet; double-check your '. 'typing.') ->setValue($user->getEmail()) ->setError($e_email)); if ($editable) { $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save')); } $panel = new AphrontPanelView(); $panel->setHeader('Email Settings'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return id(new AphrontNullView()) ->appendChild( array( $notice, $panel, )); } } diff --git a/src/applications/people/controller/settings/panels/emailpref/PhabricatorUserEmailPreferenceSettingsPanelController.php b/src/applications/people/controller/settings/panels/emailpref/PhabricatorUserEmailPreferenceSettingsPanelController.php index b4632d64ab..8b7e5c73d2 100644 --- a/src/applications/people/controller/settings/panels/emailpref/PhabricatorUserEmailPreferenceSettingsPanelController.php +++ b/src/applications/people/controller/settings/panels/emailpref/PhabricatorUserEmailPreferenceSettingsPanelController.php @@ -1,212 +1,212 @@ getRequest(); $user = $request->getUser(); $preferences = $user->loadPreferences(); $pref_re_prefix = PhabricatorUserPreferences::PREFERENCE_RE_PREFIX; $pref_no_self_mail = PhabricatorUserPreferences::PREFERENCE_NO_SELF_MAIL; $errors = array(); if ($request->isFormPost()) { if ($request->getStr($pref_re_prefix) == 'default') { $preferences->unsetPreference($pref_re_prefix); } else { $preferences->setPreference( $pref_re_prefix, $request->getBool($pref_re_prefix)); } $preferences->setPreference( $pref_no_self_mail, $request->getStr($pref_no_self_mail)); $new_tags = $request->getArr('mailtags'); $mailtags = $preferences->getPreference('mailtags', array()); foreach ($this->getMailTags() as $key => $label) { $mailtags[$key] = (bool)idx($new_tags, $key, false); } $preferences->setPreference('mailtags', $mailtags); $preferences->save(); return id(new AphrontRedirectResponse()) ->setURI('/settings/page/emailpref/?saved=true'); } $notice = null; if (!$errors) { if ($request->getStr('saved')) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle('Changes Saved'); $notice->appendChild('

    Your changes have been saved.

    '); } } else { $notice = new AphrontErrorView(); $notice->setTitle('Form Errors'); $notice->setErrors($errors); } $re_prefix_default = PhabricatorEnv::getEnvConfig('metamta.re-prefix') ? 'Enabled' : 'Disabled'; $re_prefix_value = $preferences->getPreference($pref_re_prefix); if ($re_prefix_value === null) { $re_prefix_value = 'defualt'; } else { $re_prefix_value = $re_prefix_value ? 'true' : 'false'; } $form = new AphrontFormView(); $form ->setUser($user) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Self Actions') ->setName($pref_no_self_mail) ->setOptions( array( '0' => 'Send me an email when I take an action', '1' => 'Do not send me an email when I take an action', )) ->setCaption('You can disable email about your own actions.') ->setValue($preferences->getPreference($pref_no_self_mail, 0))) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Add "Re:" Prefix') ->setName($pref_re_prefix) ->setCaption( 'Enable this option to fix threading in Mail.app on OS X Lion, '. 'or if you like "Re:" in your email subjects.') ->setOptions( array( 'default' => 'Use Server Default ('.$re_prefix_default.')', 'true' => 'Enable "Re:" prefix', 'false' => 'Disable "Re:" prefix', )) ->setValue($re_prefix_value)); $form ->appendChild( '
    '. '

    '. 'You can customize what mail you receive from Phabricator here.'. '

    '. '

    '. 'NOTE: If an update makes several changes (like '. 'adding CCs to a task, closing it, and adding a comment) you will '. 'still receive an email as long as at least one of the changes '. 'is set to notify you.'. '

    ' ); $mailtags = $preferences->getPreference('mailtags', array()); $form ->appendChild( $this->buildMailTagCheckboxes( $this->getDifferentialMailTags(), $mailtags) ->setLabel('Differential')) ->appendChild( $this->buildMailTagCheckboxes( $this->getManiphestMailTags(), $mailtags) ->setLabel('Maniphest')); $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save Preferences')); $panel = new AphrontPanelView(); $panel->setHeader('Email Preferences'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return id(new AphrontNullView()) ->appendChild( array( $notice, $panel, )); } private function getMailTags() { return array( MetaMTANotificationType::TYPE_DIFFERENTIAL_CC => "Send me email when a revision's CCs change.", MetaMTANotificationType::TYPE_DIFFERENTIAL_COMMITTED => "Send me email when a revision is committed.", MetaMTANotificationType::TYPE_MANIPHEST_PROJECTS => "Send me email when a task's associated projects change.", MetaMTANotificationType::TYPE_MANIPHEST_PRIORITY => "Send me email when a task's priority changes.", MetaMTANotificationType::TYPE_MANIPHEST_CC => "Send me email when a task's CCs change.", ); } private function getManiphestMailTags() { return array_select_keys( $this->getMailTags(), array( MetaMTANotificationType::TYPE_MANIPHEST_PROJECTS, MetaMTANotificationType::TYPE_MANIPHEST_PRIORITY, MetaMTANotificationType::TYPE_MANIPHEST_CC, )); } private function getDifferentialMailTags() { return array_select_keys( $this->getMailTags(), array( MetaMTANotificationType::TYPE_DIFFERENTIAL_CC, MetaMTANotificationType::TYPE_DIFFERENTIAL_COMMITTED, )); } private function buildMailTagCheckboxes( array $tags, array $prefs) { $control = new AphrontFormCheckboxControl(); foreach ($tags as $key => $label) { $control->addCheckbox( 'mailtags['.$key.']', 1, $label, idx($prefs, $key, 1)); } return $control; } } diff --git a/src/applications/people/controller/settings/panels/oauth/PhabricatorUserOAuthSettingsPanelController.php b/src/applications/people/controller/settings/panels/oauth/PhabricatorUserOAuthSettingsPanelController.php index 54444dcaa3..df735b9e02 100644 --- a/src/applications/people/controller/settings/panels/oauth/PhabricatorUserOAuthSettingsPanelController.php +++ b/src/applications/people/controller/settings/panels/oauth/PhabricatorUserOAuthSettingsPanelController.php @@ -1,172 +1,172 @@ provider = $oauth_provider; return $this; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $provider = $this->provider; $notice = null; $provider_name = $provider->getProviderName(); $provider_key = $provider->getProviderKey(); $oauth_info = id(new PhabricatorUserOAuthInfo())->loadOneWhere( 'userID = %d AND oauthProvider = %s', $user->getID(), $provider->getProviderKey()); $form = new AphrontFormView(); $form ->setUser($user); $forms = array(); $forms[] = $form; if (!$oauth_info) { $form ->appendChild( '

    There is currently no '. phutil_escape_html($provider_name).' account linked to your '. 'Phabricator account. You can link an account, which will allow you '. 'to use it to log into Phabricator.

    '); $auth_uri = $provider->getAuthURI(); $client_id = $provider->getClientID(); $redirect_uri = $provider->getRedirectURI(); $minimum_scope = $provider->getMinimumScope(); $form ->setAction($auth_uri) ->setMethod('GET') ->addHiddenInput('redirect_uri', $redirect_uri) ->addHiddenInput('client_id', $client_id) ->addHiddenInput('scope', $minimum_scope); foreach ($provider->getExtraAuthParameters() as $key => $value) { $form->addHiddenInput($key, $value); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Link '.$provider_name." Account \xC2\xBB")); } else { $form ->appendChild( '

    Your account is linked with '. 'a '.phutil_escape_html($provider_name).' account. You may use your '. phutil_escape_html($provider_name).' credentials to log into '. 'Phabricator.

    ') ->appendChild( id(new AphrontFormStaticControl()) ->setLabel($provider_name.' ID') ->setValue($oauth_info->getOAuthUID())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel($provider_name.' Name') ->setValue($oauth_info->getAccountName())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel($provider_name.' URI') ->setValue($oauth_info->getAccountURI())); if (!$provider->isProviderLinkPermanent()) { $unlink = 'Unlink '.$provider_name.' Account'; $unlink_form = new AphrontFormView(); $unlink_form ->setUser($user) ->appendChild( '

    You may unlink this account '. 'from your '.phutil_escape_html($provider_name).' account. This '. 'will prevent you from logging in with your '. phutil_escape_html($provider_name).' credentials.

    ') ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/oauth/'.$provider_key.'/unlink/', $unlink)); $forms['Unlink Account'] = $unlink_form; } $expires = $oauth_info->getTokenExpires(); if ($expires) { if ($expires <= time()) { $expires = "Expired"; } else { $expires = phabricator_datetime($expires, $user); } } else { $expires = 'No Information Available'; } $scope = $oauth_info->getTokenScope(); if (!$scope) { $scope = 'No Information Available'; } $status = $oauth_info->getTokenStatus(); $status = PhabricatorUserOAuthInfo::getReadableTokenStatus($status); $token_form = new AphrontFormView(); $token_form ->setUser($user) ->appendChild( '

    insert rap about tokens

    ') ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Token Status') ->setValue($status)) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Expires') ->setValue($expires)) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Scope') ->setValue($scope)); $forms['Account Token Information'] = $token_form; } $panel = new AphrontPanelView(); $panel->setHeader($provider_name.' Account Settings'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); foreach ($forms as $name => $form) { if ($name) { $panel->appendChild('

    '.$name.'

    '); } $panel->appendChild($form); } return id(new AphrontNullView()) ->appendChild( array( $notice, $panel, )); } } diff --git a/src/applications/people/controller/settings/panels/password/PhabricatorUserPasswordSettingsPanelController.php b/src/applications/people/controller/settings/panels/password/PhabricatorUserPasswordSettingsPanelController.php index 9e6475fefa..7ef23aec4f 100644 --- a/src/applications/people/controller/settings/panels/password/PhabricatorUserPasswordSettingsPanelController.php +++ b/src/applications/people/controller/settings/panels/password/PhabricatorUserPasswordSettingsPanelController.php @@ -1,159 +1,159 @@ getRequest(); $user = $request->getUser(); $editable = $this->getAccountEditable(); // There's no sense in showing a change password panel if the user // can't change their password if (!$editable || !PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) { return new Aphront400Response(); } $min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length'); $min_len = (int)$min_len; // NOTE: To change your password, you need to prove you own the account, // either by providing the old password or by carrying a token to // the workflow from a password reset email. $token = $request->getStr('token'); if ($token) { $valid_token = $user->validateEmailToken($token); } else { $valid_token = false; } $e_old = true; $e_new = true; $e_conf = true; $errors = array(); if ($request->isFormPost()) { if (!$valid_token) { if (!$user->comparePassword($request->getStr('old_pw'))) { $errors[] = 'The old password you entered is incorrect.'; $e_old = 'Invalid'; } } $pass = $request->getStr('new_pw'); $conf = $request->getStr('conf_pw'); if (strlen($pass) < $min_len) { $errors[] = 'Your new password is too short.'; $e_new = 'Too Short'; } if ($pass !== $conf) { $errors[] = 'New password and confirmation do not match.'; $e_conf = 'Invalid'; } if (!$errors) { $user->setPassword($pass); // This write is unguarded because the CSRF token has already // been checked in the call to $request->isFormPost() and // the CSRF token depends on the password hash, so when it // is changed here the CSRF token check will fail. $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $user->save(); unset($unguarded); if ($valid_token) { // If this is a password set/reset, kick the user to the home page // after we update their account. $next = '/'; } else { $next = '/settings/page/password/?saved=true'; } return id(new AphrontRedirectResponse())->setURI($next); } } $notice = null; if (!$errors) { if ($request->getStr('saved')) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle('Changes Saved'); $notice->appendChild('

    Your password has been updated.

    '); } } else { $notice = new AphrontErrorView(); $notice->setTitle('Error Changing Password'); $notice->setErrors($errors); } $len_caption = null; if ($min_len) { $len_caption = 'Minimum password length: '.$min_len.' characters.'; } $form = new AphrontFormView(); $form ->setUser($user) ->addHiddenInput('token', $token); if (!$valid_token) { $form->appendChild( id(new AphrontFormPasswordControl()) ->setLabel('Old Password') ->setError($e_old) ->setName('old_pw')); } $form ->appendChild( id(new AphrontFormPasswordControl()) ->setLabel('New Password') ->setError($e_new) ->setName('new_pw')); $form ->appendChild( id(new AphrontFormPasswordControl()) ->setLabel('Confirm Password') ->setCaption($len_caption) ->setError($e_conf) ->setName('conf_pw')); $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save')); $panel = new AphrontPanelView(); $panel->setHeader('Change Password'); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return id(new AphrontNullView()) ->appendChild( array( $notice, $panel, )); } } diff --git a/src/applications/people/controller/settings/panels/preferences/PhabricatorUserPreferenceSettingsPanelController.php b/src/applications/people/controller/settings/panels/preferences/PhabricatorUserPreferenceSettingsPanelController.php index 2ce95ac507..29a47d64ed 100644 --- a/src/applications/people/controller/settings/panels/preferences/PhabricatorUserPreferenceSettingsPanelController.php +++ b/src/applications/people/controller/settings/panels/preferences/PhabricatorUserPreferenceSettingsPanelController.php @@ -1,127 +1,127 @@ getRequest(); $user = $request->getUser(); $preferences = $user->loadPreferences(); $pref_monospaced = PhabricatorUserPreferences::PREFERENCE_MONOSPACED; $pref_editor = PhabricatorUserPreferences::PREFERENCE_EDITOR; $pref_titles = PhabricatorUserPreferences::PREFERENCE_TITLES; if ($request->isFormPost()) { $monospaced = $request->getStr($pref_monospaced); // Prevent the user from doing stupid things. $monospaced = preg_replace('/[^a-z0-9 ,"]+/i', '', $monospaced); $preferences->setPreference($pref_titles, $request->getStr($pref_titles)); $preferences->setPreference($pref_editor, $request->getStr($pref_editor)); $preferences->setPreference($pref_monospaced, $monospaced); $preferences->save(); return id(new AphrontRedirectResponse()) ->setURI('/settings/page/preferences/?saved=true'); } $example_string = << PhabricatorEnv::getDoclink( 'article/User_Guide:_Configuring_an_External_Editor.html'), ), 'User Guide: Configuring an External Editor'); $form = id(new AphrontFormView()) ->setUser($user) ->setAction('/settings/page/preferences/') ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Page Titles') ->setName($pref_titles) ->setValue($preferences->getPreference($pref_titles)) ->setOptions( array( 'glyph' => "In page titles, show Tool names as unicode glyphs: \xE2\x9A\x99", 'text' => 'In page titles, show Tool names as plain text: [Differential]', ))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Editor Link') ->setName($pref_editor) ->setCaption( 'Link to edit files in external editor. '. '%f is replaced by filename, %l by line number, %r by repository '. 'callsign. '. "For documentation, see {$editor_doc_link}.") ->setValue($preferences->getPreference($pref_editor))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Monospaced Font') ->setName($pref_monospaced) ->setCaption( 'Overrides default fonts in tools like Differential. '. '(Default: 10px "Menlo", "Consolas", "Monaco", '. 'monospace)') ->setValue($preferences->getPreference($pref_monospaced))) ->appendChild( id(new AphrontFormMarkupControl()) ->setValue( '
    '.
               phutil_escape_html($example_string).
               '
    ')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save Preferences')); $panel = new AphrontPanelView(); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->setHeader('Display Preferences'); $panel->appendChild($form); $error_view = null; if ($request->getStr('saved') === 'true') { $error_view = id(new AphrontErrorView()) ->setTitle('Preferences Saved') ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) ->setErrors(array('Your preferences have been saved.')); } return id(new AphrontNullView()) ->appendChild( array( $error_view, $panel, )); } } diff --git a/src/applications/people/controller/settings/panels/profile/PhabricatorUserProfileSettingsPanelController.php b/src/applications/people/controller/settings/panels/profile/PhabricatorUserProfileSettingsPanelController.php index 0d0194d571..fedd49b405 100644 --- a/src/applications/people/controller/settings/panels/profile/PhabricatorUserProfileSettingsPanelController.php +++ b/src/applications/people/controller/settings/panels/profile/PhabricatorUserProfileSettingsPanelController.php @@ -1,170 +1,170 @@ getRequest(); $user = $request->getUser(); $profile = id(new PhabricatorUserProfile())->loadOneWhere( 'userPHID = %s', $user->getPHID()); if (!$profile) { $profile = new PhabricatorUserProfile(); $profile->setUserPHID($user->getPHID()); } $errors = array(); if ($request->isFormPost()) { $profile->setTitle($request->getStr('title')); $profile->setBlurb($request->getStr('blurb')); if (!empty($_FILES['image'])) { $err = idx($_FILES['image'], 'error'); if ($err != UPLOAD_ERR_NO_FILE) { $file = PhabricatorFile::newFromPHPUpload( $_FILES['image'], array( 'authorPHID' => $user->getPHID(), )); $okay = $file->isTransformableImage(); if ($okay) { $xformer = new PhabricatorImageTransformer(); // Generate the large picture for the profile page. $large_xformed = $xformer->executeProfileTransform( $file, $width = 280, $min_height = 140, $max_height = 420); $profile->setProfileImagePHID($large_xformed->getPHID()); // Generate the small picture for comments, etc. $small_xformed = $xformer->executeProfileTransform( $file, $width = 50, $min_height = 50, $max_height = 50); $user->setProfileImagePHID($small_xformed->getPHID()); } else { $errors[] = 'Only valid image files (jpg, jpeg, png or gif) '. 'will be accepted.'; } } } if (!$errors) { $user->save(); $profile->save(); $response = id(new AphrontRedirectResponse()) ->setURI('/settings/page/profile/?saved=true'); return $response; } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Form Errors'); $error_view->setErrors($errors); } else { if ($request->getStr('saved')) { $error_view = new AphrontErrorView(); $error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $error_view->setTitle('Changes Saved'); $error_view->appendChild('

    Your changes have been saved.

    '); $error_view = $error_view->render(); } } $file = id(new PhabricatorFile())->loadOneWhere( 'phid = %s', $user->getProfileImagePHID()); if ($file) { $img_src = $file->getBestURI(); } else { $img_src = null; } $profile_uri = PhabricatorEnv::getURI('/p/'.$user->getUsername().'/'); $form = new AphrontFormView(); $form ->setUser($request->getUser()) ->setAction('/settings/page/profile/') ->setEncType('multipart/form-data') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Title') ->setName('title') ->setValue($profile->getTitle()) ->setCaption('Serious business title.')) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel('Profile URI') ->setValue( phutil_render_tag( 'a', array( 'href' => $profile_uri, ), phutil_escape_html($profile_uri)))) ->appendChild( '

    Write something about yourself! '. 'Make sure to include important information like '. 'your favorite pokemon and which Starcraft race you play.

    ') ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Blurb') ->setName('blurb') ->setValue($profile->getBlurb())) ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel('Profile Image') ->setValue( phutil_render_tag( 'img', array( 'src' => $img_src, )))) ->appendChild( id(new AphrontFormFileControl()) ->setLabel('Change Image') ->setName('image')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save') ->addCancelButton('/p/'.$user->getUsername().'/')); $panel = new AphrontPanelView(); $panel->setHeader('Edit Profile Details'); $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); return id(new AphrontNullView()) ->appendChild( array( $error_view, $panel, )); } } diff --git a/src/applications/people/controller/settings/panels/sshkeys/PhabricatorUserSSHKeysSettingsPanelController.php b/src/applications/people/controller/settings/panels/sshkeys/PhabricatorUserSSHKeysSettingsPanelController.php index 29c0319621..953b30032c 100644 --- a/src/applications/people/controller/settings/panels/sshkeys/PhabricatorUserSSHKeysSettingsPanelController.php +++ b/src/applications/people/controller/settings/panels/sshkeys/PhabricatorUserSSHKeysSettingsPanelController.php @@ -1,269 +1,269 @@ getRequest(); $user = $request->getUser(); $edit = $request->getStr('edit'); $delete = $request->getStr('delete'); if (!$edit && !$delete) { return $this->renderKeyListView(); } $id = nonempty($edit, $delete); if ($id && is_numeric($id)) { // NOTE: Prevent editing/deleting of keys you don't own. $key = id(new PhabricatorUserSSHKey())->loadOneWhere( 'userPHID = %s AND id = %d', $user->getPHID(), $id); if (!$key) { return new Aphront404Response(); } } else { $key = new PhabricatorUserSSHKey(); $key->setUserPHID($user->getPHID()); } if ($delete) { return $this->processDelete($key); } $e_name = true; $e_key = true; $errors = array(); $entire_key = $key->getEntireKey(); if ($request->isFormPost()) { $key->setName($request->getStr('name')); $entire_key = $request->getStr('key'); if (!strlen($entire_key)) { $errors[] = 'You must provide an SSH Public Key.'; $e_key = 'Required'; } else { $parts = str_replace("\n", '', trim($entire_key)); $parts = preg_split('/\s+/', $parts); if (count($parts) == 2) { $parts[] = ''; // Add an empty comment part. } else if (count($parts) == 3) { // This is the expected case. } else { if (preg_match('/private\s*key/i', $entire_key)) { // Try to give the user a better error message if it looks like // they uploaded a private key. $e_key = 'Invalid'; $errors[] = 'Provide your public key, not your private key!'; } else { $e_key = 'Invalid'; $errors[] = 'Provided public key is not properly formatted.'; } } if (!$errors) { list($type, $body, $comment) = $parts; if (!preg_match('/^ssh-dsa|ssh-rsa$/', $type)) { $e_key = 'Invalid'; $errors[] = 'Public key should be "ssh-dsa" or "ssh-rsa".'; } else { $key->setKeyType($type); $key->setKeyBody($body); $key->setKeyComment($comment); $e_key = null; } } } if (!strlen($key->getName())) { $errors[] = 'You must name this public key.'; $e_name = 'Required'; } else { $e_name = null; } if (!$errors) { try { $key->save(); return id(new AphrontRedirectResponse()) ->setURI(self::PANEL_BASE_URI); } catch (AphrontQueryDuplicateKeyException $ex) { $e_key = 'Duplicate'; $errors[] = 'This public key is already associated with a user '. 'account.'; } } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Form Errors'); $error_view->setErrors($errors); } $is_new = !$key->getID(); if ($is_new) { $header = 'Add New SSH Public Key'; $save = 'Add Key'; } else { $header = 'Edit SSH Public Key'; $save = 'Save Changes'; } $form = id(new AphrontFormView()) ->setUser($user) ->addHiddenInput('edit', $is_new ? 'true' : $key->getID()) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Name') ->setName('name') ->setValue($key->getName()) ->setError($e_name)) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Public Key') ->setName('key') ->setValue($entire_key) ->setError($e_key)) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton(self::PANEL_BASE_URI) ->setValue($save)); $panel = new AphrontPanelView(); $panel->setHeader($header); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->appendChild($form); return id(new AphrontNullView()) ->appendChild( array( $error_view, $panel, )); } private function renderKeyListView() { $request = $this->getRequest(); $user = $request->getUser(); $keys = id(new PhabricatorUserSSHKey())->loadAllWhere( 'userPHID = %s', $user->getPHID()); $rows = array(); foreach ($keys as $key) { $rows[] = array( phutil_render_tag( 'a', array( 'href' => '/settings/page/sshkeys/?edit='.$key->getID(), ), phutil_escape_html($key->getName())), phutil_escape_html($key->getKeyComment()), phutil_escape_html($key->getKeyType()), phabricator_date($key->getDateCreated(), $user), phabricator_time($key->getDateCreated(), $user), javelin_render_tag( 'a', array( 'href' => '/settings/page/sshkeys/?delete='.$key->getID(), 'class' => 'small grey button', 'sigil' => 'workflow', ), 'Delete'), ); } $table = new AphrontTableView($rows); $table->setNoDataString("You haven't added any SSH Public Keys."); $table->setHeaders( array( 'Name', 'Comment', 'Type', 'Created', 'Time', '', )); $table->setColumnClasses( array( 'wide pri', '', '', '', 'right', 'action', )); $panel = new AphrontPanelView(); $panel->addButton( phutil_render_tag( 'a', array( 'href' => '/settings/page/sshkeys/?edit=true', 'class' => 'green button', ), 'Add New Public Key')); $panel->setHeader('SSH Public Keys'); $panel->appendChild($table); return $panel; } private function processDelete(PhabricatorUserSSHKey $key) { $request = $this->getRequest(); $user = $request->getUser(); $name = phutil_escape_html($key->getName()); if ($request->isDialogFormPost()) { $key->delete(); return id(new AphrontReloadResponse()) ->setURI(self::PANEL_BASE_URI); } $dialog = id(new AphrontDialogView()) ->setUser($user) ->addHiddenInput('delete', $key->getID()) ->setTitle('Really delete SSH Public Key?') ->appendChild( '

    The key "'.$name.'" will be permanently deleted, '. 'and you will not longer be able to use the corresponding private key '. 'to authenticate.

    ') ->addSubmitButton('Delete Public Key') ->addCancelButton(self::PANEL_BASE_URI); return id(new AphrontDialogResponse()) ->setDialog($dialog); } } diff --git a/src/applications/phid/controller/lookup/PhabricatorPHIDLookupController.php b/src/applications/phid/controller/lookup/PhabricatorPHIDLookupController.php index 88aff7772c..b4b60bd2ef 100644 --- a/src/applications/phid/controller/lookup/PhabricatorPHIDLookupController.php +++ b/src/applications/phid/controller/lookup/PhabricatorPHIDLookupController.php @@ -1,110 +1,110 @@ getRequest(); if ($request->isFormPost()) { $phids = $request->getStrList('phids'); if ($phids) { $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $rows = array(); foreach ($handles as $handle) { if ($handle->getURI()) { $link = phutil_render_tag( 'a', array( 'href' => $handle->getURI(), ), phutil_escape_html($handle->getURI())); } else { $link = null; } $rows[] = array( phutil_escape_html($handle->getPHID()), phutil_escape_html($handle->getType()), phutil_escape_html($handle->getName()), phutil_escape_html($handle->getEmail()), $link, ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'PHID', 'Type', 'Name', 'Email', 'URI', )); $table->setColumnClasses( array( null, null, null, null, 'wide', )); $panel = new AphrontPanelView(); $panel->setHeader('PHID Handles'); $panel->appendChild($table); return $this->buildStandardPageResponse( $panel, array( 'title' => 'PHID Lookup Results', )); } } $lookup_form = new AphrontFormView(); $lookup_form->setUser($request->getUser()); $lookup_form ->setAction('/phid/') ->appendChild( id(new AphrontFormTextAreaControl()) ->setName('phids') // ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) TODO ->setCaption('Enter PHIDs separated by spaces or commas.')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Lookup PHIDs')); $lookup_panel = new AphrontPanelView(); $lookup_panel->setHeader('Lookup PHIDs'); $lookup_panel->appendChild($lookup_form); $lookup_panel->setWidth(AphrontPanelView::WIDTH_WIDE); return $this->buildStandardPageResponse( array( $lookup_panel, ), array( 'title' => 'PHID Lookup', )); } } diff --git a/src/applications/phriction/controller/delete/PhrictionDeleteController.php b/src/applications/phriction/controller/delete/PhrictionDeleteController.php index b6e25efee4..ada62b50a5 100644 --- a/src/applications/phriction/controller/delete/PhrictionDeleteController.php +++ b/src/applications/phriction/controller/delete/PhrictionDeleteController.php @@ -1,61 +1,61 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $document = id(new PhrictionDocument())->load($this->id); if (!$document) { return new Aphront404Response(); } $document_uri = PhrictionDocument::getSlugURI($document->getSlug()); if ($request->isFormPost()) { $editor = id(PhrictionDocumentEditor::newForSlug($document->getSlug())) ->setUser($user) ->delete(); return id(new AphrontRedirectResponse())->setURI($document_uri); } $dialog = id(new AphrontDialogView()) ->setUser($user) ->setTitle('Delete document?') ->appendChild( 'Really delete this document? You can recover it later by reverting '. 'to a previous version.') ->addSubmitButton('Delete') ->addCancelButton($document_uri); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/phriction/controller/diff/PhrictionDiffController.php b/src/applications/phriction/controller/diff/PhrictionDiffController.php index c713276021..0da52379fe 100644 --- a/src/applications/phriction/controller/diff/PhrictionDiffController.php +++ b/src/applications/phriction/controller/diff/PhrictionDiffController.php @@ -1,274 +1,274 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $document = id(new PhrictionDocument())->load($this->id); if (!$document) { return new Aphront404Response(); } $current = id(new PhrictionContent())->load($document->getContentID()); $l = $request->getInt('l'); $r = $request->getInt('r'); $ref = $request->getStr('ref'); if ($ref) { list($l, $r) = explode(',', $ref); } $content = id(new PhrictionContent())->loadAllWhere( 'documentID = %d AND version IN (%Ld)', $document->getID(), array($l, $r)); $content = mpull($content, null, 'getVersion'); $content_l = idx($content, $l, null); $content_r = idx($content, $r, null); if (!$content_l || !$content_r) { return new Aphront404Response(); } $text_l = $content_l->getContent(); $text_r = $content_r->getContent(); $text_l = wordwrap($text_l, 80); $text_r = wordwrap($text_r, 80); $engine = new PhabricatorDifferenceEngine(); $changeset = $engine->generateChangesetFromFileContent($text_l, $text_r); $changeset->setOldProperties( array( 'Title' => $content_l->getTitle(), )); $changeset->setNewProperties( array( 'Title' => $content_r->getTitle(), )); $whitespace_mode = DifferentialChangesetParser::WHITESPACE_SHOW_ALL; $parser = new DifferentialChangesetParser(); $parser->setChangeset($changeset); $parser->setRenderingReference("{$l},{$r}"); $parser->setWhitespaceMode($whitespace_mode); $spec = $request->getStr('range'); list($range_s, $range_e, $mask) = DifferentialChangesetParser::parseRangeSpecification($spec); $output = $parser->render($range_s, $range_e, $mask); if ($request->isAjax()) { return id(new AphrontAjaxResponse())->setContent($output); } require_celerity_resource('differential-changeset-view-css'); require_celerity_resource('syntax-highlighting-css'); require_celerity_resource('phriction-document-css'); Javelin::initBehavior('differential-show-more', array( 'uri' => '/phriction/diff/'.$document->getID().'/', 'whitespace' => $whitespace_mode, )); $slug = $document->getSlug(); $revert_l = $this->renderRevertButton($content_l, $current); $revert_r = $this->renderRevertButton($content_r, $current); $crumbs = new AphrontCrumbsView(); $crumbs->setCrumbs( array( 'Phriction', phutil_render_tag( 'a', array( 'href' => PhrictionDocument::getSlugURI($slug), ), phutil_escape_html($current->getTitle())), phutil_render_tag( 'a', array( 'href' => '/phriction/history/'.$document->getSlug().'/', ), 'History'), phutil_escape_html("Changes Between Version {$l} and Version {$r}"), )); $comparison_table = $this->renderComparisonTable( array( $content_r, $content_l, )); $navigation_table = null; if ($l + 1 == $r) { $nav_l = ($l > 1); $nav_r = ($r != $current->getVersion()); $uri = $request->getRequestURI(); if ($nav_l) { $link_l = phutil_render_tag( 'a', array( 'href' => $uri->alter('l', $l - 1)->alter('r', $r - 1), ), "\xC2\xAB Previous Change"); } else { $link_l = 'Original Change'; } $link_r = null; if ($nav_r) { $link_r = phutil_render_tag( 'a', array( 'href' => $uri->alter('l', $l + 1)->alter('r', $r + 1), ), "Next Change \xC2\xBB"); } else { $link_r = 'Most Recent Change'; } $navigation_table = '
    '; } $output = '
    '. $comparison_table->render(). '
    '. '
    '. $navigation_table. ''. ''. '
    '.$revert_l.''.$revert_r.'
    '. $output. '
    '; return $this->buildStandardPageResponse( array( $crumbs, $output, ), array( 'title' => 'Document History', )); } private function renderRevertButton( PhrictionContent $content, PhrictionContent $current) { $document_id = $content->getDocumentID(); $version = $content->getVersion(); if ($content->getChangeType() == PhrictionChangeType::CHANGE_DELETE) { // Don't show an edit/revert button for changes which deleted the content // since it's silly. return null; } if ($content->getID() == $current->getID()) { return phutil_render_tag( 'a', array( 'href' => '/phriction/edit/'.$document_id.'/', 'class' => 'button', ), 'Edit Current Version'); } return phutil_render_tag( 'a', array( 'href' => '/phriction/edit/'.$document_id.'/?revert='.$version, 'class' => 'button', ), 'Revert to Version '.phutil_escape_html($version).'...'); } private function renderComparisonTable(array $content) { $user = $this->getRequest()->getUser(); $phids = mpull($content, 'getAuthorPHID'); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $rows = array(); foreach ($content as $c) { $rows[] = array( phabricator_date($c->getDateCreated(), $user), phabricator_time($c->getDateCreated(), $user), phutil_escape_html('Version '.$c->getVersion()), $handles[$c->getAuthorPHID()]->renderLink(), phutil_escape_html($c->getDescription()), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Date', 'Time', 'Version', 'Author', 'Description', )); $table->setColumnClasses( array( '', 'right', 'pri', '', 'wide', )); return $table; } } diff --git a/src/applications/phriction/controller/document/PhrictionDocumentController.php b/src/applications/phriction/controller/document/PhrictionDocumentController.php index c74663d4d1..f0fe6e0b6a 100644 --- a/src/applications/phriction/controller/document/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/document/PhrictionDocumentController.php @@ -1,384 +1,384 @@ slug = $data['slug']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $slug = PhrictionDocument::normalizeSlug($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 PhrictionDocument())->loadOneWhere( 'slug = %s', $slug); $breadcrumbs = $this->renderBreadcrumbs($slug); $version_note = null; if (!$document) { $create_uri = '/phriction/edit/?slug='.$slug; $page_content = '
    '. 'No content here!
    '. 'No document found at '.phutil_escape_html($slug).'. '. 'You can '. phutil_render_tag( 'a', array( 'href' => $create_uri, ), 'create a new document').'.'. '
    '; $page_title = 'Page Not Found'; $button = phutil_render_tag( 'a', array( 'href' => $create_uri, 'class' => 'green button', ), 'Create Page'); $buttons = $button; } 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()) { $version_note = new AphrontErrorView(); $version_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $version_note->setTitle('Older Version'); $version_note->appendChild( 'You are viewing an older version of this document, as it '. 'appeared on '. phabricator_datetime($content->getDateCreated(), $user).'.'); } } else { $content = id(new PhrictionContent())->load($document->getContentID()); } $page_title = $content->getTitle(); $project_phid = null; if (PhrictionDocument::isProjectSlug($slug)) { $project = id(new PhabricatorProject())->loadOneWhere( 'phrictionSlug = %s', PhrictionDocument::getProjectSlugIdentifier($slug)); $project_phid = $project->getPHID(); } $phids = array_filter( array( $content->getAuthorPHID(), $project_phid, )); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $age = time() - $content->getDateCreated(); $age = floor($age / (60 * 60 * 24)); if ($age < 1) { $when = 'today'; } else if ($age == 1) { $when = 'yesterday'; } else { $when = "{$age} days ago"; } $project_info = null; if ($project_phid) { $project_info = '
    This document is about the project '. $handles[$project_phid]->renderLink().'.'; } $byline = ''; $doc_status = $document->getStatus(); if ($doc_status == PhrictionDocumentStatus::STATUS_EXISTS) { $core_content = $content->renderContent(); } else if ($doc_status == PhrictionDocumentStatus::STATUS_DELETED) { $notice = new AphrontErrorView(); $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $notice->setTitle('Document Deleted'); $notice->appendChild( '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 { throw new Exception("Unknown document status '{$doc_status}'!"); } $page_content = '
    '. $byline. $core_content. '
    '; $edit_button = phutil_render_tag( 'a', array( 'href' => '/phriction/edit/'.$document->getID().'/', 'class' => 'button', ), 'Edit Document'); $history_button = phutil_render_tag( 'a', array( 'href' => PhrictionDocument::getSlugURI($slug, 'history'), 'class' => 'button grey', ), 'View History'); // these float right so history_button which is right most goes first $buttons = $history_button.$edit_button; } if ($version_note) { $version_note = $version_note->render(); } $children = $this->renderChildren($slug); $page = '
    '. $buttons. '

    '.phutil_escape_html($page_title).'

    '. $breadcrumbs. '
    '. $version_note. $page_content. $children; return $this->buildStandardPageResponse( $page, array( 'title' => 'Phriction - '.$page_title, )); } private function renderBreadcrumbs($slug) { $ancestor_handles = array(); $ancestral_slugs = PhrictionDocument::getSlugAncestry($slug); $ancestral_slugs[] = $slug; if ($ancestral_slugs) { $empty_slugs = array_fill_keys($ancestral_slugs, null); $ancestors = id(new PhrictionDocument())->loadAllWhere( 'slug IN (%Ls)', $ancestral_slugs); $ancestors = mpull($ancestors, null, 'getSlug'); $ancestor_phids = mpull($ancestors, 'getPHID'); $handles = array(); if ($ancestor_phids) { $handles = id(new PhabricatorObjectHandleData($ancestor_phids)) ->loadHandles(); } $ancestor_handles = array(); foreach ($ancestral_slugs as $slug) { if (isset($ancestors[$slug])) { $ancestor_handles[] = $handles[$ancestors[$slug]->getPHID()]; } else { $handle = new PhabricatorObjectHandle(); $handle->setName(PhrictionDocument::getDefaultSlugTitle($slug)); $handle->setURI(PhrictionDocument::getSlugURI($slug)); $ancestor_handles[] = $handle; } } } $breadcrumbs = array(); foreach ($ancestor_handles as $ancestor_handle) { $breadcrumbs[] = $ancestor_handle->renderLink(); } $list = phutil_render_tag( 'a', array( 'href' => '/phriction/', ), 'Document Index'); return '
    '. $list.' · '. ''. implode(" \xC2\xBB ", $breadcrumbs). ''. '
    '; } private function renderChildren($slug) { $document_dao = new PhrictionDocument(); $content_dao = new PhrictionContent(); $conn = $document_dao->establishConnection('r'); $limit = 50; $d_child = PhrictionDocument::getSlugDepth($slug) + 1; $d_grandchild = PhrictionDocument::getSlugDepth($slug) + 2; // Select children and grandchildren. $children = queryfx_all( $conn, 'SELECT d.slug, d.depth, c.title FROM %T d JOIN %T c ON d.contentID = c.id WHERE d.slug LIKE %> AND d.depth IN (%d, %d) AND d.status = %d ORDER BY d.depth, c.title LIMIT %d', $document_dao->getTableName(), $content_dao->getTableName(), ($slug == '/' ? '' : $slug), $d_child, $d_grandchild, PhrictionDocumentStatus::STATUS_EXISTS, $limit); 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['depth'] == $d_grandchild) { $more_children = false; } } $show_grandchildren = false; } else { $show_grandchildren = true; $more_children = false; } $grandchildren = array(); foreach ($children as $key => $child) { if ($child['depth'] == $d_child) { continue; } else { unset($children[$key]); if ($show_grandchildren) { $ancestors = PhrictionDocument::getSlugAncestry($child['slug']); $grandchildren[end($ancestors)][] = $child; } } } // Fill in any missing children. $known_slugs = ipull($children, null, 'slug'); foreach ($grandchildren as $slug => $ignored) { if (empty($known_slugs[$slug])) { $children[] = array( 'slug' => $slug, 'depth' => $d_child, 'title' => PhrictionDocument::getDefaultSlugTitle($slug), 'empty' => true, ); } } $list = array(); $list[] = '
      '; foreach ($children as $child) { $list[] = $this->renderChildDocumentLink($child); $grand = idx($grandchildren, $child['slug'], array()); if ($grand) { $list[] = '
        '; foreach ($grand as $grandchild) { $list[] = $this->renderChildDocumentLink($grandchild); } $list[] = '
      '; } } if ($more_children) { $list[] = '
    • More...
    • '; } $list[] = '
    '; $list = implode("\n", $list); return '
    '. '
    Document Hierarchy
    '. $list. '
    '; } private function renderChildDocumentLink(array $info) { $title = nonempty($info['title'], '(Untitled Document)'); $item = phutil_render_tag( 'a', array( 'href' => PhrictionDocument::getSlugURI($info['slug']), ), phutil_escape_html($title)); if (isset($info['empty'])) { $item = ''.$item.''; } return '
  • '.$item.'
  • '; } } diff --git a/src/applications/phriction/controller/documentpreview/PhrictionDocumentPreviewController.php b/src/applications/phriction/controller/documentpreview/PhrictionDocumentPreviewController.php index c7297fdf2c..3790a67cf2 100644 --- a/src/applications/phriction/controller/documentpreview/PhrictionDocumentPreviewController.php +++ b/src/applications/phriction/controller/documentpreview/PhrictionDocumentPreviewController.php @@ -1,51 +1,51 @@ getRequest(); $document = $request->getStr('document'); $draft_key = $request->getStr('draftkey'); if ($draft_key) { $table = new PhabricatorDraft(); queryfx( $table->establishConnection('w'), 'INSERT INTO %T (authorPHID, draftKey, draft) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE draft = VALUES(draft)', $table->getTableName(), $request->getUser()->getPHID(), $draft_key, $document); } $content_obj = new PhrictionContent(); $content_obj->setContent($document); $engine = PhabricatorMarkupEngine::newPhrictionMarkupEngine(); $content = $content_obj->renderContent(); return id(new AphrontAjaxResponse())->setContent($content); } } diff --git a/src/applications/phriction/controller/edit/PhrictionEditController.php b/src/applications/phriction/controller/edit/PhrictionEditController.php index e3ffa7a943..6a36fd81b7 100644 --- a/src/applications/phriction/controller/edit/PhrictionEditController.php +++ b/src/applications/phriction/controller/edit/PhrictionEditController.php @@ -1,264 +1,264 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if ($this->id) { $document = id(new PhrictionDocument())->load($this->id); if (!$document) { return new Aphront404Response(); } $revert = $request->getInt('revert'); if ($revert) { $content = id(new PhrictionContent())->loadOneWhere( 'documentID = %d AND version = %d', $document->getID(), $revert); if (!$content) { return new Aphront404Response(); } } else { $content = id(new PhrictionContent())->load($document->getContentID()); } } else { $slug = $request->getStr('slug'); $slug = PhrictionDocument::normalizeSlug($slug); if (!$slug) { return new Aphront404Response(); } $document = id(new PhrictionDocument())->loadOneWhere( 'slug = %s', $slug); if ($document) { $content = id(new PhrictionContent())->load($document->getContentID()); } else { $document = new PhrictionDocument(); $document->setSlug($slug); $content = new PhrictionContent(); $content->setSlug($slug); $default_title = PhrictionDocument::getDefaultSlugTitle($slug); $content->setTitle($default_title); } } if ($request->getBool('nodraft')) { $draft = null; $draft_key = null; } else { if ($document->getPHID()) { $draft_key = $document->getPHID().':'.$content->getVersion(); } else { $draft_key = 'phriction:'.$content->getSlug(); } $draft = id(new PhabricatorDraft())->loadOneWhere( 'authorPHID = %s AND draftKey = %s', $user->getPHID(), $draft_key); } require_celerity_resource('phriction-document-css'); $e_title = true; $errors = array(); if ($request->isFormPost()) { $title = $request->getStr('title'); if (!strlen($title)) { $e_title = 'Required'; $errors[] = 'Document title is required.'; } else { $e_title = null; } if (!count($errors)) { $editor = id(PhrictionDocumentEditor::newForSlug($document->getSlug())) ->setUser($user) ->setTitle($title) ->setContent($request->getStr('content')) ->setDescription($request->getStr('description')); $editor->save(); if ($draft) { $draft->delete(); } $uri = PhrictionDocument::getSlugURI($document->getSlug()); return id(new AphrontRedirectResponse())->setURI($uri); } } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle('Form Errors') ->setErrors($errors); } if ($document->getID()) { $panel_header = 'Edit Phriction Document'; $submit_button = 'Save Changes'; $delete_button = phutil_render_tag( 'a', array( 'href' => '/phriction/delete/'.$document->getID().'/', 'class' => 'grey button', ), 'Delete Document'); } else { $panel_header = 'Create New Phriction Document'; $submit_button = 'Create Document'; $delete_button = null; } $uri = $document->getSlug(); $uri = PhrictionDocument::getSlugURI($uri); $uri = PhabricatorEnv::getProductionURI($uri); $remarkup_reference = phutil_render_tag( 'a', array( 'href' => PhabricatorEnv::getDoclink('article/Remarkup_Reference.html'), 'tabindex' => '-1', 'target' => '_blank', ), 'Formatting Reference'); $cancel_uri = PhrictionDocument::getSlugURI($document->getSlug()); if ($draft && strlen($draft->getDraft()) && ($draft->getDraft() != $content->getContent())) { $content_text = $draft->getDraft(); $discard = phutil_render_tag( 'a', array( 'href' => $request->getRequestURI()->alter('nodraft', true), ), 'discard this draft'); $draft_note = new AphrontErrorView(); $draft_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $draft_note->setTitle('Recovered Draft'); $draft_note->appendChild( '

    Showing a saved draft of your edits, you can '.$discard.'.

    '); } else { $content_text = $content->getContent(); $draft_note = null; } $form = id(new AphrontFormView()) ->setUser($user) ->setAction($request->getRequestURI()->getPath()) ->addHiddenInput('slug', $document->getSlug()) ->addHiddenInput('nodraft', $request->getBool('nodraft')) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Title') ->setValue($content->getTitle()) ->setError($e_title) ->setName('title')) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('URI') ->setValue($uri)) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Content') ->setValue($content_text) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setName('content') ->setID('document-textarea') ->setEnableDragAndDropFileUploads(true) ->setCaption($remarkup_reference)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Edit Notes') ->setValue($content->getDescription()) ->setError(null) ->setName('description')) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($submit_button)); $panel = id(new AphrontPanelView()) ->setWidth(AphrontPanelView::WIDTH_WIDE) ->setHeader($panel_header) ->appendChild($form); if ($delete_button) { $panel->addButton($delete_button); } $preview_panel = '
    Document Preview
    Loading preview...
    '; Javelin::initBehavior( 'phriction-document-preview', array( 'preview' => 'document-preview', 'textarea' => 'document-textarea', 'uri' => '/phriction/preview/?draftkey='.$draft_key, )); return $this->buildStandardPageResponse( array( $draft_note, $error_view, $panel, $preview_panel, ), array( 'title' => 'Edit Document', )); } } diff --git a/src/applications/phriction/controller/history/PhrictionHistoryController.php b/src/applications/phriction/controller/history/PhrictionHistoryController.php index dd23997b1c..9dabf13ed1 100644 --- a/src/applications/phriction/controller/history/PhrictionHistoryController.php +++ b/src/applications/phriction/controller/history/PhrictionHistoryController.php @@ -1,171 +1,171 @@ slug = $data['slug']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $document = id(new PhrictionDocument())->loadOneWhere( 'slug = %s', PhrictionDocument::normalizeSlug($this->slug)); if (!$document) { return new Aphront404Response(); } $current = id(new PhrictionContent())->load($document->getContentID()); $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page')); $pager->setURI($request->getRequestURI(), 'page'); $history = id(new PhrictionContent())->loadAllWhere( 'documentID = %d ORDER BY version DESC LIMIT %d, %d', $document->getID(), $pager->getOffset(), $pager->getPageSize() + 1); $history = $pager->sliceResults($history); $author_phids = mpull($history, 'getAuthorPHID'); $handles = id(new PhabricatorObjectHandleData($author_phids)) ->loadHandles(); $rows = array(); foreach ($history as $content) { $uri = PhrictionDocument::getSlugURI($document->getSlug()); $version = $content->getVersion(); $diff_uri = new PhutilURI('/phriction/diff/'.$document->getID().'/'); $vs_previous = 'Created'; if ($content->getVersion() != 1) { $uri = $diff_uri ->alter('l', $content->getVersion() - 1) ->alter('r', $content->getVersion()); $vs_previous = phutil_render_tag( 'a', array( 'href' => $uri, ), 'Show Change'); } $vs_head = 'Current'; if ($content->getID() != $document->getContentID()) { $uri = $diff_uri ->alter('l', $content->getVersion()) ->alter('r', $current->getVersion()); $vs_head = phutil_render_tag( 'a', array( 'href' => $uri, ), 'Show Later Changes'); } $change_type = PhrictionChangeType::getChangeTypeLabel( $content->getChangeType()); $rows[] = array( phabricator_date($content->getDateCreated(), $user), phabricator_time($content->getDateCreated(), $user), phutil_render_tag( 'a', array( 'href' => $uri.'?v='.$version, ), 'Version '.$version), $handles[$content->getAuthorPHID()]->renderLink(), $change_type, phutil_escape_html($content->getDescription()), $vs_previous, $vs_head, ); } $crumbs = new AphrontCrumbsView(); $crumbs->setCrumbs( array( 'Phriction', phutil_render_tag( 'a', array( 'href' => PhrictionDocument::getSlugURI($document->getSlug()), ), phutil_escape_html($current->getTitle()) ), 'History', )); $table = new AphrontTableView($rows); $table->setHeaders( array( 'Date', 'Time', 'Version', 'Author', 'Type', 'Description', 'Against Previous', 'Against Current', )); $table->setColumnClasses( array( '', 'right', 'pri', '', '', 'wide', '', '', )); $panel = new AphrontPanelView(); $panel->setHeader('Document History'); $panel->appendChild($table); $panel->appendChild($pager); return $this->buildStandardPageResponse( array( $crumbs, $panel, ), array( 'title' => 'Document History', )); } } diff --git a/src/applications/phriction/controller/list/PhrictionListController.php b/src/applications/phriction/controller/list/PhrictionListController.php index 48118ba2af..9eeed965b0 100644 --- a/src/applications/phriction/controller/list/PhrictionListController.php +++ b/src/applications/phriction/controller/list/PhrictionListController.php @@ -1,177 +1,177 @@ view = idx($data, 'view'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $views = array( 'all' => 'All Documents', 'updates' => 'Recently Updated', ); if (empty($views[$this->view])) { $this->view = 'all'; } $nav = new AphrontSideNavView(); foreach ($views as $view => $name) { $nav->addNavItem( phutil_render_tag( 'a', array( 'href' => '/phriction/list/'.$view.'/', 'class' => ($this->view == $view) ? 'aphront-side-nav-selected' : null, ), phutil_escape_html($name))); } $pager = new AphrontPagerView(); $pager->setURI($request->getRequestURI(), 'page'); $pager->setOffset($request->getInt('page')); $documents = $this->loadDocuments($pager); $content = mpull($documents, 'getContent'); $phids = mpull($content, 'getAuthorPHID'); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $rows = array(); foreach ($documents as $document) { $content = $document->getContent(); $rows[] = array( $handles[$content->getAuthorPHID()]->renderLink(), phutil_render_tag( 'a', array( 'href' => PhrictionDocument::getSlugURI($document->getSlug()), ), phutil_escape_html($content->getTitle())), phabricator_date($content->getDateCreated(), $user), phabricator_time($content->getDateCreated(), $user), ); } $document_table = new AphrontTableView($rows); $document_table->setHeaders( array( 'Last Editor', 'Title', 'Last Update', 'Time', )); $document_table->setColumnClasses( array( '', 'wide pri', '', 'right', '', 'right', )); $view_headers = array( 'all' => 'All Documents', 'updates' => 'Recently Updated Documents', ); $view_header = $view_headers[$this->view]; $panel = new AphrontPanelView(); $panel->setHeader($view_header); $panel->appendChild($document_table); $panel->appendChild($pager); $nav->appendChild($panel); return $this->buildStandardPageResponse($nav, array( 'title' => 'Phriction Main' )); } private function loadDocuments(AphrontPagerView $pager) { // TODO: Do we want/need a query object for this? $document_dao = new PhrictionDocument(); $content_dao = new PhrictionContent(); $conn = $document_dao->establishConnection('r'); switch ($this->view) { case 'all': $data = queryfx_all( $conn, 'SELECT * FROM %T ORDER BY id DESC LIMIT %d, %d', $document_dao->getTableName(), $pager->getOffset(), $pager->getPageSize() + 1); break; case 'updates': // TODO: This query is a little suspicious, verify we don't need to key // or change it once we get more data. $data = queryfx_all( $conn, 'SELECT d.* FROM %T d JOIN %T c ON c.documentID = d.id GROUP BY c.documentID ORDER BY MAX(c.id) DESC LIMIT %d, %d', $document_dao->getTableName(), $content_dao->getTableName(), $pager->getOffset(), $pager->getPageSize() + 1); break; default: throw new Exception("Unknown view '{$this->view}'!"); } $data = $pager->sliceResults($data); $documents = $document_dao->loadAllFromArray($data); if ($documents) { $content = $content_dao->loadAllWhere( 'documentID IN (%Ld)', mpull($documents, 'getID')); $content = mpull($content, null, 'getDocumentID'); foreach ($documents as $document) { $document->attachContent($content[$document->getID()]); } } return $documents; } } diff --git a/src/applications/project/controller/create/PhabricatorProjectCreateController.php b/src/applications/project/controller/create/PhabricatorProjectCreateController.php index 9ff9c1214b..e2a2ebe21e 100644 --- a/src/applications/project/controller/create/PhabricatorProjectCreateController.php +++ b/src/applications/project/controller/create/PhabricatorProjectCreateController.php @@ -1,147 +1,147 @@ getRequest(); $user = $request->getUser(); $project = new PhabricatorProject(); $project->setAuthorPHID($user->getPHID()); $profile = new PhabricatorProjectProfile(); $e_name = true; $errors = array(); if ($request->isFormPost()) { try { $xactions = array(); $xaction = new PhabricatorProjectTransaction(); $xaction->setTransactionType( PhabricatorProjectTransactionType::TYPE_NAME); $xaction->setNewValue($request->getStr('name')); $xactions[] = $xaction; $editor = new PhabricatorProjectEditor($project); $editor->setUser($user); $editor->applyTransactions($xactions); } catch (PhabricatorProjectNameCollisionException $ex) { $e_name = 'Not Unique'; $errors[] = $ex->getMessage(); } $profile->setBlurb($request->getStr('blurb')); if (!$errors) { $project->save(); $profile->setProjectPHID($project->getPHID()); $profile->save(); id(new PhabricatorProjectAffiliation()) ->setUserPHID($user->getPHID()) ->setProjectPHID($project->getPHID()) ->setRole('Owner') ->setIsOwner(true) ->save(); if ($request->isAjax()) { return id(new AphrontAjaxResponse()) ->setContent(array( 'phid' => $project->getPHID(), 'name' => $project->getName(), )); } else { return id(new AphrontRedirectResponse()) ->setURI('/project/view/'.$project->getID().'/'); } } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Form Errors'); $error_view->setErrors($errors); } if ($request->isAjax()) { $form = new AphrontFormLayoutView(); } else { $form = new AphrontFormView(); $form->setUser($user); } $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Name') ->setName('name') ->setValue($project->getName()) ->setError($e_name)) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Blurb') ->setName('blurb') ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) ->setValue($profile->getBlurb())); if ($request->isAjax()) { if ($error_view) { $error_view->setWidth(AphrontErrorView::WIDTH_DIALOG); } $dialog = id(new AphrontDialogView()) ->setUser($user) ->setWidth(AphrontDialogView::WIDTH_FORM) ->setTitle('Create a New Project') ->appendChild($error_view) ->appendChild($form) ->addSubmitButton('Create Project') ->addCancelButton('/project/'); return id(new AphrontDialogResponse())->setDialog($dialog); } else { $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Create') ->addCancelButton('/project/')); $panel = new AphrontPanelView(); $panel ->setWidth(AphrontPanelView::WIDTH_FORM) ->setHeader('Create a New Project') ->appendChild($form); return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Create new Project', )); } } } diff --git a/src/applications/project/controller/list/PhabricatorProjectListController.php b/src/applications/project/controller/list/PhabricatorProjectListController.php index e020af7943..35f53abf17 100644 --- a/src/applications/project/controller/list/PhabricatorProjectListController.php +++ b/src/applications/project/controller/list/PhabricatorProjectListController.php @@ -1,181 +1,181 @@ filter = idx($data, 'filter'); } public function processRequest() { $request = $this->getRequest(); $nav = new AphrontSideNavFilterView(); $nav ->setBaseURI(new PhutilURI('/project/filter/')) ->addLabel('User') ->addFilter('active', 'Active') ->addFilter('owned', 'Owned') ->addSpacer() ->addLabel('All') ->addFilter('all', 'All Projects'); $this->filter = $nav->selectFilter($this->filter, 'active'); $pager = new AphrontPagerView(); $pager->setPageSize(250); $pager->setURI($request->getRequestURI(), 'page'); $pager->setOffset($request->getInt('page')); $query = new PhabricatorProjectQuery(); $query->setOffset($pager->getOffset()); $query->setLimit($pager->getPageSize() + 1); $view_phid = $request->getUser()->getPHID(); $status_filter = PhabricatorProjectQuery::STATUS_ANY; switch ($this->filter) { case 'active': $table_header = 'Active Projects'; $query->setMembers(array($view_phid)); $query->withStatus(PhabricatorProjectQuery::STATUS_ACTIVE); break; case 'owned': $table_header = 'Owned Projects'; $query->setOwners(array($view_phid)); $query->withStatus($status_filter); break; case 'all': $table_header = 'All Projects'; $query->withStatus($status_filter); break; } $projects = $query->execute(); $projects = $pager->sliceResults($projects); $project_phids = mpull($projects, 'getPHID'); $profiles = array(); if ($projects) { $profiles = id(new PhabricatorProjectProfile())->loadAllWhere( 'projectPHID in (%Ls)', $project_phids); $profiles = mpull($profiles, null, 'getProjectPHID'); } $affil_groups = array(); if ($projects) { $affil_groups = PhabricatorProjectAffiliation::loadAllForProjectPHIDs( $project_phids); } $tasks = array(); $groups = array(); if ($project_phids) { $query = id(new ManiphestTaskQuery()) ->withProjects($project_phids) ->withAnyProject(true) ->withStatus(ManiphestTaskQuery::STATUS_OPEN) ->setLimit(PHP_INT_MAX); $tasks = $query->execute(); foreach ($tasks as $task) { foreach ($task->getProjectPHIDs() as $phid) { $groups[$phid][] = $task; } } } $rows = array(); foreach ($projects as $project) { $phid = $project->getPHID(); $profile = idx($profiles, $phid); $affiliations = $affil_groups[$phid]; $group = idx($groups, $phid, array()); $task_count = count($group); $population = count($affiliations); if ($profile) { $blurb = $profile->getBlurb(); $blurb = phutil_utf8_shorten($blurb, 64); } else { $blurb = null; } $rows[] = array( phutil_render_tag( 'a', array( 'href' => '/project/view/'.$project->getID().'/', ), phutil_escape_html($project->getName())), phutil_escape_html( PhabricatorProjectStatus::getNameForStatus($project->getStatus())), phutil_escape_html($blurb), phutil_escape_html($population), phutil_render_tag( 'a', array( 'href' => '/maniphest/view/all/?projects='.$phid, ), phutil_escape_html($task_count)), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Project', 'Status', 'Description', 'Population', 'Open Tasks', )); $table->setColumnClasses( array( 'pri', '', 'wide', '', '' )); $panel = new AphrontPanelView(); $panel->setHeader($table_header); $panel->setCreateButton('Create New Project', '/project/create/'); $panel->appendChild($table); $panel->appendChild($pager); $nav->appendChild($panel); return $this->buildStandardPageResponse( $nav, array( 'title' => 'Projects', )); } } diff --git a/src/applications/project/controller/profile/PhabricatorProjectProfileController.php b/src/applications/project/controller/profile/PhabricatorProjectProfileController.php index 6674bd17d7..c2ed64909e 100644 --- a/src/applications/project/controller/profile/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/profile/PhabricatorProjectProfileController.php @@ -1,380 +1,380 @@ id = idx($data, 'id'); $this->page = idx($data, 'page'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $project = id(new PhabricatorProject())->load($this->id); if (!$project) { return new Aphront404Response(); } $profile = $project->loadProfile(); if (!$profile) { $profile = new PhabricatorProjectProfile(); } $src_phid = $profile->getProfileImagePHID(); if (!$src_phid) { $src_phid = $user->getProfileImagePHID(); } $file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $src_phid); if ($file) { $picture = $file->getBestURI(); } else { $picture = null; } $members = mpull($project->loadAffiliations(), null, 'getUserPHID'); $nav_view = new AphrontSideNavFilterView(); $uri = new PhutilURI('/project/view/'.$project->getID().'/'); $nav_view->setBaseURI($uri); $external_arrow = "\xE2\x86\x97"; $tasks_uri = '/maniphest/view/all/?projects='.$project->getPHID(); $slug = PhrictionDocument::normalizeSlug($project->getName()); $phriction_uri = '/w/projects/'.$slug; $edit_uri = '/project/edit/'.$project->getID().'/'; $nav_view->addFilter('dashboard', 'Dashboard'); $nav_view->addSpacer(); $nav_view->addFilter('feed', 'Feed'); $nav_view->addFilter(null, 'Tasks '.$external_arrow, $tasks_uri); $nav_view->addFilter(null, 'Wiki '.$external_arrow, $phriction_uri); $nav_view->addFilter('people', 'People'); $nav_view->addFilter('about', 'About'); $nav_view->addSpacer(); $nav_view->addFilter(null, "Edit Project\xE2\x80\xA6", $edit_uri); $this->page = $nav_view->selectFilter($this->page, 'dashboard'); require_celerity_resource('phabricator-profile-css'); switch ($this->page) { case 'dashboard': $content = $this->renderTasksPage($project, $profile); $query = new PhabricatorFeedQuery(); $query->setFilterPHIDs( array( $project->getPHID(), )); $stories = $query->execute(); $content .= $this->renderStories($stories); break; case 'about': $content = $this->renderAboutPage($project, $profile); break; case 'people': $content = $this->renderPeoplePage($project, $profile); break; case 'feed': $content = $this->renderFeedPage($project, $profile); break; default: throw new Exception("Unimplemented filter '{$this->page}'."); } $content = '
    '.$content.'
    '; $nav_view->appendChild($content); $header = new PhabricatorProfileHeaderView(); $header->setName($project->getName()); $header->setDescription( phutil_utf8_shorten($profile->getBlurb(), 1024)); $header->setProfilePicture($picture); $action = null; if (empty($members[$user->getPHID()])) { $action = phabricator_render_form( $user, array( 'action' => '/project/update/'.$project->getID().'/join/', 'method' => 'post', ), phutil_render_tag( 'button', array( 'class' => 'green', ), 'Join Project')); } else { $action = javelin_render_tag( 'a', array( 'href' => '/project/update/'.$project->getID().'/leave/', 'sigil' => 'workflow', 'class' => 'grey button', ), 'Leave Project...'); } $header->addAction($action); $header->appendChild($nav_view); return $this->buildStandardPageResponse( $header, array( 'title' => $project->getName().' Project', )); } private function renderAboutPage( PhabricatorProject $project, PhabricatorProjectProfile $profile) { $viewer = $this->getRequest()->getUser(); $blurb = $profile->getBlurb(); $blurb = phutil_escape_html($blurb); $blurb = str_replace("\n", '
    ', $blurb); $phids = array_merge( array($project->getAuthorPHID()), $project->getSubprojectPHIDs() ); $phids = array_unique($phids); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $timestamp = phabricator_datetime($project->getDateCreated(), $viewer); $about = '

    About

    Creator '.$handles[$project->getAuthorPHID()]->renderLink().'
    Created '.$timestamp.'
    PHID '.phutil_escape_html($project->getPHID()).'
    Blurb '.$blurb.'
    '; if ($project->getSubprojectPHIDs()) { $table = $this->renderSubprojectTable( $handles, $project->getSubprojectPHIDs()); $subproject_list = $table->render(); } else { $subproject_list = '

    No subprojects.

    '; } $about .= '
    '. '

    Subprojects

    '. '
    '. $subproject_list. '
    '. '
    '; return $about; } private function renderPeoplePage( PhabricatorProject $project, PhabricatorProjectProfile $profile) { $affiliations = $project->loadAffiliations(); $phids = mpull($affiliations, 'getUserPHID'); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $affiliated = array(); foreach ($affiliations as $affiliation) { $user = $handles[$affiliation->getUserPHID()]->renderLink(); $role = phutil_escape_html($affiliation->getRole()); $affiliated[] = '
  • '.$user.' — '.$role.'
  • '; } if ($affiliated) { $affiliated = '
      '.implode("\n", $affiliated).'
    '; } else { $affiliated = '

    No one is affiliated with this project.

    '; } return '
    '. '

    People

    '. '
    '. $affiliated. '
    '. '
    '; } private function renderFeedPage( PhabricatorProject $project, PhabricatorProjectProfile $profile) { $query = new PhabricatorFeedQuery(); $query->setFilterPHIDs(array($project->getPHID())); $stories = $query->execute(); if (!$stories) { return 'There are no stories about this project.'; } $query = new PhabricatorFeedQuery(); $query->setFilterPHIDs( array( $project->getPHID(), )); $stories = $query->execute(); return $this->renderStories($stories); } private function renderStories(array $stories) { $builder = new PhabricatorFeedBuilder($stories); $builder->setUser($this->getRequest()->getUser()); $view = $builder->buildView(); return '
    '. '

    Activity Feed

    '. '
    '. $view->render(). '
    '. '
    '; } private function renderTasksPage( PhabricatorProject $project, PhabricatorProjectProfile $profile) { $query = id(new ManiphestTaskQuery()) ->withProjects(array($project->getPHID())) ->withStatus(ManiphestTaskQuery::STATUS_OPEN) ->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY) ->setLimit(10) ->setCalculateRows(true); $tasks = $query->execute(); $count = $query->getRowCount(); $phids = mpull($tasks, 'getOwnerPHID'); $phids = array_filter($phids); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $task_views = array(); foreach ($tasks as $task) { $view = id(new ManiphestTaskSummaryView()) ->setTask($task) ->setHandles($handles) ->setUser($this->getRequest()->getUser()); $task_views[] = $view->render(); } if (empty($tasks)) { $task_views = 'No open tasks.'; } else { $task_views = implode('', $task_views); } $open = number_format($count); $more_link = phutil_render_tag( 'a', array( 'href' => '/maniphest/view/all/?projects='.$project->getPHID(), ), "View All Open Tasks \xC2\xBB"); $content = '

    '. "Open Tasks ({$open})". '

    '. '
    '. $task_views. ''. '
    '; return $content; } private function renderSubprojectTable( PhabricatorObjectHandleData $handles, $subprojects_phids) { $rows = array(); foreach ($subprojects_phids as $subproject_phid) { $phid = $handles[$subproject_phid]->getPHID(); $rows[] = array( phutil_escape_html($handles[$phid]->getFullName()), phutil_render_tag( 'a', array( 'class' => 'small grey button', 'href' => $handles[$phid]->getURI(), ), 'View Project Profile'), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Name', '', )); $table->setColumnClasses( array( 'pri', 'action right', )); return $table; } } diff --git a/src/applications/project/controller/profileedit/PhabricatorProjectProfileEditController.php b/src/applications/project/controller/profileedit/PhabricatorProjectProfileEditController.php index 42d71d4678..a30a74fe79 100644 --- a/src/applications/project/controller/profileedit/PhabricatorProjectProfileEditController.php +++ b/src/applications/project/controller/profileedit/PhabricatorProjectProfileEditController.php @@ -1,310 +1,310 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $project = id(new PhabricatorProject())->load($this->id); if (!$project) { return new Aphront404Response(); } $profile = $project->loadProfile(); if (empty($profile)) { $profile = new PhabricatorProjectProfile(); } if ($project->getSubprojectPHIDs()) { $phids = $project->getSubprojectPHIDs(); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $subprojects = mpull($handles, 'getFullName', 'getPHID'); } else { $subprojects = array(); } $options = PhabricatorProjectStatus::getStatusMap(); $affiliations = $project->loadAffiliations(); $affiliations = mpull($affiliations, null, 'getUserPHID'); $e_name = true; $errors = array(); $state = null; if ($request->isFormPost()) { try { $xactions = array(); $xaction = new PhabricatorProjectTransaction(); $xaction->setTransactionType( PhabricatorProjectTransactionType::TYPE_NAME); $xaction->setNewValue($request->getStr('name')); $xactions[] = $xaction; $xaction = new PhabricatorProjectTransaction(); $xaction->setTransactionType( PhabricatorProjectTransactionType::TYPE_STATUS); $xaction->setNewValue($request->getStr('status')); $xactions[] = $xaction; $editor = new PhabricatorProjectEditor($project); $editor->setUser($user); $editor->applyTransactions($xactions); } catch (PhabricatorProjectNameCollisionException $ex) { $e_name = 'Not Unique'; $errors[] = $ex->getMessage(); } $project->setSubprojectPHIDs($request->getArr('set_subprojects')); $profile->setBlurb($request->getStr('blurb')); if (!strlen($project->getName())) { $e_name = 'Required'; $errors[] = 'Project name is required.'; } else { $e_name = null; } if (!empty($_FILES['image'])) { $err = idx($_FILES['image'], 'error'); if ($err != UPLOAD_ERR_NO_FILE) { $file = PhabricatorFile::newFromPHPUpload( $_FILES['image'], array( 'authorPHID' => $user->getPHID(), )); $okay = $file->isTransformableImage(); if ($okay) { $xformer = new PhabricatorImageTransformer(); $xformed = $xformer->executeThumbTransform( $file, $x = 50, $y = 50); $profile->setProfileImagePHID($xformed->getPHID()); } else { $errors[] = 'Only valid image files (jpg, jpeg, png or gif) '. 'will be accepted.'; } } } $resources = $request->getStr('resources'); $resources = json_decode($resources, true); if (!is_array($resources)) { throw new Exception( "Project resource information was not correctly encoded in the ". "request."); } $state = array(); foreach ($resources as $resource) { $user_phid = $resource['phid']; if (!$user_phid) { continue; } if (isset($state[$user_phid])) { // TODO: We should deal with this better -- the user has entered // the same resource more than once. } $state[$user_phid] = array( 'phid' => $user_phid, 'role' => $resource['role'], 'owner' => $resource['owner'], ); } $all_phids = array_merge(array_keys($state), array_keys($affiliations)); $all_phids = array_unique($all_phids); $delete_affiliations = array(); $save_affiliations = array(); foreach ($all_phids as $phid) { $old = idx($affiliations, $phid); $new = idx($state, $phid); if ($old && !$new) { $delete_affiliations[] = $affiliations[$phid]; continue; } if (!$old) { $affil = new PhabricatorProjectAffiliation(); $affil->setUserPHID($phid); } else { $affil = $old; } $affil->setRole((string)$new['role']); $affil->setIsOwner((int)$new['owner']); $save_affiliations[] = $affil; } if (!$errors) { $project->save(); $profile->setProjectPHID($project->getPHID()); $profile->save(); foreach ($delete_affiliations as $affil) { $affil->delete(); } foreach ($save_affiliations as $save) { $save->setProjectPHID($project->getPHID()); $save->save(); } return id(new AphrontRedirectResponse()) ->setURI('/project/view/'.$project->getID().'/'); } else { $phids = array_keys($state); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); foreach ($state as $phid => $info) { $state[$phid]['name'] = $handles[$phid]->getFullName(); } } } else { $phids = mpull($affiliations, 'getUserPHID'); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $state = array(); foreach ($affiliations as $affil) { $user_phid = $affil->getUserPHID(); $state[] = array( 'phid' => $user_phid, 'name' => $handles[$user_phid]->getFullName(), 'role' => $affil->getRole(), 'owner' => $affil->getIsOwner(), ); } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Form Errors'); $error_view->setErrors($errors); } $header_name = 'Edit Project'; $title = 'Edit Project'; $action = '/project/edit/'.$project->getID().'/'; require_celerity_resource('project-edit-css'); $form = new AphrontFormView(); $form ->setID('project-edit-form') ->setUser($user) ->setAction($action) ->setEncType('multipart/form-data') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Name') ->setName('name') ->setValue($project->getName()) ->setError($e_name)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Project Status') ->setName('status') ->setOptions($options) ->setValue($project->getStatus())) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Blurb') ->setName('blurb') ->setValue($profile->getBlurb())) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/projects/') ->setLabel('Subprojects') ->setName('set_subprojects') ->setValue($subprojects)) ->appendChild( id(new AphrontFormFileControl()) ->setLabel('Change Image') ->setName('image')) ->appendChild( '

    Resources

    '. ''. '
    '. '
    '. javelin_render_tag( 'a', array( 'href' => '#', 'class' => 'button green', 'sigil' => 'add-resource', 'mustcapture' => true, ), 'Add New Resource'). '
    '. '

    '. '
    '. javelin_render_tag( 'table', array( 'sigil' => 'resources', 'class' => 'project-resource-table', ), ''). '
    ') ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/project/view/'.$project->getID().'/') ->setValue('Save')); $template = new AphrontTokenizerTemplateView(); $template = $template->render(); Javelin::initBehavior( 'projects-resource-editor', array( 'root' => 'project-edit-form', 'tokenizerTemplate' => $template, 'tokenizerSource' => '/typeahead/common/users/', 'input' => 'resources', 'state' => array_values($state), )); $panel = new AphrontPanelView(); $panel->setHeader($header_name); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->appendChild($form); return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => $title, )); } } diff --git a/src/applications/project/controller/update/PhabricatorProjectUpdateController.php b/src/applications/project/controller/update/PhabricatorProjectUpdateController.php index bbb14e76af..29e589042c 100644 --- a/src/applications/project/controller/update/PhabricatorProjectUpdateController.php +++ b/src/applications/project/controller/update/PhabricatorProjectUpdateController.php @@ -1,113 +1,113 @@ id = $data['id']; $this->action = $data['action']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $project = id(new PhabricatorProject())->load($this->id); if (!$project) { return new Aphront404Response(); } $process_action = false; switch ($this->action) { case 'join': $process_action = $request->isFormPost(); break; case 'leave': $process_action = $request->isDialogFormPost(); break; default: return new Aphront404Response(); } $project_uri = '/project/view/'.$project->getID().'/'; if ($process_action) { $xactions = array(); switch ($this->action) { case 'join': $affils = $project->loadAffiliations(); $affils = mpull($affils, null, 'getUserPHID'); if (empty($affils[$user->getPHID()])) { $affils[$user->getPHID()] = true; $xaction = new PhabricatorProjectTransaction(); $xaction->setTransactionType( PhabricatorProjectTransactionType::TYPE_MEMBERS); $xaction->setNewValue(array_keys($affils)); $xactions[] = $xaction; } break; case 'leave': $affils = $project->loadAffiliations(); $affils = mpull($affils, null, 'getUserPHID'); if (isset($affils[$user->getPHID()])) { unset($affils[$user->getPHID()]); $xaction = new PhabricatorProjectTransaction(); $xaction->setTransactionType( PhabricatorProjectTransactionType::TYPE_MEMBERS); $xaction->setNewValue(array_keys($affils)); $xactions[] = $xaction; } break; } if ($xactions) { $editor = new PhabricatorProjectEditor($project); $editor->setUser($user); $editor->applyTransactions($xactions); } return id(new AphrontRedirectResponse())->setURI($project_uri); } $dialog = null; switch ($this->action) { case 'leave': $dialog = new AphrontDialogView(); $dialog->setUser($user); $dialog->setTitle('Really leave project?'); $dialog->appendChild( '

    Your tremendous contributions to this project will be sorely '. 'missed. Are you sure you want to leave?

    '); $dialog->addCancelButton($project_uri); $dialog->addSubmitButton('Leave Project'); break; default: return new Aphront404Response(); } return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/repository/controller/arcansistprojectedit/PhabricatorRepositoryArcanistProjectEditController.php b/src/applications/repository/controller/arcansistprojectedit/PhabricatorRepositoryArcanistProjectEditController.php index 920b358678..08c5abb248 100644 --- a/src/applications/repository/controller/arcansistprojectedit/PhabricatorRepositoryArcanistProjectEditController.php +++ b/src/applications/repository/controller/arcansistprojectedit/PhabricatorRepositoryArcanistProjectEditController.php @@ -1,127 +1,127 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $project = id(new PhabricatorRepositoryArcanistProject())->load($this->id); if (!$project) { return new Aphront404Response(); } $repositories = id(new PhabricatorRepository())->loadAll(); $repos = array( 0 => 'None', ); foreach ($repositories as $repository) { $callsign = $repository->getCallsign(); $name = $repository->getname(); $repos[$repository->getID()] = "r{$callsign} ({$name})"; } if ($request->isFormPost()) { $indexed = $request->getStrList('symbolIndexLanguages'); $indexed = array_map('strtolower', $indexed); $project->setSymbolIndexLanguages($indexed); $project->setSymbolIndexProjects($request->getArr('symbolIndexProjects')); $repo_id = $request->getInt('repository', 0); if (isset($repos[$repo_id])) { $project->setRepositoryID($repo_id); $project->save(); return id(new AphrontRedirectResponse()) ->setURI('/repository/'); } } $langs = $project->getSymbolIndexLanguages(); if ($langs) { $langs = implode(', ', $langs); } else { $langs = null; } if ($project->getSymbolIndexProjects()) { $uses = id(new PhabricatorRepositoryArcanistProject())->loadAllWhere( 'phid in (%Ls)', $project->getSymbolIndexProjects()); $uses = mpull($uses, 'getName', 'getPHID'); } else { $uses = array(); } $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Name') ->setValue($project->getName())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('PHID') ->setValue($project->getPHID())) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Repository') ->setOptions($repos) ->setName('repository') ->setValue($project->getRepositoryID())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Indexed Languages') ->setName('symbolIndexLanguages') ->setCaption('Separate with commas, for example: php, py') ->setValue($langs)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('Uses Symbols From') ->setName('symbolIndexProjects') ->setDatasource('/typeahead/common/arcanistprojects/') ->setValue($uses)) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/repository/') ->setValue('Save')); $panel = new AphrontPanelView(); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->setHeader('Edit Arcanist Project'); $panel->appendChild($form); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Edit Project', )); } } diff --git a/src/applications/repository/controller/create/PhabricatorRepositoryCreateController.php b/src/applications/repository/controller/create/PhabricatorRepositoryCreateController.php index 8dafb637c1..2381eda603 100644 --- a/src/applications/repository/controller/create/PhabricatorRepositoryCreateController.php +++ b/src/applications/repository/controller/create/PhabricatorRepositoryCreateController.php @@ -1,136 +1,136 @@ getRequest(); $user = $request->getUser(); $e_name = true; $e_callsign = true; $repository = new PhabricatorRepository(); $type_map = PhabricatorRepositoryType::getAllRepositoryTypes(); $errors = array(); if ($request->isFormPost()) { $repository->setName($request->getStr('name')); $repository->setCallsign($request->getStr('callsign')); $repository->setVersionControlSystem($request->getStr('type')); if (!strlen($repository->getName())) { $e_name = 'Required'; $errors[] = 'Repository name is required.'; } else { $e_name = null; } if (!strlen($repository->getCallsign())) { $e_callsign = 'Required'; $errors[] = 'Callsign is required.'; } else if (!preg_match('/^[A-Z]+$/', $repository->getCallsign())) { $e_callsign = 'Invalid'; $errors[] = 'Callsign must be ALL UPPERCASE LETTERS.'; } else { $e_callsign = null; } if (empty($type_map[$repository->getVersionControlSystem()])) { $errors[] = 'Invalid version control system.'; } if (!$errors) { try { $repository->save(); return id(new AphrontRedirectResponse()) ->setURI('/repository/edit/'.$repository->getID().'/'); } catch (AphrontQueryDuplicateKeyException $ex) { $e_callsign = 'Duplicate'; $errors[] = 'Callsign must be unique. Another repository already '. 'uses that callsign.'; } } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setErrors($errors); $error_view->setTitle('Form Errors'); } $form = new AphrontFormView(); $form ->setUser($user) ->setAction('/repository/create/') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Name') ->setName('name') ->setValue($repository->getName()) ->setError($e_name) ->setCaption('Human-readable repository name.')) ->appendChild( '

    Select a "Callsign" — a '. 'short, uppercase string to identify revisions in this repository. If '. 'you choose "EX", revisions in this repository will be identified '. 'with the prefix "rEX".

    ') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Callsign') ->setName('callsign') ->setValue($repository->getCallsign()) ->setError($e_callsign) ->setCaption( 'Short, UPPERCASE identifier. Once set, it can not be changed.')) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Type') ->setName('type') ->setOptions($type_map) ->setValue($repository->getVersionControlSystem())) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Create Repository') ->addCancelButton('/repository/')); $panel = new AphrontPanelView(); $panel->setHeader('Create Repository'); $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Create Repository', )); } } diff --git a/src/applications/repository/controller/delete/PhabricatorRepositoryDeleteController.php b/src/applications/repository/controller/delete/PhabricatorRepositoryDeleteController.php index 8f570ba375..344cbb24b7 100644 --- a/src/applications/repository/controller/delete/PhabricatorRepositoryDeleteController.php +++ b/src/applications/repository/controller/delete/PhabricatorRepositoryDeleteController.php @@ -1,56 +1,56 @@ id = $data['id']; } public function processRequest() { $repository = id(new PhabricatorRepository())->load($this->id); if (!$repository) { return new Aphront404Response(); } $request = $this->getRequest(); if ($request->isDialogFormPost()) { $repository->delete(); return id(new AphrontRedirectResponse())->setURI('/repository/'); } $dialog = new AphrontDialogView(); $dialog ->setUser($request->getUser()) ->setTitle('Really delete repository?') ->appendChild( '

    Really delete the "'.phutil_escape_html($repository->getName()). '" ('.phutil_escape_html($repository->getCallsign()).') repository? '. 'This operation can not be undone.

    ') ->setSubmitURI('/repository/delete/'.$this->id.'/') ->addSubmitButton('Delete Repository') ->addCancelButton('/repository/'); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/repository/controller/edit/PhabricatorRepositoryEditController.php b/src/applications/repository/controller/edit/PhabricatorRepositoryEditController.php index b8b34f07ca..b29d22ac18 100644 --- a/src/applications/repository/controller/edit/PhabricatorRepositoryEditController.php +++ b/src/applications/repository/controller/edit/PhabricatorRepositoryEditController.php @@ -1,696 +1,696 @@ id = $data['id']; $this->view = idx($data, 'view'); } public function processRequest() { $request = $this->getRequest(); $repository = id(new PhabricatorRepository())->load($this->id); if (!$repository) { return new Aphront404Response(); } $views = array( 'basic' => 'Basics', 'tracking' => 'Tracking', ); $this->repository = $repository; if (!isset($views[$this->view])) { reset($views); $this->view = key($views); } $nav = new AphrontSideNavView(); foreach ($views as $view => $name) { $nav->addNavItem( phutil_render_tag( 'a', array( 'class' => ($view == $this->view ? 'aphront-side-nav-selected' : null), 'href' => '/repository/edit/'.$repository->getID().'/'.$view.'/', ), phutil_escape_html($name))); } $this->sideNav = $nav; switch ($this->view) { case 'basic': return $this->processBasicRequest(); case 'tracking': return $this->processTrackingRequest(); default: throw new Exception("Unknown view."); } } protected function processBasicRequest() { $request = $this->getRequest(); $user = $request->getUser(); $repository = $this->repository; $repository_id = $repository->getID(); $errors = array(); $e_name = true; if ($request->isFormPost()) { $repository->setName($request->getStr('name')); if (!strlen($repository->getName())) { $e_name = 'Required'; $errors[] = 'Repository name is required.'; } else { $e_name = null; } $repository->setDetail('description', $request->getStr('description')); $repository->setDetail('encoding', $request->getStr('encoding')); if (!$errors) { $repository->save(); return id(new AphrontRedirectResponse()) ->setURI('/repository/edit/'.$repository_id.'/basic/?saved=true'); } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setErrors($errors); $error_view->setTitle('Form Errors'); } else if ($request->getStr('saved')) { $error_view = new AphrontErrorView(); $error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $error_view->setTitle('Changes Saved'); $error_view->appendChild( 'Repository changes were saved.'); } $encoding_doc_link = PhabricatorEnv::getDoclink( 'article/User_Guide:_UTF-8_and_Character_Encoding.html'); $form = new AphrontFormView(); $form ->setUser($user) ->setAction('/repository/edit/'.$repository->getID().'/') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Name') ->setName('name') ->setValue($repository->getName()) ->setError($e_name) ->setCaption('Human-readable repository name.')) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Description') ->setName('description') ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) ->setValue($repository->getDetail('description'))) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Callsign') ->setName('callsign') ->setValue($repository->getCallsign())) ->appendChild('

    '. 'If source code in this repository uses a character '. 'encoding other than UTF-8 (for example, ISO-8859-1), '. 'specify it here. You can usually leave this field blank. '. 'See User Guide: '. ''. 'UTF-8 and Character Encoding'. ' for more information.'. '

    ') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Encoding') ->setName('encoding') ->setValue($repository->getDetail('encoding'))) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Type') ->setName('type') ->setValue($repository->getVersionControlSystem())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('ID') ->setValue($repository->getID())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('PHID') ->setValue($repository->getPHID())) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save')); $panel = new AphrontPanelView(); $panel->setHeader('Edit Repository'); $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $nav = $this->sideNav; $nav->appendChild($error_view); $nav->appendChild($panel); return $this->buildStandardPageResponse( $nav, array( 'title' => 'Edit Repository', )); } private function processTrackingRequest() { $request = $this->getRequest(); $user = $request->getUser(); $repository = $this->repository; $repository_id = $repository->getID(); $errors = array(); $e_uri = null; $e_path = null; $is_git = false; $is_svn = false; $is_mercurial = false; $e_ssh_key = null; $e_ssh_keyfile = null; $e_branch = null; switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $is_git = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $is_svn = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $is_mercurial = true; break; default: throw new Exception("Unsupported VCS!"); } $has_branches = ($is_git || $is_mercurial); $has_local = ($is_git || $is_mercurial); $has_branch_filter = ($is_git); $has_http_support = $is_svn; if ($request->isFormPost()) { $tracking = ($request->getStr('tracking') == 'enabled' ? true : false); $repository->setDetail('tracking-enabled', $tracking); $repository->setDetail('remote-uri', $request->getStr('uri')); if ($has_local) { $repository->setDetail('local-path', $request->getStr('path')); } if ($has_branch_filter) { $branch_filter = $request->getStrList('branch-filter'); $branch_filter = array_fill_keys($branch_filter, true); $repository->setDetail('branch-filter', $branch_filter); } $repository->setDetail( 'pull-frequency', max(1, $request->getInt('frequency'))); if ($has_branches) { $repository->setDetail( 'default-branch', $request->getStr('default-branch')); if ($is_git) { $branch_name = $repository->getDetail('default-branch'); if (strpos($branch_name, '/') !== false) { $e_branch = 'Invalid'; $errors[] = "Your branch name should not specify an explicit ". "remote. For instance, use 'master', not ". "'origin/master'."; } } } $repository->setDetail( 'default-owners-path', $request->getStr( 'default-owners-path', '/')); $repository->setDetail('ssh-login', $request->getStr('ssh-login')); $repository->setDetail('ssh-key', $request->getStr('ssh-key')); $repository->setDetail('ssh-keyfile', $request->getStr('ssh-keyfile')); $repository->setDetail('http-login', $request->getStr('http-login')); $repository->setDetail('http-pass', $request->getStr('http-pass')); if ($repository->getDetail('ssh-key') && $repository->getDetail('ssh-keyfile')) { $errors[] = "Specify only one of 'SSH Private Key' and 'SSH Private Key File', ". "not both."; $e_ssh_key = 'Choose Only One'; $e_ssh_keyfile = 'Choose Only One'; } $repository->setDetail( 'herald-disabled', $request->getInt('herald-disabled', 0)); if ($is_svn) { $repository->setUUID($request->getStr('uuid')); $subpath = ltrim($request->getStr('svn-subpath'), '/'); if ($subpath) { $subpath = rtrim($subpath, '/').'/'; } $repository->setDetail('svn-subpath', $subpath); } $repository->setDetail( 'detail-parser', $request->getStr( 'detail-parser', 'PhabricatorRepositoryDefaultCommitMessageDetailParser')); if ($tracking) { if (!$repository->getDetail('remote-uri')) { $e_uri = 'Required'; $errors[] = "Repository URI is required."; } else if ($is_svn && !preg_match('@/$@', $repository->getDetail('remote-uri'))) { $e_uri = 'Invalid'; $errors[] = 'Subversion Repository Root must end in a slash ("/").'; } else { $e_uri = null; } if ($has_local) { if (!$repository->getDetail('local-path')) { $e_path = 'Required'; $errors[] = "Local path is required."; } else { $e_path = null; } } } if (!$errors) { $repository->save(); return id(new AphrontRedirectResponse()) ->setURI('/repository/edit/'.$repository_id.'/tracking/?saved=true'); } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setErrors($errors); $error_view->setTitle('Form Errors'); } else if ($request->getStr('saved')) { $error_view = new AphrontErrorView(); $error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE); $error_view->setTitle('Changes Saved'); $error_view->appendChild( 'Tracking changes were saved. You may need to restart the daemon '. 'before changes will take effect.'); } else if (!$repository->isTracked()) { $error_view = new AphrontErrorView(); $error_view->setSeverity(AphrontErrorView::SEVERITY_WARNING); $error_view->setTitle('Repository Not Tracked'); $error_view->appendChild( 'Tracking is currently "Disabled" for this repository, so it will '. 'not be imported into Phabricator. You can enable it below.'); } switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $is_git = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $is_svn = true; break; } $doc_href = PhabricatorEnv::getDoclink('article/Diffusion_User_Guide.html'); $user_guide_link = phutil_render_tag( 'a', array( 'href' => $doc_href, ), 'Diffusion User Guide'); $form = new AphrontFormView(); $form ->setUser($user) ->setAction('/repository/edit/'.$repository->getID().'/tracking/') ->appendChild( '

    Phabricator can track '. 'repositories, importing commits as they happen and notifying '. 'Differential, Diffusion, Herald, and other services. To enable '. 'tracking for a repository, configure it here and then start (or '. 'restart) the daemons. More information is available in the '. ''.$user_guide_link.'.

    '); $form ->appendChild( '

    Basics

    ') ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Repository Name') ->setValue($repository->getName())) ->appendChild( id(new AphrontFormSelectControl()) ->setName('tracking') ->setLabel('Tracking') ->setOptions(array( 'disabled' => 'Disabled', 'enabled' => 'Enabled', )) ->setValue( $repository->isTracked() ? 'enabled' : 'disabled')) ->appendChild('
    '); $form->appendChild( '

    Remote URI

    '. '
    '); $clone_command = null; $fetch_command = null; if ($is_git) { $clone_command = 'git clone'; $fetch_command = 'git fetch'; } else if ($is_mercurial) { $clone_command = 'hg clone'; $fetch_command = 'hg pull'; } $uri_label = 'Repository URI'; if ($has_local) { if ($is_git) { $instructions = 'Enter the URI to clone this repository from. It should look like '. 'git@github.com:example/example.git, '. 'ssh://user@host.com/git/example.git, or '. 'file:///local/path/to/repo'; } else if ($is_mercurial) { $instructions = 'Enter the URI to clone this repository from. It should look '. 'something like ssh://user@host.com/hg/example'; } $form->appendChild( '

    '.$instructions.'

    '); } else if ($is_svn) { $instructions = 'Enter the Repository Root for this SVN repository. '. 'You can figure this out by running svn info and looking at '. 'the value in the Repository Root field. It should be a URI '. 'and look like http://svn.example.org/svn/ or '. 'svn+ssh://svn.example.com/svnroot/'; $form->appendChild( '

    '.$instructions.'

    '); $uri_label = 'Repository Root'; } $form ->appendChild( id(new AphrontFormTextControl()) ->setName('uri') ->setLabel($uri_label) ->setID('remote-uri') ->setValue($repository->getDetail('remote-uri')) ->setError($e_uri)); $form->appendChild( '
    '. 'If you want to connect to this repository over SSH, enter the '. 'username and private key to use. You can leave these fields blank if '. 'the repository does not use SSH.'. '
    '); $form ->appendChild( id(new AphrontFormTextControl()) ->setName('ssh-login') ->setLabel('SSH User') ->setValue($repository->getDetail('ssh-login'))) ->appendChild( id(new AphrontFormTextAreaControl()) ->setName('ssh-key') ->setLabel('SSH Private Key') ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) ->setValue($repository->getDetail('ssh-key')) ->setError($e_ssh_key) ->setCaption('Specify the entire private key, or...')) ->appendChild( id(new AphrontFormTextControl()) ->setName('ssh-keyfile') ->setLabel('SSH Private Key File') ->setValue($repository->getDetail('ssh-keyfile')) ->setError($e_ssh_keyfile) ->setCaption( '...specify a path on disk where the daemon should '. 'look for a private key.')); if ($has_http_support) { $form ->appendChild( '
    '. 'If you want to connect to this repository over HTTP Basic Auth, '. 'enter the username and password to use. You can leave these '. 'fields blank if the repository does not use HTTP Basic Auth.'. '
    ') ->appendChild( id(new AphrontFormTextControl()) ->setName('http-login') ->setLabel('HTTP Basic Login') ->setValue($repository->getDetail('http-login'))) ->appendChild( id(new AphrontFormPasswordControl()) ->setName('http-pass') ->setLabel('HTTP Basic Password') ->setValue($repository->getDetail('http-pass'))); } $form ->appendChild( '
    '. 'To test your authentication configuration, save this '. 'form and then run this script:'. ''. 'phabricator/ $ ./scripts/repository/test_connection.php '. phutil_escape_html($repository->getCallsign()). ''. 'This will verify that your configuration is correct and the '. 'daemons can connect to the remote repository and pull changes '. 'from it.'. '
    '); $form->appendChild('
    '); $form->appendChild( '

    Importing Repository Information

    '. '
    '); if ($has_local) { $form->appendChild( '

    Select a path on local disk '. 'which the daemons should '.$clone_command.' the repository '. 'into. This must be readable and writable by the daemons, and '. 'readable by the webserver. The daemons will '.$fetch_command. ' and keep this repository up to date.

    '); $form->appendChild( id(new AphrontFormTextControl()) ->setName('path') ->setLabel('Local Path') ->setValue($repository->getDetail('local-path')) ->setError($e_path)); } else if ($is_svn) { $form->appendChild( '

    If you only want to parse one '. 'subpath of the repository, specify it here, relative to the '. 'repository root (e.g., trunk/ or projects/wheel/). '. 'If you want to parse multiple subdirectories, create a separate '. 'Phabricator repository for each one.

    '); $form->appendChild( id(new AphrontFormTextControl()) ->setName('svn-subpath') ->setLabel('Subpath') ->setValue($repository->getDetail('svn-subpath')) ->setError($e_path)); } if ($has_branch_filter) { $branch_filter_str = implode( ', ', array_keys($repository->getDetail('branch-filter', array()))); $form ->appendChild( id(new AphrontFormTextControl()) ->setName('branch-filter') ->setLabel('Track Only') ->setValue($branch_filter_str) ->setCaption( 'Optional list of branches to track. Other branches will be '. 'completely ignored. If left empty, all branches are tracked. '. 'Example: master, release')); } $form ->appendChild( id(new AphrontFormTextControl()) ->setName('frequency') ->setLabel('Pull Frequency') ->setValue($repository->getDetail('pull-frequency', 15)) ->setCaption( 'Number of seconds daemon should sleep between requests. Larger '. 'numbers reduce load but also decrease responsiveness.')); $form->appendChild('
    '); $form->appendChild( '

    Application Configuration

    '. '
    '); if ($has_branches) { $default_branch_name = null; if ($is_mercurial) { $default_branch_name = 'default'; } else if ($is_git) { $default_branch_name = 'master'; } $form ->appendChild( id(new AphrontFormTextControl()) ->setName('default-branch') ->setLabel('Default Branch') ->setValue( $repository->getDetail( 'default-branch', $default_branch_name)) ->setError($e_branch) ->setCaption( 'Default branch to show in Diffusion.')); } $form ->appendChild( id(new AphrontFormTextControl()) ->setName('default-owners-path') ->setLabel('Default Owners Path') ->setValue( $repository->getDetail( 'default-owners-path', '/')) ->setCaption('Default path in Owners tool.')); $form ->appendChild( id(new AphrontFormSelectControl()) ->setName('herald-disabled') ->setLabel('Herald Enabled') ->setValue($repository->getDetail('herald-disabled', 0)) ->setOptions( array( 0 => 'Enabled - Send Email', 1 => 'Disabled - Do Not Send Email', )) ->setCaption( 'You can temporarily disable Herald commit notifications when '. 'reparsing a repository or importing a new repository.')); $parsers = id(new PhutilSymbolLoader()) ->setAncestorClass('PhabricatorRepositoryCommitMessageDetailParser') ->selectSymbolsWithoutLoading(); $parsers = ipull($parsers, 'name', 'name'); $form ->appendChild( '

    If you extend the commit '. 'message format, you can provide a new parser which will extract '. 'extra information from it when commits are imported. This is an '. 'advanced feature, and using the default parser will be suitable '. 'in most cases.

    ') ->appendChild( id(new AphrontFormSelectControl()) ->setName('detail-parser') ->setLabel('Detail Parser') ->setOptions($parsers) ->setValue( $repository->getDetail( 'detail-parser', 'PhabricatorRepositoryDefaultCommitMessageDetailParser'))); if ($is_svn) { $form ->appendChild( id(new AphrontFormTextControl()) ->setName('uuid') ->setLabel('UUID') ->setValue($repository->getUUID()) ->setCaption('Repository UUID from svn info.')); } $form->appendChild('
    '); $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Save Configuration')); $panel = new AphrontPanelView(); $panel->setHeader('Repository Tracking'); $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $nav = $this->sideNav; $nav->appendChild($error_view); $nav->appendChild($panel); return $this->buildStandardPageResponse( $nav, array( 'title' => 'Edit Repository Tracking', )); } } diff --git a/src/applications/repository/controller/list/PhabricatorRepositoryListController.php b/src/applications/repository/controller/list/PhabricatorRepositoryListController.php index 5aa8d5d1dd..e2c2bf1121 100644 --- a/src/applications/repository/controller/list/PhabricatorRepositoryListController.php +++ b/src/applications/repository/controller/list/PhabricatorRepositoryListController.php @@ -1,168 +1,168 @@ getRequest(); $user = $request->getUser(); $is_admin = $user->getIsAdmin(); $repos = id(new PhabricatorRepository())->loadAll(); $rows = array(); foreach ($repos as $repo) { if ($repo->isTracked()) { $diffusion_link = phutil_render_tag( 'a', array( 'href' => '/diffusion/'.$repo->getCallsign().'/', ), 'View in Diffusion'); } else { $diffusion_link = 'Not Tracked'; } $rows[] = array( phutil_escape_html($repo->getCallsign()), phutil_escape_html($repo->getName()), PhabricatorRepositoryType::getNameForRepositoryType( $repo->getVersionControlSystem()), $diffusion_link, phutil_render_tag( 'a', array( 'class' => 'button small grey', 'href' => '/repository/edit/'.$repo->getID().'/', ), 'Edit'), javelin_render_tag( 'a', array( 'class' => 'button small grey', 'href' => '/repository/delete/'.$repo->getID().'/', 'sigil' => 'workflow', ), 'Delete'), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Callsign', 'Repository', 'Type', 'Diffusion', '', '' )); $table->setColumnClasses( array( null, 'wide', null, null, 'action', 'action', )); $table->setColumnVisibility( array( true, true, true, true, $is_admin, $is_admin, )); $panel = new AphrontPanelView(); $panel->setHeader('Repositories'); if ($is_admin) { $panel->setCreateButton('Create New Repository', '/repository/create/'); } $panel->appendChild($table); $projects = id(new PhabricatorRepositoryArcanistProject())->loadAll(); $rows = array(); foreach ($projects as $project) { $repo = idx($repos, $project->getRepositoryID()); if ($repo) { $repo_name = phutil_escape_html($repo->getName()); } else { $repo_name = '-'; } $rows[] = array( phutil_escape_html($project->getName()), $repo_name, phutil_render_tag( 'a', array( 'href' => '/repository/project/'.$project->getID().'/', 'class' => 'button grey small', ), 'Edit'), ); } $project_table = new AphrontTableView($rows); $project_table->setHeaders( array( 'Project ID', 'Repository', '', )); $project_table->setColumnClasses( array( '', 'wide', 'action', )); $project_table->setColumnVisibility( array( true, true, $is_admin, )); $project_panel = new AphrontPanelView(); $project_panel->setHeader('Arcanist Projects'); $project_panel->appendChild($project_table); return $this->buildStandardPageResponse( array( $panel, $project_panel, ), array( 'title' => 'Repository List', )); } } diff --git a/src/applications/search/controller/attach/PhabricatorSearchAttachController.php b/src/applications/search/controller/attach/PhabricatorSearchAttachController.php index c722b88168..d46e7d6772 100644 --- a/src/applications/search/controller/attach/PhabricatorSearchAttachController.php +++ b/src/applications/search/controller/attach/PhabricatorSearchAttachController.php @@ -1,273 +1,274 @@ phid = $data['phid']; $this->type = $data['type']; $this->action = idx($data, 'action', self::ACTION_ATTACH); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $handle_data = new PhabricatorObjectHandleData(array($this->phid)); $handles = $handle_data->loadHandles(); $handle = $handles[$this->phid]; $object_type = $handle->getType(); $attach_type = $this->type; $objects = $handle_data->loadObjects(); $object = idx($objects, $this->phid); if (!$object) { return new Aphront404Response(); } if ($request->isFormPost()) { $phids = explode(';', $request->getStr('phids')); $phids = array_filter($phids); $phids = array_values($phids); switch ($this->action) { case self::ACTION_MERGE: return $this->performMerge($object, $handle, $phids); case self::ACTION_DEPENDENCIES: case self::ACTION_ATTACH: $two_way = true; if ($this->action == self::ACTION_DEPENDENCIES) { $two_way = false; $this->detectGraphCycles( $object, $attach_type, $phids); } $editor = new PhabricatorObjectAttachmentEditor( $object_type, $object); $editor->setUser($this->getRequest()->getUser()); $editor->attachObjects( $attach_type, $phids, $two_way); return id(new AphrontReloadResponse())->setURI($handle->getURI()); default: throw new Exception("Unsupported attach action."); } } else { switch ($this->action) { case self::ACTION_ATTACH: case self::ACTION_DEPENDENCIES: $phids = $object->getAttachedPHIDs($attach_type); break; default: $phids = array(); break; } } $strings = $this->getStrings(); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $obj_dialog = new PhabricatorObjectSelectorDialog(); $obj_dialog ->setUser($user) ->setHandles($handles) ->setFilters(array( 'assigned' => 'Assigned to Me', 'created' => 'Created By Me', 'open' => 'All Open '.$strings['target_plural_noun'], 'all' => 'All '.$strings['target_plural_noun'], )) ->setSelectedFilter($strings['selected']) ->setCancelURI($handle->getURI()) ->setSearchURI('/search/select/'.$attach_type.'/') ->setTitle($strings['title']) ->setHeader($strings['header']) ->setButtonText($strings['button']) ->setInstructions($strings['instructions']); $dialog = $obj_dialog->buildDialog(); return id(new AphrontDialogResponse())->setDialog($dialog); } private function performMerge( ManiphestTask $task, PhabricatorObjectHandle $handle, array $phids) { $user = $this->getRequest()->getUser(); $response = id(new AphrontReloadResponse())->setURI($handle->getURI()); $phids = array_fill_keys($phids, true); unset($phids[$task->getPHID()]); // Prevent merging a task into itself. if (!$phids) { return $response; } $targets = id(new ManiphestTask())->loadAllWhere( 'phid in (%Ls) ORDER BY id ASC', array_keys($phids)); if (empty($targets)) { return $response; } $editor = new ManiphestTransactionEditor(); $task_names = array(); $merge_into_name = 'T'.$task->getID(); $cc_vector = array(); $cc_vector[] = $task->getCCPHIDs(); foreach ($targets as $target) { $cc_vector[] = $target->getCCPHIDs(); $cc_vector[] = array( $target->getAuthorPHID(), $target->getOwnerPHID()); $close_task = id(new ManiphestTransaction()) ->setAuthorPHID($user->getPHID()) ->setTransactionType(ManiphestTransactionType::TYPE_STATUS) ->setNewValue(ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE) ->setComments("\xE2\x9C\x98 Merged into {$merge_into_name}."); $editor->applyTransactions($target, array($close_task)); $task_names[] = 'T'.$target->getID(); } $all_ccs = array_mergev($cc_vector); $all_ccs = array_filter($all_ccs); $all_ccs = array_unique($all_ccs); $task_names = implode(', ', $task_names); $add_ccs = id(new ManiphestTransaction()) ->setAuthorPHID($user->getPHID()) ->setTransactionType(ManiphestTransactionType::TYPE_CCS) ->setNewValue($all_ccs) ->setComments("\xE2\x97\x80 Merged tasks: {$task_names}."); $editor->applyTransactions($task, array($add_ccs)); return $response; } private function getStrings() { switch ($this->type) { case PhabricatorPHIDConstants::PHID_TYPE_DREV: $noun = 'Revisions'; $selected = 'created'; break; case PhabricatorPHIDConstants::PHID_TYPE_TASK: $noun = 'Tasks'; $selected = 'assigned'; break; } switch ($this->action) { case self::ACTION_ATTACH: $dialog_title = "Manage Attached {$noun}"; $header_text = "Currently Attached {$noun}"; $button_text = "Save {$noun}"; $instructions = null; break; case self::ACTION_MERGE: $dialog_title = "Merge Duplicate Tasks"; $header_text = "Tasks To Merge"; $button_text = "Merge {$noun}"; $instructions = "These tasks will be merged into the current task and then closed. ". "The current task will grow stronger."; break; case self::ACTION_DEPENDENCIES: $dialog_title = "Edit Dependencies"; $header_text = "Current Dependencies"; $button_text = "Save Dependencies"; $instructions = null; break; } return array( 'target_plural_noun' => $noun, 'selected' => $selected, 'title' => $dialog_title, 'header' => $header_text, 'button' => $button_text, 'instructions' => $instructions, ); } private function detectGraphCycles( $object, $attach_type, array $phids) { // Detect graph cycles. $graph = new PhabricatorObjectGraph(); $graph->setEdgeType($attach_type); $graph->addNodes(array( $object->getPHID() => $phids, )); $graph->loadGraph(); foreach ($phids as $phid) { $cycle = $graph->detectCycles($phid); if (!$cycle) { continue; } // TODO: Improve this behavior so it's not all-or-nothing? $handles = id(new PhabricatorObjectHandleData($cycle)) ->loadHandles(); $names = array(); foreach ($cycle as $cycle_phid) { $names[] = $handles[$cycle_phid]->getFullName(); } $names = implode(" \xE2\x86\x92 ", $names); $which = $handles[$phid]->getFullName(); throw new Exception( "You can not create a dependency on '{$which}' because it ". "would create a circular dependency: {$names}."); } } } diff --git a/src/applications/search/controller/attach/__init__.php b/src/applications/search/controller/attach/__init__.php index e9184e5468..95997943f5 100644 --- a/src/applications/search/controller/attach/__init__.php +++ b/src/applications/search/controller/attach/__init__.php @@ -1,27 +1,27 @@ phid = $data['phid']; } public function processRequest() { $engine = PhabricatorSearchEngineSelector::newSelector()->newEngine(); $document = $engine->reconstructDocument($this->phid); if (!$document) { return new Aphront404Response(); } $panels = array(); $panel = new AphrontPanelView(); $panel->setHeader('Abstract Document Index'); $props = array( 'PHID' => phutil_escape_html($document->getPHID()), 'Title' => phutil_escape_html($document->getDocumentTitle()), 'Type' => phutil_escape_html($document->getDocumentType()), ); $rows = array(); foreach ($props as $name => $value) { $rows[] = array($name, $value); } $table = new AphrontTableView($rows); $table->setColumnClasses( array( 'header', '', )); $panel->appendChild($table); $panels[] = $panel; $panel = new AphrontPanelView(); $panel->setHeader('Document Fields'); $fields = $document->getFieldData(); $rows = array(); foreach ($fields as $field) { list($name, $corpus, $aux_phid) = $field; $rows[] = array( phutil_escape_html($name), phutil_escape_html(nonempty($aux_phid, null)), str_replace("\n", '
    ', phutil_escape_html($corpus)), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Field', 'Aux PHID', 'Corpus', )); $table->setColumnClasses( array( '', '', 'wide', )); $panel->appendChild($table); $panels[] = $panel; $panel = new AphrontPanelView(); $panel->setHeader('Document Relationships'); $relationships = $document->getRelationshipData(); $phids = ipull($relationships, 1); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $rows = array(); foreach ($relationships as $relationship) { list($type, $phid, $rtype, $time) = $relationship; $rows[] = array( phutil_escape_html($type), phutil_escape_html($phid), phutil_escape_html($rtype), $handles[$phid]->renderLink(), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'Relationship', 'Related PHID', 'Related Type', 'Related Handle', )); $table->setColumnClasses( array( '', '', '', 'wide', )); $panel->appendChild($table); $panels[] = $panel; return $this->buildStandardPageResponse( $panels, array( 'title' => 'Raw Index', )); } } diff --git a/src/applications/search/controller/search/PhabricatorSearchController.php b/src/applications/search/controller/search/PhabricatorSearchController.php index 4531eaf202..5faa2f1838 100644 --- a/src/applications/search/controller/search/PhabricatorSearchController.php +++ b/src/applications/search/controller/search/PhabricatorSearchController.php @@ -1,257 +1,259 @@ key = idx($data, 'key'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if ($this->key) { $query = id(new PhabricatorSearchQuery())->loadOneWhere( 'queryKey = %s', $this->key); if (!$query) { return new Aphront404Response(); } } else { $query = new PhabricatorSearchQuery(); if ($request->isFormPost()) { $query_str = $request->getStr('query'); $response = PhabricatorJumpNavHandler::jumpPostResponse($query_str); if ($response) { return $response; } else { $query->setQuery($query_str); if ($request->getStr('scope')) { switch ($request->getStr('scope')) { case PhabricatorSearchScope::SCOPE_OPEN_REVISIONS: $query->setParameter('open', 1); $query->setParameter( 'type', PhabricatorPHIDConstants::PHID_TYPE_DREV); break; case PhabricatorSearchScope::SCOPE_OPEN_TASKS: $query->setParameter('open', 1); $query->setParameter( 'type', PhabricatorPHIDConstants::PHID_TYPE_TASK); break; case PhabricatorSearchScope::SCOPE_WIKI: $query->setParameter( 'type', PhabricatorPHIDConstants::PHID_TYPE_WIKI); break; case PhabricatorSearchScope::SCOPE_COMMITS: $query->setParameter( 'type', PhabricatorPHIDConstants::PHID_TYPE_CMIT); break; default: break; } } else { if (strlen($request->getStr('type'))) { $query->setParameter('type', $request->getStr('type')); } if ($request->getArr('author')) { $query->setParameter('author', $request->getArr('author')); } if ($request->getArr('owner')) { $query->setParameter('owner', $request->getArr('owner')); } if ($request->getInt('open')) { $query->setParameter('open', $request->getInt('open')); } if ($request->getArr('project')) { $query->setParameter('project', $request->getArr('project')); } } $query->save(); return id(new AphrontRedirectResponse()) ->setURI('/search/'.$query->getQueryKey().'/'); } } } $more = PhabricatorEnv::getEnvConfig('search.more-document-types', array()); $options = array( '' => 'All Documents', PhabricatorPHIDConstants::PHID_TYPE_DREV => 'Differential Revisions', PhabricatorPHIDConstants::PHID_TYPE_CMIT => 'Repository Commits', PhabricatorPHIDConstants::PHID_TYPE_TASK => 'Maniphest Tasks', PhabricatorPHIDConstants::PHID_TYPE_WIKI => 'Phriction Documents', PhabricatorPHIDConstants::PHID_TYPE_USER => 'Phabricator Users', ) + $more; $status_options = array( 0 => 'Open and Closed Documents', 1 => 'Open Documents', ); $phids = array_merge( $query->getParameter('author', array()), $query->getParameter('owner', array()), $query->getParameter('project', array()) ); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $author_value = array_select_keys( $handles, $query->getParameter('author', array())); $author_value = mpull($author_value, 'getFullName', 'getPHID'); $owner_value = array_select_keys( $handles, $query->getParameter('owner', array())); $owner_value = mpull($owner_value, 'getFullName', 'getPHID'); $project_value = array_select_keys( $handles, $query->getParameter('project', array())); $project_value = mpull($project_value, 'getFullName', 'getPHID'); $search_form = new AphrontFormView(); $search_form ->setUser($user) ->setAction('/search/') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Search') ->setName('query') ->setValue($query->getQuery())) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Document Type') ->setName('type') ->setOptions($options) ->setValue($query->getParameter('type'))) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Document Status') ->setName('open') ->setOptions($status_options) ->setValue($query->getParameter('open'))) ->appendChild( id(new AphrontFormTokenizerControl()) ->setName('author') ->setLabel('Author') ->setDatasource('/typeahead/common/users/') ->setValue($author_value)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setName('owner') ->setLabel('Owner') ->setDatasource('/typeahead/common/searchowner/') ->setValue($owner_value) ->setCaption( 'Tip: search for "Up For Grabs" to find unowned documents.')) ->appendChild( id(new AphrontFormTokenizerControl()) ->setName('project') ->setLabel('Project') ->setDatasource('/typeahead/common/projects/') ->setValue($project_value)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Search')); $search_panel = new AphrontPanelView(); $search_panel->setHeader('Search Phabricator'); $search_panel->appendChild($search_form); require_celerity_resource('phabricator-search-results-css'); if ($query->getID()) { $limit = 20; $pager = new AphrontPagerView(); $pager->setURI($request->getRequestURI(), 'page'); $pager->setPageSize($limit); $pager->setOffset($request->getInt('page')); $query->setParameter('limit', $limit + 1); $query->setParameter('offset', $pager->getOffset()); $engine = PhabricatorSearchEngineSelector::newSelector()->newEngine(); $results = $engine->executeSearch($query); $results = ipull($results, 'phid'); $results = $pager->sliceResults($results); if ($results) { $loader = new PhabricatorObjectHandleData($results); $handles = $loader->loadHandles(); $objects = $loader->loadObjects(); $results = array(); foreach ($handles as $phid => $handle) { $view = new PhabricatorSearchResultView(); $view->setHandle($handle); $view->setQuery($query); $view->setObject($objects[$phid]); $results[] = $view->render(); } $results = '
    '. implode("\n", $results). '
    '. $pager->render(). '
    '. '
    '; } else { $results = '
    '. '

    No search results.

    '. '
    '; } } else { $results = null; } return $this->buildStandardPageResponse( array( $search_panel, $results, ), array( 'title' => 'Search Results', )); } } diff --git a/src/applications/search/controller/select/PhabricatorSearchSelectController.php b/src/applications/search/controller/select/PhabricatorSearchSelectController.php index aa6ee22b93..815e56d7a7 100644 --- a/src/applications/search/controller/select/PhabricatorSearchSelectController.php +++ b/src/applications/search/controller/select/PhabricatorSearchSelectController.php @@ -1,119 +1,119 @@ type = $data['type']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $query = new PhabricatorSearchQuery(); $query_str = $request->getStr('query'); $matches = array(); $query->setQuery($query_str); $query->setParameter('type', $this->type); switch ($request->getStr('filter')) { case 'assigned': $query->setParameter('owner', array($user->getPHID())); $query->setParameter('open', 1); break; case 'created'; $query->setParameter('author', array($user->getPHID())); $query->setParameter('open', 1); break; case 'open': $query->setParameter('open', 1); break; } $engine = PhabricatorSearchEngineSelector::newSelector()->newEngine(); $results = $engine->executeSearch($query); $phids = array_fill_keys(ipull($results, 'phid'), true); $phids += $this->queryObjectNames($query_str); $phids = array_keys($phids); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $data = array(); foreach ($handles as $handle) { $view = new PhabricatorHandleObjectSelectorDataView($handle); $data[] = $view->renderData(); } return id(new AphrontAjaxResponse())->setContent($data); } private function queryObjectNames($query) { $pattern = null; switch ($this->type) { case PhabricatorPHIDConstants::PHID_TYPE_TASK: $pattern = '/\bT(\d+)\b/'; break; case PhabricatorPHIDConstants::PHID_TYPE_DREV: $pattern = '/\bD(\d+)\b/'; break; } if (!$pattern) { return array(); } $matches = array(); preg_match_all($pattern, $query, $matches); if (!$matches) { return array(); } $object_ids = $matches[1]; if (!$object_ids) { return array(); } switch ($this->type) { case PhabricatorPHIDConstants::PHID_TYPE_DREV: $objects = id(new DifferentialRevision())->loadAllWhere( 'id IN (%Ld)', $object_ids); break; case PhabricatorPHIDConstants::PHID_TYPE_TASK: $objects = id(new ManiphestTask())->loadAllWhere( 'id IN (%Ld)', $object_ids); break; } return array_fill_keys(mpull($objects, 'getPHID'), true); } } diff --git a/src/applications/search/controller/select/__init__.php b/src/applications/search/controller/select/__init__.php index 01924ab54a..97fce3240c 100644 --- a/src/applications/search/controller/select/__init__.php +++ b/src/applications/search/controller/select/__init__.php @@ -1,22 +1,22 @@ getRequest(); $user = $request->getUser(); $poll = new PhabricatorSlowvotePoll(); $poll->setAuthorPHID($user->getPHID()); $e_question = true; $e_response = true; $errors = array(); $responses = $request->getArr('response'); if ($request->isFormPost()) { $poll->setQuestion($request->getStr('question')); $poll->setResponseVisibility($request->getInt('response_visibility')); $poll->setShuffle($request->getBool('shuffle', false)); $poll->setMethod($request->getInt('method')); if (!strlen($poll->getQuestion())) { $e_question = 'Required'; $errors[] = 'You must ask a poll question.'; } else { $e_question = null; } $responses = array_filter($responses); if (empty($responses)) { $errors[] = 'You must offer at least one response.'; $e_response = 'Required'; } else { $e_response = null; } if (empty($errors)) { $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()); } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle('Form Errors'); $error_view->setErrors($errors); } $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( '

    Resolve issues and build '. 'consensus through protracted deliberation.

    ') ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Question') ->setName('question') ->setValue($poll->getQuestion()) ->setError($e_question)); for ($ii = 0; $ii < 10; $ii++) { $n = ($ii + 1); $response = id(new AphrontFormTextControl()) ->setLabel("Response {$n}") ->setName('response[]') ->setValue(idx($responses, $ii, '')); if ($ii == 0) { $response->setError($e_response); } $form->appendChild($response); } $poll_type_options = array( PhabricatorSlowvotePoll::METHOD_PLURALITY => 'Plurality (Single Choice)', PhabricatorSlowvotePoll::METHOD_APPROVAL => 'Approval (Multiple Choice)', ); $response_type_options = array( PhabricatorSlowvotePoll::RESPONSES_VISIBLE => 'Allow anyone to see the responses', PhabricatorSlowvotePoll::RESPONSES_VOTERS => 'Require a vote to see the responses', PhabricatorSlowvotePoll::RESPONSES_OWNER => 'Only I can see the responses', ); $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Vote Type') ->setName('method') ->setValue($poll->getMethod()) ->setOptions($poll_type_options)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Responses') ->setName('response_visibility') ->setValue($poll->getResponseVisibility()) ->setOptions($response_type_options)) ->appendChild( id(new AphrontFormCheckboxControl()) ->setLabel('Shuffle') ->addCheckbox( 'shuffle', 1, 'Show choices in random order', $poll->getShuffle())) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Create Slowvote') ->addCancelButton('/vote/')); $panel = new AphrontPanelView(); $panel->setWidth(AphrontPanelView::WIDTH_FORM); $panel->setHeader('Create Slowvote'); $panel->appendChild($form); return $this->buildStandardPageResponse( array( $error_view, $panel, ), array( 'title' => 'Create Slowvote', )); } } diff --git a/src/applications/slowvote/controller/list/PhabricatorSlowvoteListController.php b/src/applications/slowvote/controller/list/PhabricatorSlowvoteListController.php index cd4a4c7c0c..5076778e5d 100644 --- a/src/applications/slowvote/controller/list/PhabricatorSlowvoteListController.php +++ b/src/applications/slowvote/controller/list/PhabricatorSlowvoteListController.php @@ -1,190 +1,190 @@ view = idx($data, 'view'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $views = array( self::VIEW_ALL => 'All Slowvotes', self::VIEW_CREATED => 'Created', self::VIEW_VOTED => 'Voted In', ); $view = isset($views[$this->view]) ? $this->view : self::VIEW_ALL; $side_nav = $this->renderSideNav($views, $view); $pager = new AphrontPagerView(); $pager->setOffset($request->getInt('page')); $pager->setURI($request->getRequestURI(), 'page'); $polls = $this->loadPolls($pager, $view); $phids = mpull($polls, 'getAuthorPHID'); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $rows = array(); foreach ($polls as $poll) { $rows[] = array( 'V'.$poll->getID(), phutil_render_tag( 'a', array( 'href' => '/V'.$poll->getID(), ), phutil_escape_html($poll->getQuestion())), $handles[$poll->getAuthorPHID()]->renderLink(), phabricator_date($poll->getDateCreated(), $user), phabricator_time($poll->getDateCreated(), $user), ); } $table = new AphrontTableView($rows); $table->setColumnClasses( array( '', 'pri wide', '', '', 'right', )); $table->setHeaders( array( 'ID', 'Poll', 'Author', 'Date', 'Time', )); $panel = new AphrontPanelView(); $panel->setHeader($this->getTableHeader($view)); $panel->setCreateButton('Create Slowvote', '/vote/create/'); $panel->appendChild($table); $panel->appendChild($pager); $side_nav->appendChild($panel); return $this->buildStandardPageResponse( $side_nav, array( 'title' => 'Slowvotes', )); } private function loadPolls(AphrontPagerView $pager, $view) { $request = $this->getRequest(); $user = $request->getUser(); $poll = new PhabricatorSlowvotePoll(); $conn = $poll->establishConnection('r'); $offset = $pager->getOffset(); $limit = $pager->getPageSize() + 1; switch ($view) { case self::VIEW_ALL: $data = queryfx_all( $conn, 'SELECT * FROM %T ORDER BY id DESC LIMIT %d, %d', $poll->getTableName(), $offset, $limit); break; case self::VIEW_CREATED: $data = queryfx_all( $conn, 'SELECT * FROM %T WHERE authorPHID = %s ORDER BY id DESC LIMIT %d, %d', $poll->getTableName(), $user->getPHID(), $offset, $limit); break; case self::VIEW_VOTED: $choice = new PhabricatorSlowvoteChoice(); $data = queryfx_all( $conn, 'SELECT p.* FROM %T p JOIN %T o ON o.pollID = p.id WHERE o.authorPHID = %s GROUP BY p.id ORDER BY p.id DESC LIMIT %d, %d', $poll->getTableName(), $choice->getTableName(), $user->getPHID(), $offset, $limit); break; } $data = $pager->sliceResults($data); return $poll->loadAllFromArray($data); } private function renderSideNav(array $views, $view) { $side_nav = new AphrontSideNavView(); foreach ($views as $key => $name) { $side_nav->addNavItem( phutil_render_tag( 'a', array( 'href' => '/vote/view/'.$key.'/', 'class' => ($view == $key) ? 'aphront-side-nav-selected' : null, ), phutil_escape_html($name))); } return $side_nav; } private function getTableHeader($view) { static $headers = array( self::VIEW_ALL => 'Slowvotes Not Yet Consumed by the Ravages of Time', self::VIEW_CREATED => 'Slowvotes Birthed from Your Noblest of Great Minds', self::VIEW_VOTED => 'Slowvotes Within Which You Express Your Mighty Opinion', ); return idx($headers, $view); } } diff --git a/src/applications/slowvote/controller/poll/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/poll/PhabricatorSlowvotePollController.php index 067a52421b..f046b4cb24 100644 --- a/src/applications/slowvote/controller/poll/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/poll/PhabricatorSlowvotePollController.php @@ -1,460 +1,460 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $viewer_phid = $user->getPHID(); $poll = id(new PhabricatorSlowvotePoll())->load($this->id); if (!$poll) { return new Aphront404Response(); } $options = id(new PhabricatorSlowvoteOption())->loadAllWhere( 'pollID = %d', $poll->getID()); $choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere( 'pollID = %d', $poll->getID()); $comments = id(new PhabricatorSlowvoteComment())->loadAllWhere( 'pollID = %d', $poll->getID()); $choices_by_option = mgroup($choices, 'getOptionID'); $comments_by_user = mpull($comments, null, 'getAuthorPHID'); $choices_by_user = mgroup($choices, 'getAuthorPHID'); $viewer_choices = idx($choices_by_user, $viewer_phid, array()); $viewer_comment = idx($comments_by_user, $viewer_phid, null); $comment_text = null; if ($viewer_comment) { $comment_text = $viewer_comment->getCommentText(); } if ($request->isFormPost()) { $comment = idx($comments_by_user, $viewer_phid, null); if ($comment) { $comment->delete(); } $comment_text = $request->getStr('comments'); if (strlen($comment_text)) { id(new PhabricatorSlowvoteComment()) ->setAuthorPHID($viewer_phid) ->setPollID($poll->getID()) ->setCommentText($comment_text) ->save(); } $votes = $request->getArr('vote'); switch ($poll->getMethod()) { case PhabricatorSlowvotePoll::METHOD_PLURALITY: // Enforce only one vote. $votes = array_slice($votes, 0, 1); break; case PhabricatorSlowvotePoll::METHOD_APPROVAL: // No filtering. break; default: throw new Exception("Unknown poll method!"); } foreach ($viewer_choices as $viewer_choice) { $viewer_choice->delete(); } foreach ($votes as $vote) { id(new PhabricatorSlowvoteChoice()) ->setAuthorPHID($viewer_phid) ->setPollID($poll->getID()) ->setOptionID($vote) ->save(); } return id(new AphrontRedirectResponse())->setURI('/V'.$poll->getID()); } require_celerity_resource('phabricator-slowvote-css'); $phids = array_merge( mpull($choices, 'getAuthorPHID'), mpull($comments, 'getAuthorPHID'), array( $poll->getAuthorPHID(), )); $query = new PhabricatorObjectHandleData($phids); $handles = $query->loadHandles(); $objects = $query->loadObjects(); if ($poll->getShuffle()) { shuffle($options); } $option_markup = array(); foreach ($options as $option) { $option_markup[] = $this->renderPollOption( $poll, $viewer_choices, $option); } $option_markup = implode("\n", $option_markup); $comments_by_option = array(); switch ($poll->getMethod()) { case PhabricatorSlowvotePoll::METHOD_PLURALITY: $choice_ids = array(); foreach ($choices_by_user as $user_phid => $user_choices) { $choice_ids[$user_phid] = head($user_choices)->getOptionID(); } foreach ($comments as $comment) { $choice = idx($choice_ids, $comment->getAuthorPHID()); if ($choice) { $comments_by_option[$choice][] = $comment; } } break; case PhabricatorSlowvotePoll::METHOD_APPROVAL: // All comments are grouped in approval voting. break; default: throw new Exception("Unknown poll method!"); } $result_markup = $this->renderResultMarkup( $poll, $options, $choices, $comments, $viewer_choices, $choices_by_option, $comments_by_option, $handles, $objects); if ($viewer_choices) { $instructions = 'Your vote has been recorded... but there is still ample time to '. 'rethink your position. Have you thoroughly considered all possible '. 'eventualities?'; } else { $instructions = 'This is a weighty matter indeed. Consider your choices with the '. 'greatest of care.'; } $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( '

    '.$instructions.'

    ') ->appendChild( id(new AphrontFormMarkupControl()) ->setLabel('Vote') ->setValue($option_markup)) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Comments') ->setHeight(AphrontFormTextAreaControl::HEIGHT_SHORT) ->setName('comments') ->setValue($comment_text)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Cautiously Engage in Deliberations')); $panel = new AphrontPanelView(); $panel->setHeader(phutil_escape_html($poll->getQuestion())); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->appendChild($form); $panel->appendChild('

    '); $panel->appendChild($result_markup); return $this->buildStandardPageResponse( $panel, array( 'title' => 'V'.$poll->getID().' '.$poll->getQuestion(), )); } private function renderComments(array $comments, array $handles) { $viewer = $this->getRequest()->getUser(); $engine = PhabricatorMarkupEngine::newSlowvoteMarkupEngine(); $comment_markup = array(); foreach ($comments as $comment) { $handle = $handles[$comment->getAuthorPHID()]; $markup = $engine->markupText($comment->getCommentText()); require_celerity_resource('phabricator-remarkup-css'); $comment_markup[] = ''. ''. $handle->renderLink(). '
    '. phabricator_datetime($comment->getDateCreated(), $viewer). '
    '. ''. '
    '. $markup. '
    '. ''. ''; } if ($comment_markup) { $comment_markup = phutil_render_tag( 'table', array( 'class' => 'phabricator-slowvote-comments', ), implode("\n", $comment_markup)); } else { $comment_markup = null; } return $comment_markup; } private function renderPollOption( PhabricatorSlowvotePoll $poll, array $viewer_choices, PhabricatorSlowvoteOption $option) { $id = $option->getID(); switch ($poll->getMethod()) { case PhabricatorSlowvotePoll::METHOD_PLURALITY: // Render a radio button. $selected_option = head($viewer_choices); if ($selected_option) { $selected = $selected_option->getOptionID(); } else { $selected = null; } if ($selected == $id) { $checked = "checked"; } else { $checked = null; } $input = phutil_render_tag( 'input', array( 'type' => 'radio', 'name' => 'vote[]', 'value' => $id, 'checked' => $checked, )); break; case PhabricatorSlowvotePoll::METHOD_APPROVAL: // Render a check box. $checked = null; foreach ($viewer_choices as $choice) { if ($choice->getOptionID() == $id) { $checked = 'checked'; break; } } $input = phutil_render_tag( 'input', array( 'type' => 'checkbox', 'name' => 'vote[]', 'checked' => $checked, 'value' => $id, )); break; default: throw new Exception("Unknown poll method!"); } if ($checked) { $checked_class = 'phabricator-slowvote-checked'; } else { $checked_class = null; } return phutil_render_tag( 'label', array( 'class' => 'phabricator-slowvote-label '.$checked_class, ), $input.phutil_escape_html($option->getName())); } private function renderVoteCount( PhabricatorSlowvotePoll $poll, array $choices, array $chosen) { switch ($poll->getMethod()) { case PhabricatorSlowvotePoll::METHOD_PLURALITY: $out_of_total = count($choices); break; case PhabricatorSlowvotePoll::METHOD_APPROVAL: // Count unique respondents for approval votes. $out_of_total = count(mpull($choices, null, 'getAuthorPHID')); break; default: throw new Exception("Unknown poll method!"); } return sprintf( '%d / %d (%d%%)', number_format(count($chosen)), number_format($out_of_total), $out_of_total ? round(100 * count($chosen) / $out_of_total) : 0); } private function renderResultMarkup( PhabricatorSlowvotePoll $poll, array $options, array $choices, array $comments, array $viewer_choices, array $choices_by_option, array $comments_by_option, array $handles, array $objects) { $viewer_phid = $this->getRequest()->getUser()->getPHID(); $can_see_responses = false; $need_vote = false; switch ($poll->getResponseVisibility()) { case PhabricatorSlowvotePoll::RESPONSES_VISIBLE: $can_see_responses = true; break; case PhabricatorSlowvotePoll::RESPONSES_VOTERS: $can_see_responses = (bool)$viewer_choices; $need_vote = true; break; case PhabricatorSlowvotePoll::RESPONSES_OWNER: $can_see_responses = ($viewer_phid == $poll->getAuthorPHID()); break; } $result_markup = id(new AphrontFormLayoutView()) ->appendChild('

    Ongoing Deliberation

    '); if (!$can_see_responses) { if ($need_vote) { $reason = "You must vote to see the results."; } else { $reason = "The results are not public."; } $result_markup ->appendChild( '

    '.$reason.'

    '); return $result_markup; } foreach ($options as $option) { $id = $option->getID(); $chosen = idx($choices_by_option, $id, array()); $users = array_select_keys($handles, mpull($chosen, 'getAuthorPHID')); if ($users) { $user_markup = array(); foreach ($users as $handle) { $object = idx($objects, $handle->getPHID()); if (!$object) { continue; } $profile_image = $handle->getImageURI(); $user_markup[] = phutil_render_tag( 'a', array( 'href' => $handle->getURI(), 'class' => 'phabricator-slowvote-facepile', ), phutil_render_tag( 'img', array( 'src' => $profile_image, ))); } $user_markup = implode('', $user_markup); } else { $user_markup = 'This option has failed to appeal to anyone.'; } $comment_markup = $this->renderComments( idx($comments_by_option, $id, array()), $handles); $vote_count = $this->renderVoteCount( $poll, $choices, $chosen); $result_markup->appendChild( '
    '. '
    '. $vote_count. '
    '. '

    '.phutil_escape_html($option->getName()).'

    '. '
    '. $user_markup. '
    '. '
    '. $comment_markup. '
    '); } if ($poll->getMethod() == PhabricatorSlowvotePoll::METHOD_APPROVAL && $comments) { $comment_markup = $this->renderComments( $comments, $handles); $result_markup->appendChild( '

    Motions Proposed for Consideration

    '); $result_markup->appendChild($comment_markup); } return $result_markup; } } diff --git a/src/applications/status/base/PhabricatorStatusController.php b/src/applications/status/base/PhabricatorStatusController.php index 9316d5da16..ac6e6d2eea 100644 --- a/src/applications/status/base/PhabricatorStatusController.php +++ b/src/applications/status/base/PhabricatorStatusController.php @@ -1,30 +1,30 @@ setContent("ALIVE\n"); return $response; } } diff --git a/src/applications/typeahead/controller/common/PhabricatorTypeaheadCommonDatasourceController.php b/src/applications/typeahead/controller/common/PhabricatorTypeaheadCommonDatasourceController.php index a19a4551d2..a62ed9f1d3 100644 --- a/src/applications/typeahead/controller/common/PhabricatorTypeaheadCommonDatasourceController.php +++ b/src/applications/typeahead/controller/common/PhabricatorTypeaheadCommonDatasourceController.php @@ -1,197 +1,197 @@ type = $data['type']; } public function processRequest() { $request = $this->getRequest(); $query = $request->getStr('q'); $need_users = false; $need_all_users = false; $need_lists = false; $need_projs = false; $need_repos = false; $need_packages = false; $need_upforgrabs = false; $need_arcanist_projects = false; $need_noproject = false; switch ($this->type) { case 'searchowner': $need_users = true; $need_upforgrabs = true; break; case 'searchproject': $need_projs = true; $need_noproject = true; break; case 'users': $need_users = true; break; case 'mailable': $need_users = true; $need_lists = true; break; case 'projects': $need_projs = true; break; case 'repositories': $need_repos = true; break; case 'packages': $need_packages = true; break; case 'accounts': $need_users = true; $need_all_users = true; break; case 'arcanistprojects': $need_arcanist_projects = true; break; } $data = array(); if ($need_upforgrabs) { $data[] = array( 'upforgrabs (Up For Grabs)', null, ManiphestTaskOwner::OWNER_UP_FOR_GRABS, ); } if ($need_noproject) { $data[] = array( 'noproject (No Project)', null, ManiphestTaskOwner::PROJECT_NO_PROJECT, ); } if ($need_users) { $columns = array( 'isSystemAgent', 'isDisabled', 'userName', 'realName', 'phid'); if ($query) { $conn_r = id(new PhabricatorUser())->establishConnection('r'); $ids = queryfx_all( $conn_r, 'SELECT DISTINCT userID FROM %T WHERE token LIKE %>', PhabricatorUser::NAMETOKEN_TABLE, $query); $ids = ipull($ids, 'userID'); if ($ids) { $users = id(new PhabricatorUser())->loadColumnsWhere( $columns, 'id IN (%Ld)', $ids); } else { $users = array(); } } else { $users = id(new PhabricatorUser())->loadColumns($columns); } foreach ($users as $user) { if (!$need_all_users) { if ($user->getIsSystemAgent()) { continue; } if ($user->getIsDisabled()) { continue; } } $data[] = array( $user->getUsername().' ('.$user->getRealName().')', '/p/'.$user->getUsername(), $user->getPHID(), $user->getUsername(), ); } } if ($need_lists) { $lists = id(new PhabricatorMetaMTAMailingList())->loadAll(); foreach ($lists as $list) { $data[] = array( $list->getName(), $list->getURI(), $list->getPHID(), ); } } if ($need_projs) { $projs = id(new PhabricatorProject())->loadAll(); foreach ($projs as $proj) { $data[] = array( $proj->getName(), '/project/view/'.$proj->getID().'/', $proj->getPHID(), ); } } if ($need_repos) { $repos = id(new PhabricatorRepository())->loadAll(); foreach ($repos as $repo) { $data[] = array( 'r'.$repo->getCallsign().' ('.$repo->getName().')', '/diffusion/'.$repo->getCallsign().'/', $repo->getPHID(), 'r'.$repo->getCallsign(), ); } } if ($need_packages) { $packages = id(new PhabricatorOwnersPackage())->loadAll(); foreach ($packages as $package) { $data[] = array( $package->getName(), '/owners/package/'.$package->getID().'/', $package->getPHID(), ); } } if ($need_arcanist_projects) { $arcprojs = id(new PhabricatorRepositoryArcanistProject())->loadAll(); foreach ($arcprojs as $proj) { $data[] = array( $proj->getName(), null, $proj->getPHID(), ); } } return id(new AphrontAjaxResponse()) ->setContent($data); } } diff --git a/src/applications/uiexample/controller/render/PhabricatorUIExampleRenderController.php b/src/applications/uiexample/controller/render/PhabricatorUIExampleRenderController.php index 720a67b6a5..25294c5e1b 100644 --- a/src/applications/uiexample/controller/render/PhabricatorUIExampleRenderController.php +++ b/src/applications/uiexample/controller/render/PhabricatorUIExampleRenderController.php @@ -1,91 +1,91 @@ class = idx($data, 'class'); } public function processRequest() { $classes = id(new PhutilSymbolLoader()) ->setAncestorClass('PhabricatorUIExample') ->selectAndLoadSymbols(); $classes = ipull($classes, 'name', 'name'); $selected = null; foreach ($classes as $class => $ignored) { $classes[$class] = newv($class, array()); if ($this->class == $classes[$class]->getName()) { $selected = $class; } } if (!$selected) { reset($classes); $selected = key($classes); } $nav = new AphrontSideNavView(); foreach ($classes as $class => $obj) { $name = $obj->getName(); $nav->addNavItem( phutil_render_tag( 'a', array( 'href' => '/uiexample/view/'.$name.'/', 'class' => ($selected == $class) ? 'aphront-side-nav-selected' : null, ), phutil_escape_html($obj->getName()))); } require_celerity_resource('phabricator-ui-example-css'); $example = $classes[$selected]; $example->setRequest($this->getRequest()); $nav->appendChild( '
    '. '

    '. phutil_escape_html($example->getName()). ' ('.get_class($example).')'. '

    '. '

    ' .$example->getDescription(). '

    '. '
    '); $nav->appendChild($example->renderExample()); return $this->buildStandardPageResponse( $nav, array( 'title' => 'UI Example', )); } } diff --git a/src/applications/xhpastview/controller/run/PhabricatorXHPASTViewRunController.php b/src/applications/xhpastview/controller/run/PhabricatorXHPASTViewRunController.php index 1d3cd9d9ae..35fa193b12 100644 --- a/src/applications/xhpastview/controller/run/PhabricatorXHPASTViewRunController.php +++ b/src/applications/xhpastview/controller/run/PhabricatorXHPASTViewRunController.php @@ -1,73 +1,73 @@ getRequest(); $user = $request->getUser(); if ($request->isFormPost()) { $source = $request->getStr('source'); $future = xhpast_get_parser_future($source); $resolved = $future->resolve(); // This is just to let it throw exceptions if stuff is broken. $parse_tree = XHPASTTree::newFromDataAndResolvedExecFuture( $source, $resolved); list($err, $stdout, $stderr) = $resolved; $storage_tree = new PhabricatorXHPASTViewParseTree(); $storage_tree->setInput($source); $storage_tree->setStdout($stdout); $storage_tree->setAuthorPHID($user->getPHID()); $storage_tree->save(); return id(new AphrontRedirectResponse()) ->setURI('/xhpast/view/'.$storage_tree->getID().'/'); } $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Source') ->setName('source') ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Parse')); $panel = new AphrontPanelView(); $panel->setHeader('Generate XHP AST'); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->appendChild($form); return $this->buildStandardPageResponse( $panel, array( 'title' => 'XHPAST View', )); } } diff --git a/src/applications/xhpastview/controller/viewframe/PhabricatorXHPASTViewFrameController.php b/src/applications/xhpastview/controller/viewframe/PhabricatorXHPASTViewFrameController.php index f8da52dbc5..1b99ae19d6 100644 --- a/src/applications/xhpastview/controller/viewframe/PhabricatorXHPASTViewFrameController.php +++ b/src/applications/xhpastview/controller/viewframe/PhabricatorXHPASTViewFrameController.php @@ -1,43 +1,43 @@ id = $data['id']; } public function processRequest() { $id = $this->id; return $this->buildStandardPageResponse( phutil_render_tag( 'iframe', array( 'src' => '/xhpast/frameset/'.$id.'/', 'frameborder' => '0', 'style' => 'width: 100%; height: 800px;', '')), array( 'title' => 'XHPAST View', )); } } diff --git a/src/applications/xhpastview/controller/viewframeset/PhabricatorXHPASTViewFramesetController.php b/src/applications/xhpastview/controller/viewframeset/PhabricatorXHPASTViewFramesetController.php index dfbb022b64..a1e96cd7d4 100644 --- a/src/applications/xhpastview/controller/viewframeset/PhabricatorXHPASTViewFramesetController.php +++ b/src/applications/xhpastview/controller/viewframeset/PhabricatorXHPASTViewFramesetController.php @@ -1,42 +1,42 @@ id = $data['id']; } public function processRequest() { $id = $this->id; $response = new AphrontWebpageResponse(); $response->setFrameable(true); $response->setContent( ''. ''. ''. ''. ''); return $response; } } diff --git a/src/applications/xhpastview/controller/viewinput/PhabricatorXHPASTViewInputController.php b/src/applications/xhpastview/controller/viewinput/PhabricatorXHPASTViewInputController.php index c16873769e..1767fb9efe 100644 --- a/src/applications/xhpastview/controller/viewinput/PhabricatorXHPASTViewInputController.php +++ b/src/applications/xhpastview/controller/viewinput/PhabricatorXHPASTViewInputController.php @@ -1,27 +1,27 @@ getStorageTree()->getInput(); return $this->buildXHPASTViewPanelResponse( phutil_escape_html($input)); } } diff --git a/src/applications/xhpastview/controller/viewstream/PhabricatorXHPASTViewStreamController.php b/src/applications/xhpastview/controller/viewstream/PhabricatorXHPASTViewStreamController.php index ca8dd96087..755b5aca13 100644 --- a/src/applications/xhpastview/controller/viewstream/PhabricatorXHPASTViewStreamController.php +++ b/src/applications/xhpastview/controller/viewstream/PhabricatorXHPASTViewStreamController.php @@ -1,48 +1,48 @@ getStorageTree(); $input = $storage->getInput(); $stdout = $storage->getStdout(); $tree = XHPASTTree::newFromDataAndResolvedExecFuture( $input, array(0, $stdout, '')); $tokens = array(); foreach ($tree->getRawTokenStream() as $id => $token) { $seq = $id; $name = $token->getTypeName(); $title = "Token {$seq}: {$name}"; $tokens[] = phutil_render_tag( 'span', array( 'title' => $title, 'class' => 'token', ), phutil_escape_html($token->getValue())); } return $this->buildXHPASTViewPanelResponse(implode('', $tokens)); } } diff --git a/src/applications/xhpastview/controller/viewtree/PhabricatorXHPASTViewTreeController.php b/src/applications/xhpastview/controller/viewtree/PhabricatorXHPASTViewTreeController.php index 49243f09ad..ae21ec34c9 100644 --- a/src/applications/xhpastview/controller/viewtree/PhabricatorXHPASTViewTreeController.php +++ b/src/applications/xhpastview/controller/viewtree/PhabricatorXHPASTViewTreeController.php @@ -1,61 +1,61 @@ getStorageTree(); $input = $storage->getInput(); $stdout = $storage->getStdout(); $tree = XHPASTTree::newFromDataAndResolvedExecFuture( $input, array(0, $stdout, '')); $tree = '
      '.$this->buildTree($tree->getRootNode()).'
    '; return $this->buildXHPASTViewPanelResponse($tree); } protected function buildTree($root) { try { $name = $root->getTypeName(); $title = $root->getDescription(); } catch (Exception $ex) { $name = '???'; $title = '???'; } $tree = array(); $tree[] = '
  • '. phutil_render_tag( 'span', array( 'title' => $title, ), phutil_escape_html($name)). '
  • '; foreach ($root->getChildren() as $child) { $tree[] = '
      '.$this->buildTree($child).'
    '; } return implode("\n", $tree); } } diff --git a/src/applications/xhprof/controller/profile/PhabricatorXHProfProfileController.php b/src/applications/xhprof/controller/profile/PhabricatorXHProfProfileController.php index b2ee553fb3..695aae2df1 100644 --- a/src/applications/xhprof/controller/profile/PhabricatorXHProfProfileController.php +++ b/src/applications/xhprof/controller/profile/PhabricatorXHProfProfileController.php @@ -1,68 +1,68 @@ phid = $data['phid']; } public function processRequest() { $file = id(new PhabricatorFile())->loadOneWhere( 'phid = %s', $this->phid); if (!$file) { return new Aphront404Response(); } $data = $file->loadFileData(); $data = unserialize($data); if (!$data) { throw new Exception("Failed to unserialize XHProf profile!"); } $request = $this->getRequest(); $symbol = $request->getStr('symbol'); $is_framed = $request->getBool('frame'); if ($symbol) { $view = new PhabricatorXHProfProfileSymbolView(); $view->setSymbol($symbol); } else { $view = new PhabricatorXHProfProfileTopLevelView(); $view->setLimit(100); } $view->setBaseURI($request->getRequestURI()->getPath()); $view->setIsFramed($is_framed); $view->setProfileData($data); return $this->buildStandardPageResponse( $view, array( 'title' => 'Profile', 'frame' => $is_framed, )); } } diff --git a/src/infrastructure/celerity/controller/CelerityResourceController.php b/src/infrastructure/celerity/controller/CelerityResourceController.php index 0fe43c64ee..6c1f081404 100644 --- a/src/infrastructure/celerity/controller/CelerityResourceController.php +++ b/src/infrastructure/celerity/controller/CelerityResourceController.php @@ -1,104 +1,104 @@ path = $data['path']; $this->hash = $data['hash']; $this->package = !empty($data['package']); } public function processRequest() { $path = $this->path; // Sanity checking to keep this from exposing anything sensitive. $path = preg_replace('@(//|\\.\\.)@', '', $path); $matches = null; if (!preg_match('/\.(css|js)$/', $path, $matches)) { throw new Exception("Only CSS and JS resources may be served."); } if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && !PhabricatorEnv::getEnvConfig('celerity.force-disk-reads')) { // Return a "304 Not Modified". We don't care about the value of this // field since we never change what resource is served by a given URI. return $this->makeResponseCacheable(new Aphront304Response()); } $type = $matches[1]; $root = dirname(phutil_get_library_root('phabricator')); if ($this->package) { $map = CelerityResourceMap::getInstance(); $paths = $map->resolvePackage($this->hash); if (!$paths) { return new Aphront404Response(); } try { $data = array(); foreach ($paths as $path) { $data[] = Filesystem::readFile($root.'/webroot/'.$path); } $data = implode("\n\n", $data); } catch (Exception $ex) { return new Aphront404Response(); } } else { try { $data = Filesystem::readFile($root.'/webroot/'.$path); } catch (Exception $ex) { return new Aphront404Response(); } } $response = new AphrontFileResponse(); $response->setContent($data); switch ($type) { case 'css': $response->setMimeType("text/css; charset=utf-8"); break; case 'js': $response->setMimeType("text/javascript; charset=utf-8"); break; } return $this->makeResponseCacheable($response); } private function makeResponseCacheable(AphrontResponse $response) { $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); $response->setLastModified(time()); return $response; } } diff --git a/src/storage/lisk/dao/__tests__/LiskIsolationTestCase.php b/src/storage/lisk/dao/__tests__/LiskIsolationTestCase.php index 93d7cf79ec..5c8081075b 100644 --- a/src/storage/lisk/dao/__tests__/LiskIsolationTestCase.php +++ b/src/storage/lisk/dao/__tests__/LiskIsolationTestCase.php @@ -1,122 +1,128 @@ assertEqual(null, $dao->getID(), 'Expect no ID.'); $this->assertEqual(null, $dao->getPHID(), 'Expect no PHID.'); $dao->save(); // Effects insert $id = $dao->getID(); $phid = $dao->getPHID(); $this->assertEqual(true, (bool)$id, 'Expect ID generated.'); $this->assertEqual(true, (bool)$phid, 'Expect PHID generated.'); $dao->save(); // Effects update $this->assertEqual($id, $dao->getID(), 'Expect ID unchanged.'); $this->assertEqual($phid, $dao->getPHID(), 'Expect PHID unchanged.'); } public function testEphemeral() { $dao = new LiskIsolationTestDAO(); $dao->save(); $dao->makeEphemeral(); - $this->assertException( - 'LiskEphemeralObjectException', - function() use ($dao) { - $dao->save(); - } - ); + $this->tryTestCases( + array( + $dao, + ), + array( + false, + ), + array($this, 'saveDAO')); + } + + public function saveDAO($dao) { + $dao->save(); } public function testIsolationContainment() { $dao = new LiskIsolationTestDAO(); try { $dao->establishLiveConnection('r'); $this->assertFailure( "LiskIsolationTestDAO did not throw an exception when instructed to ". "explicitly connect to an external database."); } catch (LiskIsolationTestDAOException $ex) { // Expected, pass. } } public function testMagicMethods() { $dao = new LiskIsolationTestDAO(); $this->assertEqual( null, $dao->getName(), 'getName() on empty object'); $this->assertEqual( $dao, $dao->setName('x'), 'setName() returns $this'); $this->assertEqual( 'y', $dao->setName('y')->getName(), 'setName() has an effect'); $ex = null; try { $dao->gxxName(); } catch (Exception $thrown) { $ex = $thrown; } $this->assertEqual( true, (bool)$ex, 'Typoing "get" should throw.'); $ex = null; try { $dao->sxxName('z'); } catch (Exception $thrown) { $ex = $thrown; } $this->assertEqual( true, (bool)$ex, 'Typoing "set" should throw.'); $ex = null; try { $dao->madeUpMethod(); } catch (Exception $thrown) { $ex = $thrown; } $this->assertEqual( true, (bool)$ex, 'Made up method should throw.'); } }