Page MenuHomestyx hydra

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
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 @@
<?php
/**
* This file is automatically generated. Use 'phutil_mapper.php' to rebuild it.
* @generated
*/
phutil_register_library_map(array(
'class' =>
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group console
*/
-class DarkConsoleController extends PhabricatorController {
+final class DarkConsoleController extends PhabricatorController {
protected $op;
protected $data;
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group aphront
*/
class AphrontDefaultApplicationConfiguration
extends AphrontApplicationConfiguration {
public function __construct() {
}
public function getApplicationName() {
return 'aphront-default';
}
public function getURIMap() {
return $this->getResourceURIMapRules() + array(
'/(?:(?P<filter>jump)/)?' =>
'PhabricatorDirectoryMainController',
'/(?:(?P<filter>feed)/)' => array(
'public/' => 'PhabricatorFeedPublicStreamController',
'(?:(?P<subfilter>[^/]+)/)?' =>
'PhabricatorDirectoryMainController',
),
'/directory/' => array(
'(?P<id>\d+)/'
=> 'PhabricatorDirectoryCategoryViewController',
'edit/'
=> 'PhabricatorDirectoryEditController',
'item/edit/(?:(?P<id>\d+)/)?'
=> 'PhabricatorDirectoryItemEditController',
'item/delete/(?P<id>\d+)/'
=> 'PhabricatorDirectoryItemDeleteController',
'category/edit/(?:(?P<id>\d+)/)?'
=> 'PhabricatorDirectoryCategoryEditController',
'category/delete/(?P<id>\d+)/'
=> 'PhabricatorDirectoryCategoryDeleteController',
),
'/file/' => array(
'' => 'PhabricatorFileListController',
'filter/(?P<filter>\w+)/' => 'PhabricatorFileListController',
'upload/' => 'PhabricatorFileUploadController',
'dropupload/' => 'PhabricatorFileDropUploadController',
'delete/(?P<id>\d+)/' => 'PhabricatorFileDeleteController',
'info/(?P<phid>[^/]+)/' => 'PhabricatorFileInfoController',
'data/(?P<key>[^/]+)/(?P<phid>[^/]+)/.*'
=> 'PhabricatorFileDataController',
// TODO: This is a deprecated version of /data/. Remove it after
// old links have had a chance to rot.
'alt/(?P<key>[^/]+)/(?P<phid>[^/]+)/'
=> 'PhabricatorFileDataController',
'macro/' => array(
'' => 'PhabricatorFileMacroListController',
'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorFileMacroEditController',
'delete/(?P<id>\d+)/' => 'PhabricatorFileMacroDeleteController',
),
'proxy/' => 'PhabricatorFileProxyController',
'xform/(?P<transform>[^/]+)/(?P<phid>[^/]+)/'
=> 'PhabricatorFileTransformController',
),
'/phid/' => array(
'' => 'PhabricatorPHIDLookupController',
),
'/people/' => array(
'' => 'PhabricatorPeopleListController',
'logs/' => 'PhabricatorPeopleLogsController',
'edit/(?:(?P<id>\d+)/(?:(?P<view>\w+)/)?)?'
=> 'PhabricatorPeopleEditController',
),
'/p/(?P<username>\w+)/(?:(?P<page>\w+)/)?'
=> 'PhabricatorPeopleProfileController',
'/conduit/' => array(
'' => 'PhabricatorConduitConsoleController',
'method/(?P<method>[^/]+)/' => 'PhabricatorConduitConsoleController',
'log/' => 'PhabricatorConduitLogController',
'log/view/(?P<view>[^/]+)/' => 'PhabricatorConduitLogController',
'token/' => 'PhabricatorConduitTokenController',
),
'/api/(?P<method>[^/]+)' => 'PhabricatorConduitAPIController',
'/D(?P<id>\d+)' => 'DifferentialRevisionViewController',
'/differential/' => array(
'' => 'DifferentialRevisionListController',
'filter/(?P<filter>\w+)/(?:(?P<username>\w+)/)?' =>
'DifferentialRevisionListController',
'stats/(?P<filter>\w+)/' => 'DifferentialRevisionStatsController',
'diff/' => array(
'(?P<id>\d+)/' => 'DifferentialDiffViewController',
'create/' => 'DifferentialDiffCreateController',
),
'changeset/' => 'DifferentialChangesetViewController',
'revision/edit/(?:(?P<id>\d+)/)?'
=> 'DifferentialRevisionEditController',
'comment/' => array(
'preview/(?P<id>\d+)/' => 'DifferentialCommentPreviewController',
'save/' => 'DifferentialCommentSaveController',
'inline/' => array(
'preview/(?P<id>\d+)/' =>
'DifferentialInlineCommentPreviewController',
'edit/(?P<id>\d+)/' => 'DifferentialInlineCommentEditController',
),
),
'subscribe/(?P<action>add|rem)/(?P<id>\d+)/'
=> 'DifferentialSubscribeController',
),
'/typeahead/' => array(
'common/(?P<type>\w+)/'
=> 'PhabricatorTypeaheadCommonDatasourceController',
),
'/mail/' => array(
'' => 'PhabricatorMetaMTAListController',
'send/' => 'PhabricatorMetaMTASendController',
'view/(?P<id>\d+)/' => 'PhabricatorMetaMTAViewController',
'lists/' => 'PhabricatorMetaMTAMailingListsController',
'lists/edit/(?:(?P<id>\d+)/)?'
=> 'PhabricatorMetaMTAMailingListEditController',
'receive/' => 'PhabricatorMetaMTAReceiveController',
'received/' => 'PhabricatorMetaMTAReceivedListController',
'sendgrid/' => 'PhabricatorMetaMTASendGridReceiveController',
),
'/login/' => array(
'' => 'PhabricatorLoginController',
'email/' => 'PhabricatorEmailLoginController',
'etoken/(?P<token>\w+)/' => 'PhabricatorEmailTokenController',
'refresh/' => 'PhabricatorRefreshCSRFController',
'validate/' => 'PhabricatorLoginValidateController',
),
'/logout/' => 'PhabricatorLogoutController',
'/oauth/' => array(
'(?P<provider>\w+)/' => array(
'login/' => 'PhabricatorOAuthLoginController',
'diagnose/' => 'PhabricatorOAuthDiagnosticsController',
'unlink/' => 'PhabricatorOAuthUnlinkController',
),
),
'/oauthserver/' => array(
'auth/' => 'PhabricatorOAuthServerAuthController',
'test/' => 'PhabricatorOAuthServerTestController',
'token/' => 'PhabricatorOAuthServerTokenController',
'clientauthorization/' => array(
'' => 'PhabricatorOAuthClientAuthorizationListController',
'delete/(?P<phid>[^/]+)/' =>
'PhabricatorOAuthClientAuthorizationDeleteController',
'edit/(?P<phid>[^/]+)/' =>
'PhabricatorOAuthClientAuthorizationEditController',
),
'client/' => array(
'' => 'PhabricatorOAuthClientListController',
'create/' => 'PhabricatorOAuthClientEditController',
'delete/(?P<phid>[^/]+)/' => 'PhabricatorOAuthClientDeleteController',
'edit/(?P<phid>[^/]+)/' => 'PhabricatorOAuthClientEditController',
'view/(?P<phid>[^/]+)/' => 'PhabricatorOAuthClientViewController',
),
),
'/xhprof/' => array(
'profile/(?P<phid>[^/]+)/' => 'PhabricatorXHProfProfileController',
),
'/~/' => 'DarkConsoleController',
'/settings/' => array(
'(?:page/(?P<page>[^/]+)/)?' => 'PhabricatorUserSettingsController',
),
'/maniphest/' => array(
'' => 'ManiphestTaskListController',
'view/(?P<view>\w+)/' => 'ManiphestTaskListController',
'report/(?:(?P<view>\w+)/)?' => 'ManiphestReportController',
'batch/' => 'ManiphestBatchEditController',
'task/' => array(
'create/' => 'ManiphestTaskEditController',
'edit/(?P<id>\d+)/' => 'ManiphestTaskEditController',
- 'descriptionchange/(?P<id>\d+)/' =>
+ 'descriptionchange/(?:(?P<id>\d+)/)?' =>
'ManiphestTaskDescriptionChangeController',
- 'descriptiondiff/' =>
- 'ManiphestTaskDescriptionDiffController',
'descriptionpreview/' =>
'ManiphestTaskDescriptionPreviewController',
),
'transaction/' => array(
'save/' => 'ManiphestTransactionSaveController',
'preview/(?P<id>\d+)/' => 'ManiphestTransactionPreviewController',
),
'export/(?P<key>[^/]+)/' => 'ManiphestExportController',
),
'/T(?P<id>\d+)' => 'ManiphestTaskDetailController',
'/repository/' => array(
'' => 'PhabricatorRepositoryListController',
'create/' => 'PhabricatorRepositoryCreateController',
'edit/(?P<id>\d+)/(?:(?P<view>\w+)?/)?' =>
'PhabricatorRepositoryEditController',
'delete/(?P<id>\d+)/' => 'PhabricatorRepositoryDeleteController',
'project/(?P<id>\d+)/' =>
'PhabricatorRepositoryArcanistProjectEditController',
),
'/search/' => array(
'' => 'PhabricatorSearchController',
'(?P<key>[^/]+)/' => 'PhabricatorSearchController',
'attach/(?P<phid>[^/]+)/(?P<type>\w+)/(?:(?P<action>\w+)/)?'
=> 'PhabricatorSearchAttachController',
'select/(?P<type>\w+)/'
=> 'PhabricatorSearchSelectController',
'index/(?P<phid>[^/]+)/' => 'PhabricatorSearchIndexController',
),
'/project/' => array(
'' => 'PhabricatorProjectListController',
'filter/(?P<filter>[^/]+)/' => 'PhabricatorProjectListController',
'edit/(?P<id>\d+)/' => 'PhabricatorProjectProfileEditController',
'view/(?P<id>\d+)/(?:(?P<page>\w+)/)?'
=> 'PhabricatorProjectProfileController',
'create/' => 'PhabricatorProjectCreateController',
'update/(?P<id>\d+)/(?P<action>[^/]+)/'
=> 'PhabricatorProjectUpdateController',
),
'/r(?P<callsign>[A-Z]+)(?P<commit>[a-z0-9]+)'
=> 'DiffusionCommitController',
'/diffusion/' => array(
'' => 'DiffusionHomeController',
'(?P<callsign>[A-Z]+)/' => array(
'' => 'DiffusionRepositoryController',
'repository/'.
'(?P<path>[^/]+)/'
=> 'DiffusionRepositoryController',
'change/'.
'(?P<path>.*?)'.
'(?:[;](?P<commit>[a-z0-9]+))?'
=> 'DiffusionChangeController',
'history/'.
'(?P<path>.*?)'.
'(?:[;](?P<commit>[a-z0-9]+))?'
=> 'DiffusionHistoryController',
'browse/'.
'(?P<path>.*?)'.
'(?:[;](?P<commit>[a-z0-9]+))?'.
'(?:[$](?P<line>\d+(?:-\d+)?))?'
=> 'DiffusionBrowseController',
'diff/'.
'(?P<path>.*?)'.
'(?:[;](?P<commit>[a-z0-9]+))?'
=> 'DiffusionDiffController',
'lastmodified/'.
'(?P<path>.*?)'.
'(?:[;](?P<commit>[a-z0-9]+))?'
=> 'DiffusionLastModifiedController',
),
'services/' => array(
'path/' => array(
'complete/' => 'DiffusionPathCompleteController',
'validate/' => 'DiffusionPathValidateController',
),
),
'symbol/(?P<name>[^/]+)/' => 'DiffusionSymbolController',
),
'/daemon/' => array(
'task/(?P<id>\d+)/' => 'PhabricatorWorkerTaskDetailController',
'task/(?P<id>\d+)/(?P<action>[^/]+)/'
=> 'PhabricatorWorkerTaskUpdateController',
'log/' => array(
'' => 'PhabricatorDaemonLogListController',
'combined/' => 'PhabricatorDaemonCombinedLogController',
'(?P<id>\d+)/' => 'PhabricatorDaemonLogViewController',
),
'timeline/' => 'PhabricatorDaemonTimelineConsoleController',
'timeline/(?P<id>\d+)/' => 'PhabricatorDaemonTimelineEventController',
'' => 'PhabricatorDaemonConsoleController',
),
'/herald/' => array(
'' => 'HeraldHomeController',
'view/(?P<view>[^/]+)/' => array(
'' => 'HeraldHomeController',
'(?P<global>global)/' => 'HeraldHomeController'
),
'new/(?:(?P<type>[^/]+)/)?' => 'HeraldNewController',
'rule/(?:(?P<id>\d+)/)?' => 'HeraldRuleController',
'history/(?P<id>\d+)/' => 'HeraldRuleEditHistoryController',
'delete/(?P<id>\d+)/' => 'HeraldDeleteController',
'test/' => 'HeraldTestConsoleController',
'all/' => array(
'' => 'HeraldAllRulesController',
'view/(?P<view>[^/]+)/' => 'HeraldAllRulesController',
),
'transcript/' => 'HeraldTranscriptListController',
'transcript/(?P<id>\d+)/(?:(?P<filter>\w+)/)?'
=> 'HeraldTranscriptController',
),
'/uiexample/' => array(
'' => 'PhabricatorUIExampleRenderController',
'view/(?P<class>[^/]+)/' => 'PhabricatorUIExampleRenderController',
),
'/owners/' => array(
'' => 'PhabricatorOwnersListController',
'view/(?P<view>[^/]+)/' => 'PhabricatorOwnersListController',
'edit/(?P<id>\d+)/' => 'PhabricatorOwnersEditController',
'new/' => 'PhabricatorOwnersEditController',
'package/(?P<id>\d+)/' => 'PhabricatorOwnersDetailController',
'delete/(?P<id>\d+)/' => 'PhabricatorOwnersDeleteController',
),
'/audit/' => array(
'' => 'PhabricatorAuditListController',
'view/(?P<filter>[^/]+)/(?:(?P<name>[^/]+)/)?'
=> 'PhabricatorAuditListController',
'addcomment/' => 'PhabricatorAuditAddCommentController',
'preview/(?P<id>\d+)/' => 'PhabricatorAuditPreviewController',
),
'/xhpast/' => array(
'' => 'PhabricatorXHPASTViewRunController',
'view/(?P<id>\d+)/'
=> 'PhabricatorXHPASTViewFrameController',
'frameset/(?P<id>\d+)/'
=> 'PhabricatorXHPASTViewFramesetController',
'input/(?P<id>\d+)/'
=> 'PhabricatorXHPASTViewInputController',
'tree/(?P<id>\d+)/'
=> 'PhabricatorXHPASTViewTreeController',
'stream/(?P<id>\d+)/'
=> 'PhabricatorXHPASTViewStreamController',
),
'/status/' => 'PhabricatorStatusController',
'/paste/' => array(
'' => 'PhabricatorPasteListController',
'filter/(?P<filter>\w+)/' => 'PhabricatorPasteListController',
),
'/P(?P<id>\d+)' => 'PhabricatorPasteViewController',
'/help/' => array(
'keyboardshortcut/' => 'PhabricatorHelpKeyboardShortcutController',
),
'/countdown/' => array(
''
=> 'PhabricatorCountdownListController',
'(?P<id>\d+)/'
=> 'PhabricatorCountdownViewController',
'edit/(?:(?P<id>\d+)/)?'
=> 'PhabricatorCountdownEditController',
'delete/(?P<id>\d+)/'
=> 'PhabricatorCountdownDeleteController'
),
'/V(?P<id>\d+)' => 'PhabricatorSlowvotePollController',
'/vote/' => array(
'(?:view/(?P<view>\w+)/)?' => 'PhabricatorSlowvoteListController',
'create/' => 'PhabricatorSlowvoteCreateController',
),
// Match "/w/" with slug "/".
'/w(?P<slug>/)' => 'PhrictionDocumentController',
// Match "/w/x/y/z/" with slug "x/y/z/".
'/w/(?P<slug>.+/)' => 'PhrictionDocumentController',
'/phriction/' => array(
'' => 'PhrictionListController',
'list/(?P<view>[^/]+)/' => 'PhrictionListController',
'history(?P<slug>/)' => 'PhrictionHistoryController',
'history/(?P<slug>.+/)' => 'PhrictionHistoryController',
'edit/(?:(?P<id>\d+)/)?' => 'PhrictionEditController',
'delete/(?P<id>\d+)/' => 'PhrictionDeleteController',
'preview/' => 'PhrictionDocumentPreviewController',
'diff/(?P<id>\d+)/' => 'PhrictionDiffController',
),
'/calendar/' => array(
'' => 'PhabricatorCalendarBrowseController',
),
'/drydock/' => array(
'' => 'DrydockResourceListController',
'resource/' => 'DrydockResourceListController',
'resource/allocate/' => 'DrydockResourceAllocateController',
'host/' => array(
'' => 'DrydockHostListController',
'edit/' => 'DrydockHostEditController',
'edit/(?P<id>\d+)/' => 'DrydockhostEditController',
),
'lease/' => 'DrydockLeaseListController',
),
'/chatlog/' => array(
'' =>
'PhabricatorChatLogChannelListController',
'channel/(?P<channel>[^/]+)/' =>
'PhabricatorChatLogChannelLogController',
),
'/aphlict/' => 'PhabricatorAphlictTestPageController',
);
}
protected function getResourceURIMapRules() {
return array(
'/res/' => array(
'(?P<package>pkg/)?(?P<hash>[a-f0-9]{8})/(?P<path>.+\.(?: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 =
'<div class="aphront-unhandled-exception">'.
'<div class="exception-message">'.$message.'</div>'.
$trace.
'</div>';
$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(
'<div style="padding: 2em 0;">'.
$response->buildResponseString().
'</div>');
$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 = '<em>(Internal)</em>';
}
$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
'<div class="exception-trace">'.
'<div class="exception-trace-header">Stack Trace</div>'.
$table->render().
'</div>';
}
}
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDisabledUserController extends PhabricatorAuthController {
+final class PhabricatorDisabledUserController
+ extends PhabricatorAuthController {
public function shouldRequireEnabledUser() {
return false;
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if (!$user->getIsDisabled()) {
return new Aphront404Response();
}
$failure_view = new AphrontRequestFailureView();
$failure_view->setHeader('Account Disabled');
$failure_view->appendChild('<p>Your account has been disabled.</p>');
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorEmailLoginController extends PhabricatorAuthController {
+final class PhabricatorEmailLoginController
+ extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function processRequest() {
$request = $this->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 = <<<EOBODY
You can use this link to reset your Phabricator password:
{$uri}
EOBODY;
} else {
$body = <<<EOBODY
Condolences on forgetting your password. You can use this link to reset it:
{$uri}
After you set a new password, consider writing it down on a sticky note and
attaching it to your monitor so you don't forget again! Choosing a very short,
easy-to-remember password like "cat" or "1234" might also help.
Best Wishes,
Phabricator
EOBODY;
}
// NOTE: Don't set the user as 'from', or they may not receive the
// mail if they have the "don't send me email about my own actions"
// preference set.
$mail = new PhabricatorMetaMTAMail();
$mail->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(
'<p>An email has been sent with a link you can use to login.</p>');
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('<h1>Forgot Password / Email Login</h1>');
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorEmailTokenController extends PhabricatorAuthController {
+final class PhabricatorEmailTokenController
+ extends PhabricatorAuthController {
private $token;
public function shouldRequireLogin() {
return false;
}
public function willProcessRequest(array $data) {
$this->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(
'<p>You are already logged in.</p>');
$view->appendChild(
'<div class="aphront-failure-continue">'.
'<a class="button" href="/">Return Home</a>'.
'</div>');
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(
'<p>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.</p>');
$view->appendChild(
'<div class="aphront-failure-continue">'.
'<a class="button" href="/login/email/">Send Another Email</a>'.
'</div>');
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorLoginController extends PhabricatorAuthController {
+final class PhabricatorLoginController
+ extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function processRequest() {
$request = $this->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/<method>';
$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(
'<a href="/login/email/">'.
'Forgot your password? / Email Login</a>'));
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.<br /><br />'.
'<strong>You can not use '.
phutil_escape_html($provider_name).' to register a new '.
'account.</strong>';
$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(
'<p class="aphront-form-instructions">'.$body.'</p>')
->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('<h1>'.$name.'</h1>');
$panel->appendChild($form);
$panel->appendChild('<br />');
}
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorLogoutController extends PhabricatorAuthController {
+final class PhabricatorLogoutController
+ extends PhabricatorAuthController {
public function shouldRequireLogin() {
return true;
}
public function shouldRequireEnabledUser() {
// Allow disabled users to logout.
return false;
}
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorOAuthLoginController extends PhabricatorAuthController {
+final class PhabricatorOAuthLoginController
+ extends PhabricatorAuthController {
private $provider;
private $userID;
private $accessToken;
private $tokenExpires;
private $oauthState;
public function shouldRequireLogin() {
return false;
}
public function willProcessRequest(array $data) {
$this->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(
'<p>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.</p>',
$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(
'<p>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.</p>',
$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(
'<p>Link your %s account to your Phabricator account?</p>',
$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(
'<p>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.</p>',
$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(
'<p>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.</p>',
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorOAuthDiagnosticsController
+final class PhabricatorOAuthDiagnosticsController
extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function willProcessRequest(array $data) {
$this->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 = '<strong style="color: #00aa00;">OK</strong>';
$res_no = '<strong style="color: #aa0000;">NO</strong>';
$res_na = '<strong style="color: #999999;">N/A</strong>';
$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(
'<p class="aphront-panel-instructions">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.</p>');
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorOAuthDefaultRegistrationController
+final class PhabricatorOAuthDefaultRegistrationController
extends PhabricatorOAuthRegistrationController {
public function processRequest() {
$provider = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRefreshCSRFController extends PhabricatorAuthController {
+final class PhabricatorRefreshCSRFController extends PhabricatorAuthController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorOAuthUnlinkController extends PhabricatorAuthController {
+final class PhabricatorOAuthUnlinkController extends PhabricatorAuthController {
private $provider;
public function willProcessRequest(array $data) {
$this->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(
'<p><strong>You will not be able to login</strong> using this account '.
'once you unlink it. Continue?</p>');
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorLoginValidateController extends PhabricatorAuthController {
+final class PhabricatorLoginValidateController
+ extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function processRequest() {
$request = $this->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[] = '<li>'.phutil_escape_html($failure).'</li>';
}
$list = '<ul>'.implode("\n", $list).'</ul>';
$view = new AphrontRequestFailureView();
$view->setHeader('Login Failed');
$view->appendChild(
'<p>Login failed:</p>'.
$list.
'<p><strong>Clear your cookies</strong> and try again.</p>');
$view->appendChild(
'<div class="aphront-failure-continue">'.
'<a class="button" href="/login/">Try Again</a>'.
'</div>');
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class Phabricator404Controller extends PhabricatorController {
+final class Phabricator404Controller extends PhabricatorController {
public function processRequest() {
return new Aphront404Response();
}
}
diff --git a/src/applications/base/controller/redirect/PhabricatorRedirectController.php b/src/applications/base/controller/redirect/PhabricatorRedirectController.php
index ad04a47407..b7b03fdf7a 100644
--- a/src/applications/base/controller/redirect/PhabricatorRedirectController.php
+++ b/src/applications/base/controller/redirect/PhabricatorRedirectController.php
@@ -1,31 +1,31 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRedirectController extends PhabricatorController {
+final class PhabricatorRedirectController extends PhabricatorController {
private $uri;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorCalendarBrowseController
+final class PhabricatorCalendarBrowseController
extends PhabricatorCalendarController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$months = array();
for ($ii = 1; $ii <= 12; $ii++) {
$month_view = new AphrontCalendarMonthView($ii, 2011);
$month_view->setUser($user);
$months[] = '<div style="padding: 2em;">';
$months[] = $month_view;
$months[] = '</div>';
}
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group conduit
*/
-class PhabricatorConduitAPIController
+final class PhabricatorConduitAPIController
extends PhabricatorConduitController {
public function shouldRequireLogin() {
return false;
}
private $method;
public function willProcessRequest(array $data) {
$this->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 = '<pre style="white-space: pre-wrap;">'.$value.'</pre>';
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group conduit
*/
-class PhabricatorConduitConsoleController
+final class PhabricatorConduitConsoleController
extends PhabricatorConduitController {
private $method;
public function willProcessRequest(array $data) {
$this->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[] = '<ul>';
foreach ($error_types as $error => $meaning) {
$error_description[] =
'<li>'.
'<strong>'.phutil_escape_html($error).':</strong> '.
phutil_escape_html($meaning).
'</li>';
}
$error_description[] = '</ul>';
$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(
'<p class="aphront-form-instructions">Enter parameters using '.
'<strong>JSON</strong>. For instance, to enter a list, type: '.
'<tt>["apple", "banana", "cherry"]</tt>');
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group conduit
*/
-class PhabricatorConduitLogController extends PhabricatorConduitController {
+final class PhabricatorConduitLogController
+ extends PhabricatorConduitController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group conduit
*/
-class PhabricatorConduitTokenController extends PhabricatorConduitController {
+final class PhabricatorConduitTokenController
+ extends PhabricatorConduitController {
public function processRequest() {
$user = $this->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(
'<p class="aphront-form-instructions">Copy and paste this token into '.
'the prompt given to you by "arc install-certificate":</p>'.
'<p style="padding: 0 0 1em 4em;">'.
'<strong>'.phutil_escape_html($token->getToken()).'</strong>'.
'</p>'.
'<p class="aphront-form-instructions">arc will then complete the '.
'install process for you.</p>');
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorCountdownDeleteController
+final class PhabricatorCountdownDeleteController
extends PhabricatorCountdownController {
public function willProcessRequest(array $data) {
$this->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(
'<p>Are you sure you want to delete the countdown "'.
phutil_escape_html($timer->getTitle()).'"?</p>');
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorCountdownEditController
+final class PhabricatorCountdownEditController
extends PhabricatorCountdownController {
private $id;
public function willProcessRequest(array $data) {
$this->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: '.
'<tt>2011-12-25</tt> or '.
'<tt>3 hours</tt> or '.
'<tt>June 8 2011, 5 PM</tt>.'))
->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorCountdownListController
+final class PhabricatorCountdownListController
extends PhabricatorCountdownController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorCountdownViewController
+final class PhabricatorCountdownViewController
extends PhabricatorCountdownController {
private $id;
public function willProcessRequest(array $data) {
$this->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 =
'<div class="phabricator-timer">
<h1 class="phabricator-timer-header">'.
phutil_escape_html($timer->getTitle()).' &middot; '.
phabricator_datetime($timer->getDatePoint(), $user).
'</h1>
<div class="phabricator-timer-pane">
<table class="phabricator-timer-table">
<tr>
<th>Days</th>
<th>Hours</th>
<th>Minutes</th>
<th>Seconds</th>
</tr>
<tr>
<td id="phabricator-timer-days"></td>
<td id="phabricator-timer-hours"></td>
<td id="phabricator-timer-minutes"></td>
<td id="phabricator-timer-seconds"></td>
</table>
</div>'.
$chrome_link.
'</div>';
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDaemonCombinedLogController
+final class PhabricatorDaemonCombinedLogController
extends PhabricatorDaemonController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDaemonConsoleController extends PhabricatorDaemonController {
+final class PhabricatorDaemonConsoleController
+ extends PhabricatorDaemonController {
public function processRequest() {
$logs = id(new PhabricatorDaemonLog())->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'.
' &middot; '.
phutil_render_tag(
'a',
array(
'href' => '/daemon/log/',
),
'View All Daemons').
' &middot; '.
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDaemonLogListController extends PhabricatorDaemonController {
+final class PhabricatorDaemonLogListController
+ extends PhabricatorDaemonController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDaemonLogViewController extends PhabricatorDaemonController {
+final class PhabricatorDaemonLogViewController
+ extends PhabricatorDaemonController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDaemonTimelineConsoleController
+final class PhabricatorDaemonTimelineConsoleController
extends PhabricatorDaemonController {
public function processRequest() {
$timeline_table = new PhabricatorTimelineEvent('NULL');
$events = queryfx_all(
$timeline_table->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDaemonTimelineEventController
+final class PhabricatorDaemonTimelineEventController
extends PhabricatorDaemonController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorWorkerTaskDetailController
+final class PhabricatorWorkerTaskDetailController
extends PhabricatorDaemonController {
private $id;
public function willProcessRequest(array $data) {
$this->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(
'<p>This task may have recently completed.</p>');
$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 =
"<strong>NOTE:</strong> ".
"You can manually retry this task by running this script:".
"<pre>".
"phabricator/\$ ./scripts/repository/reparse.php ".
"r".
phutil_escape_html($repository->getCallsign()).
phutil_escape_html($commit->getCommitIdentifier()).
" ".
"--change".
"</pre>";
}
}
}
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorWorkerTaskUpdateController
+final class PhabricatorWorkerTaskUpdateController
extends PhabricatorDaemonController {
private $id;
private $action;
public function willProcessRequest(array $data) {
$this->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(
'<p>The work this task represents will never be performed if you '.
'delete it. Are you sure you want to delete it?</p>');
$dialog->addSubmitButton('Delete Task');
break;
case 'release':
$dialog->setTitle('Really free task lease?');
$dialog->appendChild(
'<p>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?</p>');
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DifferentialChangesetViewController extends DifferentialController {
+final class DifferentialChangesetViewController extends DifferentialController {
public function shouldRequireLogin() {
return !$this->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(
'<div class="differential-review-stage" '.
'id="differential-review-stage">'.
$detail->render().
'</div>');
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DifferentialCommentPreviewController extends DifferentialController {
+final class DifferentialCommentPreviewController
+ extends DifferentialController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DifferentialCommentSaveController extends DifferentialController {
+final class DifferentialCommentSaveController extends DifferentialController {
public function processRequest() {
$request = $this->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(
'<p>'.phutil_escape_html($no_effect->getMessage()).'</p>');
if (strlen($comment) || $has_inlines) {
$dialog->addSubmitButton('Post as Comment');
$dialog->appendChild('<br />');
$dialog->appendChild(
'<p>Do you want to post your feedback anyway, as a normal '.
'comment?</p>');
}
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DifferentialDiffCreateController extends DifferentialController {
+final class DifferentialDiffCreateController extends DifferentialController {
public function processRequest() {
$request = $this->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(
'<p class="aphront-form-instructions">The best way to create a '.
"Differential diff is by using $arcanist_link, but you ".
'can also just paste a diff (e.g., from <tt>svn diff</tt> or '.
'<tt>git diff</tt>) into this box or upload it as a file if you '.
'really want.</p>')
->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DifferentialDiffViewController extends DifferentialController {
+final class DifferentialDiffViewController extends DifferentialController {
private $id;
public function willProcessRequest(array $data) {
$this->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("<h1>This diff belongs to revision {$link}</h1>");
} else {
$action_panel = new AphrontPanelView();
$action_panel->setHeader('Preview Diff');
$action_panel->setWidth(AphrontPanelView::WIDTH_WIDE);
$action_panel->appendChild(
'<p class="aphront-panel-instructions">Review the diff for '.
'correctness. When you are satisfied, either <strong>create a new '.
'revision</strong> or <strong>update an existing revision</strong>.');
// TODO: implmenent optgroup support in AphrontFormSelectControl?
$select = array();
$select[] = '<optgroup label="Create New Revision">';
$select[] = '<option value="">Create a new Revision...</option>';
$select[] = '</optgroup>';
$revision_data = new DifferentialRevisionListData(
DifferentialRevisionListData::QUERY_OPEN_OWNED,
array($request->getUser()->getPHID()));
$revisions = $revision_data->loadRevisions();
if ($revisions) {
$select[] = '<optgroup label="Update Existing Revision">';
foreach ($revisions as $revision) {
$select[] = phutil_render_tag(
'option',
array(
'value' => $revision->getID(),
),
phutil_escape_html($revision->getTitle()));
}
$select[] = '</optgroup>';
}
$select =
'<select name="revisionID">'.
implode("\n", $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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DifferentialInlineCommentEditController extends DifferentialController {
+final class DifferentialInlineCommentEditController
+ extends DifferentialController {
private $revisionID;
public function willProcessRequest(array $data) {
$this->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('<p>Delete this inline comment?</p>');
$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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DifferentialInlineCommentPreviewController
+final class DifferentialInlineCommentPreviewController
extends DifferentialController {
private $revisionID;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DifferentialRevisionEditController extends DifferentialController {
+final class DifferentialRevisionEditController extends DifferentialController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DifferentialRevisionListController extends DifferentialController {
+final class DifferentialRevisionListController extends DifferentialController {
private $filter;
private $username;
public function shouldRequireLogin() {
return !$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DifferentialRevisionStatsController extends DifferentialController {
+final class DifferentialRevisionStatsController extends DifferentialController {
private $filter;
public function shouldRequireLogin() {
return true;
}
private function loadRevisions($phid) {
$table = new DifferentialRevision();
$conn_r = $table->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DifferentialRevisionViewController extends DifferentialController {
+final class DifferentialRevisionViewController extends DifferentialController {
private $revisionID;
public function shouldRequireLogin() {
return !$this->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(
'<p>All specified reviewers are disabled. You may want to add '.
'some new reviewers.</p>');
} else {
$reviewer_warning->appendChild(
'<p>This revision has no specified reviewers. You may want to '.
'add some.</p>');
}
}
$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(
"<p>This diff is very large and affects {$count} files. Use ".
"Table of Contents to open files in a standalone view. ".
"<strong>".
phutil_render_tag(
'a',
array(
'href' => $request_uri->alter('large', 'true'),
),
'Show All Files Inline').
"</strong>");
$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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DifferentialSubscribeController extends DifferentialController {
+final class DifferentialSubscribeController extends DifferentialController {
private $id;
private $action;
public function willProcessRequest(array $data) {
$this->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('<p>'.$prompt.'</p>')
->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DiffusionBrowseController extends DiffusionController {
+final class DiffusionBrowseController extends DiffusionController {
public function processRequest() {
$drequest = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DiffusionChangeController extends DiffusionController {
+final class DiffusionChangeController extends DiffusionController {
public function processRequest() {
$drequest = $this->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[] =
'<div class="differential-primary-pane">'.
$changeset_view->render().
'</div>';
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DiffusionCommitController extends DiffusionController {
+final class DiffusionCommitController extends DiffusionController {
const CHANGES_LIMIT = 100;
public function processRequest() {
$drequest = $this->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(
'<div class="diffusion-commit-view">'.
'<div class="diffusion-commit-dateline">'.
'r'.$callsign.$commit->getCommitIdentifier().
' &middot; '.
phabricator_datetime($commit->getEpoch(), $user).
'</div>'.
'<h1>Revision Detail</h1>'.
'<div class="diffusion-commit-details">'.
$property_table.
'<hr />'.
'<div class="diffusion-commit-message phabricator-remarkup">'.
$engine->markupText($commit_data->getCommitMessage()).
'</div>'.
'</div>'.
'</div>');
$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 =
'<div class="differential-primary-pane">'.
$change_list->render().
'</div>';
$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[] =
'<tr>'.
'<th>'.$key.':</th>'.
'<td>'.$value.'</td>'.
'</tr>';
}
return
'<table class="diffusion-commit-properties">'.
implode("\n", $rows).
'</table>';
}
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 =
'<div class="aphront-panel-preview">
<div id="audit-preview">
<div class="aphront-panel-preview-loading-text">
Loading preview...
</div>
</div>
</div>';
$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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DiffusionDiffController extends DiffusionController {
+final class DiffusionDiffController extends DiffusionController {
public function willProcessRequest(array $data) {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DiffusionBrowseFileController extends DiffusionController {
+final class DiffusionBrowseFileController extends DiffusionController {
// Image types we want to display inline using <img> 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 = '<select name="view">';
foreach ($select_map as $k => $v) {
$option = phutil_render_tag(
'option',
array(
'value' => $k,
'selected' => ($k == $selected) ? 'selected' : null,
),
phutil_escape_html($v));
$select .= $option;
}
$select .= '</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.
'<button>View</button>');
$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('<div style="clear: both;"></div>');
// 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() ?
'<th style="background: '.$color.'; width: 2em;"></th>' : '').
'<th style="background: '.$color.'; width: 9em;"></th>'.
'<th style="background: '.$color.'"></th>';
} 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 .
'<th style="background: '.$color.
'; width: 12em;">'.$revision_link.'</th>'.
'<th style="background: '.$color.'; width: 12em'.
'; font-weight: normal; color: #333;">'.$author_link.'</th>';
$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 = '<tr style="background: #ffff00;">';
if ($targ == '') {
$targ = '<a id="scroll_target"></a>';
Javelin::initBehavior('diffusion-jump-to',
array('target' => 'scroll_target'));
}
} else {
$tr = '<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.
'<th class="diffusion-wide-link">'.$l.'</th>'.
'<td>'.$targ.$line.'</td></tr>';
++$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DiffusionHistoryController extends DiffusionController {
+final class DiffusionHistoryController extends DiffusionController {
public function processRequest() {
$drequest = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DiffusionHomeController extends DiffusionController {
+final class DiffusionHomeController extends DiffusionController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DiffusionLastModifiedController extends DiffusionController {
+final class DiffusionLastModifiedController extends DiffusionController {
public function processRequest() {
$drequest = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DiffusionPathCompleteController extends DiffusionController {
+final class DiffusionPathCompleteController extends DiffusionController {
public function willProcessRequest(array $data) {
// Don't build a DiffusionRequest.
}
public function processRequest() {
$request = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DiffusionPathValidateController extends DiffusionController {
+final class DiffusionPathValidateController extends DiffusionController {
public function willProcessRequest(array $data) {
// Don't build a DiffusionRequest.
}
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DiffusionRepositoryController extends DiffusionController {
+final class DiffusionRepositoryController extends DiffusionController {
public function processRequest() {
$drequest = $this->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 &middot; {$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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class DiffusionSymbolController extends DiffusionController {
+final class DiffusionSymbolController extends DiffusionController {
private $name;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDirectoryCategoryDeleteController
+final class PhabricatorDirectoryCategoryDeleteController
extends PhabricatorDirectoryController {
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDirectoryCategoryEditController
+final class PhabricatorDirectoryCategoryEditController
extends PhabricatorDirectoryController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDirectoryItemDeleteController
+final class PhabricatorDirectoryItemDeleteController
extends PhabricatorDirectoryController {
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDirectoryItemEditController
+final class PhabricatorDirectoryItemEditController
extends PhabricatorDirectoryController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorDirectoryMainController
+final class PhabricatorDirectoryMainController
extends PhabricatorDirectoryController {
private $filter;
private $subfilter;
public function willProcessRequest(array $data) {
$this->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 <a href="/project/">projects you are a member of</a> '.
'need triage.</p>');
}
$panel = new AphrontPanelView();
$panel->setHeader('Needs Triage');
$panel->setCaption(
'Open tasks with "Needs Triage" priority in '.
'<a href="/project/">projects you are a member of</a>.');
$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
'<div style="padding: 1em 3em;">'.
'<div style="margin: 0 1em;">'.
'<h1 style="font-size: 18px; '.
'border-bottom: 1px solid #aaaaaa; '.
'padding: 0;">Feed</h1>'.
'</div>'.
$feed_view->render().
'<div class="phabricator-feed-frame">'.
$new_link.
$old_link.
'</div>'.
'</div>';
}
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 <tt>D123</tt> to quickly jump to '.
'it. See '.$doc_link.' or type <tt>help</tt>.');
$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('<div class="phabricator-app-buttons">');
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(
'<div class="phabricator-app-button">'.
$button.
$caption.
'</div>');
}
$view->appendChild('<div style="clear: both;"></div></div>');
return $view;
}
private function renderMiniPanel($title, $body) {
$panel = new AphrontMiniPanelView();
$panel->appendChild(
phutil_render_tag(
'p',
array(
),
'<strong>'.$title.':</strong> '.$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorFileDeleteController extends PhabricatorFileController {
+final class PhabricatorFileDeleteController extends PhabricatorFileController {
private $id;
public function willProcessRequest(array $data) {
$this->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(
"<p>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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorFileDropUploadController extends PhabricatorFileController {
+final class PhabricatorFileDropUploadController
+ extends PhabricatorFileController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorFileListController extends PhabricatorFileController {
+final class PhabricatorFileListController extends PhabricatorFileController {
private $filter;
private $showUploader;
private $useBasicUploader = false;
private $listAuthor;
private $listRows;
private $listRowClasses;
private $listHeader;
private $showListPager = true;
private $listPager;
private $pagerOffset;
private $pagerPageSize;
private function setFilter($filter) {
$this->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 &middot; {$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) : '<em>no name</em>')),
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorFileMacroDeleteController extends PhabricatorFileController {
+final class PhabricatorFileMacroDeleteController
+ extends PhabricatorFileController {
private $id;
public function willProcessRequest(array $data) {
$this->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(
'<p>Really delete the much-beloved image macro "'.
phutil_escape_html($macro->getName()).'"? It will be sorely missed.'.
'</p>')
->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorFileMacroEditController extends PhabricatorFileController {
+final class PhabricatorFileMacroEditController
+ extends PhabricatorFileController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorFileMacroListController extends PhabricatorFileController {
+final class PhabricatorFileMacroListController
+ extends PhabricatorFileController {
+
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorFileProxyController extends PhabricatorFileController {
+final class PhabricatorFileProxyController extends PhabricatorFileController {
private $uri;
public function processRequest() {
if (!PhabricatorEnv::getEnvConfig('files.enable-proxy')) {
return new Aphront400Response();
}
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorFileTransformController extends PhabricatorFileController {
+final class PhabricatorFileTransformController
+ extends PhabricatorFileController {
private $transform;
private $phid;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorFileUploadController extends PhabricatorFileController {
+final class PhabricatorFileUploadController extends PhabricatorFileController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorHelpKeyboardShortcutController
+final class PhabricatorHelpKeyboardShortcutController
extends PhabricatorHelpController {
public function processRequest() {
$request = $this->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[] = '<kbd>'.phutil_escape_html($stroke).'</kbd>';
}
$keystrokes = implode(' or ', $keystrokes);
$rows[] =
'<tr>'.
'<th>'.$keystrokes.'</th>'.
'<td>'.phutil_escape_html($shortcut['description']).'</td>'.
'</tr>';
}
$table =
'<table class="keyboard-shortcut-help">'.
implode('', $rows).
'</table>';
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldAllRulesController extends HeraldController {
+final class HeraldAllRulesController extends HeraldController {
private $view;
private $viewPHID;
private $filter;
public function getFilter() {
return $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldDeleteController extends HeraldController {
+final class HeraldDeleteController extends HeraldController {
private $id;
public function getFilter() {
// note this controller is only used from a dialog-context at the moment
// and there is actually no "delete" filter
return 'delete';
}
public function willProcessRequest(array $data) {
$this->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 ".
"'<strong>".phutil_escape_html($rule->getName())."</strong>'?");
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldRuleEditHistoryController extends HeraldController {
+final class HeraldRuleEditHistoryController extends HeraldController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldHomeController extends HeraldController {
+final class HeraldHomeController extends HeraldController {
private $view;
private $filter;
private $global;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldNewController extends HeraldController {
+final class HeraldNewController extends HeraldController {
private $contentType;
public function getFilter() {
return 'new';
}
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldRuleController extends HeraldController {
+final class HeraldRuleController extends HeraldController {
private $id;
private $filter;
public function getFilter() {
return $this->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('<div id="author-input"/>'))
->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 <strong>${rule_type_name}</strong> rule triggers for " .
"<strong>${content_type_name}</strong>."))
->appendChild(
'<h1>Conditions</h1>'.
'<div class="aphront-form-inset">'.
'<div style="float: right;">'.
javelin_render_tag(
'a',
array(
'href' => '#',
'class' => 'button green',
'sigil' => 'create-condition',
'mustcapture' => true,
),
'Create New Condition').
'</div>'.
'<p>When '.$must_match_selector.' these conditions are met:</p>'.
'<div style="clear: both;"></div>'.
javelin_render_tag(
'table',
array(
'sigil' => 'rule-conditions',
'class' => 'herald-condition-table',
),
'').
'</div>')
->appendChild(
'<h1>Action</h1>'.
'<div class="aphront-form-inset">'.
'<div style="float: right;">'.
javelin_render_tag(
'a',
array(
'href' => '#',
'class' => 'button green',
'sigil' => 'create-action',
'mustcapture' => true,
),
'Create New Action').
'</div>'.
'<p>'.
'Take these actions '.$repetition_selector.' this rule matches:'.
'</p>'.
'<div style="clear: both;"></div>'.
javelin_render_tag(
'table',
array(
'sigil' => 'rule-actions',
'class' => 'herald-action-table',
),
'').
'</div>')
->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 =
'<select name="must_match">' .
implode("\n", $must_match) .
'</select>';
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 =
'<select name="repetition_policy">' .
implode("\n", $tags) .
'</select>';
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldTestConsoleController extends HeraldController {
+final class HeraldTestConsoleController extends HeraldController {
public function getFilter() {
return 'test';
}
public function processRequest() {
$request = $this->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(
'<p class="aphront-form-instructions">Enter an object to test rules '.
'for, like a Diffusion commit (e.g., <tt>rX123</tt>) or a '.
'Differential revision (e.g., <tt>D123</tt>). You will be shown the '.
'results of a dry run on the object.</p>')
->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldTranscriptController extends HeraldController {
+final class HeraldTranscriptController extends HeraldController {
const FILTER_AFFECTED = 'affected';
const FILTER_OWNED = 'owned';
const FILTER_ALL = 'all';
private $id;
private $filter;
private $handles;
public function getFilter() {
return 'transcript';
}
public function willProcessRequest(array $data) {
$this->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(
'<p>Details of this transcript have been garbage collected.</p>');
$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 =
<tools:notice title="Dry Run">
This was a dry run to test Herald rules, no actions were executed.
</tools:notice>;
}
*/
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
'<span class="condition-test-value">'.
phutil_escape_html($value).
'</span>';
}
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 = '<empty>';
}
} 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 = '<span class="outcome-success">SUCCESS</span>';
} else {
$outcome = '<span class="outcome-failure">FAILURE</span>';
}
$outcome .= ' '.phutil_escape_html($apply_xscript->getAppliedReason());
$rows[] = array(
phutil_escape_html($action_names[$apply_xscript->getAction()]),
$target,
'<strong>Taken because:</strong> '.
phutil_escape_html($apply_xscript->getReason()).
'<br />'.
'<strong>Outcome:</strong> '.$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 =
'<div class="herald-condition-note">'.
phutil_escape_html($cond->getNote()).
'</div>';
} else {
$note = null;
}
if ($cond->getResult()) {
$result =
'<span class="herald-outcome condition-pass">'.
"\xE2\x9C\x93".
'</span>';
} else {
$result =
'<span class="herald-outcome condition-fail">'.
"\xE2\x9C\x98".
'</span>';
}
$cond_markup[] =
'<li>'.
$result.' Condition: '.
phutil_escape_html($field_names[$cond->getFieldName()]).
' '.
phutil_escape_html($condition_names[$cond->getCondition()]).
' '.
$this->renderConditionTestValue($cond, $handles).
$note.
'</li>';
}
if ($rule->getResult()) {
$result = '<span class="herald-outcome rule-pass">PASS</span>';
$class = 'herald-rule-pass';
} else {
$result = '<span class="herald-outcome rule-fail">FAIL</span>';
$class = 'herald-rule-fail';
}
$cond_markup[] =
'<li>'.$result.' '.phutil_escape_html($rule->getReason()).'</li>';
/*
if ($rule->getResult()) {
$actions = idx($action_xscript, $rule_id, array());
if ($actions) {
$cond_markup[] = <li><div class="action-header">Actions</div></li>;
foreach ($actions as $action) {
$target = $action->getTarget();
if ($target) {
foreach ((array)$target as $k => $phid) {
$target[$k] = $handles[$phid]->getName();
}
$target = <strong>: {implode(', ', $target)}</strong>;
}
$cond_markup[] =
<li>
{$action_names[$action->getAction()]}
{$target}
</li>;
}
}
}
*/
$user_phid = $this->getRequest()->getUser()->getPHID();
$name = $rule->getRuleName();
if ($rule->getRuleOwner() == $user_phid) {
// $name = <a href={"/herald/rule/".$rule->getRuleID()."/"}>{$name}</a>;
}
$rule_markup[] =
phutil_render_tag(
'li',
array(
'class' => $class,
),
'<div class="rule-name">'.
'<strong>'.phutil_escape_html($name).'</strong> '.
phutil_escape_html($handles[$rule->getRuleOwner()]->getName()).
'</div>'.
'<ul>'.implode("\n", $cond_markup).'</ul>');
}
$panel = new AphrontPanelView();
$panel->setHeader('Rule Details');
$panel->appendChild(
'<ul class="herald-explain-list">'.
implode("\n", $rule_markup).
'</ul>');
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class HeraldTranscriptListController extends HeraldController {
+final class HeraldTranscriptListController extends HeraldController {
public function getFilter() {
return 'transcript';
}
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTaskDescriptionChangeController extends ManiphestController {
+final class ManiphestTaskDescriptionChangeController
+ extends ManiphestController {
private $transactionID;
protected function setTransactionID($transaction_id) {
$this->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 @@
-<?php
-
-/*
- * Copyright 2012 Facebook, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * @group maniphest
- */
-final class ManiphestTaskDescriptionDiffController
- extends ManiphestTaskDescriptionChangeController {
-
- public function willProcessRequest(array $data) {
- $ref = $this->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 @@
-<?php
-/**
- * This file is automatically generated. Lint this module to rebuild it.
- * @generated
- */
-
-
-
-phutil_require_module('phabricator', 'applications/maniphest/controller/descriptionchange');
-
-
-phutil_require_source('ManiphestTaskDescriptionDiffController.php');
diff --git a/src/applications/maniphest/controller/descriptionpreview/ManiphestTaskDescriptionPreviewController.php b/src/applications/maniphest/controller/descriptionpreview/ManiphestTaskDescriptionPreviewController.php
index 5d1f2b8ad3..70b640f9ef 100644
--- a/src/applications/maniphest/controller/descriptionpreview/ManiphestTaskDescriptionPreviewController.php
+++ b/src/applications/maniphest/controller/descriptionpreview/ManiphestTaskDescriptionPreviewController.php
@@ -1,41 +1,42 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTaskDescriptionPreviewController extends ManiphestController {
+final class ManiphestTaskDescriptionPreviewController
+ extends ManiphestController {
public function processRequest() {
$request = $this->getRequest();
$description = $request->getStr('description');
$engine = PhabricatorMarkupEngine::newManiphestMarkupEngine();
$content =
'<div class="maniphest-task-description">'.
'<div class="phabricator-remarkup">'.
$engine->markupText($description).
'</div>'.
'</div>';
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTaskDetailController extends ManiphestController {
+final class ManiphestTaskDetailController extends ManiphestController {
private $id;
public function willProcessRequest(array $data) {
$this->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'] =
'<strong>'.
ManiphestTaskStatus::getTaskStatusFullName($task->getStatus()).
'</strong>';
$dict['Assigned To'] = $task->getOwnerPHID()
? $handles[$task->getOwnerPHID()]->renderLink()
: '<em>None</em>';
$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'] = '<em>None</em>';
}
$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'] = '<em>None</em>';
}
$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('<br />', $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('<br />', $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'] =
'<div class="maniphest-task-description">'.
'<div class="phabricator-remarkup">'.
$engine->markupText($task->getDescription()).
'</div>'.
'</div>';
require_celerity_resource('maniphest-task-detail-css');
$table = array();
foreach ($dict as $key => $value) {
$table[] =
'<tr>'.
'<th>'.phutil_escape_html($key).':</th>'.
'<td>'.$value.'</td>'.
'</tr>';
}
$table =
'<table class="maniphest-task-properties">'.
implode("\n", $table).
'</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 <strong>'.
$handles[$parent_task->getPHID()]->renderLink().
'</strong>');
} else if ($workflow == 'create') {
$context_bar = new AphrontContextBarView();
$context_bar->addButton('<label>Create Another:</label>');
$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 =
'<div class="maniphest-panel">'.
$action_list->render().
'<div class="maniphest-task-detail-core">'.
'<h1>'.
'<span class="aphront-headsup-object-name">'.
phutil_escape_html('T'.$task->getID()).
'</span>'.
' '.
phutil_escape_html($task->getTitle()).
'</h1>'.
$table.
'</div>'.
'</div>';
$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 =
'<div class="aphront-panel-preview">
<div id="transaction-preview">
<div class="aphront-panel-preview-loading-text">
Loading preview...
</div>
</div>
</div>';
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTaskEditController extends ManiphestController {
+final class ManiphestTaskEditController extends ManiphestController {
private $id;
public function willProcessRequest(array $data) {
$this->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('<br />', $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: '.
'<tt>'.phutil_escape_html($email_create).'</tt>';
}
$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 =
'<div class="aphront-panel-preview aphront-panel-preview-full">
<div class="maniphest-description-preview-header">
Description Preview
</div>
<div id="description-preview">
<div class="aphront-panel-preview-loading-text">
Loading preview...
</div>
</div>
</div>';
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTaskListController extends ManiphestController {
+final class ManiphestTaskListController extends ManiphestController {
const DEFAULT_PAGE_SIZE = 1000;
private $view;
public function willProcessRequest(array $data) {
$this->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('<<maniphest>>');
$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('<div class="maniphest-list-container">');
if (!$have_tasks) {
$list_container->appendChild(
'<h1 class="maniphest-task-group-header">'.
'No matching tasks.'.
'</h1>');
} 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(
'<div class="maniphest-total-result-count">'.
"Displaying tasks {$cur} - {$max} of {$tot}.".
'</div>');
$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(
'<h1 class="maniphest-task-group-header">'.
phutil_escape_html($group).' ('.$count.')'.
'</h1>');
$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('</div>');
$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 &raquo;');
$export = javelin_render_tag(
'a',
array(
'href' => '/maniphest/export/'.$search_query->getQueryKey().'/',
'class' => 'grey button',
),
'Export Tasks to Excel...');
return
'<div class="maniphest-batch-editor">'.
'<div class="batch-editor-header">Batch Task Editor</div>'.
'<table class="maniphest-batch-editor-layout">'.
'<tr>'.
'<td>'.
$select_all.
$select_none.
'</td>'.
'<td>'.
$export.
'</td>'.
'<td id="batch-select-status-cell">'.
'0 Selected Tasks'.
'</td>'.
'<td class="batch-select-submit-cell">'.$submit.'</td>'.
'</tr>'.
'</table>'.
'</table>';
}
}
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTransactionPreviewController extends ManiphestController {
+final class ManiphestTransactionPreviewController extends ManiphestController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
-class ManiphestTransactionSaveController extends ManiphestController {
+final class ManiphestTransactionSaveController extends ManiphestController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group maniphest
*/
class ManiphestTransactionListView extends ManiphestView {
private $transactions;
private $handles;
private $user;
private $markupEngine;
private $preview;
private $auxiliaryFields;
public function setTransactions(array $transactions) {
$this->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
'<div class="maniphest-transaction-list-view">'.
implode("\n", $views).
'</div>';
}
}
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMetaMTAListController extends PhabricatorMetaMTAController {
+final class PhabricatorMetaMTAListController
+ extends PhabricatorMetaMTAController {
public function processRequest() {
// Get a page of mails together with pager.
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMetaMTAMailingListEditController
+final class PhabricatorMetaMTAMailingListEditController
extends PhabricatorMetaMTAController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMetaMTAMailingListsController
+final class PhabricatorMetaMTAMailingListsController
extends PhabricatorMetaMTAController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMetaMTAReceiveController
+final class PhabricatorMetaMTAReceiveController
extends PhabricatorMetaMTAController {
public function processRequest() {
$request = $this->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(
'<p class="aphront-form-instructions">This form will simulate '.
'sending mail to an object.</p>')
->appendChild(
id(new AphrontFormTextControl())
->setLabel('To')
->setName('obj')
->setCaption('e.g. <tt>D1234</tt> or <tt>T1234</tt>'))
->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMetaMTAReceivedListController
+final class PhabricatorMetaMTAReceivedListController
extends PhabricatorMetaMTAController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMetaMTASendController extends PhabricatorMetaMTAController {
+final class PhabricatorMetaMTASendController
+ extends PhabricatorMetaMTAController {
public function processRequest() {
$request = $this->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 =
'<p class="aphront-form-instructions">This form will send a normal '.
'email using the settings you have configured for Phabricator. For more '.
'information, see '.$doclink.'.</p>';
$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(
'<p>This installation of Phabricator is currently set to use '.
'<tt>PhabricatorMailImplementationTestAdapter</tt> 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.</p>');
}
$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: <tt>differential-cc, differential-comment</tt>'))
->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMetaMTASendGridReceiveController
+final class PhabricatorMetaMTASendGridReceiveController
extends PhabricatorMetaMTAController {
public function shouldRequireLogin() {
return false;
}
public function shouldRequireAdmin() {
return false;
}
public function processRequest() {
// No CSRF for SendGrid.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorMetaMTAViewController extends PhabricatorMetaMTAController {
+final class PhabricatorMetaMTAViewController
+ extends PhabricatorMetaMTAController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorOwnersDeleteController extends PhabricatorOwnersController {
+final class PhabricatorOwnersDeleteController
+ extends PhabricatorOwnersController {
private $id;
public function willProcessRequest(array $data) {
$this->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(
'<p>Are you sure you want to delete the "'.
phutil_escape_html($package->getName()).'" package? This operation '.
'can not be undone.</p>')
->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorOwnersDetailController extends PhabricatorOwnersController {
+final class PhabricatorOwnersDetailController
+ extends PhabricatorOwnersController {
private $id;
private $package;
public function willProcessRequest(array $data) {
$this->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 =
'<strong>'.$handles[$primary_phid]->renderLink().'</strong>';
}
$rows[] = array(
'Primary Owner',
$primary_owner,
);
$owner_links = array();
foreach ($owners as $owner) {
$owner_links[] = $handles[$owner->getUserPHID()]->renderLink();
}
$owner_links = implode('<br />', $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 = '<strong>'.phutil_escape_html($callsign).'</strong>';
$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('<br />', $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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorOwnersEditController extends PhabricatorOwnersController {
+final class PhabricatorOwnersEditController
+ extends PhabricatorOwnersController {
private $id;
public function willProcessRequest(array $data) {
$this->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(
'<h1>Paths</h1>'.
'<div class="aphront-form-inset" id="path-editor">'.
'<div style="float: right;">'.
javelin_render_tag(
'a',
array(
'href' => '#',
'class' => 'button green',
'sigil' => 'addpath',
'mustcapture' => true,
),
'Add New Path').
'</div>'.
'<p>Specify the files and directories which comprise this '.
'package.</p>'.
'<div style="clear: both;"></div>'.
javelin_render_tag(
'table',
array(
'class' => 'owners-path-editor-table',
'sigil' => 'paths',
),
'').
'</div>')
->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorOwnersListController extends PhabricatorOwnersController {
+final class PhabricatorOwnersListController
+ extends PhabricatorOwnersController {
protected $view;
public function willProcessRequest(array $data) {
$this->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] = '<strong>'.$pkg_owners[$key].'</strong>';
}
}
$pkg_owners = implode('<br />', $pkg_owners);
$pkg_paths = idx($paths, $package->getID(), array());
foreach ($pkg_paths as $key => $path) {
$repo = $handles[$path->getRepositoryPHID()]->getName();
$pkg_paths[$key] =
'<strong>'.phutil_escape_html($repo).'</strong> '.
phutil_render_tag(
'a',
array(
'href' => '/diffusion/'.$repo.'/browse/:'.$path->getPath(),
),
phutil_escape_html($path->getPath()));
}
$pkg_paths = implode('<br />', $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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorPasteListController extends PhabricatorPasteController {
+final class PhabricatorPasteListController extends PhabricatorPasteController {
private $filter;
private $errorView;
private $errorText;
private $paste;
private $pasteText;
private $offset;
private $pageSize;
private $author;
private function setFilter($filter) {
$this->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 &middot; {$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorPasteViewController extends PhabricatorPasteController {
+final class PhabricatorPasteViewController extends PhabricatorPasteController {
private $id;
public function willProcessRequest(array $data) {
$this->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[] = '<tr id="'.$anchor.'"><th>'.$link.'</th>'.
'<td style="white-space: pre-wrap;">'.$line.'</td></tr>';
++$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorPeopleEditController extends PhabricatorPeopleController {
+final class PhabricatorPeopleEditController
+ extends PhabricatorPeopleController {
public function shouldRequireAdmin() {
return true;
}
private $id;
private $view;
public function willProcessRequest(array $data) {
$this->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('<p>Your changes were saved.</p>');
$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(
'<p class="aphront-form-instructions">NOTE: You can not edit your own '.
'role.</p>');
}
$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(
'<p class="aphront-form-instructions">You can use this certificate '.
'to write scripts or bots which interface with Phabricator over '.
'Conduit.</p>');
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorPeopleListController extends PhabricatorPeopleController {
+final class PhabricatorPeopleListController
+ extends PhabricatorPeopleController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorPeopleLogsController extends PhabricatorPeopleController {
+final class PhabricatorPeopleLogsController
+ extends PhabricatorPeopleController {
public function shouldRequireAdmin() {
return true;
}
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorPeopleProfileController extends PhabricatorPeopleController {
+final class PhabricatorPeopleProfileController
+ extends PhabricatorPeopleController {
private $username;
private $page;
public function willProcessRequest(array $data) {
$this->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(
'<div style="padding: 1em;">'.$content.'</div>');
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 =
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">Basic Information</h1>
<div class="phabricator-profile-info-pane">
<table class="phabricator-profile-info-table">
<tr>
<th>PHID</th>
<td>'.phutil_escape_html($user->getPHID()).'</td>
</tr>
<tr>
<th>User Since</th>
<td>'.phabricator_datetime($user->getDateCreated(),
$viewer).
'</td>
</tr>
</table>
</div>
</div>';
$content .=
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">Flavor Text</h1>
<div class="phabricator-profile-info-pane">
<table class="phabricator-profile-info-table">
<tr>
<th>Blurb</th>
<td>'.$blurb.'</td>
</tr>
</table>
</div>
</div>';
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
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">Activity Feed</h1>
<div class="phabricator-profile-info-pane">
'.$view->render().'
</div>
</div>';
}
}
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserSettingsController extends PhabricatorPeopleController {
+final class PhabricatorUserSettingsController
+ extends PhabricatorPeopleController {
private $page;
private $pages;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserAccountSettingsPanelController
+final class PhabricatorUserAccountSettingsPanelController
extends PhabricatorUserSettingsPanelController {
public function processRequest() {
$request = $this->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('<p>Your changes have been saved.</p>');
$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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserConduitSettingsPanelController
+final class PhabricatorUserConduitSettingsPanelController
extends PhabricatorUserSettingsPanelController {
public function processRequest() {
$request = $this->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(
'<p>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(
'<p>Your old certificate has been destroyed and you have been issued '.
'a new certificate. Sessions established under the old certificate '.
'are no longer valid.</p>');
$notice = $notice->render();
} else {
$notice = null;
}
$cert_form = new AphrontFormView();
$cert_form
->setUser($user)
->appendChild(
'<p class="aphront-form-instructions">This certificate allows you to '.
'authenticate over Conduit, the Phabricator API. Normally, you just '.
'run <tt>arc install-certificate</tt> 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(
'<p class="aphront-form-instructions">You can regenerate this '.
'certificate, which will invalidate the old certificate and create '.
'a new one.</p>')
->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserEmailSettingsPanelController
+final class PhabricatorUserEmailSettingsPanelController
extends PhabricatorUserSettingsPanelController {
public function processRequest() {
$request = $this->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('<p>Your changes have been saved.</p>');
}
} 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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserEmailPreferenceSettingsPanelController
+final class PhabricatorUserEmailPreferenceSettingsPanelController
extends PhabricatorUserSettingsPanelController {
public function processRequest() {
$request = $this->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('<p>Your changes have been saved.</p>');
}
} 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(
'<br />'.
'<p class="aphront-form-instructions">'.
'You can customize what mail you receive from Phabricator here.'.
'</p>'.
'<p class="aphront-form-instructions">'.
'<strong>NOTE:</strong> 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.'.
'</p>'
);
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserOAuthSettingsPanelController
+final class PhabricatorUserOAuthSettingsPanelController
extends PhabricatorUserSettingsPanelController {
private $provider;
public function setOAuthProvider(PhabricatorOAuthProvider $oauth_provider) {
$this->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(
'<p class="aphront-form-instructions">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.</p>');
$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(
'<p class="aphront-form-instructions">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.</p>')
->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(
'<p class="aphront-form-instructions">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.</p>')
->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(
'<p class="aphront-from-instructions">insert rap about tokens</p>')
->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('<br /><br /><h1>'.$name.'</h1>');
}
$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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserPasswordSettingsPanelController
+final class PhabricatorUserPasswordSettingsPanelController
extends PhabricatorUserSettingsPanelController {
public function processRequest() {
$request = $this->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('<p>Your password has been updated.</p>');
}
} 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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserPreferenceSettingsPanelController
+final class PhabricatorUserPreferenceSettingsPanelController
extends PhabricatorUserSettingsPanelController {
public function processRequest() {
$request = $this->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 = <<<EXAMPLE
// This is what your monospaced font currently looks like.
function helloWorld() {
alert("Hello world!");
}
EXAMPLE;
$editor_doc_link = phutil_render_tag(
'a',
array(
'href' => 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(
'<pre class="PhabricatorMonospaced">'.
phutil_escape_html($example_string).
'</pre>'))
->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserProfileSettingsPanelController
+final class PhabricatorUserProfileSettingsPanelController
extends PhabricatorUserSettingsPanelController {
public function processRequest() {
$request = $this->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('<p>Your changes have been saved.</p>');
$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(
'<p class="aphront-form-instructions">Write something about yourself! '.
'Make sure to include <strong>important information</strong> like '.
'your favorite pokemon and which Starcraft race you play.</p>')
->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUserSSHKeysSettingsPanelController
+final class PhabricatorUserSSHKeysSettingsPanelController
extends PhabricatorUserSettingsPanelController {
const PANEL_BASE_URI = '/settings/page/sshkeys/';
public static function isEnabled() {
return PhabricatorEnv::getEnvConfig('auth.sshkeys.enabled');
}
public function processRequest() {
$request = $this->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(
'<p>The key "<strong>'.$name.'</strong>" will be permanently deleted, '.
'and you will not longer be able to use the corresponding private key '.
'to authenticate.</p>')
->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorPHIDLookupController
+final class PhabricatorPHIDLookupController
extends PhabricatorPHIDController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
-class PhrictionDeleteController extends PhrictionController {
+final class PhrictionDeleteController extends PhrictionController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
-class PhrictionDiffController
+final class PhrictionDiffController
extends PhrictionController {
private $id;
public function willProcessRequest(array $data) {
$this->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 =
'<table class="phriction-history-nav-table">
<tr>
<td class="nav-prev">'.$link_l.'</td>
<td class="nav-next">'.$link_r.'</td>
</tr>
</table>';
}
$output =
'<div class="phriction-document-history-diff">'.
$comparison_table->render().
'<br />'.
'<br />'.
$navigation_table.
'<table class="phriction-revert-table">'.
'<tr><td>'.$revert_l.'</td><td>'.$revert_r.'</td>'.
'</table>'.
$output.
'</div>';
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
-class PhrictionDocumentController
+final class PhrictionDocumentController
extends PhrictionController {
private $slug;
public function willProcessRequest(array $data) {
$this->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 =
'<div class="phriction-content">'.
'<em>No content here!</em><br />'.
'No document found at <tt>'.phutil_escape_html($slug).'</tt>. '.
'You can <strong>'.
phutil_render_tag(
'a',
array(
'href' => $create_uri,
),
'create a new document').'</strong>.'.
'</div>';
$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 =
'<br />This document is about the project '.
$handles[$project_phid]->renderLink().'.';
}
$byline =
'<div class="phriction-byline">'.
"Last updated {$when} by ".
$handles[$content->getAuthorPHID()]->renderLink().'.'.
$project_info.
'</div>';
$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 =
'<div class="phriction-content">'.
$byline.
$core_content.
'</div>';
$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 =
'<div class="phriction-header">'.
$buttons.
'<h1>'.phutil_escape_html($page_title).'</h1>'.
$breadcrumbs.
'</div>'.
$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
'<div class="phriction-breadcrumbs">'.
$list.' &middot; '.
'<span class="phriction-document-crumbs">'.
implode(" \xC2\xBB ", $breadcrumbs).
'</span>'.
'</div>';
}
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[] = '<ul>';
foreach ($children as $child) {
$list[] = $this->renderChildDocumentLink($child);
$grand = idx($grandchildren, $child['slug'], array());
if ($grand) {
$list[] = '<ul>';
foreach ($grand as $grandchild) {
$list[] = $this->renderChildDocumentLink($grandchild);
}
$list[] = '</ul>';
}
}
if ($more_children) {
$list[] = '<li>More...</li>';
}
$list[] = '</ul>';
$list = implode("\n", $list);
return
'<div class="phriction-children">'.
'<div class="phriction-children-header">Document Hierarchy</div>'.
$list.
'</div>';
}
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 = '<em>'.$item.'</em>';
}
return '<li>'.$item.'</li>';
}
}
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
-class PhrictionDocumentPreviewController
+final class PhrictionDocumentPreviewController
extends PhrictionController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
-class PhrictionEditController
+final class PhrictionEditController
extends PhrictionController {
private $id;
public function willProcessRequest(array $data) {
$this->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(
'<p>Showing a saved draft of your edits, you can '.$discard.'.</p>');
} 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 =
'<div class="aphront-panel-preview aphront-panel-preview-wide">
<div class="phriction-document-preview-header">
Document Preview
</div>
<div id="document-preview">
<div class="aphront-panel-preview-loading-text">
Loading preview...
</div>
</div>
</div>';
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
-class PhrictionHistoryController
+final class PhrictionHistoryController
extends PhrictionController {
private $slug;
public function willProcessRequest(array $data) {
$this->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 = '<em>Created</em>';
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 = '<em>Current</em>';
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group phriction
*/
-class PhrictionListController
+final class PhrictionListController
extends PhrictionController {
private $view;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorProjectCreateController
+final class PhabricatorProjectCreateController
extends PhabricatorProjectController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorProjectListController
+final class PhabricatorProjectListController
extends PhabricatorProjectController {
private $filter;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorProjectProfileController
+final class PhabricatorProjectProfileController
extends PhabricatorProjectController {
private $id;
private $page;
public function willProcessRequest(array $data) {
$this->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 = '<div style="padding: 1em;">'.$content.'</div>';
$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", '<br />', $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 =
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">About</h1>
<div class="phabricator-profile-info-pane">
<table class="phabricator-profile-info-table">
<tr>
<th>Creator</th>
<td>'.$handles[$project->getAuthorPHID()]->renderLink().'</td>
</tr>
<tr>
<th>Created</th>
<td>'.$timestamp.'</td>
</tr>
<tr>
<th>PHID</th>
<td>'.phutil_escape_html($project->getPHID()).'</td>
</tr>
<tr>
<th>Blurb</th>
<td>'.$blurb.'</td>
</tr>
</table>
</div>
</div>';
if ($project->getSubprojectPHIDs()) {
$table = $this->renderSubprojectTable(
$handles,
$project->getSubprojectPHIDs());
$subproject_list = $table->render();
} else {
$subproject_list = '<p><em>No subprojects.</em></p>';
}
$about .=
'<div class="phabricator-profile-info-group">'.
'<h1 class="phabricator-profile-info-header">Subprojects</h1>'.
'<div class="phabricator-profile-info-pane">'.
$subproject_list.
'</div>'.
'</div>';
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[] = '<li>'.$user.' &mdash; '.$role.'</li>';
}
if ($affiliated) {
$affiliated = '<ul>'.implode("\n", $affiliated).'</ul>';
} else {
$affiliated = '<p><em>No one is affiliated with this project.</em></p>';
}
return
'<div class="phabricator-profile-info-group">'.
'<h1 class="phabricator-profile-info-header">People</h1>'.
'<div class="phabricator-profile-info-pane">'.
$affiliated.
'</div>'.
'</div>';
}
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
'<div class="phabricator-profile-info-group">'.
'<h1 class="phabricator-profile-info-header">Activity Feed</h1>'.
'<div class="phabricator-profile-info-pane">'.
$view->render().
'</div>'.
'</div>';
}
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 = '<em>No open tasks.</em>';
} 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 =
'<div class="phabricator-profile-info-group">
<h1 class="phabricator-profile-info-header">'.
"Open Tasks ({$open})".
'</h1>'.
'<div class="phabricator-profile-info-pane">'.
$task_views.
'<div class="phabricator-profile-info-pane-more-link">'.
$more_link.
'</div>'.
'</div>
</div>';
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorProjectProfileEditController
+final class PhabricatorProjectProfileEditController
extends PhabricatorProjectController {
public function willProcessRequest(array $data) {
$this->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(
'<h1>Resources</h1>'.
'<input type="hidden" name="resources" id="resources" />'.
'<div class="aphront-form-inset">'.
'<div style="float: right;">'.
javelin_render_tag(
'a',
array(
'href' => '#',
'class' => 'button green',
'sigil' => 'add-resource',
'mustcapture' => true,
),
'Add New Resource').
'</div>'.
'<p></p>'.
'<div style="clear: both;"></div>'.
javelin_render_tag(
'table',
array(
'sigil' => 'resources',
'class' => 'project-resource-table',
),
'').
'</div>')
->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorProjectUpdateController
+final class PhabricatorProjectUpdateController
extends PhabricatorProjectController {
private $id;
private $action;
public function willProcessRequest(array $data) {
$this->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(
'<p>Your tremendous contributions to this project will be sorely '.
'missed. Are you sure you want to leave?</p>');
$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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryArcanistProjectEditController
+final class PhabricatorRepositoryArcanistProjectEditController
extends PhabricatorRepositoryController {
private $id;
public function willProcessRequest(array $data) {
$this->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: <tt>php, py</tt>')
->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryCreateController
+final class PhabricatorRepositoryCreateController
extends PhabricatorRepositoryController {
public function processRequest() {
$request = $this->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(
'<p class="aphront-form-instructions">Select a "Callsign" &mdash; 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".</p>')
->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryDeleteController
+final class PhabricatorRepositoryDeleteController
extends PhabricatorRepositoryController {
private $id;
public function willProcessRequest(array $data) {
$this->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(
'<p>Really delete the "'.phutil_escape_html($repository->getName()).
'" ('.phutil_escape_html($repository->getCallsign()).') repository? '.
'This operation can not be undone.</p>')
->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryEditController
+final class PhabricatorRepositoryEditController
extends PhabricatorRepositoryController {
private $id;
private $view;
private $repository;
private $sideNav;
public function willProcessRequest(array $data) {
$this->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('
<p class="aphront-form-instructions">'.
'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: '.
'<a href="'.$encoding_doc_link.'">'.
'UTF-8 and Character Encoding'.
'</a> for more information.'.
'</p>')
->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(
'<p class="aphront-form-instructions">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 '.
'<strong>'.$user_guide_link.'</strong>.</p>');
$form
->appendChild(
'<h1>Basics</h1><div class="aphront-form-inset">')
->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('</div>');
$form->appendChild(
'<h1>Remote URI</h1>'.
'<div class="aphront-form-inset">');
$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 '.
'<tt>git@github.com:example/example.git</tt>, '.
'<tt>ssh://user@host.com/git/example.git</tt>, or '.
'<tt>file:///local/path/to/repo</tt>';
} else if ($is_mercurial) {
$instructions =
'Enter the URI to clone this repository from. It should look '.
'something like <tt>ssh://user@host.com/hg/example</tt>';
}
$form->appendChild(
'<p class="aphront-form-instructions">'.$instructions.'</p>');
} else if ($is_svn) {
$instructions =
'Enter the <strong>Repository Root</strong> for this SVN repository. '.
'You can figure this out by running <tt>svn info</tt> and looking at '.
'the value in the <tt>Repository Root</tt> field. It should be a URI '.
'and look like <tt>http://svn.example.org/svn/</tt> or '.
'<tt>svn+ssh://svn.example.com/svnroot/</tt>';
$form->appendChild(
'<p class="aphront-form-instructions">'.$instructions.'</p>');
$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(
'<div class="aphront-form-instructions">'.
'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.'.
'</div>');
$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, <em>or</em>...'))
->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(
'<div class="aphront-form-instructions">'.
'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.'.
'</div>')
->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(
'<div class="aphront-form-important">'.
'To test your authentication configuration, <strong>save this '.
'form</strong> and then run this script:'.
'<code>'.
'phabricator/ $ ./scripts/repository/test_connection.php '.
phutil_escape_html($repository->getCallsign()).
'</code>'.
'This will verify that your configuration is correct and the '.
'daemons can connect to the remote repository and pull changes '.
'from it.'.
'</div>');
$form->appendChild('</div>');
$form->appendChild(
'<h1>Importing Repository Information</h1>'.
'<div class="aphront-form-inset">');
if ($has_local) {
$form->appendChild(
'<p class="aphront-form-instructions">Select a path on local disk '.
'which the daemons should <tt>'.$clone_command.'</tt> the repository '.
'into. This must be readable and writable by the daemons, and '.
'readable by the webserver. The daemons will <tt>'.$fetch_command.
'</tt> and keep this repository up to date.</p>');
$form->appendChild(
id(new AphrontFormTextControl())
->setName('path')
->setLabel('Local Path')
->setValue($repository->getDetail('local-path'))
->setError($e_path));
} else if ($is_svn) {
$form->appendChild(
'<p class="aphront-form-instructions">If you only want to parse one '.
'subpath of the repository, specify it here, relative to the '.
'repository root (e.g., <tt>trunk/</tt> or <tt>projects/wheel/</tt>). '.
'If you want to parse multiple subdirectories, create a separate '.
'Phabricator repository for each one.</p>');
$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: <tt>master, release</tt>'));
}
$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('</div>');
$form->appendChild(
'<h1>Application Configuration</h1>'.
'<div class="aphront-form-inset">');
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(
'<p class="aphront-form-instructions">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.</p>')
->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 <tt>svn info</tt>.'));
}
$form->appendChild('</div>');
$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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorRepositoryListController
+final class PhabricatorRepositoryListController
extends PhabricatorRepositoryController {
public function shouldRequireAdmin() {
return false;
}
public function processRequest() {
$request = $this->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 = '<em>Not Tracked</em>';
}
$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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group search
*/
-class PhabricatorSearchAttachController extends PhabricatorSearchController {
+final class PhabricatorSearchAttachController
+ extends PhabricatorSearchBaseController {
private $phid;
private $type;
private $action;
const ACTION_ATTACH = 'attach';
const ACTION_MERGE = 'merge';
const ACTION_DEPENDENCIES = 'dependencies';
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'aphront/response/dialog');
phutil_require_module('phabricator', 'aphront/response/reload');
phutil_require_module('phabricator', 'applications/maniphest/constants/status');
phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
phutil_require_module('phabricator', 'applications/maniphest/editor/transaction');
phutil_require_module('phabricator', 'applications/maniphest/storage/task');
phutil_require_module('phabricator', 'applications/maniphest/storage/transaction');
phutil_require_module('phabricator', 'applications/phid/constants');
phutil_require_module('phabricator', 'applications/phid/graph');
phutil_require_module('phabricator', 'applications/phid/handle/data');
-phutil_require_module('phabricator', 'applications/search/controller/search');
+phutil_require_module('phabricator', 'applications/search/controller/base');
phutil_require_module('phabricator', 'applications/search/editor/attach');
phutil_require_module('phabricator', 'view/control/objectselector');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorSearchAttachController.php');
diff --git a/src/applications/search/controller/index/PhabricatorSearchIndexController.php b/src/applications/search/controller/index/PhabricatorSearchIndexController.php
index 208a5e6430..cb712c7097 100644
--- a/src/applications/search/controller/index/PhabricatorSearchIndexController.php
+++ b/src/applications/search/controller/index/PhabricatorSearchIndexController.php
@@ -1,144 +1,145 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group search
*/
-class PhabricatorSearchIndexController extends PhabricatorSearchBaseController {
+final class PhabricatorSearchIndexController
+ extends PhabricatorSearchBaseController {
private $phid;
public function shouldRequireAdmin() {
// This basically shows you all the text of any object in the system, so
// make it admin-only.
return true;
}
public function willProcessRequest(array $data) {
$this->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", '<br />', 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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group search
*/
-class PhabricatorSearchController extends PhabricatorSearchBaseController {
+final class PhabricatorSearchController
+ extends PhabricatorSearchBaseController {
+
private $key;
public function willProcessRequest(array $data) {
$this->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 =
'<div class="phabricator-search-result-list">'.
implode("\n", $results).
'<div class="search-results-pager">'.
$pager->render().
'</div>'.
'</div>';
} else {
$results =
'<div class="phabricator-search-result-list">'.
'<p class="phabricator-search-no-results">No search results.</p>'.
'</div>';
}
} 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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group search
*/
-class PhabricatorSearchSelectController
- extends PhabricatorSearchController {
+final class PhabricatorSearchSelectController
+ extends PhabricatorSearchBaseController {
private $type;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/ajax');
phutil_require_module('phabricator', 'applications/differential/storage/revision');
phutil_require_module('phabricator', 'applications/maniphest/storage/task');
phutil_require_module('phabricator', 'applications/phid/constants');
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'applications/phid/handle/view/selector');
-phutil_require_module('phabricator', 'applications/search/controller/search');
+phutil_require_module('phabricator', 'applications/search/controller/base');
phutil_require_module('phabricator', 'applications/search/selector/base');
phutil_require_module('phabricator', 'applications/search/storage/query');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorSearchSelectController.php');
diff --git a/src/applications/slowvote/controller/create/PhabricatorSlowvoteCreateController.php b/src/applications/slowvote/controller/create/PhabricatorSlowvoteCreateController.php
index b4af23d6db..e0057f3ee0 100644
--- a/src/applications/slowvote/controller/create/PhabricatorSlowvoteCreateController.php
+++ b/src/applications/slowvote/controller/create/PhabricatorSlowvoteCreateController.php
@@ -1,163 +1,163 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group slowvote
*/
-class PhabricatorSlowvoteCreateController
+final class PhabricatorSlowvoteCreateController
extends PhabricatorSlowvoteController {
public function processRequest() {
$request = $this->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(
'<p class="aphront-form-instructions">Resolve issues and build '.
'consensus through protracted deliberation.</p>')
->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group slowvote
*/
-class PhabricatorSlowvoteListController
+final class PhabricatorSlowvoteListController
extends PhabricatorSlowvoteController {
private $view;
const VIEW_ALL = 'all';
const VIEW_CREATED = 'created';
const VIEW_VOTED = 'voted';
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @group slowvote
*/
-class PhabricatorSlowvotePollController
+final class PhabricatorSlowvotePollController
extends PhabricatorSlowvoteController {
private $id;
public function willProcessRequest(array $data) {
$this->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(
'<p class="aphront-form-instructions">'.$instructions.'</p>')
->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('<br /><br />');
$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[] =
'<tr>'.
'<th>'.
$handle->renderLink().
'<div class="phabricator-slowvote-datestamp">'.
phabricator_datetime($comment->getDateCreated(), $viewer).
'</div>'.
'<td>'.
'<div class="phabricator-remarkup">'.
$markup.
'</div>'.
'</td>'.
'</tr>';
}
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('<h1>Ongoing Deliberation</h1>');
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(
'<p class="aphront-form-instructions"><em>'.$reason.'</em></p>');
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(
'<div>'.
'<div class="phabricator-slowvote-count">'.
$vote_count.
'</div>'.
'<h1>'.phutil_escape_html($option->getName()).'</h1>'.
'<hr class="phabricator-slowvote-hr" />'.
$user_markup.
'<div style="clear: both;">'.
'<hr class="phabricator-slowvote-hr" />'.
$comment_markup.
'</div>');
}
if ($poll->getMethod() == PhabricatorSlowvotePoll::METHOD_APPROVAL &&
$comments) {
$comment_markup = $this->renderComments(
$comments,
$handles);
$result_markup->appendChild(
'<h1>Motions Proposed for Consideration</h1>');
$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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorStatusController extends PhabricatorController {
+final class PhabricatorStatusController extends PhabricatorController {
public function shouldRequireLogin() {
return false;
}
public function processRequest() {
$response = new AphrontWebpageResponse();
$response->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorTypeaheadCommonDatasourceController
+final class PhabricatorTypeaheadCommonDatasourceController
extends PhabricatorTypeaheadDatasourceController {
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorUIExampleRenderController
+final class PhabricatorUIExampleRenderController
extends PhabricatorUIExampleController {
private $class;
public function willProcessRequest(array $data) {
$this->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(
'<div class="phabricator-ui-example-header">'.
'<h1 class="phabricator-ui-example-name">'.
phutil_escape_html($example->getName()).
' ('.get_class($example).')'.
'</h1>'.
'<p class="phabricator-ui-example-description">'
.$example->getDescription().
'</p>'.
'</div>');
$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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorXHPASTViewRunController
+final class PhabricatorXHPASTViewRunController
extends PhabricatorXHPASTViewController {
public function processRequest() {
$request = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorXHPASTViewFrameController
+final class PhabricatorXHPASTViewFrameController
extends PhabricatorXHPASTViewController {
private $id;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorXHPASTViewFramesetController
+final class PhabricatorXHPASTViewFramesetController
extends PhabricatorXHPASTViewController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$id = $this->id;
$response = new AphrontWebpageResponse();
$response->setFrameable(true);
$response->setContent(
'<frameset cols="33%, 34%, 33%">'.
'<frame src="/xhpast/input/'.$id.'/" />'.
'<frame src="/xhpast/tree/'.$id.'/" />'.
'<frame src="/xhpast/stream/'.$id.'/" />'.
'</frameset>');
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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorXHPASTViewInputController
+final class PhabricatorXHPASTViewInputController
extends PhabricatorXHPASTViewPanelController {
public function processRequest() {
$input = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorXHPASTViewStreamController
+final class PhabricatorXHPASTViewStreamController
extends PhabricatorXHPASTViewPanelController {
public function processRequest() {
$storage = $this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorXHPASTViewTreeController
+final class PhabricatorXHPASTViewTreeController
extends PhabricatorXHPASTViewPanelController {
public function processRequest() {
$storage = $this->getStorageTree();
$input = $storage->getInput();
$stdout = $storage->getStdout();
$tree = XHPASTTree::newFromDataAndResolvedExecFuture(
$input,
array(0, $stdout, ''));
$tree = '<ul>'.$this->buildTree($tree->getRootNode()).'</ul>';
return $this->buildXHPASTViewPanelResponse($tree);
}
protected function buildTree($root) {
try {
$name = $root->getTypeName();
$title = $root->getDescription();
} catch (Exception $ex) {
$name = '???';
$title = '???';
}
$tree = array();
$tree[] =
'<li>'.
phutil_render_tag(
'span',
array(
'title' => $title,
),
phutil_escape_html($name)).
'</li>';
foreach ($root->getChildren() as $child) {
$tree[] = '<ul>'.$this->buildTree($child).'</ul>';
}
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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-class PhabricatorXHProfProfileController
+final class PhabricatorXHProfProfileController
extends PhabricatorXHProfController {
private $phid;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
- * Copyright 2011 Facebook, Inc.
+ * Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Delivers CSS and JS resources to the browser. This controller handles all
* ##/res/## requests, and manages caching, package construction, and resource
* preprocessing.
*
* @group celerity
*/
-class CelerityResourceController extends AphrontController {
+final class CelerityResourceController extends AphrontController {
private $path;
private $hash;
private $package;
public function willProcessRequest(array $data) {
$this->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 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class LiskIsolationTestCase extends PhabricatorTestCase {
public function testIsolatedWrites() {
$dao = new LiskIsolationTestDAO();
$this->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.');
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 20, 8:30 AM (21 h, 3 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
e7/58/fe99e38f737b37651f58c72dbae7
Default Alt Text
(981 KB)

Event Timeline